diff --git a/exam-api1.zip b/exam-api1.zip new file mode 100644 index 0000000..4f4a4be Binary files /dev/null and b/exam-api1.zip differ diff --git a/exam-api1/pom.xml b/exam-api1/pom.xml new file mode 100644 index 0000000..3fd6282 --- /dev/null +++ b/exam-api1/pom.xml @@ -0,0 +1,214 @@ + + 4.0.0 + com.yfhl + exam-api + jar + 1.0 + exam-api + + org.springframework.boot + spring-boot-starter-parent + 2.1.4.RELEASE + + + + 2.0.24 + 3.7.0 + 4.1.1 + 2.9.2 + 5.5.1 + 3.8 + 8.0.11 + 3.4.1 + 1.18.4 + 3.0.11.RELEASE + 2.1.1.RELEASE + 3.9 + 2.17.2 + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-quartz + + + + org.aspectj + aspectjweaver + 1.9.5 + + + + com.alibaba + fastjson + ${fastjson.version} + + + + net.sf.dozer + dozer + ${dozer.version} + + + commons-collections + commons-collections + + + org.slf4j + slf4j-api + + + + + + + + + com.baomidou + mybatis-plus + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + mysql + mysql-connector-java + ${mysql.driver.version} + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + org.dom4j + dom4j + 2.1.1 + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + org.slf4j + slf4j-api + + + + + + com.github.xiaoymin + swagger-bootstrap-ui + 1.9.3 + + + + + org.apache.poi + poi + ${poi.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + org.apache.poi + poi-ooxml-schemas + ${poi.version} + + + + + com.auth0 + java-jwt + 3.7.0 + + + + + org.apache.shiro + shiro-spring-boot-starter + 1.8.0 + + + + + com.alibaba + druid-spring-boot-starter + 1.2.6 + + + + commons-io + commons-io + 2.11.0 + + + + + + + ${project.name} + compile + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + UTF-8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + true + + + + + + + diff --git a/exam-api1/src/main/java/com/yf/exam/ExamApplication.java b/exam-api1/src/main/java/com/yf/exam/ExamApplication.java new file mode 100644 index 0000000..cbcee70 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ExamApplication.java @@ -0,0 +1,70 @@ +package com.yf.exam; + +import com.yf.exam.core.api.utils.JsonConverter; +import lombok.extern.log4j.Log4j2; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +/** + * 云帆在线考试系统 + * 系统主启动类,负责Spring Boot应用的初始化和配置 + * @author bool + * @email 18365918@qq.com + * @date 2020-03-04 19:41 + */ +@Log4j2 +@SpringBootApplication +public class ExamApplication implements WebMvcConfigurer { + + /** + * 应用主入口方法 + * 启动Spring Boot应用并打印访问信息 + * @param args 命令行参数 + * @throws UnknownHostException 当无法获取本地主机地址时抛出 + */ + public static void main(String[] args) throws UnknownHostException { + // 启动Spring Boot应用 + ConfigurableApplicationContext application = SpringApplication.run(ExamApplication.class, args); + // 获取环境配置信息 + Environment env = application.getEnvironment(); + // 获取本机IP地址 + String ip = InetAddress.getLocalHost().getHostAddress(); + // 获取服务器端口 + String port = env.getProperty("server.port"); + // 获取应用上下文路径 + String path = env.getProperty("server.servlet.context-path"); + + // 未配置上下文路径时默认设为空白 + if(path == null){ + path = ""; + } + + // 打印系统启动成功信息和访问地址 + log.info("\n----------------------------------------------------------\n\t" + + "云帆考试系统启动成功,访问路径如下:\n\t" + + "本地路径: \t\thttp://localhost:" + port + path + "/\n\t" + + "网络地址: \thttp://" + ip + ":" + port + path + "/\n\t" + + "API文档: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" + + "----------------------------------------------------------"); + } + + /** + * 扩展HTTP消息转换器 + * 配置FastJson作为JSON序列化工具,并设置最高优先级 + * @param converters HTTP消息转换器列表 + */ + @Override + public void extendMessageConverters(List> converters) { + // 保留原有converter,把新增fastConverter插入集合头,保证优先级 + converters.add(0, JsonConverter.fastConverter()); + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/ability/Constant.java b/exam-api1/src/main/java/com/yf/exam/ability/Constant.java new file mode 100644 index 0000000..9880ea6 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/Constant.java @@ -0,0 +1,15 @@ +package com.yf.exam.ability; + + +/** + * 通用常量 + * @author bool + */ +public class Constant { + + + /** + * 文件上传路径 + */ + public static final String FILE_PREFIX = "/upload/file/"; +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/job/enums/JobGroup.java b/exam-api1/src/main/java/com/yf/exam/ability/job/enums/JobGroup.java new file mode 100644 index 0000000..2159361 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/job/enums/JobGroup.java @@ -0,0 +1,13 @@ +package com.yf.exam.ability.job.enums; + +/** + * 任务分组 + * @author van + */ +public interface JobGroup { + + /** + * 系统任务 + */ + String SYSTEM = "system"; +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/job/enums/JobPrefix.java b/exam-api1/src/main/java/com/yf/exam/ability/job/enums/JobPrefix.java new file mode 100644 index 0000000..2536f0e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/job/enums/JobPrefix.java @@ -0,0 +1,14 @@ +package com.yf.exam.ability.job.enums; + +/** + * 任务前缀 + * @author bool + */ +public interface JobPrefix { + + /** + * 强制交卷的 + */ + String BREAK_EXAM = "break_exam_"; + +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/job/service/JobService.java b/exam-api1/src/main/java/com/yf/exam/ability/job/service/JobService.java new file mode 100644 index 0000000..cb465de --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/job/service/JobService.java @@ -0,0 +1,53 @@ +package com.yf.exam.ability.job.service; + +/** + * 任务业务类,用于动态处理任务信息 + * @author bool + * @date 2020/11/29 下午2:17 + */ +public interface JobService { + + + /** + * 任务数据 + */ + String TASK_DATA = "taskData"; + + /** + * 添加定时任务 + * @param jobClass + * @param jobName + * @param cron + * @param data + */ + void addCronJob(Class jobClass, String jobName, String cron, String data); + + /** + * 添加立即执行的任务 + * @param jobClass + * @param jobName + * @param data + */ + void addCronJob(Class jobClass, String jobName, String data); + + /** + * 暂停任务 + * @param jobName + * @param jobGroup + */ + void pauseJob(String jobName, String jobGroup); + + /** + * 恢复任务 + * @param triggerName + * @param triggerGroup + */ + void resumeJob(String triggerName, String triggerGroup); + + /** + * 删除job + * @param jobName + * @param jobGroup + */ + void deleteJob(String jobName, String jobGroup); +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java new file mode 100644 index 0000000..aafdfdb --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/job/service/impl/JobServiceImpl.java @@ -0,0 +1,123 @@ +package com.yf.exam.ability.job.service.impl; + +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.yf.exam.ability.job.enums.JobGroup; +import com.yf.exam.ability.job.service.JobService; +import lombok.extern.log4j.Log4j2; +import org.quartz.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +/** + * @author bool + */ +@Log4j2 +@Service +public class JobServiceImpl implements JobService { + + /** + * Quartz定时任务核心的功能实现类 + */ + private Scheduler scheduler; + + /** + * 注入 + * @param schedulerFactoryBean + */ + public JobServiceImpl(@Autowired SchedulerFactoryBean schedulerFactoryBean) { + scheduler = schedulerFactoryBean.getScheduler(); + } + + + @Override + public void addCronJob(Class jobClass, String jobName, String cron, String data) { + + + String jobGroup = JobGroup.SYSTEM; + + // 自动命名 + if(StringUtils.isEmpty(jobName)){ + jobName = jobClass.getSimpleName().toUpperCase() + "_"+IdWorker.getIdStr(); + } + + try { + JobKey jobKey = JobKey.jobKey(jobName, jobGroup); + JobDetail jobDetail = scheduler.getJobDetail(jobKey); + if (jobDetail != null) { + log.info("++++++++++任务:{} 已存在", jobName); + this.deleteJob(jobName, jobGroup); + } + + log.info("++++++++++构建任务:{},{},{},{},{} ", jobClass.toString(), jobName, jobGroup, cron, data); + + //构建job信息 + jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build(); + //用JopDataMap来传递数据 + jobDetail.getJobDataMap().put(TASK_DATA, data); + + //按新的cronExpression表达式构建一个新的trigger + Trigger trigger = null; + + // 有表达式的按表达式 + if(!StringUtils.isEmpty(cron)){ + log.info("+++++表达式执行:"+ JSON.toJSONString(jobDetail)); + //表达式调度构建器 + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); + trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build(); + }else{ + // 无表达式则立即执行 + log.info("+++++立即执行:"+ JSON.toJSONString(jobDetail)); + trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).startNow().build(); + } + + scheduler.scheduleJob(jobDetail, trigger); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @Override + public void addCronJob(Class jobClass, String jobName, String data) { + // 立即执行任务 + this.addCronJob(jobClass, jobName, null, data); + } + + + @Override + public void pauseJob(String jobName, String jobGroup) { + try { + TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); + scheduler.pauseTrigger(triggerKey); + log.info("++++++++++暂停任务:{}", jobName); + } catch (SchedulerException e) { + e.printStackTrace(); + } + } + + @Override + public void resumeJob(String jobName, String jobGroup) { + try { + TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); + scheduler.resumeTrigger(triggerKey); + log.info("++++++++++重启任务:{}", jobName); + } catch (SchedulerException e) { + e.printStackTrace(); + } + } + + @Override + public void deleteJob(String jobName, String jobGroup) { + try { + JobKey jobKey = JobKey.jobKey(jobName,jobGroup); + scheduler.deleteJob(jobKey); + log.info("++++++++++删除任务:{}", jobKey); + } catch (SchedulerException e) { + e.printStackTrace(); + } + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java b/exam-api1/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java new file mode 100644 index 0000000..3bc2190 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/shiro/CNFilterFactoryBean.java @@ -0,0 +1,29 @@ +package com.yf.exam.ability.shiro; + +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.filter.InvalidRequestFilter; +import org.apache.shiro.web.filter.mgt.DefaultFilter; +import org.apache.shiro.web.filter.mgt.FilterChainManager; + +import javax.servlet.Filter; +import java.util.Map; + +/** + * 自定义过滤器,用于处理中文URL问题 + * 如:下载文件中包含中文会返回400错误,https://youdomain.com/upload/file/云帆考试系统用户手册.pdf + * @author van + */ +public class CNFilterFactoryBean extends ShiroFilterFactoryBean { + + @Override + protected FilterChainManager createFilterChainManager() { + FilterChainManager manager = super.createFilterChainManager(); + // URL携带中文400,servletPath中文校验bug + Map filterMap = manager.getFilters(); + Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); + if (invalidRequestFilter instanceof InvalidRequestFilter) { + ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false); + } + return manager; + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java b/exam-api1/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java new file mode 100644 index 0000000..76af5c5 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/shiro/ShiroRealm.java @@ -0,0 +1,131 @@ +package com.yf.exam.ability.shiro; + + +import com.yf.exam.ability.shiro.jwt.JwtToken; +import com.yf.exam.ability.shiro.jwt.JwtUtils; +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +import com.yf.exam.modules.sys.user.service.SysUserRoleService; +import com.yf.exam.modules.sys.user.service.SysUserService; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.List; + +/** + * 用户登录鉴权和获取用户授权 + * @author bool + */ +@Component +@Slf4j +public class ShiroRealm extends AuthorizingRealm { + + @Autowired + @Lazy + private SysUserService sysUserService; + + @Autowired + @Lazy + private SysUserRoleService sysUserRoleService; + + + @Override + public boolean supports(AuthenticationToken token) { + return token instanceof JwtToken; + } + + + /** + * 详细授权认证 + * @param principals + * @return + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + + String userId = null; + if (principals != null) { + SysUserLoginDTO user = (SysUserLoginDTO) principals.getPrimaryPrincipal(); + userId = user.getId(); + } + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + + // 查找用户角色 + List roles = sysUserRoleService.listRoles(userId); + info.setRoles(new HashSet<>(roles)); + + log.info("++++++++++校验详细权限完成"); + return info; + } + + /** + * 校验用户的账号密码是否正确 + * @param auth + * @return + * @throws AuthenticationException + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { + String token = (String) auth.getCredentials(); + if (token == null) { + throw new AuthenticationException("token为空!"); + } + + // 校验token有效性 + SysUserLoginDTO user = this.checkToken(token); + return new SimpleAuthenticationInfo(user, token, getName()); + } + + + /** + * 校验Token的有效性 + * @param token + * @return + * @throws AuthenticationException + */ + public SysUserLoginDTO checkToken(String token) throws AuthenticationException { + + // 查询用户信息 + log.debug("++++++++++校验用户token: "+ token); + + // 从token中获取用户名 + String username = JwtUtils.getUsername(token); + log.debug("++++++++++用户名: "+ username); + + if (username == null) { + throw new AuthenticationException("无效的token"); + } + + // 查找登录用户对象 + SysUserLoginDTO user = sysUserService.token(token); + + // 校验token是否失效 + if (!JwtUtils.verify(token, username)) { + throw new AuthenticationException("登陆失效,请重试登陆!"); + } + + return user; + } + + + + /** + * 清除当前用户的权限认证缓存 + * @param principals + */ + @Override + public void clearCache(PrincipalCollection principals) { + super.clearCache(principals); + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java b/exam-api1/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java new file mode 100644 index 0000000..88cf448 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/shiro/aop/JwtFilter.java @@ -0,0 +1,53 @@ +package com.yf.exam.ability.shiro.aop; + + +import com.yf.exam.ability.shiro.jwt.JwtToken; +import com.yf.exam.aspect.utils.InjectUtils; +import com.yf.exam.modules.Constant; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 鉴权登录拦截器 + * @author bool + */ +@Slf4j +public class JwtFilter extends BasicHttpAuthenticationFilter { + + /** + * 执行登录认证 + * @param request + * @param response + * @param mappedValue + * @return + */ + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + try { + executeLogin(request, response); + return true; + } catch (Exception e) { + // 写出统一错误信息 + InjectUtils.restError((HttpServletResponse) response); + return false; + } + } + + + @Override + protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + String token = httpServletRequest.getHeader(Constant.TOKEN); + + JwtToken jwtToken = new JwtToken(token); + // 提交给realm进行登入,如果错误他会抛出异常并被捕获 + getSubject(request, response).login(jwtToken); + // 如果没有抛出异常则代表登入成功,返回true + return true; + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java b/exam-api1/src/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java new file mode 100644 index 0000000..d5baab3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/shiro/jwt/JwtToken.java @@ -0,0 +1,33 @@ +package com.yf.exam.ability.shiro.jwt; + +import lombok.Data; +import org.apache.shiro.authc.AuthenticationToken; + +/** + * @author bool + */ +@Data +public class JwtToken implements AuthenticationToken { + + private static final long serialVersionUID = 1L; + + /** + * JWT的字符token + */ + private String token; + + + public JwtToken(String token) { + this.token = token; + } + + @Override + public Object getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java b/exam-api1/src/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java new file mode 100644 index 0000000..4a66759 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/shiro/jwt/JwtUtils.java @@ -0,0 +1,99 @@ +package com.yf.exam.ability.shiro.jwt; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.yf.exam.core.utils.file.Md5Util; + +import java.util.Calendar; +import java.util.Date; + +/** + * JWT工具类 + * @author bool + */ +public class JwtUtils { + + /** + * 有效期24小时 + */ + private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; + + + /** + * 校验是否正确 + * @param token + * @param username + * @return + */ + public static boolean verify(String token, String username) { + try { + // 根据密码生成JWT效验器 + Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); + JWTVerifier verifier = JWT.require(algorithm) + .withClaim("username", username) + .build(); + // 效验TOKEN + verifier.verify(token); + return true; + } catch (Exception exception) { + return false; + } + } + + + + + + /** + * 从Token中解密获得用户名 + * @param token + * @return + */ + public static String getUsername(String token) { + try { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaim("username").asString(); + } catch (JWTDecodeException e) { + return null; + } + } + + /** + * 生成JWT Token字符串 + * @param username + * @return + */ + public static String sign(String username) { + Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); + Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); + // 附带username信息 + return JWT.create() + .withClaim("username", username) + .withExpiresAt(date).sign(algorithm); + + } + + /** + * 根据用户名和秘钥,生成一个新的秘钥,用于JWT加强一些安全性 + * @param userName + * @return + */ + private static String encryptSecret(String userName){ + + // 一个简单的登录规则,用户名+当前月份为加密串,意思每个月会变,要重新登录 + // 可自行修改此规则 + Calendar cl = Calendar.getInstance(); + cl.setTimeInMillis(System.currentTimeMillis()); + StringBuffer sb = new StringBuffer(userName) + .append("&") + .append(cl.get(Calendar.MONTH)); + + // 获取MD5 + String secret = Md5Util.md5(sb.toString()); + + return Md5Util.md5(userName + "&" + secret); + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/upload/config/UploadConfig.java b/exam-api1/src/main/java/com/yf/exam/ability/upload/config/UploadConfig.java new file mode 100644 index 0000000..e35d73d --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/upload/config/UploadConfig.java @@ -0,0 +1,32 @@ +package com.yf.exam.ability.upload.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + + +/** + * 文件上传配置 + * @author van + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "conf.upload") +public class UploadConfig { + + /** + * 访问路径 + */ + private String url; + + /** + * 物理目录 + */ + private String dir; + + /** + * 允许的后缀 + */ + private String [] allowExtensions; + +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java b/exam-api1/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java new file mode 100644 index 0000000..4c85250 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/upload/controller/UploadController.java @@ -0,0 +1,57 @@ +package com.yf.exam.ability.upload.controller; + + +import com.yf.exam.ability.Constant; +import com.yf.exam.ability.upload.dto.UploadReqDTO; +import com.yf.exam.ability.upload.dto.UploadRespDTO; +import com.yf.exam.ability.upload.service.UploadService; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 本地文件上传下载请求类 + * @author bool + */ +@Log4j2 +@Api(tags = {"文件上传"}) +@RestController +public class UploadController extends BaseController { + + @Autowired + private UploadService uploadService; + + /** + * 文件上传 + * @param reqDTO + * @return + */ + @PostMapping("/common/api/file/upload") + @ApiOperation(value = "文件上传", notes = "此接口较为特殊,参数都通过表单方式提交,而非JSON") + public ApiRest upload(@ModelAttribute UploadReqDTO reqDTO) { + // 上传并返回URL + UploadRespDTO respDTO = uploadService.upload(reqDTO); + return super.success(respDTO); + } + + /** + * 独立文件下载 + * @param request + * @param response + */ + @GetMapping(Constant.FILE_PREFIX+"**") + @ApiOperation(value = "文件下载", notes = "文件下载") + public void download(HttpServletRequest request, HttpServletResponse response) { + uploadService.download(request, response); + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java b/exam-api1/src/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java new file mode 100644 index 0000000..df2f286 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/upload/dto/UploadReqDTO.java @@ -0,0 +1,22 @@ +package com.yf.exam.ability.upload.dto; + + +import com.yf.exam.core.api.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件上传请求类 + * @author + * @date 2019-12-26 17:54 + */ +@Data +@ApiModel(value="文件上传参数", description="文件上传参数") +public class UploadReqDTO extends BaseDTO { + + @ApiModelProperty(value = "上传文件内容", required=true) + private MultipartFile file; + +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java b/exam-api1/src/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java new file mode 100644 index 0000000..b91106e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/upload/dto/UploadRespDTO.java @@ -0,0 +1,23 @@ +package com.yf.exam.ability.upload.dto; + +import com.yf.exam.core.api.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 上传文件结果 + * @author bool + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ApiModel(value="文件上传响应", description="文件上传响应") +public class UploadRespDTO extends BaseDTO { + + @ApiModelProperty(value = "上传后的完整的URL地址", required=true) + private String url; + +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/upload/service/UploadService.java b/exam-api1/src/main/java/com/yf/exam/ability/upload/service/UploadService.java new file mode 100644 index 0000000..ef516ec --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/upload/service/UploadService.java @@ -0,0 +1,30 @@ +package com.yf.exam.ability.upload.service; + +import com.yf.exam.ability.upload.dto.UploadReqDTO; +import com.yf.exam.ability.upload.dto.UploadRespDTO; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 阿里云OSS业务类 + * @author bool + * @date 2019-07-12 16:45 + */ +public interface UploadService { + + /** + * 文件上传 + * @param reqDTO + * @return + */ + UploadRespDTO upload(UploadReqDTO reqDTO); + + /** + * 下载文件 + * @param request + * @param response + */ + void download(HttpServletRequest request, HttpServletResponse response); + +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java new file mode 100644 index 0000000..ef70f40 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/upload/service/impl/UploadServiceImpl.java @@ -0,0 +1,135 @@ +package com.yf.exam.ability.upload.service.impl; + +import com.yf.exam.ability.Constant; +import com.yf.exam.ability.upload.config.UploadConfig; +import com.yf.exam.ability.upload.dto.UploadReqDTO; +import com.yf.exam.ability.upload.dto.UploadRespDTO; +import com.yf.exam.ability.upload.service.UploadService; +import com.yf.exam.ability.upload.utils.FileUtils; +import com.yf.exam.core.exception.ServiceException; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.io.FilenameUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * 文件上传业务类 + * @author bool + * @date 2019-07-30 21:02 + */ +@Log4j2 +@Service +public class UploadServiceImpl implements UploadService { + + @Autowired + private UploadConfig conf; + + @Override + public UploadRespDTO upload(UploadReqDTO reqDTO) { + + + // 文件内容 + MultipartFile file = reqDTO.getFile(); + + // 验证文件后缀 + boolean allow = FilenameUtils.isExtension(file.getOriginalFilename(), conf.getAllowExtensions()); + if(!allow){ + throw new ServiceException("文件类型不允许上传!"); + } + // 上传文件夹 + String fileDir = conf.getDir(); + // 真实物理地址 + String fullPath; + try { + + // 新文件 + String filePath = FileUtils.processPath(file); + // 文件保存地址 + fullPath = fileDir + filePath; + // 创建文件夹 + FileUtils.checkDir(fullPath); + // 上传文件 + FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(fullPath)); + + return this.generateResult(filePath); + + } catch (IOException e) { + e.printStackTrace(); + throw new ServiceException("文件上传失败:"+e.getMessage()); + } + } + + + + @Override + public void download(HttpServletRequest request, HttpServletResponse response) { + + // 获取真实的文件路径 + String filePath = this.getRealPath(request.getRequestURI()); + + // 处理中文问题 + try { + filePath = URLDecoder.decode(filePath, "utf-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + System.out.println("++++完整路径为:"+filePath); + + try { + FileUtils.writeRange(request, response, filePath); + } catch (IOException e) { + response.setStatus(404); + log.error("预览文件失败" + e.getMessage()); + } + } + + + /** + * 构造返回 + * @param fileName + * @return + */ + private UploadRespDTO generateResult(String fileName) { + + //获取加速域名 + String domain = conf.getUrl(); + + // 返回结果 + return new UploadRespDTO(domain + fileName); + } + + + /** + * 获取真实物理文件地址 + * @param uri + * @return + */ + public String getRealPath(String uri){ + + String regx = Constant.FILE_PREFIX+"(.*)"; + + // 查找全部变量 + Pattern pattern = Pattern.compile(regx); + Matcher m = pattern.matcher(uri); + if (m.find()) { + String str = m.group(1); + return conf.getDir() + str; + } + + return null; + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/upload/utils/FileUtils.java b/exam-api1/src/main/java/com/yf/exam/ability/upload/utils/FileUtils.java new file mode 100644 index 0000000..539ecb0 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/upload/utils/FileUtils.java @@ -0,0 +1,172 @@ +package com.yf.exam.ability.upload.utils; + +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.yf.exam.core.utils.DateUtils; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Date; + +/** + * 文件工具类 + * @author bool + */ +public class FileUtils { + + /** + * 后缀分割符号 + */ + private static final String SUFFIX_SPLIT = "."; + + + /** + * 支持以断点的方式输出文件,提供文件在线预览和视频在线播放 + * @param request + * @param response + * @param filePath + * @throws IOException + */ + public static void writeRange(HttpServletRequest request, + HttpServletResponse response, String filePath) throws IOException { + + // 读取文件 + File file = new File(filePath); + + //只读模式 + RandomAccessFile randomFile = new RandomAccessFile(file, "r"); + long contentLength = randomFile.length(); + String range = request.getHeader("Range"); + int start = 0, end = 0; + if (range != null && range.startsWith("bytes=")) { + String[] values = range.split("=")[1].split("-"); + start = Integer.parseInt(values[0]); + if (values.length > 1) { + end = Integer.parseInt(values[1]); + } + } + int requestSize; + if (end != 0 && end > start) { + requestSize = end - start + 1; + } else { + requestSize = Integer.MAX_VALUE; + } + + byte[] buffer = new byte[128]; + response.setContentType(MediaUtils.getContentType(filePath)); + response.setHeader("Accept-Ranges", "bytes"); + response.setHeader("ETag", file.getName()); + response.setHeader("Last-Modified", new Date().toString()); + //第一次请求只返回content length来让客户端请求多次实际数据 + if (range == null) { + response.setHeader("Content-length", contentLength + ""); + } else { + //以后的多次以断点续传的方式来返回视频数据 + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + long requestStart = 0, requestEnd = 0; + String[] ranges = range.split("="); + if (ranges.length > 1) { + String[] rangeData = ranges[1].split("-"); + requestStart = Integer.parseInt(rangeData[0]); + if (rangeData.length > 1) { + requestEnd = Integer.parseInt(rangeData[1]); + } + } + long length; + if (requestEnd > 0) { + length = requestEnd - requestStart + 1; + response.setHeader("Content-length", "" + length); + response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength); + } else { + length = contentLength - requestStart; + response.setHeader("Content-length", "" + length); + response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1) + "/" + contentLength); + } + } + ServletOutputStream out = response.getOutputStream(); + int needSize = requestSize; + randomFile.seek(start); + while (needSize > 0) { + int len = randomFile.read(buffer); + if (needSize < buffer.length) { + out.write(buffer, 0, needSize); + } else { + out.write(buffer, 0, len); + if (len < buffer.length) { + break; + } + } + needSize -= buffer.length; + } + randomFile.close(); + out.close(); + } + + + + + /** + * 重命名文件 + * @param fileName + * @return + */ + public static String renameFile(String fileName) { + + //没有后缀名不处理 + if (!fileName.contains(SUFFIX_SPLIT)) { + return fileName; + } + + //文件后缀 + String extension = FilenameUtils.getExtension(fileName); + + //以系统时间命名 + return IdWorker.getIdStr() + "."+ extension; + + } + + + /** + * 处理新的文件路径,为上传文件预设目录,如:2021/01/01/xxx.jpg,要注意的是,前面没有斜杠 + * @param file 文件 + * @return + */ + public static String processPath(MultipartFile file){ + + // 创建OSSClient实例。 + String fileName = file.getOriginalFilename(); + + // 需要重命名 + fileName = renameFile(fileName); + + //获得上传的文件夹 + String dir = DateUtils.formatDate(new Date(), "yyyy/MM/dd/"); + + return new StringBuffer(dir).append(fileName).toString(); + + } + + /** + * 检查文件夹是否存在,不存在则创建 + * @param fileName + * @return + */ + public static void checkDir(String fileName){ + int index = fileName.lastIndexOf("/"); + if(index == -1){ + return; + } + + File file = new File(fileName.substring(0,index)); + if(!file.exists()){ + file.mkdirs(); + } + } + + +} diff --git a/exam-api1/src/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java b/exam-api1/src/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java new file mode 100644 index 0000000..b4394c0 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/ability/upload/utils/MediaUtils.java @@ -0,0 +1,47 @@ +package com.yf.exam.ability.upload.utils; + +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * 媒体工具,判断媒体类型 + * @author bool + * @date 2019-11-14 16:21 + */ +public class MediaUtils { + + public static final Map MEDIA_MAP = new HashMap(){ + { + + //本来是pdf的 + put(".pdf", "application/pdf"); + + //视频 + put(".mp4", "video,video/mp4"); + + } + }; + + /** + * 获得文件类型 + * @param filePath + * @return + */ + public static String getContentType(String filePath){ + + if(!StringUtils.isBlank(filePath) + && filePath.indexOf(".")!=-1) { + + // 后缀转换成小写 + String suffix = filePath.substring(filePath.lastIndexOf(".")).toLowerCase(); + + if (MEDIA_MAP.containsKey(suffix)) { + return MEDIA_MAP.get(suffix); + } + } + + return "application/octet-stream"; + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/aspect/DictAspect.java b/exam-api1/src/main/java/com/yf/exam/aspect/DictAspect.java new file mode 100644 index 0000000..441a154 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/aspect/DictAspect.java @@ -0,0 +1,317 @@ +package com.yf.exam.aspect; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.yf.exam.core.annon.Dict; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.utils.Reflections; +import com.yf.exam.modules.sys.system.service.SysDictService; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 数据字典AOP类,处理数据字典值 + * + * @author bool + */ +@Aspect +@Component +@Slf4j +public class DictAspect { + + @Autowired + private SysDictService sysDictService; + + /** + * 切入Controller执行 + * @param pjp + * @return + * @throws Throwable + */ + @Around("execution(public * com.yf.exam..*.*Controller.*(..))") + public Object doAround(ProceedingJoinPoint pjp) throws Throwable { + return this.translate(pjp); + } + + /** + * 进行翻译并返回,调用前必须实现:BaseDictService + * + * @param pjp + * @return + * @throws Throwable + */ + public Object translate(ProceedingJoinPoint pjp) throws Throwable { + // 处理字典 + return this.parseAllDictText(pjp.proceed()); + } + + /** + * 转换全部数据字典 + * + * @param result + */ + private Object parseAllDictText(Object result) { + + // 非ApiRest类型不处理 + if (result instanceof ApiRest) { + parseFullDictText(result); + } + + return result; + } + + + /** + * 转换所有类型的数据字典、包含子列表 + * + * @param result + */ + private void parseFullDictText(Object result) { + + try { + + Object rest = ((ApiRest) result).getData(); + + // 不处理普通数据类型 + if (rest == null || this.isBaseType(rest.getClass())) { + return; + } + + // 分页的 + if (rest instanceof IPage) { + List items = new ArrayList<>(16); + for (Object record : ((IPage) rest).getRecords()) { + Object item = this.parseObject(record); + items.add(item); + } + ((IPage) rest).setRecords(items); + return; + } + + // 数据列表的 + if (rest instanceof List) { + List items = new ArrayList<>(); + for (Object record : ((List) rest)) { + Object item = this.parseObject(record); + items.add(item); + } + // 重新回写值 + ((ApiRest) result).setData(items); + return; + } + + // 处理单对象 + Object item = this.parseObject(((ApiRest) result).getData()); + ((ApiRest) result).setData(item); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 处理数据字典值 + * + * @param record + * @return + */ + public Object parseObject(Object record) { + + if (record == null) { + return null; + } + + // 不处理普通数据类型 + if (this.isBaseType(record.getClass())) { + return record; + } + + // 转换JSON字符 + String json = JSON.toJSONString(record); + JSONObject item = JSONObject.parseObject(json); + + for (Field field : Reflections.getAllFields(record)) { + + // 如果是List类型 + if (List.class.isAssignableFrom(field.getType())) { + try { + List list = this.processList(field, item.getObject(field.getName(), List.class)); + item.put(field.getName(), list); + continue; + } catch (Exception e) { + e.printStackTrace(); + } + continue; + } + + // 处理普通字段 + if (field.getAnnotation(Dict.class) != null) { + String code = field.getAnnotation(Dict.class).dicCode(); + String text = field.getAnnotation(Dict.class).dicText(); + String table = field.getAnnotation(Dict.class).dictTable(); + String key = String.valueOf(item.get(field.getName())); + + //翻译字典值对应的txt + String textValue = this.translateDictValue(code, text, table, key); + if (StringUtils.isEmpty(textValue)) { + textValue = ""; + } + item.put(field.getName() + "_dictText", textValue); + continue; + } + + //日期格式转换 + if ("java.util.Date".equals(field.getType().getName()) && item.get(field.getName()) != null) { + + // 获取注解 + JsonFormat ann = field.getAnnotation(JsonFormat.class); + // 格式化方式 + SimpleDateFormat fmt; + + // 使用注解指定的 + if (ann != null && !StringUtils.isEmpty(ann.pattern())) { + fmt = new SimpleDateFormat(ann.pattern()); + } else { + // 默认时间样式 + fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + } + item.put(field.getName(), fmt.format(new Date((Long) item.get(field.getName())))); + continue; + + } + } + + return item; + } + + /** + * 获得类型为List的值 + * + * @param field + * @return + */ + private List processList(Field field, List list) { + + // 空判断 + if (list == null || list.size() == 0) { + return new ArrayList<>(); + } + + // 获得List属性的真实类 + Type genericType = field.getGenericType(); + Class actualType = null; + if (genericType instanceof ParameterizedType) { + // 尝试获取数据类型 + ParameterizedType pt = (ParameterizedType) genericType; + try { + actualType = (Class) pt.getActualTypeArguments()[0]; + }catch (Exception e){ + return list; + } + } + + // 常规列表无需处理 + if (isBaseType(actualType)) { + return list; + } + + // 返回列表 + List result = new ArrayList<>(16); + + for (int i = 0; i < list.size(); i++) { + // 创建实例-->赋值-->字典处理 + Object data = list.get(i); + try { + data = JSON.parseObject(JSON.toJSONString(data), actualType); + }catch (Exception e){ + // 转换出错不处理 + } + + // 处理后的数据 + Object pds = this.parseObject(data); + result.add(pds); + } + + return result; + } + + /** + * 翻译实现 + * + * @param code + * @param text + * @param table + * @param key + * @return + */ + private String translateDictValue(String code, String text, String table, String key) { + if (StringUtils.isEmpty(key)) { + return null; + } + try { + // 翻译值 + String dictText = null; + if (!StringUtils.isEmpty(table)) { + dictText = sysDictService.findDict(table, text, code, key.trim()); + } + + if (!StringUtils.isEmpty(dictText)) { + return dictText; + } + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + /** + * 判断是否基本类型 + * + * @param clazz + * @return + */ + private boolean isBaseType(Class clazz) { + + + // 基础数据类型 + if (clazz.equals(java.lang.Integer.class) || + clazz.equals(java.lang.Byte.class) || + clazz.equals(java.lang.Long.class) || + clazz.equals(java.lang.Double.class) || + clazz.equals(java.lang.Float.class) || + clazz.equals(java.lang.Character.class) || + clazz.equals(java.lang.Short.class) || + clazz.equals(java.lang.Boolean.class)) { + return true; + } + + // String类型 + if (clazz.equals(java.lang.String.class)) { + return true; + } + + // 数字 + if (clazz.equals(java.lang.Number.class)) { + return true; + } + + return false; + } + + +} diff --git a/exam-api1/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java b/exam-api1/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java new file mode 100644 index 0000000..6b958ca --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/aspect/mybatis/QueryInterceptor.java @@ -0,0 +1,144 @@ +package com.yf.exam.aspect.mybatis; + +import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +import lombok.extern.log4j.Log4j2; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Plugin; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.reflection.DefaultReflectorFactory; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.apache.shiro.SecurityUtils; + +import java.io.StringReader; +import java.sql.Connection; +import java.util.Properties; + +/** + * 查询拦截器,用于拦截处理通用的信息、如用户ID、多租户信息等; + * 特别注意:此处继承了PaginationInterceptor分页,分页必须在拦截数据后执行,否则容易出现分页不准确,分页计数大于实际数量等问题 + * @author bool + */ +@Log4j2 +@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),}) +public class QueryInterceptor extends PaginationInterceptor implements Interceptor { + + /** + * 客户ID + */ + private static final String USER_FILTER = "{{userId}}"; + + + + @Override + public Object intercept(Invocation invocation) throws Throwable { + + StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); + MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); + MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); + + //sql语句类型 + SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); + + // 只过滤查询的 + if (SqlCommandType.SELECT == sqlCommandType) { + // 获得原始SQL + String sql = statementHandler.getBoundSql().getSql(); + + // 不处理 + if(!sql.contains(USER_FILTER)){ + return super.intercept(invocation); + } + // 处理SQL语句 + String outSql = this.parseSql(sql); + // 设置SQL + metaObject.setValue("delegate.boundSql.sql", outSql); + // 再分页 + return super.intercept(invocation); + } + + return invocation.proceed(); + } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + + } + + + + /** + * 获取当前登录用户 + * @return + */ + private SysUserLoginDTO getLoginUser() { + + try { + return SecurityUtils.getSubject().getPrincipal() != null ? (SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal() : null; + } catch (Exception e) { + return null; + } + } + + /** + * 替换用户ID + * @param sql + * @return + */ + private String processUserId(String sql) { + + // 当前用户 + SysUserLoginDTO user = this.getLoginUser(); + String userId = user.getId(); + if(StringUtils.isNotBlank(userId)){ + return sql.replace(USER_FILTER, userId); + } + return null; + } + + /** + * 处理注入用户信息 + * @param src + * @return + */ + private String parseSql(String src) { + + CCJSqlParserManager parserManager = new CCJSqlParserManager(); + try { + + Select select = (Select) parserManager.parse(new StringReader(src)); + PlainSelect selectBody = (PlainSelect) select.getSelectBody(); + + // 过滤客户 + String sql = selectBody.toString(); + + // 过滤用户ID + sql = this.processUserId(sql); + + // 获得SQL + return sql; + + } catch (Exception e) { + e.printStackTrace(); + } + + return src; + } + + +} diff --git a/exam-api1/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java b/exam-api1/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java new file mode 100644 index 0000000..8baae8f --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/aspect/mybatis/UpdateInterceptor.java @@ -0,0 +1,80 @@ +package com.yf.exam.aspect.mybatis; + +import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Plugin; +import org.apache.ibatis.plugin.Signature; + +import java.lang.reflect.Field; +import java.sql.Timestamp; +import java.util.Objects; +import java.util.Properties; + +/** + * 自动给创建时间个更新时间加值 + * @author bool + */ +@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) +public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor { + + /** + * 创建时间 + */ + private static final String CREATE_TIME = "createTime"; + /** + * 更新时间 + */ + private static final String UPDATE_TIME = "updateTime"; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; + // SQL操作命令 + SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); + // 获取新增或修改的对象参数 + Object parameter = invocation.getArgs()[1]; + // 获取对象中所有的私有成员变量(对应表字段) + Field[] declaredFields = parameter.getClass().getDeclaredFields(); + if (parameter.getClass().getSuperclass() != null) { + Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields(); + declaredFields = ArrayUtils.addAll(declaredFields, superField); + } + + String fieldName = null; + for (Field field : declaredFields) { + fieldName = field.getName(); + if (Objects.equals(CREATE_TIME, fieldName)) { + if (SqlCommandType.INSERT.equals(sqlCommandType)) { + field.setAccessible(true); + field.set(parameter, new Timestamp(System.currentTimeMillis())); + } + } + if (Objects.equals(UPDATE_TIME, fieldName)) { + if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { + field.setAccessible(true); + field.set(parameter, new Timestamp(System.currentTimeMillis())); + + } + } + } + return invocation.proceed(); + } + + @Override + public Object plugin(Object target) { + if (target instanceof Executor) { + return Plugin.wrap(target, this); + } + return target; + } + + @Override + public void setProperties(Properties properties) { + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java b/exam-api1/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java new file mode 100644 index 0000000..4b47fee --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/aspect/utils/InjectUtils.java @@ -0,0 +1,99 @@ +package com.yf.exam.aspect.utils; + +import com.alibaba.fastjson.JSON; +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.ApiRest; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; + +/** + * 注入工具类 + * @author bool + * @date 2019-07-17 09:32 + */ +@Log4j2 +@Component +public class InjectUtils { + + + + /** + * 给对象字段赋值 + * + * @param object 赋值的对象 + * @param value 值 + * @param fields 字段 + * @throws Exception 异常 + */ + public void setValue(Object object, Object value, String... fields) throws Exception { + + //设置同类的属性 + for (String fieldName : fields) { + + //获取当前 + Field field = this.getFiled(object.getClass(), fieldName); + if(field == null){ + continue; + } + + field.setAccessible(true); + field.set(object, value); + } + + } + + /** + * 获取字段名对应的字段 + * + * @param clazz 目标类 + * @param fieldName 字段名 + */ + private Field getFiled(Class clazz, String fieldName) { + + System.out.println("注入的类:"+clazz.toString()); + + //是否具有包含关系 + try { + //获取当前类的属性 + return clazz.getDeclaredField(fieldName); + }catch (Exception e){ + + log.error(clazz.toString() + ": not exist field, try superclass " + fieldName); + + //如果为空且存在父类,则往上找 + if(clazz.getSuperclass()!=null){ + return this.getFiled(clazz.getSuperclass(), fieldName); + } + + return null; + } + } + + + /** + * 打印结果返回 + * @param response + * @throws IOException + */ + public static void restError(HttpServletResponse response) { + + try { + + //固定错误 + ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002); + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json"); + response.getWriter().write(JSON.toJSONString(apiRest)); + response.getWriter().close(); + + }catch (IOException e){ + + } + + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/config/CorsConfig.java b/exam-api1/src/main/java/com/yf/exam/config/CorsConfig.java new file mode 100644 index 0000000..e88cb08 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/config/CorsConfig.java @@ -0,0 +1,35 @@ +package com.yf.exam.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + + +/** + * 网关全局设置,允许跨域 + * @author bool + * @date 2019-08-13 17:28 + */ + +@Configuration +public class CorsConfig { + + @Bean + public FilterRegistrationBean corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin(CorsConfiguration.ALL); + config.addAllowedHeader(CorsConfiguration.ALL); + config.addAllowedMethod(CorsConfiguration.ALL); + source.registerCorsConfiguration("/**", config); + FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + return bean; + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/config/MultipartConfig.java b/exam-api1/src/main/java/com/yf/exam/config/MultipartConfig.java new file mode 100644 index 0000000..75e6dfa --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/config/MultipartConfig.java @@ -0,0 +1,28 @@ +package com.yf.exam.config; + +import org.springframework.boot.web.servlet.MultipartConfigFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; + +import javax.servlet.MultipartConfigElement; + +/** + * 文件上传配置 + * @author bool + * @date 2019-07-29 16:23 + */ +@Configuration +public class MultipartConfig { + + @Bean + public MultipartConfigElement multipartConfigElement() { + MultipartConfigFactory factory = new MultipartConfigFactory(); + // 单个数据大小 + factory.setMaxFileSize(DataSize.ofMegabytes(5000L)); + /// 总上传数据大小 + factory.setMaxRequestSize(DataSize.ofMegabytes(5000L)); + return factory.createMultipartConfig(); + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/config/MybatisConfig.java b/exam-api1/src/main/java/com/yf/exam/config/MybatisConfig.java new file mode 100644 index 0000000..642fc25 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/config/MybatisConfig.java @@ -0,0 +1,37 @@ +package com.yf.exam.config; + +import com.yf.exam.aspect.mybatis.QueryInterceptor; +import com.yf.exam.aspect.mybatis.UpdateInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Mybatis过滤器配置 + * 注意:必须按顺序进行配置,否则容易出现业务异常 + * @author bool + */ +@Configuration +@MapperScan("com.yf.exam.modules.**.mapper") +public class MybatisConfig { + + /** + * 数据查询过滤器 + */ + @Bean + public QueryInterceptor queryInterceptor() { + QueryInterceptor query = new QueryInterceptor(); + query.setLimit(-1L); + return query; + } + + /** + * 插入数据过滤器 + */ + @Bean + public UpdateInterceptor updateInterceptor() { + return new UpdateInterceptor(); + } + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/config/ScheduledConfig.java b/exam-api1/src/main/java/com/yf/exam/config/ScheduledConfig.java new file mode 100644 index 0000000..b08f96f --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/config/ScheduledConfig.java @@ -0,0 +1,77 @@ +package com.yf.exam.config; + +import lombok.extern.log4j.Log4j2; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 任务调度配置 + * @author bool + */ +@Log4j2 +@Configuration +@EnableScheduling +@EnableAsync +public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer { + + /** + * 定时任务使用的线程池 + * @return + */ + @Bean(destroyMethod = "shutdown", name = "taskScheduler") + public ThreadPoolTaskScheduler taskScheduler(){ + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(10); + scheduler.setThreadNamePrefix("task-"); + scheduler.setAwaitTerminationSeconds(600); + scheduler.setWaitForTasksToCompleteOnShutdown(true); + return scheduler; + } + + /** + * 异步任务执行线程池 + * @return + */ + @Bean(name = "asyncExecutor") + public ThreadPoolTaskExecutor asyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setQueueCapacity(1000); + executor.setKeepAliveSeconds(600); + executor.setMaxPoolSize(20); + executor.setThreadNamePrefix("taskExecutor-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + @Override + public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { + ThreadPoolTaskScheduler taskScheduler = taskScheduler(); + scheduledTaskRegistrar.setTaskScheduler(taskScheduler); + } + + @Override + public Executor getAsyncExecutor() { + return asyncExecutor(); + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return (throwable, method, objects) -> { + log.error("异步任务执行出现异常, message {}, emthod {}, params {}", throwable, method, objects); + }; + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/config/ShiroConfig.java b/exam-api1/src/main/java/com/yf/exam/config/ShiroConfig.java new file mode 100644 index 0000000..cbb3f38 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/config/ShiroConfig.java @@ -0,0 +1,127 @@ +package com.yf.exam.config; + +import com.yf.exam.ability.shiro.CNFilterFactoryBean; +import com.yf.exam.ability.shiro.ShiroRealm; +import com.yf.exam.ability.shiro.aop.JwtFilter; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; +import org.apache.shiro.mgt.DefaultSubjectDAO; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * Shiro配置类 + * @author bool + */ +@Slf4j +@Configuration +public class ShiroConfig { + + /** + * Filter Chain定义说明 + * + * 1、一个URL可以配置多个Filter,使用逗号分隔 + * 2、当设置多个过滤器时,全部验证通过,才视为通过 + * 3、部分过滤器可指定参数,如perms,roles + */ + @Bean("shiroFilterFactoryBean") + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean shiroFilterFactoryBean = new CNFilterFactoryBean(); + shiroFilterFactoryBean.setSecurityManager(securityManager); + // 拦截器 + Map map = new LinkedHashMap<>(); + + // 需要排除的一些接口 + map.put("/exam/api/sys/user/login", "anon"); + map.put("/exam/api/sys/user/reg", "anon"); + map.put("/exam/api/sys/user/quick-reg", "anon"); + + // 获取网站基本信息 + map.put("/exam/api/sys/config/detail", "anon"); + + // 文件读取 + map.put("/upload/file/**", "anon"); + + map.put("/", "anon"); + map.put("/v2/**", "anon"); + map.put("/doc.html", "anon"); + map.put("/**/*.js", "anon"); + map.put("/**/*.css", "anon"); + map.put("/**/*.html", "anon"); + map.put("/**/*.svg", "anon"); + map.put("/**/*.pdf", "anon"); + map.put("/**/*.jpg", "anon"); + map.put("/**/*.png", "anon"); + map.put("/**/*.ico", "anon"); + + // 字体 + map.put("/**/*.ttf", "anon"); + map.put("/**/*.woff", "anon"); + map.put("/**/*.woff2", "anon"); + map.put("/druid/**", "anon"); + map.put("/swagger-ui.html", "anon"); + map.put("/swagger**/**", "anon"); + map.put("/webjars/**", "anon"); + + // 添加自己的过滤器并且取名为jwt + Map filterMap = new HashMap(1); + filterMap.put("jwt", new JwtFilter()); + shiroFilterFactoryBean.setFilters(filterMap); + map.put("/**", "jwt"); + + shiroFilterFactoryBean.setFilterChainDefinitionMap(map); + return shiroFilterFactoryBean; + } + + @Bean("securityManager") + public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(myRealm); + DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); + DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); + defaultSessionStorageEvaluator.setSessionStorageEnabled(false); + subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); + securityManager.setSubjectDAO(subjectDAO); + return securityManager; + } + + /** + * 下面的代码是添加注解支持 + * @return + */ + @Bean + @DependsOn("lifecycleBeanPostProcessor") + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); + defaultAdvisorAutoProxyCreator.setUsePrefix(true); + defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor"); + return defaultAdvisorAutoProxyCreator; + } + + @Bean + public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); + advisor.setSecurityManager(securityManager); + return advisor; + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/config/SwaggerConfig.java b/exam-api1/src/main/java/com/yf/exam/config/SwaggerConfig.java new file mode 100644 index 0000000..d4208aa --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/config/SwaggerConfig.java @@ -0,0 +1,65 @@ +package com.yf.exam.config; + +import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; +import io.swagger.annotations.ApiOperation; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.Collections; + +/** + * Swagger配置 + * @author bool + * @date 2020/8/19 20:53 + */ +@Configuration +@EnableSwagger2 +@EnableSwaggerBootstrapUI +@ConfigurationProperties(prefix = "swagger") +public class SwaggerConfig { + + + @Bean + public Docket examApi() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) + .groupName("考试模块接口") + .select() + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + .paths(PathSelectors.ant("/exam/api/**")) + .build() + .securitySchemes(Collections.singletonList(securityScheme())); + } + + + + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title("考试系统接口") + .description("考试系统接口") + .contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com")) + .version("1.0.0") + .build(); + } + + + /** + * 授权头部 + * @return + */ + @Bean + SecurityScheme securityScheme() { + return new ApiKey("token", "token", "header"); + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/annon/Dict.java b/exam-api1/src/main/java/com/yf/exam/core/annon/Dict.java new file mode 100644 index 0000000..141492a --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/annon/Dict.java @@ -0,0 +1,33 @@ +package com.yf.exam.core.annon; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据字典注解 + * 用于标记字段与数据字典的映射关系,实现字典值的转换和显示 + * @author bool + */ +// 注解只能用于字段上 +@Target(ElementType.FIELD) +//注解在运行时保留,可以通过反射机制读取 +@Retention(RetentionPolicy.RUNTIME) +public @interface Dict { + /** + * 字典类型编码 + * 指定数据字典的类型编码,用于从字典表中查询对应的字典项 + */ + String dicCode(); + /** + * 字典显示文本 + * 字典值对应的显示文本,如果不指定则根据dicCode自动查询 + */ + String dicText() default ""; + /** + * 字典表名 + * 指定字典数据所在的表名,如果不指定则使用默认的字典表 + */ + String dictTable() default ""; +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/ApiError.java b/exam-api1/src/main/java/com/yf/exam/core/api/ApiError.java new file mode 100644 index 0000000..7890d37 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/ApiError.java @@ -0,0 +1,81 @@ +package com.yf.exam.core.api; + + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 全局错误码定义,用于定义接口的响应数据, + * 枚举名称全部使用代码命名,在系统中调用,免去取名难的问题。 + * @author bool + * @date 2019-06-14 21:15 + */ +// 使用Lombok注解生成无参构造器和全参构造器 +@NoArgsConstructor +@AllArgsConstructor +public enum ApiError implements Serializable { + + + /** + * 通用错误,接口参数不全 + */ + ERROR_10010001("参数不全或类型错误!"), + ERROR_10010002("您还未登录,请先登录!"), + ERROR_10010003("数据不存在!"), + ERROR_10010012("图形验证码错误!"), + ERROR_10010013("短信验证码错误!"), + ERROR_10010014("不允许重复评论!"), + + /** + * 考试相关错误 + */ + ERROR_20010001("试题被删除,无法继续考试!"), + ERROR_20010002("您有正在进行的考试!"), + + /** + * 账户相关错误 + */ + ERROR_90010001("账号不存在,请确认!"), + ERROR_90010002("账号或密码错误!"), + ERROR_90010003("至少要包含一个角色!"), + ERROR_90010004("管理员账号无法修改!"), + ERROR_90010005("账号被禁用,请联系管理员!"), + ERROR_90010006("活动用户不足,无法开启竞拍!"), + ERROR_90010007("旧密码不正确,请确认!"), + /** + * 数据相关错误 + */ + + ERROR_60000001("数据不存在!"); + /** + * 错误信息 + * 存储每个错误码对应的详细错误描述 + */ + public String msg; + + /** + * 生成Markdown格式文档,用于更新文档用的 + * 遍历所有错误码枚举,输出为Markdown格式的键值对 + * @param args 命令行参数 + */ + public static void main(String[] args) { + // 遍历枚举中的所有错误码常量 + for (ApiError e : ApiError.values()) { + // 输出格式:'错误码数字':'错误信息' + // 移除ERROR_前缀,只保留数字部分 + System.out.println("'"+e.name().replace("ERROR_", "")+"':'"+e.msg+"',"); + } + } + + /** + * 获取错误码 + * 将枚举名称中的ERROR_前缀移除,返回纯数字的错误码 + * @return 错误码数字,如10010001、20010001等 + */ + public Integer getCode(){ + // 移除ERROR_前缀并将字符串转换为整数 + return Integer.parseInt(this.name().replace("ERROR_", "")); + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/ApiRest.java b/exam-api1/src/main/java/com/yf/exam/core/api/ApiRest.java new file mode 100644 index 0000000..cdb2fb8 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/ApiRest.java @@ -0,0 +1,73 @@ +package com.yf.exam.core.api; + + +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.exception.ServiceException; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 数据结果返回的封装 + * 统一的API响应格式封装类,用于所有接口的返回结果 + * @author bool + * @date 2018/11/20 09:48 + */ +// 使用Lombok注解自动生成getter、setter、toString等方法 +@Data +//使用Lombok注解生成无参数构造器 +@NoArgsConstructor +//Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="接口响应", description="接口响应") +public class ApiRest{ + + /** + * 响应消息 + * 用于返回操作的成功或失败描述信息 + */ + @ApiModelProperty(value = "响应消息") + private String msg; + /** + * 响应代码 + * 0为成功,1为失败,其他为具体错误码 + */ + @ApiModelProperty(value = "响应代码,0为成功,1为失败", required = true) + private Integer code; + + /** + * 请求或响应body + * 泛型数据,用于返回具体的业务数据 + */ + @ApiModelProperty(value = "响应内容") + protected T data; + + /** + * 是否成功 + * 根据响应代码判断操作是否成功 + * @return true表示成功,false表示失败 + */ + public boolean isSuccess(){ + return code.equals(0); + } + + /** + * 构造函数 + * 通过ServiceException异常对象构造响应 + * @param error ServiceException异常实例 + */ + public ApiRest(ServiceException error){ + this.code = error.getCode(); + this.msg = error.getMsg(); + } + + /** + * 构造函数 + * 通过ApiError枚举构造响应 + * @param error ApiError枚举实例 + */ + public ApiRest(ApiError error){ + this.code = error.getCode(); + this.msg = error.msg; + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/controller/BaseController.java b/exam-api1/src/main/java/com/yf/exam/core/api/controller/BaseController.java new file mode 100644 index 0000000..ba6e4f6 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/controller/BaseController.java @@ -0,0 +1,166 @@ +package com.yf.exam.core.api.controller; + + +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.exception.ServiceException; + +/** + * 基础控制器 + * 提供统一的API响应封装方法,包含成功和失败的多种构造方式 + * @author Dav + */ +public class BaseController { + + /** + * 成功默认消息 + */ + private static final Integer CODE_SUCCESS = 0; + private static final String MSG_SUCCESS = "操作成功!"; + + /** + * 失败默认消息 + */ + private static final Integer CODE_FAILURE = 1; + private static final String MSG_FAILURE = "请求失败!"; + + + /** + * 完成消息构造 + * 通用的响应消息构造方法,用于创建ApiRest对象 + * @param code 响应状态码 + * @param message 响应消息 + * @param data 响应数据 + * @param 数据类型泛型 + * @return 封装好的ApiRest响应对象 + */ + protected ApiRest message(Integer code, String message, T data){ + ApiRest response = new ApiRest<>(); + response.setCode(code); + response.setMsg(message); + if(data!=null) { + response.setData(data); + } + return response; + } + + /** + * 请求成功空数据 + * 返回成功的响应,不带任何数据 + * @param 数据类型泛型 + * @return 成功的ApiRest响应对象 + */ + protected ApiRest success(){ + return message(0, "请求成功!", null); + } + + + /** + * 请求成功,通用代码 + * 返回成功的响应,包含自定义消息和数据 + * @param message 自定义成功消息 + * @param data 响应数据 + * @param 数据类型泛型 + * @return 成功的ApiRest响应对象 + */ + protected ApiRest success(String message, T data){ + return message(CODE_SUCCESS, message, data); + } + + + /** + * 请求成功,仅内容 + * 返回成功的响应,使用默认消息和自定义数据 + * @param data 响应数据 + * @param 数据类型泛型 + * @return 成功的ApiRest响应对象 + */ + protected ApiRest success(T data){ + return message(CODE_SUCCESS, MSG_SUCCESS, data); + } + + /** + * 请求失败,完整构造 + * 返回失败的响应,包含自定义状态码、消息和数据 + * @param code 自定义失败状态码 + * @param message 自定义失败消息 + * @param data 响应数据 + * @param 数据类型泛型 + * @return 失败的ApiRest响应对象 + */ + protected ApiRest failure(Integer code, String message, T data){ + return message(code, message, data); + } + + /** + * 请求失败,消息和内容 + * 返回失败的响应,包含自定义消息和数据,使用默认失败状态码 + * @param message 自定义失败消息 + * @param data 响应数据 + * @param 数据类型泛型 + * @return 失败的ApiRest响应对象 + */ + protected ApiRest failure(String message, T data){ + return message(CODE_FAILURE, message, data); + } + + /** + * 请求失败,消息 + * 返回失败的响应,仅包含自定义消息,使用默认失败状态码 + * @param message 自定义失败消息 + * @param 数据类型泛型 + * @return 失败的ApiRest响应对象 + */ + protected ApiRest failure(String message){ + return message(CODE_FAILURE, message, null); + } + + /** + * 请求失败,仅内容 + * 返回失败的响应,包含自定义数据,使用默认失败状态码和消息 + * @param data 响应数据 + * @param 数据类型泛型 + * @return 失败的ApiRest响应对象 + */ + protected ApiRest failure(T data){ + return message(CODE_FAILURE, MSG_FAILURE, data); + } + + + /** + * 请求失败,仅内容 + * 返回失败的响应,使用默认失败状态码和消息,不带数据 + * @param 数据类型泛型 + * @return 失败的ApiRest响应对象 + */ + protected ApiRest failure(){ + return message(CODE_FAILURE, MSG_FAILURE, null); + } + + + + /** + * 请求失败,使用预定义错误码 + * 返回失败的响应,使用ApiError枚举中定义的错误码和消息 + * @param error ApiError枚举实例 + * @param data 响应数据 + * @param 数据类型泛型 + * @return 失败的ApiRest响应对象 + */ + protected ApiRest failure(ApiError error, T data){ + return message(error.getCode(), error.msg, data); + } + + + /** + * 请求失败,使用ServiceException异常 + * 返回失败的响应,从ServiceException中提取错误码和消息 + * @param ex ServiceException异常实例 + * @param 数据类型泛型 + * @return 失败的ApiRest响应对象 + */ + protected ApiRest failure(ServiceException ex){ + ApiRest apiRest = message(ex.getCode(), ex.getMsg(), null); + return apiRest; + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java new file mode 100644 index 0000000..2e91c91 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java @@ -0,0 +1,21 @@ +package com.yf.exam.core.api.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 请求和响应的基础类,用于处理序列化 + * 所有DTO类的基类,提供序列化支持 + * @author dav + * @date 2019/3/16 15:56 + */ +// 使用Lombok注解自动生成getter、setter、toString、equals、hashCode等方法 +@Data +public class BaseDTO implements Serializable { + /** + * 实现Serializable接口,支持对象序列化 + * 序列化版本号由JVM自动生成 + * 所有DTO类都应继承此类,确保网络传输和持久化的兼容性 + */ +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java new file mode 100644 index 0000000..a977df3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java @@ -0,0 +1,35 @@ +package com.yf.exam.core.api.dto; + +import com.yf.exam.core.api.dto.BaseDTO; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +/** + *

+ * 主键通用请求类,用于根据ID查询 + *

+ * + * @author 聪明笨狗 + * @since 2019-04-20 12:15 + */ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +@ApiModel(value="主键通用请求类", description="主键通用请求类") +public class BaseIdReqDTO extends BaseDTO { + /** + * 主键ID + * Swagger文档中标记为必填字段 + */ + + @ApiModelProperty(value = "主键ID", required=true) + private String id; + /** + * 用户ID + * 使用JsonIgnore注解,在JSON序列化时忽略此字段 + * 避免将用户ID暴露给前端 + */ + @JsonIgnore + private String userId; + +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java new file mode 100644 index 0000000..18d0af3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java @@ -0,0 +1,35 @@ +package com.yf.exam.core.api.dto; + +import com.yf.exam.core.api.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 主键通用响应类,用于添加后返回内容 + * 通常在新增操作完成后返回生成的主键ID + *

+ * + * @author 聪明笨狗 + * @since 2019-04-20 12:15 + */ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +//Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="主键通用响应类", description="主键通用响应类") +//Lombok注解,生成全参数构造器 +@AllArgsConstructor +//Lombok注解,生成无参数构造器 +@NoArgsConstructor +public class BaseIdRespDTO extends BaseDTO { + /** + * 主键ID + * 用于返回新增或操作成功后生成的主键标识 + * Swagger文档中标记为必填字段 + */ + @ApiModelProperty(value = "主键ID", required=true) + private String id; +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java new file mode 100644 index 0000000..eb262d6 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java @@ -0,0 +1,36 @@ +package com.yf.exam.core.api.dto; + +import com.yf.exam.core.api.dto.BaseDTO; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; +/** + * 通用ID列表类操作,用于批量删除、修改状态等 + * 支持批量操作多个ID的场景 + * @author bool + * @date 2019-08-01 19:07 + */ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +//Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="删除参数", description="删除参数") +public class BaseIdsReqDTO extends BaseDTO { + + /** + * 用户ID + * 使用JsonIgnore注解,在JSON序列化时忽略此字段 + * 避免将用户ID暴露给前端,通常在后端通过其他方式设置 + */ + @JsonIgnore + private String userId; + /** + * ID列表 + * 用于存储要操作的多个ID,支持批量处理 + * Swagger文档中标记为必填字段 + */ + @ApiModelProperty(value = "要删除的ID列表", required = true) + private List ids; +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java new file mode 100644 index 0000000..8f4befe --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java @@ -0,0 +1,44 @@ +package com.yf.exam.core.api.dto; + +import com.yf.exam.core.api.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + *

+ * 通用状态请求类,用于修改状态什么的 + *

+ * + * @author 聪明笨狗 + * @since 2019-04-20 12:15 + */ +@Data +//Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="通用状态请求类", description="通用状态请求类") +//Lombok注解,生成全参数构造器 +@AllArgsConstructor +//Lombok注解,生成无参数构造器 +@NoArgsConstructor +public class BaseStateReqDTO extends BaseDTO { + /** + * 要修改状态的ID列表 + * 支持批量操作多个对象 + * Swagger文档中标记为必填字段 + */ + + @ApiModelProperty(value = "要修改对象的ID列表", required=true) + private List ids; + /** + * 通用状态值 + * 0为正常,1为禁用 + * 可根据业务需求扩展其他状态值 + * Swagger文档中标记为必填字段 + */ + @ApiModelProperty(value = "通用状态,0为正常,1为禁用", required=true) + private Integer state; +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java b/exam-api1/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java new file mode 100644 index 0000000..01387a3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java @@ -0,0 +1,69 @@ +package com.yf.exam.core.api.dto; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 分页查询类 + * 通用分页查询参数封装,支持带查询条件的分布查询 + * @param 查询参数类型泛型 + * @author bool + */ +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="分页参数", description="分页参数") +//使用Lombok注解自动生成getter、setter等方法 +@Data +public class PagingReqDTO { + + /** + * 当前页码 + * 从1开始计数 + */ + @ApiModelProperty(value = "当前页码", required = true, example = "1") + private Integer current; + + /** + * 每页数量 + * 每页显示的记录数 + */ + @ApiModelProperty(value = "每页数量", required = true, example = "10") + private Integer size; + /** + * 查询参数 + * 泛型参数,用于封装具体的查询条件 + */ + @ApiModelProperty(value = "查询参数") + private T params; + /** + * 排序字符 + * 用于指定排序字段和排序方式,如:"create_time desc" + */ + @ApiModelProperty(value = "排序字符") + private String orderBy; + /** + * 当前用户的ID + * 使用JsonIgnore注解,在JSON序列化时忽略此字段 + * 通常在后端通过安全上下文自动设置 + */ + @JsonIgnore + @ApiModelProperty(value = "当前用户的ID") + private String userId; + /** + * 转换成MyBatis的简单分页对象 + * 将当前分页参数转换为MyBatis-Plus的Page对象 + * @return MyBatis-Plus的Page分页对象 + */ + public Page toPage(){ + Page page = new Page(); + // 设置当前页码 + page.setCurrent(this.current); + // 设置每页数量 + page.setSize(this.size); + return page; + } + + +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java b/exam-api1/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java new file mode 100644 index 0000000..4635366 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java @@ -0,0 +1,36 @@ +package com.yf.exam.core.api.dto; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +/** + * 分页响应类 + * 扩展MyBatis-Plus的Page类,自定义分页计算逻辑 + * @author bool + * @date 2019-07-20 15:17 + * @param 数据类型泛型 + */ +public class PagingRespDTO extends Page { + + /** + * 获取页面总数量 + * 重写父类的分页计算方法,确保总页数计算准确 + * 处理总记录数除以每页大小的余数情况 + * @return 总页数 + */ + @Override + public long getPages() { + // 如果每页大小为0,则总页数为0 + if (this.getSize() == 0L) { + return 0L; + } else { + // 计算基础页数:总记录数 / 每页大小 + long pages = this.getTotal() / this.getSize(); + // 如果有余数,页数加1 + if (this.getTotal() % this.getSize() != 0L) { + ++pages; + } + return pages; + } + } + +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java b/exam-api1/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java new file mode 100644 index 0000000..3b17500 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/api/utils/JsonConverter.java @@ -0,0 +1,60 @@ +package com.yf.exam.core.api.utils; + +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +/** + * JSON数据转换器,用于转换返回消息的格式 + * 配置FastJson作为Spring MVC的JSON消息转换器 + * @author dav + * @date 2018/9/11 19:30 + */ +public class JsonConverter { + /** + * FastJson消息转换器 + * 创建并配置FastJson的HTTP消息转换器 + * 用于替换Spring默认的Jackson转换器 + * + * @return 配置好的FastJson HTTP消息转换器实例 + */ + public static HttpMessageConverter fastConverter() { + // 定义一个convert转换消息的对象 + FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); + // 添加FastJson的配置信息 + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + // 设置序列化特性配置 + // 默认转换器配置多个序列化特性 + fastJsonConfig.setSerializerFeatures( + // 格式化输出,美化JSON格式 + SerializerFeature.PrettyFormat, + // 将null数字转换为0 + SerializerFeature.WriteNullNumberAsZero, + // 对Map的key进行排序 + SerializerFeature.MapSortField, + // 将null字符串转换为空字符串 + SerializerFeature.WriteNullStringAsEmpty, + // 禁用循环引用检测 + SerializerFeature.DisableCircularReferenceDetect, + // 使用日期格式输出 + SerializerFeature.WriteDateUseDateFormat, + // 将null列表转换为空数组 + SerializerFeature.WriteNullListAsEmpty); + // 设置字符编码为UTF-8 + fastJsonConfig.setCharset(Charset.forName("UTF-8")); + // 处理中文乱码问题 + List fastMediaTypes = new ArrayList<>(); + // 支持APPLICATION_JSON_UTF8媒体类型,解决中文乱码 + fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); + fastConverter.setSupportedMediaTypes(fastMediaTypes); + // 在convert中添加配置信息 + fastConverter.setFastJsonConfig(fastJsonConfig); + + return fastConverter; + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/core/enums/CommonState.java b/exam-api1/src/main/java/com/yf/exam/core/enums/CommonState.java new file mode 100644 index 0000000..aca8887 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/enums/CommonState.java @@ -0,0 +1,22 @@ +package com.yf.exam.core.enums; + +/** + * 通用的状态枚举信息 + * 定义系统中通用的状态常量,用于统一状态管理 + * @author bool + * @date 2019-09-17 17:57 + */ +public interface CommonState { + + /** + * 普通状态,正常的 + * 表示对象处于正常可用状态,如:启用、上架、正常等 + */ + Integer NORMAL = 0; + + /** + * 非正常状态,禁用,下架等 + * 表示对象处于不可用状态,如:禁用、下架、删除等 + */ + Integer ABNORMAL = 1; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/enums/OpenType.java b/exam-api1/src/main/java/com/yf/exam/core/enums/OpenType.java new file mode 100644 index 0000000..4036b94 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/enums/OpenType.java @@ -0,0 +1,21 @@ +package com.yf.exam.core.enums; + +/** + * 开放方式 + * 定义系统中资源或功能的开放范围类型 + * @author bool + */ +public interface OpenType { + + /** + * 完全开放 + * 表示资源或功能对所有用户开放,无权限限制 + */ + Integer OPEN = 1; + + /** + * 部门开放 + * 表示资源或功能仅对特定部门或组织内的用户开放 + */ + Integer DEPT_OPEN = 2; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/exception/ServiceException.java b/exam-api1/src/main/java/com/yf/exam/core/exception/ServiceException.java new file mode 100644 index 0000000..a5a1ead --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/exception/ServiceException.java @@ -0,0 +1,63 @@ +package com.yf.exam.core.exception; + +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.ApiRest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 服务异常类 + * 自定义业务异常,用于统一处理业务逻辑中的异常情况 + */ +// 使用Lombok注解自动生成getter、setter、toString等方法 +@Data +// 使用Lombok注解生成全参数构造器 +@AllArgsConstructor +// 使用Lombok注解生成无参数构造器 +@NoArgsConstructor +public class ServiceException extends RuntimeException{ + + /** + * 错误码 + * 用于标识具体的错误类型,便于前端处理 + */ + private Integer code; + + /** + * 错误消息 + * 错误的详细描述信息,用于展示给用户或日志记录 + */ + private String msg; + + /** + * 从结果初始化 + * 通过ApiRest响应对象构造异常 + * @param apiRest ApiRest响应对象 + */ + public ServiceException(ApiRest apiRest){ + this.code = apiRest.getCode(); + this.msg = apiRest.getMsg(); + } + + /** + * 从枚举中获取参数 + * 通过ApiError枚举构造异常 + * @param apiError ApiError枚举实例 + */ + public ServiceException(ApiError apiError){ + this.code = apiError.getCode(); + this.msg = apiError.msg; + } + + /** + * 异常构造 + * 通过错误消息构造异常,默认错误码为1 + * @param msg 错误消息 + */ + public ServiceException(String msg){ + this.code = 1; + this.msg = msg; + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java b/exam-api1/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java new file mode 100644 index 0000000..a4ee0c0 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/exception/ServiceExceptionHandler.java @@ -0,0 +1,55 @@ +package com.yf.exam.core.exception; + +import com.yf.exam.core.api.ApiRest; +import org.springframework.http.HttpStatus; +import org.springframework.ui.Model; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.*; + +/** + * 统一异常处理类 + * 全局异常处理器,用于统一处理Controller层抛出的异常 + * @author bool + * @date 2019-06-21 19:27 + */ +// Spring注解,声明为全局RestController异常处理器 +@RestControllerAdvice +public class ServiceExceptionHandler { + + /** + * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器 + * 可以用于注册自定义的属性编辑器或验证器 + * @param binder 数据绑定器 + */ + @InitBinder + public void initWebBinder(WebDataBinder binder){ + // 此处可以添加自定义的数据绑定逻辑 + // 如注册自定义的属性编辑器、验证器等 + } + + /** + * 把值绑定到Model中,使全局@RequestMapping可以获取到该值 + * 在所有Controller方法执行前,向Model中添加公共属性 + * @param model Spring MVC的Model对象 + */ + @ModelAttribute + public void addAttribute(Model model) { + // 此处可以向Model中添加全局共享的属性 + // 这些属性在所有Controller的RequestMapping方法中都可访问 + } + + /** + * 捕获ServiceException + * 专门处理自定义的业务异常ServiceException + * @param e 捕获到的ServiceException异常对象 + * @return 统一的API响应格式 + */ + @ExceptionHandler({com.yf.exam.core.exception.ServiceException.class}) + // 设置HTTP响应状态码为200,业务错误通过code字段区分 + @ResponseStatus(HttpStatus.OK) + public ApiRest serviceExceptionHandler(ServiceException e) { + // 将ServiceException转换为统一的API响应格式 + return new ApiRest(e); + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/BeanMapper.java b/exam-api1/src/main/java/com/yf/exam/core/utils/BeanMapper.java new file mode 100644 index 0000000..248a5be --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/BeanMapper.java @@ -0,0 +1,83 @@ +package com.yf.exam.core.utils; + +import org.dozer.DozerBeanMapper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + + +/** + * 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现: + * + * 1. 持有Mapper的单例. + * 2. 返回值类型转换. + * 3. 批量转换Collection中的所有对象. + * 4. 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数. + * + * 对象映射工具类,封装Dozer框架,提供便捷的Bean转换功能 + */ +public class BeanMapper { + + /** + * 持有Dozer单例, 避免重复创建DozerMapper消耗资源. + * 静态实例,整个应用共享,提高性能 + */ + private static DozerBeanMapper dozerBeanMapper = new DozerBeanMapper(); + + /** + * 基于Dozer转换对象的类型. + * 将源对象转换为目标类型的对象(创建新实例) + * @param source 源对象 + * @param destinationClass 目标类型Class + * @param 目标类型泛型 + * @return 转换后的目标类型对象 + */ + public static T map(Object source, Class destinationClass) { + return dozerBeanMapper.map(source, destinationClass); + } + + /** + * 基于Dozer转换Collection中对象的类型. + * 批量转换集合中的对象到目标类型 + * @param sourceList 源对象集合 + * @param destinationClass 目标类型Class + * @param 目标类型泛型 + * @return 转换后的目标类型对象列表 + */ + public static List mapList(Iterable sourceList, Class destinationClass) { + List destinationList = new ArrayList(); + for (Object sourceObject : sourceList) { + T destinationObject = dozerBeanMapper.map(sourceObject, destinationClass); + destinationList.add(destinationObject); + } + return destinationList; + } + + /** + * 基于Dozer将对象A的值拷贝到对象B中. + * 将源对象的属性值复制到已存在的目标对象中 + * @param source 源对象 + * @param destinationObject 目标对象(已存在实例) + */ + public static void copy(Object source, Object destinationObject) { + if(source!=null) { + dozerBeanMapper.map(source, destinationObject); + } + } + + /** + * 使用Java 8 Function接口转换集合 + * 提供更灵活的集合转换方式,支持自定义映射逻辑 + * @param source 源集合 + * @param mapper 映射函数 + * @param 目标类型泛型 + * @param 源类型泛型 + * @return 转换后的目标类型对象列表 + */ + public static List mapList(Collection source, Function mapper) { + return source.stream().map(mapper).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/CronUtils.java b/exam-api1/src/main/java/com/yf/exam/core/utils/CronUtils.java new file mode 100644 index 0000000..311a580 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/CronUtils.java @@ -0,0 +1,45 @@ +package com.yf.exam.core.utils; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 时间转换quartz表达式 + * 提供日期时间与Quartz Cron表达式之间的转换功能 + * @author bool + * @date 2020/11/29 下午3:00 + */ +public class CronUtils { + + /** + * 格式化数据 + * Quartz Cron表达式的时间格式:秒 分 时 日 月 星期 年 + * 格式说明: + * - ss: 秒 (0-59) + * - mm: 分 (0-59) + * - HH: 时 (0-23) + * - dd: 日 (1-31) + * - MM: 月 (1-12) + * - ? : 星期忽略(与日期冲突时使用) + * - yyyy: 年 + */ + private static final String DATE_FORMAT = "ss mm HH dd MM ? yyyy"; + + /** + * 准确的时间点到表达式 + * 将具体的日期时间转换为Quartz Cron表达式 + * 用于在指定精确时间执行任务 + * @param date 需要转换的日期时间对象 + * @return Quartz Cron表达式字符串,如果date为null则返回空字符串 + */ + public static String dateToCron(final Date date){ + // 创建指定格式的日期格式化器 + SimpleDateFormat fmt = new SimpleDateFormat(DATE_FORMAT); + String formatTimeStr = ""; + if (date != null) { + // 将Date对象格式化为Cron表达式字符串 + formatTimeStr = fmt.format(date); + } + return formatTimeStr; + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/DateUtils.java b/exam-api1/src/main/java/com/yf/exam/core/utils/DateUtils.java new file mode 100644 index 0000000..5638ce8 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/DateUtils.java @@ -0,0 +1,117 @@ +package com.yf.exam.core.utils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * 日期处理工具类 + * 提供日期计算、格式化、解析等常用日期操作功能 + * ClassName: DateUtils
+ * date: 2018年12月13日 下午6:34:02
+ * + * @author Bool + * @version + */ +public class DateUtils { + + /** + * + * calcExpDays:计算某个日期与当前日期相差的天数,如果计算的日期大于现在时间,将返回负数;否则返回正数
+ * 用于计算两个日期之间的天数差异,常用于计算过期天数、剩余天数等场景 + * @author Bool + * @param userCreateTime 需要比较的日期 + * @return 相差的天数(正数表示过去的天数,负数表示未来的天数) + * @since JDK 1.6 + */ + public static int calcExpDays(Date userCreateTime){ + + // 创建目标日期的Calendar实例 + Calendar start = Calendar.getInstance(); + start.setTime(userCreateTime); + + // 创建当前日期的Calendar实例 + Calendar now = Calendar.getInstance(); + now.setTime(new Date()); + + // 计算两个日期之间的毫秒数差值 + long l = now.getTimeInMillis() - start.getTimeInMillis(); + // 将毫秒数转换为天数(1000毫秒 * 60秒 * 60分钟 * 24小时) + int days = new Long(l / (1000 * 60 * 60 * 24)).intValue(); + return days; + } + + + /** + * + * dateNow:获取当前时间的字符串格式,根据传入的格式化来展示.
+ * 获取当前系统时间的格式化字符串表示 + * @author Bool + * @param format 日期格式化模式字符串(如:"yyyy-MM-dd HH:mm:ss") + * @return 格式化后的当前日期时间字符串 + */ + public static String dateNow(String format) { + SimpleDateFormat fmt = new SimpleDateFormat(format); + Calendar c = new GregorianCalendar(); + return fmt.format(c.getTime()); + } + + /** + * formatDate:格式化日期,返回指定的格式
+ * 将Date对象格式化为指定格式的字符串 + * @author Bool + * @param time 需要格式化的日期对象 + * @param format 日期格式化模式字符串 + * @return 格式化后的日期时间字符串 + */ + public static String formatDate(Date time, String format) { + SimpleDateFormat fmt = new SimpleDateFormat(format); + return fmt.format(time.getTime()); + } + + + + /** + * parseDate:将字符串转换成日期,使用:yyyy-MM-dd HH:mm:ss 来格式化 + * 使用默认格式解析字符串为Date对象 + * @author Bool + * @param date 需要解析的日期字符串 + * @return 解析后的Date对象,解析失败返回null + */ + public static Date parseDate(String date) { + return parseDate(date, "yyyy-MM-dd HH:mm:ss"); + } + + + /** + * + * parseDate:将字符串转换成日期,使用指定格式化来格式化 + * 使用指定格式解析字符串为Date对象 + * @author Bool + * @param date 需要解析的日期字符串 + * @param pattern 日期格式化模式字符串,如果为null则使用默认格式 + * @return 解析后的Date对象,解析失败返回null + */ + public static Date parseDate(String date, String pattern) { + + // 如果模式字符串为空,使用默认格式 + if (pattern==null) { + pattern = "yyyy-MM-dd HH:mm:ss"; + } + + // 创建指定格式的日期格式化器 + SimpleDateFormat fmt = new SimpleDateFormat(pattern); + + try { + // 尝试解析日期字符串 + return fmt.parse(date); + } catch (Exception ex) { + // 解析失败时打印异常堆栈 + ex.printStackTrace(); + } + // 解析失败返回null + return null; + + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/IpUtils.java b/exam-api1/src/main/java/com/yf/exam/core/utils/IpUtils.java new file mode 100644 index 0000000..2142e67 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/IpUtils.java @@ -0,0 +1,76 @@ +package com.yf.exam.core.utils; + + +import javax.servlet.http.HttpServletRequest; + +/** + * IP获取工具类,用户获取网络请求过来的真实IP + * 通过多种HTTP头部信息获取客户端的真实IP地址,处理代理服务器场景 + * ClassName: IpUtils
+ * date: 2018年2月13日 下午7:27:52
+ * + * @author Bool + * @version + */ +public class IpUtils { + + + /** + * + * getClientIp:通过请求获取客户端的真实IP地址 + * 按照优先级从多种HTTP头部中提取客户端真实IP,处理多层代理情况 + * @author Bool + * @param request HTTP请求对象 + * @return 客户端的真实IP地址 + */ + public static String extractClientIp(HttpServletRequest request) { + + String ip = null; + + //X-Forwarded-For:Squid 服务代理 + // 标准代理头部,格式:client, proxy1, proxy2 + String ipAddresses = request.getHeader("X-Forwarded-For"); + + // 如果X-Forwarded-For头部不存在或无效,尝试下一个头部 + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //Proxy-Client-IP:apache 服务代理 + // Apache代理服务器使用的头部 + ipAddresses = request.getHeader("Proxy-Client-IP"); + } + + // 继续尝试其他代理头部 + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //WL-Proxy-Client-IP:weblogic 服务代理 + // WebLogic代理服务器使用的头部 + ipAddresses = request.getHeader("WL-Proxy-Client-IP"); + } + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //HTTP_CLIENT_IP:有些代理服务器 + // 一些非标准代理服务器使用的头部 + ipAddresses = request.getHeader("HTTP_CLIENT_IP"); + } + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //X-Real-IP:nginx服务代理 + // Nginx代理服务器使用的头部 + ipAddresses = request.getHeader("X-Real-IP"); + } + + //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP + // 处理多个IP的情况,取第一个IP作为客户端真实IP + if (ipAddresses != null && ipAddresses.length() != 0) { + ip = ipAddresses.split(",")[0]; + } + + //还是不能获取到,最后再通过request.getRemoteAddr();获取 + // 如果所有代理头部都无法获取IP,使用Servlet API的getRemoteAddr方法 + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + ip = request.getRemoteAddr(); + } + + return ip; + } + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/Reflections.java b/exam-api1/src/main/java/com/yf/exam/core/utils/Reflections.java new file mode 100644 index 0000000..1757109 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/Reflections.java @@ -0,0 +1,403 @@ +/** + * Copyright (c) 2005-2012 springside.org.cn + */ +package com.yf.exam.core.utils; + +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.springframework.util.Assert; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 反射工具类. + * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * @author calvin + * @version 2016-01-15 + */ +@Log4j2 +public class Reflections { + + // Setter方法前缀 + private static final String SETTER_PREFIX = "set"; + + // Getter方法前缀 + private static final String GETTER_PREFIX = "get"; + + // CGLIB代理类分隔符 + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + + /** + * 获取类的所有属性,包括父类 + * 递归遍历类的继承层次结构,获取所有声明的字段 + * + * @param object 目标对象 + * @return 包含所有字段的数组,包括父类的字段 + */ + public static Field[] getAllFields(Object object) { + Class clazz = object.getClass(); + List fieldList = new ArrayList<>(); + // 循环遍历所有父类,直到Object类 + while (clazz != null) { + fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); + clazz = clazz.getSuperclass(); + } + Field[] fields = new Field[fieldList.size()]; + fieldList.toArray(fields); + return fields; + } + + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + * @param obj 目标对象 + * @param propertyName 属性名,支持点分格式(如:user.address.city) + * @return 属性值 + */ + public static Object invokeGetter(Object obj, String propertyName) { + Object object = obj; + // 按点号分割属性路径,逐级调用getter方法 + for (String name : StringUtils.split(propertyName, ".")){ + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + * @param obj 目标对象 + * @param propertyName 属性名,支持点分格式 + * @param value 要设置的值 + */ + public static void invokeSetter(Object obj, String propertyName, Object value) { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + // 遍历属性路径,对中间对象调用getter,对最终属性调用setter + for (int i=0; i[] parameterTypes, + final Object[] args) { + // 获取可访问的方法对象 + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) { + throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); + } + + try { + // 调用方法 + return method.invoke(obj, args); + } catch (Exception e) { + // 将反射异常转换为运行时异常 + throw convertReflectionExceptionToUnchecked(e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + * @param obj 目标对象 + * @param methodName 方法名 + * @param args 参数值数组 + * @return 方法返回值 + */ + public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) { + // 获取可访问的方法对象(仅按方法名匹配) + Method method = getAccessibleMethodByName(obj, methodName); + if (method == null) { + throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); + } + + try { + // 调用方法 + return method.invoke(obj, args); + } catch (Exception e) { + // 将反射异常转换为运行时异常 + throw convertReflectionExceptionToUnchecked(e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * + * 如向上转型到Object仍无法找到, 返回null. + * @param obj 目标对象 + * @param fieldName 字段名 + * @return 可访问的字段对象,找不到返回null + */ + public static Field getAccessibleField(final Object obj, final String fieldName) { + // 参数校验 + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(fieldName, "fieldName can't be blank"); + // 循环向上转型查找字段 + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { + try { + Field field = superClass.getDeclaredField(fieldName); + // 设置字段可访问 + makeAccessible(field); + return field; + } catch (NoSuchFieldException e) {//NOSONAR + // Field不在当前类定义,继续向上转型 + continue;// new add + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + * @param obj 目标对象 + * @param methodName 方法名 + * @param parameterTypes 参数类型数组 + * @return 可访问的方法对象,找不到返回null + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) { + // 参数校验 + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(methodName, "methodName can't be blank"); + + // 循环向上转型查找方法 + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { + try { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + // 设置方法可访问 + makeAccessible(method); + return method; + } catch (NoSuchMethodException e) { + // Method不在当前类定义,继续向上转型 + continue;// new add + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + * @param obj 目标对象 + * @param methodName 方法名 + * @return 可访问的方法对象,找不到返回null + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName) { + // 参数校验 + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(methodName, "methodName can't be blank"); + + // 循环向上转型查找方法(仅按方法名) + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals(methodName)) { + // 设置方法可访问 + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + * @param method 要设置为可访问的方法 + */ + public static void makeAccessible(Method method) { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + * @param field 要设置为可访问的字段 + */ + public static void makeAccessible(Field field) { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier + .isFinal(field.getModifiers())) && !field.isAccessible()) { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + * eg. + * public UserDao extends HibernateDao + * + * @param clazz The class to introspect + * @return the first generic declaration, or Object.class if cannot be determined + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + * + * 如public UserDao extends HibernateDao + * + * @param clazz clazz The class to introspect + * @param index the Index of the generic ddeclaration,start from 0. + * @return the index generic declaration, or Object.class if cannot be determined + */ + public static Class getClassGenricType(final Class clazz, final int index) { + + // 获取父类类型 + Type genType = clazz.getGenericSuperclass(); + + // 检查是否为参数化类型 + if (!(genType instanceof ParameterizedType)) { + log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + // 获取泛型参数类型数组 + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + // 检查索引是否有效 + if (index >= params.length || index < 0) { + log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + // 检查是否为Class类型 + if (!(params[index] instanceof Class)) { + log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + /** + * 获取被AOP代理后的真实类 + * @param instance 实例对象 + * @return 真实类 + */ + public static Class getUserClass(Object instance) { + Assert.notNull(instance, "Instance must not be null"); + Class clazz = instance.getClass(); + // 检查是否为CGLIB代理类 + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { + Class superClass = clazz.getSuperclass(); + // 返回父类(真实类) + if (superClass != null && !Object.class.equals(superClass)) { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + * @param e 反射异常 + * @return 转换后的运行时异常 + */ + public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) { + // 参数或访问异常转换为IllegalArgumentException + return new IllegalArgumentException(e); + } else if (e instanceof InvocationTargetException) { + // 调用目标异常转换为RuntimeException + return new RuntimeException(((InvocationTargetException) e).getTargetException()); + } else if (e instanceof RuntimeException) { + // 已经是运行时异常,直接返回 + return (RuntimeException) e; + } + // 其他受检异常包装为RuntimeException + return new RuntimeException("Unexpected Checked Exception.", e); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/SpringUtils.java b/exam-api1/src/main/java/com/yf/exam/core/utils/SpringUtils.java new file mode 100644 index 0000000..fdff7f9 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/SpringUtils.java @@ -0,0 +1,61 @@ +package com.yf.exam.core.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * Spring获取工具 + * 用于在非Spring管理类中获取Spring容器中的Bean实例 + * 实现ApplicationContextAware接口,在Spring启动时自动注入ApplicationContext + * + * @author bool + * @date 2019-12-09 15:55 + */ +// 声明为Spring组件,由Spring容器管理 +@Component +public class SpringUtils implements ApplicationContextAware { + + /** + * Spring应用上下文对象 + * 静态变量,用于在整个应用中共享ApplicationContext实例 + */ + private static ApplicationContext applicationContext; + + /** + * 实现ApplicationContextAware接口的方法 + * Spring容器在启动时会自动调用此方法,注入ApplicationContext + * @param context Spring应用上下文对象 + * @throws BeansException 如果注入过程中发生异常 + */ + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + // 将Spring注入的ApplicationContext赋值给静态变量 + applicationContext = context; + } + + /** + * 根据类型获取Spring容器中的Bean实例 + * 适用于按类型唯一匹配的Bean获取 + * @param tClass Bean的类型Class对象 + * @param Bean的泛型类型 + * @return Spring容器中指定类型的Bean实例 + */ + public static T getBean(Class tClass) { + return applicationContext.getBean(tClass); + } + + /** + * 根据名称和类型获取Spring容器中的Bean实例 + * 适用于同一类型有多个实现,需要按名称区分的场景 + * @param name Bean的名称(在Spring容器中的标识) + * @param type Bean的类型Class对象 + * @param Bean的泛型类型 + * @return Spring容器中指定名称和类型的Bean实例 + */ + public static T getBean(String name, Class type) { + return applicationContext.getBean(name, type); + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/StringUtils.java b/exam-api1/src/main/java/com/yf/exam/core/utils/StringUtils.java new file mode 100644 index 0000000..0122d90 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/StringUtils.java @@ -0,0 +1,46 @@ +package com.yf.exam.core.utils; + +import java.util.Map; + +/** + * 字符串常用工具类 + * 提供字符串处理和格式转换的常用方法 + * @author bool + * @date 2019-05-15 11:40 + */ +public class StringUtils { + + /** + * 判断是否为空字符 + * 检查字符串是否为null或空字符串 + * @param str 需要检查的字符串 + * @return 如果字符串为null或空字符串返回true,否则返回false + */ + public static boolean isBlank(String str){ + return str==null || "".equals(str); + } + + + /** + * 将MAP转换成一个xml格式,格式为value... + * 将Map中的键值对转换为XML格式字符串,常用于微信支付等接口的数据传输 + * @param params 需要转换的Map对象,键值对将作为XML的标签和内容 + * @return 转换后的XML格式字符串 + */ + public static String mapToXml(Map params){ + // 创建StringBuffer用于高效构建XML字符串 + StringBuffer sb = new StringBuffer(""); + // 遍历Map中的所有键 + for(String key:params.keySet()){ + // 为每个键值对构建XML标签:value + sb.append("<") + .append(key).append(">") + .append(params.get(key)) + .append(""); + } + + // 添加XML结束标签 + sb.append(""); + return sb.toString(); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java b/exam-api1/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java new file mode 100644 index 0000000..5500e84 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/excel/ExportExcel.java @@ -0,0 +1,434 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.yf.exam.core.utils.excel; + +import com.google.common.collect.Lists; +import com.yf.exam.core.utils.Reflections; +import com.yf.exam.core.utils.excel.annotation.ExcelField; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 导出Excel文件(导出"XLSX"格式,支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion) + * 基于注解的Excel导出工具类,支持大数据量处理和样式定制 + * @author jeeplus + * @version 2016-04-21 + */ +public class ExportExcel { + + private static Logger log = LoggerFactory.getLogger(ExportExcel.class); + + /** + * 工作薄对象 + * 使用SXSSFWorkbook支持大数据量导出,避免内存溢出 + */ + private SXSSFWorkbook wb; + + /** + * 工作表对象 + * 当前操作的Sheet页 + */ + private Sheet sheet; + + /** + * 样式列表 + * 存储预定义的单元格样式,提高性能 + */ + private Map styles; + + /** + * 当前行号 + * 记录当前写入的行位置 + */ + private int rownum; + + /** + * 注解列表(Object[]{ ExcelField, Field/Method }) + * 存储实体类的字段注解信息,用于导出映射 + */ + List annotationList = Lists.newArrayList(); + + /** + * 构造函数 + * @param title 表格标题,传"空值",表示无标题 + * @param cls 实体对象,通过annotation.ExportField获取标题 + */ + public ExportExcel(String title, Class cls){ + this(title, cls, 1); + } + + /** + * 构造函数 + * @param title 表格标题,传"空值",表示无标题 + * @param cls 实体对象,通过annotation.ExportField获取标题 + * @param type 导出类型(1:导出数据;2:导出模板) + * @param groups 导入分组 + */ + public ExportExcel(String title, Class cls, int type, int... groups){ + // Get annotation field - 获取字段注解 + Field[] fs = cls.getDeclaredFields(); + for (Field f : fs){ + ExcelField ef = f.getAnnotation(ExcelField.class); + // 检查注解是否存在且类型匹配(0-导入导出,1-仅导出,2-仅导入) + if (ef != null && (ef.type()==0 || ef.type()==type)){ + // 分组检查 - 如果指定了分组,只处理匹配分组的字段 + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, f}); + break; + } + } + } + }else{ + // 未指定分组,添加所有匹配的字段 + annotationList.add(new Object[]{ef, f}); + } + } + } + // Get annotation method - 获取方法注解 + Method[] ms = cls.getDeclaredMethods(); + for (Method m : ms){ + ExcelField ef = m.getAnnotation(ExcelField.class); + // 检查注解是否存在且类型匹配 + if (ef != null && (ef.type()==0 || ef.type()==type)){ + // 分组检查 + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, m}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, m}); + } + } + } + // Field sorting - 字段排序,按照sort值升序排列 + Collections.sort(annotationList, new Comparator() { + @Override + public int compare(Object[] o1, Object[] o2) { + return new Integer(((ExcelField)o1[0]).sort()).compareTo( + new Integer(((ExcelField)o2[0]).sort())); + } + }); + // Initialize - 初始化Excel表头 + List headerList = Lists.newArrayList(); + for (Object[] os : annotationList){ + String t = ((ExcelField)os[0]).title(); + // 如果是导出数据(type=1),则去掉注释部分(用**分隔) + if (type==1){ + String[] ss = StringUtils.split(t, "**", 2); + if (ss.length==2){ + t = ss[0]; + } + } + headerList.add(t); + } + initialize(title, headerList); + } + + /** + * 初始化函数 + * @param title 表格标题,传"空值",表示无标题 + * @param headerList 表头列表 + */ + private void initialize(String title, List headerList) { + // 创建SXSSFWorkbook,设置窗口大小为500行,支持大数据量导出 + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet("Export"); + this.styles = createStyles(wb); + // Create title - 创建标题行 + if (StringUtils.isNotBlank(title)){ + Row titleRow = sheet.createRow(rownum++); + titleRow.setHeightInPoints(30); // 设置行高 + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + // 合并标题单元格,跨所有列 + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), + titleRow.getRowNum(), titleRow.getRowNum(), headerList.size()-1)); + } + // Create header - 创建表头行 + if (headerList == null){ + throw new RuntimeException("headerList not null!"); + } + Row headerRow = sheet.createRow(rownum++); + headerRow.setHeightInPoints(16); + for (int i = 0; i < headerList.size(); i++) { + Cell cell = headerRow.createCell(i); + cell.setCellStyle(styles.get("header")); + String[] ss = StringUtils.split(headerList.get(i), "**", 2); + if (ss.length==2){ + // 标题包含批注的情况:标题**批注 + cell.setCellValue(ss[0]); + // 创建批注 + Comment comment = this.sheet.createDrawingPatriarch().createCellComment( + new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6)); + comment.setString(new XSSFRichTextString(ss[1])); + cell.setCellComment(comment); + }else{ + // 普通标题 + cell.setCellValue(headerList.get(i)); + } + sheet.autoSizeColumn(i); // 自动调整列宽 + } + // 进一步调整列宽,确保最小宽度为3000 + for (int i = 0; i < headerList.size(); i++) { + int colWidth = sheet.getColumnWidth(i)*2; + sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth); + } + log.debug("Initialize success."); + } + + /** + * 创建表格样式 + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) { + Map styles = new HashMap<>(16); + + // Title style - 标题样式:居中、粗体、16号字体 + CellStyle style = wb.createCellStyle(); + style.setAlignment(CellStyle.ALIGN_CENTER); + style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD); + style.setFont(titleFont); + styles.put("title", style); + + // Data style - 数据样式基础:细边框、垂直居中、10号字体 + style = wb.createCellStyle(); + style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + style.setBorderRight(CellStyle.BORDER_THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(CellStyle.BORDER_THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(CellStyle.BORDER_THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(CellStyle.BORDER_THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + // Data1 style - 左对齐数据样式 + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_LEFT); + styles.put("data1", style); + + // Data2 style - 居中对齐数据样式 + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_CENTER); + styles.put("data2", style); + + // Data3 style - 右对齐数据样式 + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_RIGHT); + styles.put("data3", style); + + // Header style - 表头样式:灰色背景、白色粗体文字 + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + // style.setWrapText(true); // 注释掉的自动换行 + style.setAlignment(CellStyle.ALIGN_CENTER); + style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(CellStyle.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD); + headerFont.setColor(IndexedColors.WHITE.getIndex()); + style.setFont(headerFont); + styles.put("header", style); + + return styles; + } + + /** + * 添加一行 + * @return 行对象 + */ + public Row addRow(){ + return sheet.createRow(rownum++); + } + + + /** + * 添加一个单元格 + * @param row 添加的行 + * @param column 添加列号 + * @param val 添加值 + * @return 单元格对象 + */ + public Cell addCell(Row row, int column, Object val){ + return this.addCell(row, column, val, 0, Class.class); + } + + /** + * 添加一个单元格 + * @param row 添加的行 + * @param column 添加列号 + * @param val 添加值 + * @param align 对齐方式(1:靠左;2:居中;3:靠右) + * @return 单元格对象 + */ + public Cell addCell(Row row, int column, Object val, int align, Class fieldType){ + Cell cell = row.createCell(column); + // 根据对齐方式获取对应的样式 + CellStyle style = styles.get("data"+(align>=1&&align<=3?align:"")); + try { + if (val == null){ + cell.setCellValue(""); + } else if (val instanceof String) { + cell.setCellValue((String) val); + } else if (val instanceof Integer) { + cell.setCellValue((Integer) val); + } else if (val instanceof Long) { + cell.setCellValue((Long) val); + } else if (val instanceof Double) { + cell.setCellValue((Double) val); + } else if (val instanceof Float) { + cell.setCellValue((Float) val); + } else if (val instanceof Date) { + // 日期类型特殊处理,设置日期格式 + DataFormat format = wb.createDataFormat(); + style.setDataFormat(format.getFormat("yyyy-MM-dd")); + cell.setCellValue((Date) val); + } else { + // 其他类型通过反射调用对应的Type类的setValue方法 + if (fieldType != Class.class){ + cell.setCellValue((String)fieldType.getMethod("setValue", Object.class).invoke(null, val)); + }else{ + cell.setCellValue((String)Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(), + "fieldtype."+val.getClass().getSimpleName()+"Type")).getMethod("setValue", Object.class).invoke(null, val)); + } + } + } catch (Exception ex) { + // 异常处理:记录日志并使用toString方法 + log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString()); + cell.setCellValue(val.toString()); + } + cell.setCellStyle(style); + return cell; + } + + /** + * 添加数据(通过annotation.ExportField添加数据) + * @return list 数据列表 + */ + public ExportExcel setDataList(List list){ + for (E e : list){ + int colunm = 0; + Row row = this.addRow(); + StringBuilder sb = new StringBuilder(); + for (Object[] os : annotationList){ + ExcelField ef = (ExcelField)os[0]; + Object val = null; + try{ + // 如果指定了value,使用反射调用getter方法 + if (StringUtils.isNotBlank(ef.value())){ + val = Reflections.invokeGetter(e, ef.value()); + }else{ + // 根据注解对象类型调用相应方法 + if (os[1] instanceof Field){ + val = Reflections.invokeGetter(e, ((Field)os[1]).getName()); + }else if (os[1] instanceof Method){ + val = Reflections.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {}); + } + } + }catch(Exception ex) { + log.info(ex.toString()); + val = ""; + } + // 添加单元格,使用注解中定义的对齐方式和字段类型 + this.addCell(row, colunm++, val, ef.align(), ef.fieldType()); + sb.append(val + ", "); + } + log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString()); + } + return this; + } + + /** + * 输出数据流 + * @param os 输出数据流 + */ + public ExportExcel write(OutputStream os) throws IOException{ + wb.write(os); + return this; + } + + /** + * 输出到客户端 + * @param fileName 输出文件名 + */ + public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{ + response.reset(); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setContentType("application/octet-stream; charset=utf-8"); + response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName, "utf-8")); + write(response.getOutputStream()); + return this; + } + + /** + * 清理临时文件 + * 释放SXSSFWorkbook占用的临时文件资源 + */ + public ExportExcel dispose(){ + wb.dispose(); + return this; + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java b/exam-api1/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java new file mode 100644 index 0000000..a6b6f3c --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/excel/ImportExcel.java @@ -0,0 +1,343 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.yf.exam.core.utils.excel; + +import com.google.common.collect.Lists; +import com.yf.exam.core.utils.Reflections; +import com.yf.exam.core.utils.excel.annotation.ExcelField; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.hssf.usermodel.HSSFDateUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +/** + * 导入Excel文件(支持"XLS"和"XLSX"格式) + * 基于注解的Excel导入工具类,支持数据校验和类型转换 + * @author jeeplus + * @version 2016-03-10 + */ +public class ImportExcel { + + private static Logger log = LoggerFactory.getLogger(ImportExcel.class); + + /** + * 工作薄对象 + * 用于操作Excel文件的工作簿 + */ + private Workbook wb; + + /** + * 工作表对象 + * 当前操作的Sheet页 + */ + private Sheet sheet; + + /** + * 标题行号 + * 从0开始计数,用于定位数据起始位置 + */ + private int headerNum; + + + + /** + * 构造函数 + * @param multipartFile 导入文件对象 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @param sheetIndex 工作表编号 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(MultipartFile multipartFile, int headerNum, int sheetIndex) + throws InvalidFormatException, IOException { + this(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), headerNum, sheetIndex); + } + + /** + * 构造函数 + * @param fileName 文件名,用于判断文件格式 + * @param is 导入文件输入流 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @param sheetIndex 工作表编号 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(String fileName, InputStream is, int headerNum, int sheetIndex) + throws IOException { + // 文件名校验 + if (StringUtils.isBlank(fileName)){ + throw new RuntimeException("导入文档为空!"); + }else if(fileName.toLowerCase().endsWith("xls")){ + // 处理xls格式的Excel文件(HSSFWorkbook) + this.wb = new HSSFWorkbook(is); + }else if(fileName.toLowerCase().endsWith("xlsx")){ + // 处理xlsx格式的Excel文件(XSSFWorkbook) + this.wb = new XSSFWorkbook(is); + }else{ + throw new RuntimeException("文档格式不正确!"); + } + // 工作表存在性校验 + if (this.wb.getNumberOfSheets() List getDataList(Class cls, int... groups) throws InstantiationException, IllegalAccessException{ + List annotationList = Lists.newArrayList(); + // Get annotation field - 获取字段注解 + Field[] fs = cls.getDeclaredFields(); + for (Field f : fs){ + ExcelField ef = f.getAnnotation(ExcelField.class); + // 检查注解是否存在且类型匹配(0-导入导出,2-仅导入) + if (ef != null && (ef.type()==0 || ef.type()==2)){ + // 分组检查 + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, f}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, f}); + } + } + } + // Get annotation method - 获取方法注解 + Method[] ms = cls.getDeclaredMethods(); + for (Method m : ms){ + ExcelField ef = m.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==2)){ + // 分组检查 + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, m}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, m}); + } + } + } + // Field sorting - 字段排序,按照sort值升序排列 + Collections.sort(annotationList, new Comparator() { + @Override + public int compare(Object[] o1, Object[] o2) { + return new Integer(((ExcelField)o1[0]).sort()).compareTo( + new Integer(((ExcelField)o2[0]).sort())); + } + }); + // Get excel data - 读取Excel数据 + List dataList = Lists.newArrayList(); + // 从数据起始行到结束行遍历 + for (int i = this.getDataRowNum(); i < this.getLastDataRowNum(); i++) { + // 创建实体对象实例 + E e = (E)cls.newInstance(); + int column = 0; + Row row = this.getRow(i); + StringBuilder sb = new StringBuilder(); + // 遍历注解列表,处理每个字段 + for (Object[] os : annotationList){ + Object val = this.getCellValue(row, column++); + if (val != null){ + ExcelField ef = (ExcelField)os[0]; + // Get param type and type cast - 获取参数类型并进行类型转换 + Class valType = Class.class; + if (os[1] instanceof Field){ + valType = ((Field)os[1]).getType(); + }else if (os[1] instanceof Method){ + Method method = ((Method)os[1]); + if ("get".equals(method.getName().substring(0, 3))){ + valType = method.getReturnType(); + }else if("set".equals(method.getName().substring(0, 3))){ + valType = ((Method)os[1]).getParameterTypes()[0]; + } + } + //log.debug("Import value type: ["+i+","+column+"] " + valType); + try { + // 类型转换处理 + //如果导入的java对象,需要在这里自己进行变换。 + if (valType == String.class){ + // 字符串类型处理,去除数值类型的".0"后缀 + String s = String.valueOf(val.toString()); + if(StringUtils.endsWith(s, ".0")){ + val = StringUtils.substringBefore(s, ".0"); + }else{ + val = String.valueOf(val.toString()); + } + }else if (valType == Integer.class){ + // 整型转换 + val = Double.valueOf(val.toString()).intValue(); + }else if (valType == Long.class){ + // 长整型转换 + val = Double.valueOf(val.toString()).longValue(); + }else if (valType == Double.class){ + // 双精度浮点型转换 + val = Double.valueOf(val.toString()); + }else if (valType == Float.class){ + // 单精度浮点型转换 + val = Float.valueOf(val.toString()); + }else if (valType == Date.class){ + // 日期类型转换 + SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + val=sdf.parse(val.toString()); + }else{ + // 其他自定义类型转换 + if (ef.fieldType() != Class.class){ + // 使用注解指定的字段类型转换器 + val = ef.fieldType().getMethod("getValue", String.class).invoke(null, val.toString()); + }else{ + // 使用默认的类型转换器 + val = Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(), + "fieldtype."+valType.getSimpleName()+"Type")).getMethod("getValue", String.class).invoke(null, val.toString()); + } + } + } catch (Exception ex) { + // 类型转换异常处理 + log.info("Get cell value ["+i+","+column+"] error: " + ex.toString()); + val = null; + } + // set entity value - 设置实体对象值 + if (os[1] instanceof Field){ + // 字段赋值 + Reflections.invokeSetter(e, ((Field)os[1]).getName(), val); + }else if (os[1] instanceof Method){ + // 方法赋值,自动将get方法转换为set方法 + String mthodName = ((Method)os[1]).getName(); + if ("get".equals(mthodName.substring(0, 3))){ + mthodName = "set"+StringUtils.substringAfter(mthodName, "get"); + } + Reflections.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val}); + } + } + sb.append(val+", "); + } + // 将处理好的实体对象添加到结果列表 + dataList.add(e); + log.debug("Read success: ["+i+"] "+sb.toString()); + } + return dataList; + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java b/exam-api1/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java new file mode 100644 index 0000000..4e67b7d --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/excel/annotation/ExcelField.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.yf.exam.core.utils.excel.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解定义 + * 用于标记实体类字段与Excel列的映射关系,支持导入导出功能 + * @author jeeplus + * @version 2016-03-10 + */ +// 注解可以用于方法、字段和类上 +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) +// 注解在运行时保留,可以通过反射读取 +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcelField { + + /** + * 导出字段名(默认调用当前字段的"get"方法,如指定导出字段为对象,请填写"对象名.对象属性",例:"area.name"、"office.name") + * 如果为空,则默认使用字段名作为导出字段名 + */ + String value() default ""; + + /** + * 导出字段标题(需要添加批注请用"**"分隔,标题**批注,仅对导出模板有效) + * Excel表格中显示的列标题,支持添加批注信息 + */ + String title(); + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + * 控制字段在Excel操作中的行为:0-既导出又导入,1-仅导出,2-仅导入 + */ + int type() default 0; + + /** + * 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右) + * 设置Excel单元格内容的对齐方式 + */ + int align() default 0; + + /** + * 导出字段字段排序(升序) + * 数值越小,在Excel中的列位置越靠前 + */ + int sort() default 0; + + /** + * 如果是字典类型,请设置字典的type值 + * 用于字典数据的转换,将字典值转换为显示文本 + */ + String dictType() default ""; + + /** + * 反射类型 + * 字段的数据类型,用于反射操作和数据转换 + */ + Class fieldType() default Class.class; + + /** + * 字段归属组(根据分组导出导入) + * 可以通过分组控制哪些字段参与导出导入操作 + */ + int[] groups() default {}; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java b/exam-api1/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java new file mode 100644 index 0000000..45b64f7 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.yf.exam.core.utils.excel.fieldtype; + +import com.google.common.collect.Lists; +import com.yf.exam.core.utils.StringUtils; + +import java.util.List; + +/** + * 字段类型转换 + * 处理List集合与字符串之间的相互转换,用于Excel导入导出 + * @author jeeplus + * @version 2016-5-29 + */ +public class ListType { + + /** + * 获取对象值(导入) + * 将逗号分隔的字符串转换为List集合,用于Excel导入时处理多值字段 + * @param val 逗号分隔的字符串,如:"value1,value2,value3" + * @return List 集合对象 + */ + public static Object getValue(String val) { + // 创建空的ArrayList + List list = Lists.newArrayList(); + // 检查输入字符串不为空且不为空白 + if(!StringUtils.isBlank(val)) { + // 按逗号分割字符串并遍历每个部分 + for (String s : val.split(",")) { + // 将每个分割后的字符串添加到List中 + list.add(s); + } + } + return list; + } + + /** + * 设置对象值(导出) + * 将List集合转换为逗号分隔的字符串,用于Excel导出时显示多值字段 + * @param val List 集合对象 + * @return 逗号分隔的字符串,如:"value1,value2,value3" + */ + public static String setValue(Object val) { + // 检查输入对象不为空 + if (val != null){ + // 将Object强制转换为List + List list = (List)val; + // 用于构建结果字符串的StringBuffer + StringBuffer sb = null; + // 遍历List中的每个元素 + for (String item: list){ + // 跳过空元素和空白字符串 + if(StringUtils.isBlank(item)){ + continue; + } + // 如果是第一个有效元素,初始化StringBuffer + if(sb == null){ + sb = new StringBuffer(item); + }else{ + // 后续元素前添加逗号分隔符 + sb.append(",").append(item); + } + } + + // 如果StringBuffer不为空,处理并返回结果 + if(sb!=null) { + // 移除可能存在的"[]"字符并返回字符串 + return sb.toString().replace("[]", ""); + } + } + // 输入为空时返回空字符串 + return ""; + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/file/Md5Util.java b/exam-api1/src/main/java/com/yf/exam/core/utils/file/Md5Util.java new file mode 100644 index 0000000..d1bd219 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/file/Md5Util.java @@ -0,0 +1,49 @@ +package com.yf.exam.core.utils.file; + +import java.security.MessageDigest; + + +/** + * MD5工具类 + * 提供MD5加密功能,用于字符串的不可逆加密处理 + * ClassName: MD5Util
+ * date: 2018年1月13日 下午6:54:53
+ * + * @author Bool + * @version + */ +public class Md5Util { + + + /** + * 简单MD5 + * 对输入字符串进行MD5加密,返回32位小写十六进制字符串 + * @param str 需要加密的原始字符串 + * @return 32位MD5加密后的十六进制字符串,加密失败返回null + */ + public static String md5(String str) { + + try { + // 获取MD5加密算法实例 + MessageDigest md = MessageDigest.getInstance("MD5"); + // 将字符串转换为UTF-8字节数组并进行MD5加密 + byte[] array = md.digest(str.getBytes("UTF-8")); + // 创建StringBuilder用于构建结果字符串 + StringBuilder sb = new StringBuilder(); + // 遍历加密后的字节数组 + for (byte item : array) { + // 将每个字节转换为十六进制字符串 + // item & 0xFF: 将字节转换为无符号整数(0-255) + // | 0x100: 确保结果是3位十六进制数(例如:0x1a2 -> 0x11a2) + // substring(1, 3): 取后2位,得到标准的2位十六进制表示 + sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); + } + // 返回32位小写MD5字符串 + return sb.toString(); + }catch(Exception e) { + // 加密过程中发生异常时返回null + return null; + } + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java b/exam-api1/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java new file mode 100644 index 0000000..626043e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/passwd/PassHandler.java @@ -0,0 +1,70 @@ +package com.yf.exam.core.utils.passwd; + + +import com.yf.exam.core.utils.file.Md5Util; +import org.apache.commons.lang3.RandomStringUtils; + +/** + * 通用的密码处理类,用于生成密码和校验密码 + * 提供密码加密、验证和生成功能,增强密码安全性 + * ClassName: PassGenerator
+ * date: 2017年12月13日 下午7:13:03
+ * + * @author Bool + * @version + */ +public class PassHandler { + + /** + * checkPass:校验密码是否一致 + * 验证用户输入的密码是否与数据库中存储的密码匹配 + * @author Bool + * @param inputPass 用户传入的原始密码 + * @param salt 数据库保存的密码随机码(盐值) + * @param pass 数据库保存的加密后的密码MD5 + * @return boolean 密码匹配返回true,否则返回false + */ + public static boolean checkPass(String inputPass , String salt , String pass){ + // 首先对用户输入的密码进行MD5加密 + String pwdMd5 = Md5Util.md5(inputPass); + // 将MD5加密后的密码与盐值拼接,再次进行MD5加密,然后与数据库中的密码比较 + return Md5Util.md5(pwdMd5 + salt).equals(pass); + } + + + /** + * + * buildPassword:用于用户注册时产生一个密码 + * 生成加密密码和对应的盐值,用于新用户注册或密码修改 + * @author Bool + * @param inputPass 用户输入的原始密码 + * @return PassInfo 返回一个密码对象,包含盐值和加密后的密码,需要保存到数据库 + */ + public static PassInfo buildPassword(String inputPass) { + + //产生一个6位数的随机码作为盐值,增强密码安全性 + String salt = RandomStringUtils.randomAlphabetic(6); + //双重MD5加密:先对原始密码MD5,再与盐值拼接后进行第二次MD5加密 + String encryptPassword = Md5Util.md5(Md5Util.md5(inputPass)+salt); + //返回包含盐值和加密密码的对象 + return new PassInfo(salt,encryptPassword); + } + + + /** + * main方法:测试密码生成功能 + * 用于演示密码生成过程,输出生成的密码和盐值 + * @param args 命令行参数 + */ + public static void main(String[] args) { + + // 生成测试密码"190601"的加密信息 + PassInfo info = buildPassword("190601"); + + // 输出加密后的密码(应保存到数据库) + System.out.println(info.getPassword()); + // 输出盐值(应保存到数据库) + System.out.println(info.getSalt()); + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java b/exam-api1/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java new file mode 100644 index 0000000..560aff7 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java @@ -0,0 +1,71 @@ +package com.yf.exam.core.utils.passwd; + +/** + * 密码实体 + * 用于封装密码加密后的相关信息,包括盐值和加密密码 + * ClassName: PassInfo
+ * date: 2018年2月13日 下午7:13:50
+ * + * @author Bool + * @version + */ +public class PassInfo { + + /** + * 密码随机串码(盐值) + * 用于增强密码安全性的随机字符串,每个用户唯一 + * 与密码拼接后进行加密,防止彩虹表攻击 + */ + private String salt; + + /** + * MD5后的密码 + * 经过双重MD5加密(原始密码MD5 + 盐值)后的最终密码 + * 存储到数据库中的加密密码 + */ + private String password; + + /** + * 全参数构造函数 + * 用于创建包含盐值和加密密码的密码信息对象 + * @param salt 密码盐值 + * @param password 加密后的密码 + */ + public PassInfo(String salt, String password) { + super(); + this.salt = salt; + this.password = password; + } + + /** + * 获取盐值 + * @return 密码盐值 + */ + public String getSalt() { + return salt; + } + + /** + * 设置盐值 + * @param salt 密码盐值 + */ + public void setSalt(String salt) { + this.salt = salt; + } + + /** + * 获取加密密码 + * @return 加密后的密码 + */ + public String getPassword() { + return password; + } + + /** + * 设置加密密码 + * @param password 加密后的密码 + */ + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/Constant.java b/exam-api1/src/main/java/com/yf/exam/modules/Constant.java new file mode 100644 index 0000000..9afddda --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/Constant.java @@ -0,0 +1,15 @@ +package com.yf.exam.modules; + +/** + * 通用常量类 + * 定义系统中使用的全局常量,避免魔法值,提高代码可维护性 + * @author bool + */ +public class Constant { + + /** + * 会话相关常量 + * 用于标识用户认证和会话管理的令牌 + */ + public static final String TOKEN = "token"; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java new file mode 100644 index 0000000..82eff00 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/controller/ExamController.java @@ -0,0 +1,164 @@ +package com.yf.exam.modules.exam.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.BaseIdReqDTO; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.BaseStateReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.exam.dto.ExamDTO; +import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; +import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; +import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; +import com.yf.exam.modules.exam.entity.Exam; +import com.yf.exam.modules.exam.service.ExamService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Date; + +/** +*

+* 考试控制器 +* 提供考试相关的API接口,包括考试管理、在线考试、试卷批阅等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +@Api(tags={"考试"}) +@RestController +@RequestMapping("/exam/api/exam/exam") +public class ExamController extends BaseController { + + @Autowired + private ExamService baseService; + + /** + * 添加或修改 + * 创建新考试或更新现有考试信息 + * @param reqDTO 考试保存请求数据传输对象,包含考试详细信息 + * @return 操作结果 + */ + @RequiresRoles("sa") // 需要超级管理员权限 + @ApiOperation(value = "添加或修改") + @RequestMapping(value = "/save", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody ExamSaveReqDTO reqDTO) { + //复制参数并保存到数据库 + baseService.save(reqDTO); + return super.success(); + } + + /** + * 批量删除 + * 根据ID列表批量删除考试记录 + * @param reqDTO 包含要删除的考试ID列表的请求对象 + * @return 操作结果 + */ + @RequiresRoles("sa") // 需要超级管理员权限 + @ApiOperation(value = "批量删除") + @RequestMapping(value = "/delete", method = { RequestMethod.POST}) + public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { + //根据ID删除考试记录 + baseService.removeByIds(reqDTO.getIds()); + return super.success(); + } + + /** + * 查找详情 + * 根据考试ID获取考试的详细信息 + * @param reqDTO 包含考试ID的请求对象 + * @return 考试详细信息 + */ + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/detail", method = { RequestMethod.POST}) + public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) { + // 根据ID查询考试详情 + ExamSaveReqDTO dto = baseService.findDetail(reqDTO.getId()); + return super.success(dto); + } + + /** + * 修改考试状态 + * 批量更新考试的状态(如启用、禁用等) + * @param reqDTO 包含考试ID列表和目标状态的请求对象 + * @return 操作结果 + */ + @RequiresRoles("sa") // 需要超级管理员权限 + @ApiOperation(value = "修改考试状态") + @RequestMapping(value = "/state", method = { RequestMethod.POST}) + public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) { + + // 构建查询条件,匹配指定ID的考试 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().in(Exam::getId, reqDTO.getIds()); + + // 创建更新对象,设置新状态和更新时间 + Exam exam = new Exam(); + exam.setState(reqDTO.getState()); + exam.setUpdateTime(new Date()); + + // 执行批量更新 + baseService.update(exam, wrapper); + return super.success(); + } + + + /** + * 在线考试分页查询 + * 从考生视角获取可参加的考试列表(分页) + * @param reqDTO 分页查询请求参数 + * @return 在线考试分页结果 + */ + @ApiOperation(value = "考试视角") + @RequestMapping(value = "/online-paging", method = { RequestMethod.POST}) + public ApiRest> myPaging(@RequestBody PagingReqDTO reqDTO) { + + //分页查询并转换,获取考生可参加的考试列表 + IPage page = baseService.onlinePaging(reqDTO); + return super.success(page); + } + + /** + * 考试管理分页查询 + * 管理员视角的考试列表分页查询 + * @param reqDTO 分页查询请求参数 + * @return 考试管理分页结果 + */ + @RequiresRoles("sa") // 需要超级管理员权限 + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + //分页查询并转换,获取管理员视角的考试列表 + IPage page = baseService.paging(reqDTO); + + return super.success(page); + } + + + /** + * 待阅试卷分页查询 + * 获取需要批阅的试卷列表(分页) + * @param reqDTO 分页查询请求参数 + * @return 待阅试卷分页结果 + */ + @RequiresRoles("sa") // 需要超级管理员权限 + @ApiOperation(value = "待阅试卷") + @RequestMapping(value = "/review-paging", method = { RequestMethod.POST}) + public ApiRest> reviewPaging(@RequestBody PagingReqDTO reqDTO) { + //分页查询并转换,获取需要批阅的试卷列表 + IPage page = baseService.reviewPaging(reqDTO); + return super.success(page); + } + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java new file mode 100644 index 0000000..723dc55 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java @@ -0,0 +1,115 @@ +package com.yf.exam.modules.exam.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.yf.exam.modules.paper.enums.ExamState; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** +*

+* 考试数据传输类 +* 考试核心数据传输对象,包含考试的基本信息和状态计算逻辑 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="考试", description="考试") +public class ExamDTO implements Serializable { + + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + + @ApiModelProperty(value = "ID", required=true) + private String id; + + @ApiModelProperty(value = "考试名称", required=true) + private String title; + + @ApiModelProperty(value = "考试描述", required=true) + private String content; + + @ApiModelProperty(value = "1公开2部门3定员", required=true) + private Integer openType; + + @ApiModelProperty(value = "考试状态", required=true) + private Integer state; + + @ApiModelProperty(value = "是否限时", required=true) + private Boolean timeLimit; + + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") + @DateTimeFormat(pattern = "yyyy-MM-dd") + @ApiModelProperty(value = "开始时间", required=true) + private Date startTime; + + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") + @DateTimeFormat(pattern = "yyyy-MM-dd") + @ApiModelProperty(value = "结束时间", required=true) + private Date endTime; + + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + + @ApiModelProperty(value = "总分数", required=true) + private Integer totalScore; + + @ApiModelProperty(value = "总时长(分钟)", required=true) + private Integer totalTime; + + @ApiModelProperty(value = "及格分数", required=true) + private Integer qualifyScore; + + + + + /** + * 是否结束 + * 动态计算考试状态,考虑时间限制和当前系统时间 + * 如果考试设置了时间限制,会根据当前时间自动更新状态 + * @return 计算后的考试状态 + */ + public Integer getState(){ + + // 检查考试是否设置了时间限制 + if(this.timeLimit!=null && this.timeLimit){ + + // 考试未开始:当前时间小于开始时间 + if(System.currentTimeMillis() < startTime.getTime() ){ + return ExamState.READY_START; + } + + // 考试已过期:当前时间大于结束时间 + if(System.currentTimeMillis() > endTime.getTime()){ + return ExamState.OVERDUE; + } + + // 考试进行中:当前时间在开始和结束时间之间,且考试未被禁用 + if(System.currentTimeMillis() > startTime.getTime() + && System.currentTimeMillis() < endTime.getTime() + && !ExamState.DISABLED.equals(this.state)){ + return ExamState.ENABLE; + } + + } + + // 如果没有时间限制或时间条件不满足,返回原始状态 + return this.state; + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java new file mode 100644 index 0000000..d32e3c0 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java @@ -0,0 +1,53 @@ +package com.yf.exam.modules.exam.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 考试部门数据传输类 +* 用于表示考试与部门的关联关系,控制考试的访问权限范围 +* 实现考试按部门进行权限控制的功能 +*

+* +* @author 聪明笨狗 +* @since 2020-09-03 17:24 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="考试部门", description="考试部门") +public class ExamDepartDTO implements Serializable { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + + /** + * 主键ID + * 考试部门关联记录的唯一标识 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 考试ID + * 关联的考试唯一标识 + */ + @ApiModelProperty(value = "考试ID", required=true) + private String examId; + + /** + * 部门ID + * 关联的部门唯一标识,表示该部门可以访问对应考试 + */ + @ApiModelProperty(value = "部门ID", required=true) + private String departId; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java new file mode 100644 index 0000000..51b3a26 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java @@ -0,0 +1,95 @@ +package com.yf.exam.modules.exam.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 考试题库数据传输类 +* 用于表示考试与题库的关联关系,定义考试中各题型的题目数量和分值配置 +* 实现考试题目组成和评分规则的灵活配置 +*

+* +* @author 聪明笨狗 +* @since 2020-09-05 11:14 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="考试题库", description="考试题库") +public class ExamRepoDTO implements Serializable { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + + /** + * 主键ID + * 考试题库关联记录的唯一标识 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 考试ID + * 关联的考试唯一标识 + */ + @ApiModelProperty(value = "考试ID", required=true) + private String examId; + + /** + * 题库ID + * 关联的题库唯一标识 + */ + @ApiModelProperty(value = "题库ID", required=true) + private String repoId; + + /** + * 单选题数量 + * 从该题库中抽取的单选题数量 + */ + @ApiModelProperty(value = "单选题数量", required=true) + private Integer radioCount; + + /** + * 单选题分数 + * 每道单选题的分值 + */ + @ApiModelProperty(value = "单选题分数", required=true) + private Integer radioScore; + + /** + * 多选题数量 + * 从该题库中抽取的多选题数量 + */ + @ApiModelProperty(value = "多选题数量", required=true) + private Integer multiCount; + + /** + * 多选题分数 + * 每道多选题的分值 + */ + @ApiModelProperty(value = "多选题分数", required=true) + private Integer multiScore; + + /** + * 判断题数量 + * 从该题库中抽取的判断题数量 + */ + @ApiModelProperty(value = "判断题数量", required=true) + private Integer judgeCount; + + /** + * 判断题分数 + * 每道判断题的分值 + */ + @ApiModelProperty(value = "判断题分数", required=true) + private Integer judgeScore; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java new file mode 100644 index 0000000..b364145 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java @@ -0,0 +1,51 @@ +package com.yf.exam.modules.exam.dto.ext; + +import com.yf.exam.modules.exam.dto.ExamRepoDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** +*

+* 考试题库数据传输类 +* 扩展考试题库基础DTO,增加题目数量统计信息 +*

+* +* @author 聪明笨狗 +* @since 2020-09-05 11:14 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类") +public class ExamRepoExtDTO extends ExamRepoDTO { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + + /** + * 单选题总量 + * 统计该考试题库中单选题的总数量 + */ + @ApiModelProperty(value = "单选题总量", required=true) + private Integer totalRadio; + + /** + * 多选题总量 + * 统计该考试题库中多选题的总数量 + */ + @ApiModelProperty(value = "多选题总量", required=true) + private Integer totalMulti; + + /** + * 判断题总量 + * 统计该考试题库中判断题的总数量 + */ + @ApiModelProperty(value = "判断题总量", required=true) + private Integer totalJudge; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java new file mode 100644 index 0000000..0e40cee --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java @@ -0,0 +1,47 @@ +package com.yf.exam.modules.exam.dto.request; + +import com.yf.exam.modules.exam.dto.ExamDTO; +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** +*

+* 考试保存请求类 +* 用于接收考试创建或编辑时的请求数据,扩展了基础考试信息 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="考试保存请求类", description="考试保存请求类") +public class ExamSaveReqDTO extends ExamDTO { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + + /** + * 题库列表 + * 考试关联的题库集合,包含每个题库的题目数量统计信息 + */ + @ApiModelProperty(value = "题库列表", required=true) + private List repoList; + + /** + * 考试部门列表 + * 可以参加该考试的部门ID集合,用于控制考试权限范围 + */ + @ApiModelProperty(value = "考试部门列表", required=true) + private List departIds; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java new file mode 100644 index 0000000..6aab544 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java @@ -0,0 +1,30 @@ +package com.yf.exam.modules.exam.dto.response; + +import com.yf.exam.modules.exam.dto.ExamDTO; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +/** +*

+* 考试分页响应类 +* 用于在线考试场景的分页响应数据,继承基础考试信息 +* 考生视角的考试列表展示 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类") +public class ExamOnlineRespDTO extends ExamDTO { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java new file mode 100644 index 0000000..77b7654 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java @@ -0,0 +1,47 @@ +package com.yf.exam.modules.exam.dto.response; + +import com.yf.exam.modules.exam.dto.ExamDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** +*

+* 考试分页响应类 +* 用于阅卷管理场景的分页响应数据,继承基础考试信息 +* 包含阅卷相关的统计信息,便于管理员进行试卷批阅管理 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="阅卷分页响应类", description="阅卷分页响应类") +public class ExamReviewRespDTO extends ExamDTO { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + + /** + * 考试人数 + * 参加该考试的总人数统计 + */ + @ApiModelProperty(value = "考试人数", required=true) + private Integer examUser; + + /** + * 待阅试卷 + * 需要批阅的试卷数量,用于阅卷工作量统计 + */ + @ApiModelProperty(value = "待阅试卷", required=true) + private Integer unreadPaper; + + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/Exam.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/Exam.java new file mode 100644 index 0000000..adc20af --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/Exam.java @@ -0,0 +1,121 @@ +package com.yf.exam.modules.exam.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; +import java.util.Date; + +/** +*

+* 考试实体类 +* 对应数据库表el_exam,表示考试的基本信息实体 +* 使用MyBatis-Plus的ActiveRecord模式,继承Model类 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// MyBatis-Plus注解,指定对应的数据库表名 +@TableName("el_exam") +public class Exam extends Model { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * ID + * 主键字段,使用雪花算法分配ID + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 考试名称 + * 考试的标题名称 + */ + private String title; + + /** + * 考试描述 + * 考试的详细描述信息 + */ + private String content; + + /** + * 1公开2部门3定员 + * 考试开放类型:1-公开,2-部门,3-定员 + */ + @TableField("open_type") + private Integer openType; + + /** + * 考试状态 + * 考试的状态标识 + */ + private Integer state; + + /** + * 是否限时 + * 标识考试是否有时间限制 + */ + @TableField("time_limit") + private Boolean timeLimit; + + /** + * 开始时间 + * 考试的开始时间 + */ + @TableField("start_time") + private Date startTime; + + /** + * 结束时间 + * 考试的结束时间 + */ + @TableField("end_time") + private Date endTime; + + /** + * 创建时间 + * 考试记录的创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + * 考试记录的最后更新时间 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 总分数 + * 考试的总分数 + */ + @TableField("total_score") + private Integer totalScore; + + /** + * 总时长(分钟) + * 考试的总体时长,单位为分钟 + */ + @TableField("total_time") + private Integer totalTime; + + /** + * 及格分数 + * 考试的及格分数线 + */ + @TableField("qualify_score") + private Integer qualifyScore; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java new file mode 100644 index 0000000..33da592 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java @@ -0,0 +1,53 @@ +package com.yf.exam.modules.exam.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 考试部门实体类 +* 对应数据库表el_exam_depart,表示考试与部门的关联关系 +* 用于控制考试的部门访问权限,实现按部门分配考试权限 +*

+* +* @author 聪明笨狗 +* @since 2020-09-03 17:24 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// MyBatis-Plus注解,指定对应的数据库表名 +@TableName("el_exam_depart") +public class ExamDepart extends Model { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * ID + * 主键字段,使用雪花算法分配ID + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 考试ID + * 关联的考试唯一标识,外键关联el_exam表 + */ + @TableField("exam_id") + private String examId; + + /** + * 部门ID + * 关联的部门唯一标识,外键关联部门表 + */ + @TableField("depart_id") + private String departId; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java new file mode 100644 index 0000000..a8536a3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java @@ -0,0 +1,95 @@ +package com.yf.exam.modules.exam.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 考试题库实体类 +* 对应数据库表el_exam_repo,表示考试与题库的关联关系 +* 用于配置考试中各题型的题目数量和分值,实现灵活的考试组卷规则 +*

+* +* @author 聪明笨狗 +* @since 2020-09-05 11:14 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// MyBatis-Plus注解,指定对应的数据库表名 +@TableName("el_exam_repo") +public class ExamRepo extends Model { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * ID + * 主键字段,使用雪花算法分配ID + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 考试ID + * 关联的考试唯一标识,外键关联el_exam表 + */ + @TableField("exam_id") + private String examId; + + /** + * 题库ID + * 关联的题库唯一标识,外键关联题库表 + */ + @TableField("repo_id") + private String repoId; + + /** + * 单选题数量 + * 从该题库中抽取的单选题数量 + */ + @TableField("radio_count") + private Integer radioCount; + + /** + * 单选题分数 + * 每道单选题的分值 + */ + @TableField("radio_score") + private Integer radioScore; + + /** + * 多选题数量 + * 从该题库中抽取的多选题数量 + */ + @TableField("multi_count") + private Integer multiCount; + + /** + * 多选题分数 + * 每道多选题的分值 + */ + @TableField("multi_score") + private Integer multiScore; + + /** + * 判断题数量 + * 从该题库中抽取的判断题数量 + */ + @TableField("judge_count") + private Integer judgeCount; + + /** + * 判断题分数 + * 每道判断题的分值 + */ + @TableField("judge_score") + private Integer judgeScore; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java new file mode 100644 index 0000000..80d7b8b --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java @@ -0,0 +1,18 @@ +package com.yf.exam.modules.exam.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.exam.entity.ExamDepart; + +/** +*

+* 考试部门Mapper +* 考试部门数据访问层接口,继承MyBatis-Plus的BaseMapper +* 提供对el_exam_depart表的CRUD操作 +*

+* +* @author 聪明笨狗 +* @since 2020-09-03 17:24 +*/ +public interface ExamDepartMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java new file mode 100644 index 0000000..c3091a3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java @@ -0,0 +1,50 @@ +package com.yf.exam.modules.exam.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yf.exam.modules.exam.dto.ExamDTO; +import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; +import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; +import com.yf.exam.modules.exam.entity.Exam; +import org.apache.ibatis.annotations.Param; + +/** +*

+* 考试Mapper +* 考试数据访问层接口,继承MyBatis-Plus的BaseMapper +* 提供对el_exam表的CRUD操作和自定义分页查询 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +public interface ExamMapper extends BaseMapper { + + /** + * 查找分页内容 + * 管理员视角的考试分页查询,用于考试管理列表 + * @param page 分页参数对象 + * @param query 查询条件对象 + * @return 考试分页结果 + */ + IPage paging(Page page, @Param("query") ExamDTO query); + + /** + * 查找分页内容 + * 阅卷视角的考试分页查询,用于待阅试卷列表 + * @param page 分页参数对象 + * @param query 查询条件对象 + * @return 阅卷分页结果 + */ + IPage reviewPaging(Page page, @Param("query") ExamDTO query); + + /** + * 在线考试分页响应类-考生视角 + * 考生视角的在线考试分页查询,用于考生参加考试列表 + * @param page 分页参数对象 + * @param query 查询条件对象 + * @return 在线考试分页结果 + */ + IPage online(Page page, @Param("query") ExamDTO query); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java new file mode 100644 index 0000000..71e6062 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java @@ -0,0 +1,29 @@ +package com.yf.exam.modules.exam.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; +import com.yf.exam.modules.exam.entity.ExamRepo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** +*

+* 考试题库Mapper +* 考试题库数据访问层接口,继承MyBatis-Plus的BaseMapper +* 提供对el_exam_repo表的CRUD操作和自定义查询 +*

+* +* @author 聪明笨狗 +* @since 2020-09-05 11:14 +*/ +public interface ExamRepoMapper extends BaseMapper { + + /** + * 查找考试题库列表 + * 根据考试ID查询关联的题库列表,包含题目数量统计信息 + * @param examId 考试ID + * @return 考试题库扩展信息列表 + */ + List listByExam(@Param("examId") String examId); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java new file mode 100644 index 0000000..239472e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java @@ -0,0 +1,35 @@ +package com.yf.exam.modules.exam.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.modules.exam.entity.ExamDepart; + +import java.util.List; + +/** +*

+* 考试部门业务类 +* 考试部门关联关系服务接口,定义考试与部门关联的业务操作方法 +*

+* +* @author 聪明笨狗 +* @since 2020-09-03 17:24 +*/ +public interface ExamDepartService extends IService { + + /** + * 保存全部 + * 批量保存考试与部门的关联关系,采用先删除后新增的策略 + * @param examId 考试ID + * @param departs 部门ID列表 + */ + void saveAll(String examId, List departs); + + + /** + * 根据考试查找对应的部门 + * 查询指定考试关联的所有部门ID列表 + * @param examId 考试ID + * @return 部门ID列表 + */ + List listByExam(String examId); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java new file mode 100644 index 0000000..59adb91 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java @@ -0,0 +1,43 @@ +package com.yf.exam.modules.exam.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; +import com.yf.exam.modules.exam.entity.ExamRepo; + +import java.util.List; + +/** +*

+* 考试题库业务类 +* 考试题库关联关系服务接口,定义考试与题库关联的业务操作方法 +*

+* +* @author 聪明笨狗 +* @since 2020-09-05 11:14 +*/ +public interface ExamRepoService extends IService { + + /** + * 保存全部 + * 批量保存考试与题库的关联关系,包含题目数量和分值配置 + * @param examId 考试ID + * @param list 考试题库扩展信息列表 + */ + void saveAll(String examId, List list); + + /** + * 查找考试题库列表 + * 查询指定考试关联的所有题库信息,包含题目数量统计 + * @param examId 考试ID + * @return 考试题库扩展信息列表 + */ + List listByExam(String examId); + + /** + * 清理脏数据 + * 清理指定考试的所有题库关联关系 + * @param examId 考试ID + */ + void clear(String examId); + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamService.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamService.java new file mode 100644 index 0000000..f9ee8db --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamService.java @@ -0,0 +1,69 @@ +package com.yf.exam.modules.exam.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.exam.dto.ExamDTO; +import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; +import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; +import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; +import com.yf.exam.modules.exam.entity.Exam; + +/** +*

+* 考试业务类 +* 考试核心业务服务接口,定义考试相关的业务操作方法 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +public interface ExamService extends IService { + + /** + * 保存考试信息 + * 创建或更新考试信息,包括基本信息、题库配置和部门权限 + * @param reqDTO 考试保存请求数据传输对象 + */ + void save(ExamSaveReqDTO reqDTO); + + /** + * 查找考试详情 + * 获取考试的完整信息,包括基本信息、部门权限和题库配置 + * @param id 考试ID + * @return 考试详情响应对象 + */ + ExamSaveReqDTO findDetail(String id); + + /** + * 查找考试详情--简要信息 + * 获取考试的基本信息,不包含关联的部门和题库信息 + * @param id 考试ID + * @return 考试数据传输对象 + */ + ExamDTO findById(String id); + + /** + * 分页查询数据 + * 管理员视角的考试分页查询,用于考试管理列表 + * @param reqDTO 分页请求参数 + * @return 考试分页结果 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 在线考试分页响应类-考生视角 + * 考生视角的在线考试分页查询,用于考生参加考试列表 + * @param reqDTO 分页请求参数 + * @return 在线考试分页结果 + */ + IPage onlinePaging(PagingReqDTO reqDTO); + + /** + * 待阅试卷列表 + * 阅卷视角的待阅试卷分页查询,用于试卷批阅管理 + * @param reqDTO 分页请求参数 + * @return 待阅试卷分页结果 + */ + IPage reviewPaging(PagingReqDTO reqDTO); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java new file mode 100644 index 0000000..d322f61 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java @@ -0,0 +1,73 @@ +package com.yf.exam.modules.exam.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.modules.exam.entity.ExamDepart; +import com.yf.exam.modules.exam.mapper.ExamDepartMapper; +import com.yf.exam.modules.exam.service.ExamDepartService; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** +*

+* 考试部门业务实现类 +* 实现考试与部门关联关系的业务逻辑,包括批量保存和查询功能 +*

+* +* @author 聪明笨狗 +* @since 2020-09-03 17:24 +*/ +// Spring注解,声明为服务层组件 +@Service +public class ExamDepartServiceImpl extends ServiceImpl implements ExamDepartService { + + @Override + public void saveAll(String examId, List departs) { + + // 先删除该考试原有的所有部门关联 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(ExamDepart::getExamId, examId); + this.remove(wrapper); + + // 再增加新的部门关联 + // 检查部门列表是否为空 + if(CollectionUtils.isEmpty(departs)){ + throw new ServiceException(1, "请至少选择选择一个部门!!"); + } + + // 构建要保存的考试部门关联列表 + List list = new ArrayList<>(); + for(String id: departs){ + ExamDepart depart = new ExamDepart(); + depart.setDepartId(id); + depart.setExamId(examId); + list.add(depart); + } + + // 批量保存新的部门关联关系 + this.saveBatch(list); + } + + @Override + public List listByExam(String examId) { + // 查询指定考试的所有部门关联 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(ExamDepart::getExamId, examId); + List list = this.list(wrapper); + + // 提取部门ID列表 + List ids = new ArrayList<>(); + if(!CollectionUtils.isEmpty(list)){ + for(ExamDepart item: list){ + ids.add(item.getDepartId()); + } + } + + return ids; + + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java new file mode 100644 index 0000000..1c7da31 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java @@ -0,0 +1,91 @@ +package com.yf.exam.modules.exam.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; +import com.yf.exam.modules.exam.entity.ExamRepo; +import com.yf.exam.modules.exam.mapper.ExamRepoMapper; +import com.yf.exam.modules.exam.service.ExamRepoService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +/** +*

+* 考试题库业务实现类 +* 实现考试与题库关联关系的业务逻辑,包括批量保存、查询和清理功能 +*

+* +* @author 聪明笨狗 +* @since 2020-09-05 11:14 +*/ +// Spring注解,声明为服务层组件 +@Service +public class ExamRepoServiceImpl extends ServiceImpl implements ExamRepoService { + + + /** + * 批量保存考试题库关联关系 + * 采用先删除后新增的策略更新考试题库配置 + * @param examId 考试ID + * @param list 考试题库扩展信息列表 + */ + @Transactional(rollbackFor = Exception.class) // 事务注解,遇到任何异常都回滚 + @Override + public void saveAll(String examId, List list) { + + // 先删除该考试原有的所有题库关联 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(ExamRepo::getExamId, examId); + this.remove(wrapper); + + // 再增加新的题库关联 + // 检查题库列表是否为空 + if(CollectionUtils.isEmpty(list)){ + throw new ServiceException(1, "必须选择题库!"); + } + + // 使用BeanMapper将DTO列表转换为实体列表 + List repos = BeanMapper.mapList(list, ExamRepo.class); + + // 为每个实体设置考试ID和生成主键ID + for(ExamRepo item: repos){ + item.setExamId(examId); + item.setId(IdWorker.getIdStr()); // 使用雪花算法生成唯一ID + } + + // 批量保存新的题库关联关系 + this.saveBatch(repos); + } + + /** + * 根据考试ID查询关联的题库列表 + * @param examId 考试ID + * @return 考试题库扩展信息列表 + */ + @Override + public List listByExam(String examId) { + // 调用Mapper层的自定义查询方法 + return baseMapper.listByExam(examId); + } + + /** + * 清理指定考试的题库关联关系 + * @param examId 考试ID + */ + @Override + public void clear(String examId) { + + // 删除该考试的所有题库关联 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(ExamRepo::getExamId, examId); + this.remove(wrapper); + } + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java new file mode 100644 index 0000000..1d30a90 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java @@ -0,0 +1,191 @@ +package com.yf.exam.modules.exam.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.enums.OpenType; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.exam.dto.ExamDTO; +import com.yf.exam.modules.exam.dto.ExamRepoDTO; +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; +import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO; +import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; +import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; +import com.yf.exam.modules.exam.entity.Exam; +import com.yf.exam.modules.exam.mapper.ExamMapper; +import com.yf.exam.modules.exam.service.ExamDepartService; +import com.yf.exam.modules.exam.service.ExamRepoService; +import com.yf.exam.modules.exam.service.ExamService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** +*

+* 考试业务实现类 +* 实现考试相关的核心业务逻辑,包括考试创建、编辑、查询和分页等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-07-25 16:18 +*/ +// Spring注解,声明为服务层组件 +@Service +public class ExamServiceImpl extends ServiceImpl implements ExamService { + + + @Autowired + private ExamRepoService examRepoService; + + @Autowired + private ExamDepartService examDepartService; + + @Override + public void save(ExamSaveReqDTO reqDTO) { + + // ID处理:如果ID为空则生成新ID,否则使用现有ID + String id = reqDTO.getId(); + if(StringUtils.isBlank(id)){ + id = IdWorker.getIdStr(); // 使用雪花算法生成唯一ID + } + + // 复制参数到实体对象 + Exam entity = new Exam(); + + // 计算考试总分 + this.calcScore(reqDTO); + + // 复制基本数据从DTO到实体 + BeanMapper.copy(reqDTO, entity); + entity.setId(id); + + // 修复状态:如果考试不限时且状态为2,则重置为0 + if (reqDTO.getTimeLimit()!=null + && !reqDTO.getTimeLimit() + && reqDTO.getState()!=null + && reqDTO.getState() == 2) { + entity.setState(0); + } else { + entity.setState(reqDTO.getState()); + } + + // 保存题库组卷信息 + try { + examRepoService.saveAll(id, reqDTO.getRepoList()); + }catch (DuplicateKeyException e){ + throw new ServiceException(1, "不能选择重复的题库!"); + } + + // 保存开放的部门信息(仅当开放类型为部门开放时) + if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){ + examDepartService.saveAll(id, reqDTO.getDepartIds()); + } + + // 保存或更新考试实体 + this.saveOrUpdate(entity); + } + + @Override + public ExamSaveReqDTO findDetail(String id) { + ExamSaveReqDTO respDTO = new ExamSaveReqDTO(); + // 获取考试基本信息 + Exam exam = this.getById(id); + BeanMapper.copy(exam, respDTO); + + // 获取考试部门关联信息 + List departIds = examDepartService.listByExam(id); + respDTO.setDepartIds(departIds); + + // 获取题库关联信息 + List repos = examRepoService.listByExam(id); + respDTO.setRepoList(repos); + + return respDTO; + } + + @Override + public ExamDTO findById(String id) { + ExamDTO respDTO = new ExamDTO(); + // 获取考试基本信息 + Exam exam = this.getById(id); + BeanMapper.copy(exam, respDTO); + return respDTO; + } + + @Override + public IPage paging(PagingReqDTO reqDTO) { + // 创建分页对象 + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + + // 转换结果:调用Mapper层分页查询 + IPage pageData = baseMapper.paging(page, reqDTO.getParams()); + return pageData; + } + + @Override + public IPage onlinePaging(PagingReqDTO reqDTO) { + // 创建分页对象 + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + + // 查找分页:考生视角的在线考试列表 + IPage pageData = baseMapper.online(page, reqDTO.getParams()); + + return pageData; + } + + @Override + public IPage reviewPaging(PagingReqDTO reqDTO) { + // 创建分页对象 + Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + + // 查找分页:阅卷视角的待阅试卷列表 + IPage pageData = baseMapper.reviewPaging(page, reqDTO.getParams()); + + return pageData; + } + + /** + * 计算分值 + * 根据题库配置计算考试总分 + * @param reqDTO 考试保存请求对象 + */ + private void calcScore(ExamSaveReqDTO reqDTO){ + // 主观题分数(这里实际计算的是客观题总分) + int objScore = 0; + + // 遍历所有题库配置 + List repoList = reqDTO.getRepoList(); + for(ExamRepoDTO item: repoList){ + // 计算单选题总分 + if(item.getRadioCount()!=null + && item.getRadioCount()>0 + && item.getRadioScore()!=null + && item.getRadioScore()>0){ + objScore+=item.getRadioCount()*item.getRadioScore(); + } + // 计算多选题总分 + if(item.getMultiCount()!=null + && item.getMultiCount()>0 + && item.getMultiScore()!=null + && item.getMultiScore()>0){ + objScore+=item.getMultiCount()*item.getMultiScore(); + } + // 计算判断题总分 + if(item.getJudgeCount()!=null + && item.getJudgeCount()>0 + && item.getJudgeScore()!=null + && item.getJudgeScore()>0){ + objScore+=item.getJudgeCount()*item.getJudgeScore(); + } + } + + // 设置考试总分 + reqDTO.setTotalScore(objScore); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java new file mode 100644 index 0000000..4bf0e15 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java @@ -0,0 +1,160 @@ +package com.yf.exam.modules.paper.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.BaseIdReqDTO; +import com.yf.exam.core.api.dto.BaseIdRespDTO; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; +import com.yf.exam.modules.paper.dto.request.PaperCreateReqDTO; +import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; +import com.yf.exam.modules.paper.dto.request.PaperQuQueryDTO; +import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; +import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; +import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; +import com.yf.exam.modules.paper.entity.Paper; +import com.yf.exam.modules.paper.service.PaperService; +import com.yf.exam.modules.user.UserUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** +*

+* 试卷控制器 +* 提供试卷相关的API接口,包括试卷创建、答题、交卷、查询等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +@Api(tags={"试卷"}) +@RestController +@RequestMapping("/exam/api/paper/paper") +public class PaperController extends BaseController { + + @Autowired + private PaperService baseService; + + /** + * 分页查找 + * 试卷列表分页查询,支持查询条件 + * @param reqDTO 分页请求参数,包含查询条件 + * @return 试卷列表分页结果 + */ + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + //分页查询并转换 + IPage page = baseService.paging(reqDTO); + return super.success(page); + } + + /** + * 创建试卷 + * 为用户创建新的考试试卷 + * @param reqDTO 创建试卷请求参数,包含考试ID + * @return 包含新创建试卷ID的响应 + */ + @ApiOperation(value = "创建试卷") + @RequestMapping(value = "/create-paper", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody PaperCreateReqDTO reqDTO) { + //复制参数并创建试卷 + String paperId = baseService.createPaper(UserUtils.getUserId(), reqDTO.getExamId()); + return super.success(new BaseIdRespDTO(paperId)); + } + + /** + * 试卷详情 + * 获取试卷的详细信息,包括考试信息和题目列表 + * @param reqDTO 包含试卷ID的请求参数 + * @return 试卷详情响应 + */ + @ApiOperation(value = "试卷详情") + @RequestMapping(value = "/paper-detail", method = { RequestMethod.POST}) + public ApiRest paperDetail(@RequestBody BaseIdReqDTO reqDTO) { + //根据ID查询试卷详情 + ExamDetailRespDTO respDTO = baseService.paperDetail(reqDTO.getId()); + return super.success(respDTO); + } + + /** + * 试题详情 + * 获取试卷中指定试题的详细信息 + * @param reqDTO 包含试卷ID和试题ID的请求参数 + * @return 试题详情响应 + */ + @ApiOperation(value = "试题详情") + @RequestMapping(value = "/qu-detail", method = { RequestMethod.POST}) + public ApiRest quDetail(@RequestBody PaperQuQueryDTO reqDTO) { + //根据试卷ID和试题ID查询试题详情 + PaperQuDetailDTO respDTO = baseService.findQuDetail(reqDTO.getPaperId(), reqDTO.getQuId()); + return super.success(respDTO); + } + + /** + * 填充答案 + * 保存用户对试题的答案 + * @param reqDTO 包含试卷ID、试题ID和答案的请求参数 + * @return 操作结果 + */ + @ApiOperation(value = "填充答案") + @RequestMapping(value = "/fill-answer", method = { RequestMethod.POST}) + public ApiRest fillAnswer(@RequestBody PaperAnswerDTO reqDTO) { + //保存用户答案 + baseService.fillAnswer(reqDTO); + return super.success(); + } + + /** + * 交卷操作 + * 用户完成考试并提交试卷 + * @param reqDTO 包含试卷ID的请求参数 + * @return 操作结果 + */ + @ApiOperation(value = "交卷操作") + @RequestMapping(value = "/hand-exam", method = { RequestMethod.POST}) + public ApiRest handleExam(@RequestBody BaseIdReqDTO reqDTO) { + //执行交卷操作 + baseService.handExam(reqDTO.getId()); + return super.success(); + } + + /** + * 试卷结果 + * 获取试卷的考试结果,包括得分和答题情况 + * @param reqDTO 包含试卷ID的请求参数 + * @return 试卷结果响应 + */ + @ApiOperation(value = "试卷结果") + @RequestMapping(value = "/paper-result", method = { RequestMethod.POST}) + public ApiRest paperResult(@RequestBody BaseIdReqDTO reqDTO) { + //根据ID查询试卷结果 + ExamResultRespDTO respDTO = baseService.paperResult(reqDTO.getId()); + return super.success(respDTO); + } + + /** + * 检测用户有没有中断的考试 + * 检查当前用户是否有未完成的考试试卷 + * @return 进行中的试卷信息,如果没有则返回null + */ + @ApiOperation(value = "检测进行中的考试") + @RequestMapping(value = "/check-process", method = { RequestMethod.POST}) + public ApiRest checkProcess() { + //检查用户是否有未完成的考试 + PaperDTO dto = baseService.checkProcess(UserUtils.getUserId()); + return super.success(dto); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java new file mode 100644 index 0000000..3da43da --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java @@ -0,0 +1,150 @@ +package com.yf.exam.modules.paper.dto; + +import com.yf.exam.core.annon.Dict; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** +*

+* 试卷请求类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +@Data +@ApiModel(value="试卷", description="试卷") +public class PaperDTO implements Serializable { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + * 在反序列化时验证序列化对象的版本是否与当前类定义兼容 + */ + private static final long serialVersionUID = 1L; + + /** + * 试卷唯一标识ID + * 在系统中唯一标识一份试卷 + */ + @ApiModelProperty(value = "试卷ID", required=true) + private String id; + + /** + * 用户ID,关联系统用户表 + * 使用字典注解关联sys_user表,显示用户真实姓名 + */ + @Dict(dictTable = "sys_user", dicText = "real_name", dicCode = "id") + @ApiModelProperty(value = "用户ID", required=true) + private String userId; + + /** + * 部门ID,关联系统部门表 + * 使用字典注解关联sys_depart表,显示部门名称 + */ + @Dict(dictTable = "sys_depart", dicText = "dept_name", dicCode = "id") + @ApiModelProperty(value = "部门ID", required=true) + private String departId; + + /** + * 考试规则ID,关联考试规则表 + * 标识该试卷所属的考试规则 + */ + @ApiModelProperty(value = "规则ID", required=true) + private String examId; + + /** + * 考试标题 + * 试卷的名称或标题信息 + */ + @ApiModelProperty(value = "考试标题", required=true) + private String title; + + /** + * 考试总时长(单位:分钟) + * 规定的考试完成时间限制 + */ + @ApiModelProperty(value = "考试时长", required=true) + private Integer totalTime; + + /** + * 用户实际使用时长(单位:分钟) + * 考生完成考试实际花费的时间 + */ + @ApiModelProperty(value = "用户时长", required=true) + private Integer userTime; + + /** + * 试卷总分 + * 该试卷所有题目的总分数 + */ + @ApiModelProperty(value = "试卷总分", required=true) + private Integer totalScore; + + /** + * 及格分数线 + * 考试通过所需的最低分数 + */ + @ApiModelProperty(value = "及格分", required=true) + private Integer qualifyScore; + + /** + * 客观题总分 + * 选择题、判断题等客观题的总分数 + */ + @ApiModelProperty(value = "客观分", required=true) + private Integer objScore; + + /** + * 主观题总分 + * 简答题、论述题等主观题的总分数 + */ + @ApiModelProperty(value = "主观分", required=true) + private Integer subjScore; + + /** + * 用户得分 + * 考生在该试卷中获得的实际分数 + */ + @ApiModelProperty(value = "用户得分", required=true) + private Integer userScore; + + /** + * 是否包含简答题 + * true表示包含简答题,false表示不包含 + */ + @ApiModelProperty(value = "是否包含简答题", required=true) + private Boolean hasSaq; + + /** + * 试卷状态 + * 用于标识试卷的当前状态(如:未开始、进行中、已提交、已批改等) + */ + @ApiModelProperty(value = "试卷状态", required=true) + private Integer state; + + /** + * 试卷创建时间 + * 记录试卷的生成时间 + */ + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + /** + * 试卷最后更新时间 + * 记录试卷信息的最后修改时间 + */ + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + + /** + * 考试截止时间 + * 试卷提交的最后期限,可为空表示无截止时间限制 + */ + @ApiModelProperty(value = "截止时间") + private Date limitTime; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java new file mode 100644 index 0000000..7869f6e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java @@ -0,0 +1,86 @@ +package com.yf.exam.modules.paper.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 试卷考题备选答案请求类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +@Data +@ApiModel(value="试卷考题备选答案", description="试卷考题备选答案") +public class PaperQuAnswerDTO implements Serializable { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + * 在反序列化时验证序列化对象的版本是否与当前类定义兼容 + */ + private static final long serialVersionUID = 1L; + + /** + * 自增主键ID + * 唯一标识每条试卷考题备选答案记录 + */ + @ApiModelProperty(value = "自增ID", required=true) + private String id; + + /** + * 试卷ID + * 关联所属的试卷,标识该备选答案属于哪份试卷 + */ + @ApiModelProperty(value = "试卷ID", required=true) + private String paperId; + + /** + * 回答项ID + * 关联具体的答案选项,标识具体的答案内容 + */ + @ApiModelProperty(value = "回答项ID", required=true) + private String answerId; + + /** + * 题目ID + * 关联所属的题目,标识该备选答案属于哪道题目 + */ + @ApiModelProperty(value = "题目ID", required=true) + private String quId; + + /** + * 是否为正确答案 + * true表示该选项是正确答案,false表示不是正确答案 + * 用于客观题的标准答案判定 + */ + @ApiModelProperty(value = "是否正确项", required=true) + private Boolean isRight; + + /** + * 用户是否选中该选项 + * true表示用户在答题时选择了该选项,false表示未选择 + * 记录用户的实际答题选择 + */ + @ApiModelProperty(value = "是否选中", required=true) + private Boolean checked; + + /** + * 选项排序号 + * 控制选项在界面上的显示顺序,数值越小显示越靠前 + */ + @ApiModelProperty(value = "排序", required=true) + private Integer sort; + + /** + * 选项标签 + * 用于标识选项的字母标签,如A、B、C、D等 + * 在界面上显示为选项的前缀标识 + */ + @ApiModelProperty(value = "选项标签", required=true) + private String abc; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java new file mode 100644 index 0000000..fc1ddb1 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java @@ -0,0 +1,102 @@ +package com.yf.exam.modules.paper.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 试卷考题请求类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +@Data +@ApiModel(value="试卷考题", description="试卷考题") +public class PaperQuDTO implements Serializable { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + * 在反序列化时验证序列化对象的版本是否与当前类定义兼容 + */ + private static final long serialVersionUID = 1L; + + /** + * 主键ID + * 唯一标识试卷中的每道题目记录 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 试卷ID + * 关联所属的试卷,标识该题目属于哪份试卷 + */ + @ApiModelProperty(value = "试卷ID", required=true) + private String paperId; + + /** + * 题目ID + * 关联题库中的原始题目,标识该题目的具体内容 + */ + @ApiModelProperty(value = "题目ID", required=true) + private String quId; + + /** + * 题目类型 + * 标识题目的类型,如单选题、多选题、判断题、简答题等 + * 使用数字代码表示不同的题目类型 + */ + @ApiModelProperty(value = "题目类型", required=true) + private Integer quType; + + /** + * 是否已答题 + * true表示用户已经回答了该题目,false表示未回答 + * 用于标识题目的答题状态 + */ + @ApiModelProperty(value = "是否已答", required=true) + private Boolean answered; + + /** + * 主观题答案内容 + * 存储用户对于主观题(如简答题、论述题)的文本答案 + * 对于客观题,此字段可能为空或存储其他信息 + */ + @ApiModelProperty(value = "主观答案", required=true) + private String answer; + + /** + * 题目在试卷中的排序号 + * 控制题目在试卷中的显示顺序,数值越小显示越靠前 + */ + @ApiModelProperty(value = "问题排序", required=true) + private Integer sort; + + /** + * 单题分值 + * 该题目在试卷中的满分分值 + */ + @ApiModelProperty(value = "单题分分值", required=true) + private Integer score; + + /** + * 实际得分(主要用于主观题) + * 阅卷老师给该题目评定的实际得分 + * 对于客观题,系统会自动计算得分 + */ + @ApiModelProperty(value = "实际得分(主观题)", required=true) + private Integer actualScore; + + /** + * 是否答对(主要用于客观题) + * true表示该题目回答正确,false表示回答错误 + * 对于主观题,此字段可能为空或根据得分判断 + */ + @ApiModelProperty(value = "是否答对", required=true) + private Boolean isRight; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java new file mode 100644 index 0000000..3bb7816 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java @@ -0,0 +1,45 @@ +package com.yf.exam.modules.paper.dto.ext; + +import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** +*

+* 试卷考题备选答案请求类 +* 扩展试卷考题答案基础DTO,增加试题图片和答案内容信息 +* 用于前端展示试题的完整信息和备选答案 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="试卷考题备选答案", description="试卷考题备选答案") +public class PaperQuAnswerExtDTO extends PaperQuAnswerDTO { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * 试题图片 + * 试题相关的图片URL,用于图文并茂的题目展示 + */ + @ApiModelProperty(value = "试题图片", required=true) + private String image; + + /** + * 答案内容 + * 备选答案的具体文本内容 + */ + @ApiModelProperty(value = "答案内容", required=true) + private String content; + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java new file mode 100644 index 0000000..e832e28 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java @@ -0,0 +1,52 @@ +package com.yf.exam.modules.paper.dto.ext; + +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** +*

+* 试卷考题请求类 +* 扩展试卷考题基础DTO,增加题目内容、图片和备选答案列表 +* 用于前端展示试题的完整信息和答题选项 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="试卷题目详情类", description="试卷题目详情类") +public class PaperQuDetailDTO extends PaperQuDTO { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * 图片 + * 试题相关的图片URL,用于图文并茂的题目展示 + */ + @ApiModelProperty(value = "图片", required=true) + private String image; + + /** + * 题目内容 + * 试题的正文内容,描述题目的具体问题 + */ + @ApiModelProperty(value = "题目内容", required=true) + private String content; + + /** + * 答案内容 + * 试题的备选答案列表,包含所有可能的选项 + */ + @ApiModelProperty(value = "答案内容", required=true) + List answerList; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java new file mode 100644 index 0000000..a8d70ec --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java @@ -0,0 +1,35 @@ +package com.yf.exam.modules.paper.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 试卷答题请求数据传输类 + * 用于接收用户提交的试题答案信息,继承试题查询基础类 + * 支持客观题答案列表和主观题答案文本两种答题方式 + * @author bool + */ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类") +public class PaperAnswerDTO extends PaperQuQueryDTO { + + /** + * 回答列表 + * 用于客观题(多选题等)的答案,存储用户选择的多个选项 + */ + @ApiModelProperty(value = "回答列表", required=true) + private List answers; + + /** + * 主观答案 + * 用于主观题(填空题、简答题等)的答案,存储用户输入的文本内容 + */ + @ApiModelProperty(value = "主观答案", required=true) + private String answer; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java new file mode 100644 index 0000000..428003b --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java @@ -0,0 +1,36 @@ +package com.yf.exam.modules.paper.dto.request; + +import com.yf.exam.core.api.dto.BaseDTO; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 试卷创建请求数据传输类 + * 用于接收创建新试卷的请求参数,包含考试ID和用户ID信息 + * 用户ID通过安全上下文自动设置,不暴露给前端 + * @author bool + */ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="试卷创建请求类", description="试卷创建请求类") +public class PaperCreateReqDTO extends BaseDTO { + + /** + * 用户ID + * 使用JsonIgnore注解,在JSON序列化时忽略此字段 + * 通常在后端通过安全上下文自动设置,避免前端传递 + */ + @JsonIgnore + private String userId; + + /** + * 考试ID + * 要创建试卷对应的考试唯一标识 + */ + @ApiModelProperty(value = "考试ID", required=true) + private String examId; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java new file mode 100644 index 0000000..20a0997 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java @@ -0,0 +1,67 @@ +package com.yf.exam.modules.paper.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 试卷请求类 +* 用于试卷列表查询的请求参数封装,支持多条件筛选 +* 包含用户信息、部门信息、考试信息和试卷状态等查询条件 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="试卷", description="试卷") +public class PaperListReqDTO implements Serializable { + + /** + * 序列化版本UID + * 用于保证序列化的一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + * 查询指定用户的试卷记录 + */ + @ApiModelProperty(value = "用户ID", required=true) + private String userId; + + /** + * 部门ID + * 按部门筛选试卷,用于权限控制 + */ + @ApiModelProperty(value = "部门ID", required=true) + private String departId; + + /** + * 规则ID + * 查询指定考试的试卷记录 + */ + @ApiModelProperty(value = "规则ID", required=true) + private String examId; + + /** + * 用户昵称 + * 按用户真实姓名模糊查询试卷 + */ + @ApiModelProperty(value = "用户昵称", required=true) + private String realName; + + /** + * 试卷状态 + * 按试卷状态筛选,如进行中、已交卷、已批阅等 + */ + @ApiModelProperty(value = "试卷状态", required=true) + private Integer state; + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java new file mode 100644 index 0000000..6bdf038 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java @@ -0,0 +1,34 @@ +package com.yf.exam.modules.paper.dto.request; + +import com.yf.exam.core.api.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 试卷题目详情查询请求数据传输类 + * 用于查询指定试卷中特定题目的详细信息 + * 包含试卷ID和题目ID两个必要参数,精确定位要查询的题目 + * @author bool + */ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类") +public class PaperQuQueryDTO extends BaseDTO { + + /** + * 试卷ID + * 要查询题目所属的试卷唯一标识 + */ + @ApiModelProperty(value = "试卷ID", required=true) + private String paperId; + + /** + * 题目ID + * 要查询的具体题目唯一标识 + */ + @ApiModelProperty(value = "题目ID", required=true) + private String quId; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java new file mode 100644 index 0000000..923e312 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java @@ -0,0 +1,61 @@ +package com.yf.exam.modules.paper.dto.response; + +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Calendar; +import java.util.List; + +/** + * 考试详情响应数据传输类 + * 扩展试卷基础信息,包含按题型分类的题目列表和考试剩余时间计算 + * 用于前端展示考试详情页面,支持按题型分组显示题目 + */ +// 使用Lombok注解自动生成getter、setter等方法 +@Data +// Swagger注解,用于API文档生成,定义模型名称和描述 +@ApiModel(value="考试详情", description="考试详情") +public class ExamDetailRespDTO extends PaperDTO { + + /** + * 单选题列表 + * 试卷中所有单选题的集合 + */ + @ApiModelProperty(value = "单选题列表", required=true) + private List radioList; + + /** + * 多选题列表 + * 试卷中所有多选题的集合 + */ + @ApiModelProperty(value = "多选题列表", required=true) + private List multiList; + + /** + * 判断题列表 + * 试卷中所有判断题的集合 + */ + @ApiModelProperty(value = "判断题", required=true) + private List judgeList; + + /** + * 获取剩余结束秒数 + * 动态计算考试剩余时间,基于试卷创建时间和总时长 + * @return 距离考试结束的剩余秒数 + */ + @ApiModelProperty(value = "剩余结束秒数", required=true) + public Long getLeftSeconds(){ + + // 计算考试结束时间:创建时间 + 总时长(分钟) + Calendar cl = Calendar.getInstance(); + cl.setTime(this.getCreateTime()); + cl.add(Calendar.MINUTE, getTotalTime()); + + // 计算剩余时间:(结束时间 - 当前时间)转换为秒 + return (cl.getTimeInMillis() - System.currentTimeMillis()) / 1000; + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java new file mode 100644 index 0000000..8ed5d37 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java @@ -0,0 +1,28 @@ +package com.yf.exam.modules.paper.dto.response; + +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 考试结果展示响应数据传输对象 + * 用于封装考试结束后向客户端返回的详细考试结果信息 + * 继承自PaperDTO,包含试卷基本信息,并扩展了题目详情列表 + */ +@Data +@ApiModel(value="考试结果展示响应类", description="考试结果展示响应类") +public class ExamResultRespDTO extends PaperDTO { + + /** + * 试卷中所有题目的详细信息列表 + * 包含每道题目的内容、选项、用户答案、标准答案、得分等详细信息 + * 该字段在考试结果展示时为必需数据 + */ + @ApiModelProperty(value = "问题列表", required=true) + private List quList; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java new file mode 100644 index 0000000..a433a0c --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java @@ -0,0 +1,35 @@ +package com.yf.exam.modules.paper.dto.response; + +import com.yf.exam.modules.paper.dto.PaperDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** +*

+* 试卷请求类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +@Data +@ApiModel(value="试卷列表响应类", description="试卷列表响应类") +public class PaperListRespDTO extends PaperDTO { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + * 在反序列化时验证序列化对象的版本是否与当前类定义兼容 + */ + private static final long serialVersionUID = 1L; + + /** + * 考试人员的真实姓名 + * 用于在试卷列表中显示考生的真实身份信息,而非用户名或昵称 + * 该字段在响应中为必需字段,不能为空 + */ + @ApiModelProperty(value = "人员", required=true) + private String realName; + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/Paper.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/Paper.java new file mode 100644 index 0000000..979100e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/Paper.java @@ -0,0 +1,164 @@ +package com.yf.exam.modules.paper.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +import java.util.Date; + +/** +*

+* 试卷实体类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +@Data +@TableName("el_paper") +public class Paper extends Model { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + * 在反序列化时验证序列化对象的版本是否与当前类定义兼容 + */ + private static final long serialVersionUID = 1L; + + /** + * 试卷ID + * 使用MyBatis-Plus的雪花算法分配ID,保证全局唯一性 + * 对应数据库表el_paper的主键字段 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 用户ID + * 参加考试的用户唯一标识,关联用户表 + * 对应数据库表中的user_id字段 + */ + @TableField("user_id") + private String userId; + + /** + * 部门ID + * 用户所属的部门标识,关联部门表 + * 对应数据库表中的depart_id字段 + */ + @TableField("depart_id") + private String departId; + + /** + * 规则ID + * 关联考试规则表,标识该试卷遵循的考试规则 + * 对应数据库表中的exam_id字段 + */ + @TableField("exam_id") + private String examId; + + /** + * 考试标题 + * 试卷的名称或标题,描述考试内容 + * 直接对应数据库表中的title字段 + */ + private String title; + + /** + * 考试时长 + * 规定的考试总时间,单位通常为分钟 + * 对应数据库表中的total_time字段 + */ + @TableField("total_time") + private Integer totalTime; + + /** + * 用户时长 + * 用户实际使用的考试时间,单位通常为分钟 + * 对应数据库表中的user_time字段 + */ + @TableField("user_time") + private Integer userTime; + + /** + * 试卷总分 + * 该试卷所有题目的总分数 + * 对应数据库表中的total_score字段 + */ + @TableField("total_score") + private Integer totalScore; + + /** + * 及格分 + * 考试通过所需的最低分数要求 + * 对应数据库表中的qualify_score字段 + */ + @TableField("qualify_score") + private Integer qualifyScore; + + /** + * 客观分 + * 试卷中客观题部分的总分数 + * 对应数据库表中的obj_score字段 + */ + @TableField("obj_score") + private Integer objScore; + + /** + * 主观分 + * 试卷中主观题部分的总分数 + * 对应数据库表中的subj_score字段 + */ + @TableField("subj_score") + private Integer subjScore; + + /** + * 用户得分 + * 用户在该试卷中获得的实际总分 + * 对应数据库表中的user_score字段 + */ + @TableField("user_score") + private Integer userScore; + + /** + * 是否包含简答题 + * true表示试卷中包含简答题,false表示不包含 + * 对应数据库表中的has_saq字段 + */ + @TableField("has_saq") + private Boolean hasSaq; + + /** + * 试卷状态 + * 标识试卷的当前状态,如未开始、进行中、已提交、已批改等 + * 使用数字代码表示不同的状态 + * 直接对应数据库表中的state字段 + */ + private Integer state; + + /** + * 创建时间 + * 试卷记录的创建时间,通常为开始考试的时间 + * 对应数据库表中的create_time字段 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + * 试卷记录的最后更新时间,包括答题、提交、批改等操作 + * 对应数据库表中的update_time字段 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 截止时间 + * 考试提交的截止时间,超过该时间将不能提交 + * 对应数据库表中的limit_time字段 + */ + @TableField("limit_time") + private Date limitTime; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java new file mode 100644 index 0000000..815c97b --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java @@ -0,0 +1,109 @@ +package com.yf.exam.modules.paper.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 试卷考题实体类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +@Data +@TableName("el_paper_qu") +public class PaperQu extends Model { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + * 在反序列化时验证序列化对象的版本是否与当前类定义兼容 + */ + private static final long serialVersionUID = 1L; + + /** + * 主键ID + * 使用MyBatis-Plus的雪花算法分配ID,保证全局唯一性 + * 对应数据库表el_paper_qu的主键字段 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 试卷ID + * 关联所属的试卷,标识该题目属于哪份试卷 + * 对应数据库表中的paper_id字段 + */ + @TableField("paper_id") + private String paperId; + + /** + * 题目ID + * 关联题库中的原始题目,标识该题目的具体内容 + * 对应数据库表中的qu_id字段 + */ + @TableField("qu_id") + private String quId; + + /** + * 题目类型 + * 标识题目的类型,如单选题、多选题、判断题、简答题等 + * 使用数字代码表示不同的题目类型 + * 对应数据库表中的qu_type字段 + */ + @TableField("qu_type") + private Integer quType; + + /** + * 是否已答题 + * true表示用户已经回答了该题目,false表示未回答 + * 用于标识题目的答题状态 + * 直接对应数据库表中的answered字段 + */ + private Boolean answered; + + /** + * 主观题答案内容 + * 存储用户对于主观题(如简答题、论述题)的文本答案 + * 对于客观题,此字段可能为空或存储其他信息 + * 直接对应数据库表中的answer字段 + */ + private String answer; + + /** + * 题目在试卷中的排序号 + * 控制题目在试卷中的显示顺序,数值越小显示越靠前 + * 直接对应数据库表中的sort字段 + */ + private Integer sort; + + /** + * 单题分值 + * 该题目在试卷中的满分分值 + * 直接对应数据库表中的score字段 + */ + private Integer score; + + /** + * 实际得分(主要用于主观题) + * 阅卷老师给该题目评定的实际得分 + * 对于客观题,系统会自动计算得分 + * 对应数据库表中的actual_score字段 + */ + @TableField("actual_score") + private Integer actualScore; + + /** + * 是否答对(主要用于客观题) + * true表示该题目回答正确,false表示回答错误 + * 对于主观题,此字段可能为空或根据得分判断 + * 对应数据库表中的is_right字段 + */ + @TableField("is_right") + private Boolean isRight; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java new file mode 100644 index 0000000..51007e4 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java @@ -0,0 +1,86 @@ +package com.yf.exam.modules.paper.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 试卷考题备选答案实体类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 17:31 +*/ +@Data +@TableName("el_paper_qu_answer") +public class PaperQuAnswer extends Model { + + /** + * 自增主键ID + * 使用MyBatis-Plus的雪花算法分配ID,保证全局唯一性 + * 对应数据库表el_paper_qu_answer的主键字段 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 试卷ID + * 关联所属的试卷,标识该备选答案属于哪份试卷 + * 对应数据库表中的paper_id字段 + */ + @TableField("paper_id") + private String paperId; + + /** + * 回答项ID + * 关联具体的答案选项,标识具体的答案内容 + * 对应数据库表中的answer_id字段 + */ + @TableField("answer_id") + private String answerId; + + /** + * 题目ID + * 关联所属的题目,标识该备选答案属于哪道题目 + * 对应数据库表中的qu_id字段 + */ + @TableField("qu_id") + private String quId; + + /** + * 是否为正确答案 + * true表示该选项是正确答案,false表示不是正确答案 + * 用于客观题的标准答案判定 + * 对应数据库表中的is_right字段 + */ + @TableField("is_right") + private Boolean isRight; + + /** + * 用户是否选中该选项 + * true表示用户在答题时选择了该选项,false表示未选择 + * 记录用户的实际答题选择 + * 直接对应数据库表中的checked字段 + */ + private Boolean checked; + + /** + * 选项排序号 + * 控制选项在界面上的显示顺序,数值越小显示越靠前 + * 直接对应数据库表中的sort字段 + */ + private Integer sort; + + /** + * 选项标签 + * 用于标识选项的字母标签,如A、B、C、D等 + * 在界面上显示为选项的前缀标识 + * 直接对应数据库表中的abc字段 + */ + private String abc; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java new file mode 100644 index 0000000..7062416 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java @@ -0,0 +1,40 @@ +package com.yf.exam.modules.paper.enums; + + +/** + * 考试状态 + * @author bool + * @date 2019-10-30 13:11 + */ +public interface ExamState { + + /** + * 考试中 + * 表示考试正在进行中,考生可以正常答题 + * 对应试卷的活跃状态 + */ + Integer ENABLE = 0; + + /** + * 待阅卷 + * 表示考试已结束,但主观题部分需要人工批阅 + * 适用于包含简答题、论述题等主观题的试卷 + */ + Integer DISABLED = 1; + + /** + * 已完成 + * 表示考试已完成且所有题目都已批阅完成 + * 包括客观题自动批阅和主观题人工批阅均已完成 + */ + Integer READY_START = 2; + + /** + * 已结束 + * 表示考试已超过截止时间或已强制结束 + * 考生不能再进行答题操作 + */ + Integer OVERDUE = 3; + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java new file mode 100644 index 0000000..f9e904e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java @@ -0,0 +1,41 @@ +package com.yf.exam.modules.paper.enums; + + +/** + * 试卷状态 + * @author bool + * @date 2019-10-30 13:11 + */ +public interface PaperState { + + /** + * 考试中 + * 表示试卷处于正在进行考试的状态 + * 考生可以正常答题,考试时间尚未结束 + */ + Integer ING = 0; + + /** + * 待阅卷 + * 表示试卷已提交,但需要人工批阅主观题部分 + * 适用于包含简答题、论述题等需要人工评分的题目 + */ + Integer WAIT_OPT = 1; + + /** + * 已完成 + * 表示试卷已完成所有批阅流程 + * 包括客观题自动批阅和主观题人工批阅均已完成 + * 考生可以查看最终成绩和考试结果 + */ + Integer FINISHED = 2; + + /** + * 弃考 + * 表示考生主动放弃考试或未在规定时间内完成考试 + * 考试记录会被标记为弃考状态 + */ + Integer BREAK = 3; + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java new file mode 100644 index 0000000..3d7cd3a --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java @@ -0,0 +1,59 @@ +package com.yf.exam.modules.paper.job; + +import com.yf.exam.ability.job.service.JobService; +import com.yf.exam.modules.paper.service.PaperService; +import lombok.extern.log4j.Log4j2; +import org.quartz.Job; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 超时自动交卷任务 + * 用于定时处理考试超时的试卷,自动执行强制交卷操作 + * @author bool + */ +@Log4j2 +@Component +public class BreakExamJob implements Job { + + /** + * 试卷服务,用于执行强制交卷等业务操作 + */ + @Autowired + private PaperService paperService; + + /** + * 执行超时自动交卷任务 + * 从任务上下文中获取试卷信息,并调用强制交卷服务 + * + * @param jobExecutionContext Quartz任务执行上下文,包含任务详情和数据 + * @throws JobExecutionException 当任务执行过程中发生异常时抛出 + */ + @Override + public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { + + // 从任务上下文中获取任务详情 + JobDetail detail = jobExecutionContext.getJobDetail(); + // 获取任务名称,用于日志记录和任务标识 + String name = detail.getKey().getName(); + // 获取任务组名,用于任务分组管理 + String group = detail.getKey().getGroup(); + // 从任务数据中获取试卷ID,用于指定要处理的试卷 + String data = String.valueOf(detail.getJobDataMap().get(JobService.TASK_DATA)); + + // 记录任务开始执行的日志信息 + log.info("++++++++++定时任务:处理到期的交卷"); + log.info("++++++++++jobName:{}", name); + log.info("++++++++++jobGroup:{}", group); + log.info("++++++++++taskData:{}", data); + + // 调用试卷服务的强制交卷方法,处理超时试卷 + // data参数通常为试卷ID,标识需要强制交卷的具体试卷 + paperService.handExam(data); + + } + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java new file mode 100644 index 0000000..6735b0a --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java @@ -0,0 +1,44 @@ +package com.yf.exam.modules.paper.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; +import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; +import com.yf.exam.modules.paper.entity.Paper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** +*

+* 试卷Mapper +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +public interface PaperMapper extends BaseMapper { + + /** + * 查找试卷分页 + * 根据查询条件分页查询试卷列表,包含分页信息 + * + * @param page 分页参数对象,包含当前页码、每页大小等分页信息 + * @param query 查询条件对象,包含试卷的各种筛选条件 + * @return 分页结果对象,包含分页数据和试卷列表响应数据 + */ + IPage paging(Page page, @Param("query") PaperListReqDTO query); + + + /** + * 试卷列表响应类 + * 根据查询条件获取试卷列表,不包含分页信息 + * 适用于导出或全量数据查询场景 + * + * @param query 查询条件对象,包含试卷的各种筛选条件 + * @return 试卷列表响应数据集合 + */ + List list(@Param("query") PaperDTO query); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java new file mode 100644 index 0000000..0711af3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java @@ -0,0 +1,30 @@ +package com.yf.exam.modules.paper.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; +import com.yf.exam.modules.paper.entity.PaperQuAnswer; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** +*

+* 试卷考题备选答案Mapper +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +public interface PaperQuAnswerMapper extends BaseMapper { + + /** + * 查找试卷试题答案列表 + * 根据试卷ID和题目ID查询对应的备选答案列表 + * 返回扩展的答案数据传输对象,包含更详细的答案信息 + * + * @param paperId 试卷ID,用于指定查询的试卷 + * @param quId 题目ID,用于指定查询的题目 + * @return 试卷题目答案扩展对象列表,包含答案内容、是否正确、是否选中等信息 + */ + List list(@Param("paperId") String paperId, @Param("quId") String quId); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java new file mode 100644 index 0000000..4c6c727 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java @@ -0,0 +1,49 @@ +package com.yf.exam.modules.paper.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.entity.PaperQu; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** +*

+* 试卷考题Mapper +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +public interface PaperQuMapper extends BaseMapper { + + /** + * 统计客观分 + * 计算指定试卷中所有客观题的总得分 + * 客观题包括选择题、判断题等系统可自动评分的题目类型 + * + * @param paperId 试卷ID,用于指定要统计的试卷 + * @return 客观题总得分,整数类型 + */ + int sumObjective(@Param("paperId") String paperId); + + /** + * 统计主观分 + * 计算指定试卷中所有主观题的总得分 + * 主观题包括简答题、论述题等需要人工评分的题目类型 + * + * @param paperId 试卷ID,用于指定要统计的试卷 + * @return 主观题总得分,整数类型 + */ + int sumSubjective(@Param("paperId") String paperId); + + /** + * 找出全部试题列表 + * 根据试卷ID查询该试卷下的所有题目详细信息 + * 返回扩展的题目详情数据传输对象,包含题目内容、答案、得分等完整信息 + * + * @param paperId 试卷ID,用于指定要查询的试卷 + * @return 试卷题目详情对象列表,包含题目的完整信息 + */ + List listByPaper(@Param("paperId") String paperId); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java new file mode 100644 index 0000000..baf12ea --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java @@ -0,0 +1,52 @@ +package com.yf.exam.modules.paper.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; +import com.yf.exam.modules.paper.entity.PaperQuAnswer; + +import java.util.List; + +/** +*

+* 试卷考题备选答案业务类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +public interface PaperQuAnswerService extends IService { + + /** + * 分页查询试卷考题备选答案 + * 根据分页请求参数查询备选答案列表,支持分页显示 + * + * @param reqDTO 分页请求参数,包含当前页码、每页大小和查询条件 + * @return 分页的试卷考题备选答案数据传输对象 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 查找试卷试题答案列表(用于考试界面) + * 查询指定试卷和题目的备选答案扩展信息,包含完整的答案信息 + * 适用于考试过程中显示题目选项的场景 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @param quId 题目ID,指定要查询的题目 + * @return 试卷题目答案扩展对象列表,包含答案内容、是否正确、是否选中等完整信息 + */ + List listForExam(String paperId, String quId); + + /** + * 查找答案列表(用于答案填充) + * 查询指定试卷和题目的备选答案基本信息,用于答案填充或基础查询 + * 适用于批改、统计等需要操作原始答案数据的场景 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @param quId 题目ID,指定要查询的题目 + * @return 试卷考题备选答案实体对象列表 + */ + List listForFill(String paperId, String quId); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java new file mode 100644 index 0000000..fc98897 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java @@ -0,0 +1,90 @@ +package com.yf.exam.modules.paper.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.entity.PaperQu; + +import java.util.List; + +/** +*

+* 试卷考题业务类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +public interface PaperQuService extends IService { + + /** + * 分页查询试卷考题 + * 根据分页请求参数查询考题列表,支持分页显示 + * + * @param reqDTO 分页请求参数,包含当前页码、每页大小和查询条件 + * @return 分页的试卷考题数据传输对象 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 根据试卷ID查询题目列表 + * 获取指定试卷下的所有考题,按排序号升序排列 + * 适用于考试过程、题目管理等场景 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @return 试卷考题数据传输对象列表,包含题目的基本信息 + */ + List listByPaper(String paperId); + + /** + * 根据试卷ID和题目ID查找考题详情 + * 使用复合条件精确查找特定试卷中的特定题目 + * 适用于答题、批改等需要精确定位题目的场景 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @param quId 题目ID,指定要查询的题目 + * @return 试卷考题实体对象,如果不存在则返回null + */ + PaperQu findByKey(String paperId, String quId); + + /** + * 根据组合索引更新考题信息 + * 使用复合主键(试卷ID+题目ID)来更新指定的考题信息 + * 适用于答题状态更新、得分更新等场景 + * + * @param qu 试卷考题实体对象,包含要更新的数据 + */ + void updateByKey(PaperQu qu); + + /** + * 统计客观题总分 + * 计算指定试卷中所有客观题的总得分 + * 客观题包括选择题、判断题等系统可自动评分的题目类型 + * + * @param paperId 试卷ID,指定要统计的试卷 + * @return 客观题总得分,整数类型 + */ + int sumObjective(String paperId); + + /** + * 统计主观题总分 + * 计算指定试卷中所有主观题的总得分 + * 主观题包括简答题、论述题等需要人工评分的题目类型 + * + * @param paperId 试卷ID,指定要统计的试卷 + * @return 主观题总得分,整数类型 + */ + int sumSubjective(String paperId); + + /** + * 获取考试结果用的题目详情列表 + * 查询指定试卷的所有题目详细信息,包含题目内容、答案、得分等完整信息 + * 适用于考试结果展示、成绩分析等场景 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @return 试卷题目详情扩展对象列表,包含题目的完整信息 + */ + List listForPaperResult(String paperId); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperService.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperService.java new file mode 100644 index 0000000..f1aeb9b --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperService.java @@ -0,0 +1,105 @@ +package com.yf.exam.modules.paper.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; +import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; +import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; +import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; +import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; +import com.yf.exam.modules.paper.entity.Paper; + +/** +*

+* 试卷业务类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +public interface PaperService extends IService { + + /** + * 创建新试卷 + * 为用户创建一份新的考试试卷,包含题库组题、试卷保存和定时任务设置 + * 会校验用户是否有未完成的考试,避免重复创建 + * + * @param userId 用户ID,指定要创建试卷的用户 + * @param examId 考试规则ID,指定要参加的考试规则 + * @return 创建的试卷ID + */ + String createPaper(String userId, String examId); + + /** + * 获取试卷详情 + * 查询试卷基本信息并按题型分类返回题目列表 + * 适用于考试过程中的题目展示 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @return 试卷详情响应对象,包含试卷基本信息和分类题目列表 + */ + ExamDetailRespDTO paperDetail(String paperId); + + /** + * 获取考试结果 + * 查询试卷的最终结果,包含所有题目的详细信息和得分情况 + * 适用于考试结束后的成绩查看 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @return 考试结果响应对象,包含试卷基本信息和完整题目列表 + */ + ExamResultRespDTO paperResult(String paperId); + + /** + * 查找题目详情 + * 查询指定题目的完整信息,包括题目内容和答案选项 + * 适用于考试过程中查看单个题目的详细信息 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @param quId 题目ID,指定要查询的题目 + * @return 题目详情对象,包含题目内容、答案选项等完整信息 + */ + PaperQuDetailDTO findQuDetail(String paperId, String quId); + + /** + * 填写答案 + * 处理用户提交的题目答案,更新答题状态和正确性判断 + * 支持客观题和主观题的答案提交 + * + * @param reqDTO 答题请求对象,包含试卷ID、题目ID和答案信息 + */ + void fillAnswer(PaperAnswerDTO reqDTO); + + /** + * 交卷操作 + * 处理试卷提交,计算分数,更新状态,并处理相关业务逻辑 + * 包括客观题自动评分、主观题待阅卷状态设置、错题本处理等 + * + * @param paperId 试卷ID,指定要交卷的试卷 + */ + void handExam(String paperId); + + /** + * 分页查询试卷列表 + * 根据查询条件分页查询试卷列表,支持多种筛选条件 + * 适用于试卷管理、成绩查询等场景 + * + * @param reqDTO 分页请求参数,包含分页信息和查询条件 + * @return 分页的试卷列表响应对象 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 检测是否有进行中的考试 + * 查询用户是否有正在进行的考试试卷 + * 用于防止用户重复参加考试或继续未完成的考试 + * + * @param userId 用户ID,指定要检查的用户 + * @return 进行中的试卷信息,如果没有则返回null + */ + PaperDTO checkProcess(String userId); + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java new file mode 100644 index 0000000..c9cf10d --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java @@ -0,0 +1,84 @@ +package com.yf.exam.modules.paper.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; +import com.yf.exam.modules.paper.entity.PaperQuAnswer; +import com.yf.exam.modules.paper.mapper.PaperQuAnswerMapper; +import com.yf.exam.modules.paper.service.PaperQuAnswerService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** +*

+* 试卷考题备选答案服务实现类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +@Service +public class PaperQuAnswerServiceImpl extends ServiceImpl implements PaperQuAnswerService { + + /** + * 分页查询试卷考题备选答案 + * 根据分页请求参数查询备选答案列表,支持分页显示 + * + * @param reqDTO 分页请求参数,包含当前页码、每页大小等分页信息 + * @return 分页的试卷考题备选答案数据传输对象 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + //获得数据 + IPage page = this.page(query, wrapper); + //转换结果:将实体对象分页转换为DTO对象分页 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + /** + * 获取考试用的题目答案列表 + * 查询指定试卷和题目的备选答案扩展信息,用于考试界面显示 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @param quId 题目ID,指定要查询的题目 + * @return 试卷题目答案扩展对象列表,包含完整的答案信息 + */ + @Override + public List listForExam(String paperId, String quId) { + return baseMapper.list(paperId, quId); + } + + /** + * 获取填空用的题目答案列表 + * 查询指定试卷和题目的备选答案基本信息,用于答案填充或基础查询 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @param quId 题目ID,指定要查询的题目 + * @return 试卷考题备选答案实体对象列表 + */ + @Override + public List listForFill(String paperId, String quId) { + //查询条件:根据试卷ID和题目ID精确匹配 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(PaperQuAnswer::getPaperId, paperId) + .eq(PaperQuAnswer::getQuId, quId); + + return this.list(wrapper); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java new file mode 100644 index 0000000..f1910c7 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java @@ -0,0 +1,144 @@ +package com.yf.exam.modules.paper.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.entity.PaperQu; +import com.yf.exam.modules.paper.mapper.PaperQuMapper; +import com.yf.exam.modules.paper.service.PaperQuService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** +*

+* 试卷考题服务实现类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +@Service +public class PaperQuServiceImpl extends ServiceImpl implements PaperQuService { + + /** + * 分页查询试卷考题 + * 根据分页请求参数查询考题列表,支持分页显示 + * + * @param reqDTO 分页请求参数,包含当前页码、每页大小等分页信息 + * @return 分页的试卷考题数据传输对象 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + //获得数据 + IPage page = this.page(query, wrapper); + //转换结果:将实体对象分页转换为DTO对象分页 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + /** + * 根据试卷ID查询考题列表 + * 获取指定试卷下的所有考题,按排序号升序排列 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @return 试卷考题数据传输对象列表 + */ + @Override + public List listByPaper(String paperId) { + + //查询条件:根据试卷ID匹配,并按排序号升序排列 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(PaperQu::getPaperId, paperId) + .orderByAsc(PaperQu::getSort); + + List list = this.list(wrapper); + //使用BeanMapper将实体列表转换为DTO列表 + return BeanMapper.mapList(list, PaperQuDTO.class); + } + + /** + * 根据试卷ID和题目ID查找考题 + * 用于精确查找特定试卷中的特定题目 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @param quId 题目ID,指定要查询的题目 + * @return 试卷考题实体对象,如果不存在则返回null + */ + @Override + public PaperQu findByKey(String paperId, String quId) { + //查询条件:同时匹配试卷ID和题目ID + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(PaperQu::getPaperId, paperId) + .eq(PaperQu::getQuId, quId); + + return this.getOne(wrapper, false); + } + + /** + * 根据试卷ID和题目ID更新考题 + * 使用复合主键(试卷ID+题目ID)来更新指定的考题信息 + * + * @param qu 试卷考题实体对象,包含要更新的数据 + */ + @Override + public void updateByKey(PaperQu qu) { + + //查询条件:同时匹配试卷ID和题目ID + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(PaperQu::getPaperId, qu.getPaperId()) + .eq(PaperQu::getQuId, qu.getQuId()); + + this.update(qu, wrapper); + } + + /** + * 统计客观题总分 + * 计算指定试卷中所有客观题的总得分 + * + * @param paperId 试卷ID,指定要统计的试卷 + * @return 客观题总得分 + */ + @Override + public int sumObjective(String paperId) { + return baseMapper.sumObjective(paperId); + } + + /** + * 统计主观题总分 + * 计算指定试卷中所有主观题的总得分 + * + * @param paperId 试卷ID,指定要统计的试卷 + * @return 主观题总得分 + */ + @Override + public int sumSubjective(String paperId) { + return baseMapper.sumSubjective(paperId); + } + + /** + * 获取考试结果用的题目详情列表 + * 查询指定试卷的所有题目详细信息,用于考试结果展示 + * + * @param paperId 试卷ID,指定要查询的试卷 + * @return 试卷题目详情扩展对象列表,包含题目的完整信息 + */ + @Override + public List listForPaperResult(String paperId) { + return baseMapper.listByPaper(paperId); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java new file mode 100644 index 0000000..d48decd --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java @@ -0,0 +1,590 @@ +package com.yf.exam.modules.paper.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.ability.job.enums.JobGroup; +import com.yf.exam.ability.job.enums.JobPrefix; +import com.yf.exam.ability.job.service.JobService; +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.core.utils.CronUtils; +import com.yf.exam.modules.exam.dto.ExamDTO; +import com.yf.exam.modules.exam.dto.ExamRepoDTO; +import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO; +import com.yf.exam.modules.exam.service.ExamRepoService; +import com.yf.exam.modules.exam.service.ExamService; +import com.yf.exam.modules.paper.dto.PaperDTO; +import com.yf.exam.modules.paper.dto.PaperQuDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; +import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; +import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO; +import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; +import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO; +import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO; +import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; +import com.yf.exam.modules.paper.entity.Paper; +import com.yf.exam.modules.paper.entity.PaperQu; +import com.yf.exam.modules.paper.entity.PaperQuAnswer; +import com.yf.exam.modules.paper.enums.ExamState; +import com.yf.exam.modules.paper.enums.PaperState; +import com.yf.exam.modules.paper.job.BreakExamJob; +import com.yf.exam.modules.paper.mapper.PaperMapper; +import com.yf.exam.modules.paper.service.PaperQuAnswerService; +import com.yf.exam.modules.paper.service.PaperQuService; +import com.yf.exam.modules.paper.service.PaperService; +import com.yf.exam.modules.qu.entity.Qu; +import com.yf.exam.modules.qu.entity.QuAnswer; +import com.yf.exam.modules.qu.enums.QuType; +import com.yf.exam.modules.qu.service.QuAnswerService; +import com.yf.exam.modules.qu.service.QuService; +import com.yf.exam.modules.sys.user.entity.SysUser; +import com.yf.exam.modules.sys.user.service.SysUserService; +import com.yf.exam.modules.user.book.service.UserBookService; +import com.yf.exam.modules.user.exam.service.UserExamService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.*; + +/** +*

+* 试卷服务实现类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 16:33 +*/ +@Service +public class PaperServiceImpl extends ServiceImpl implements PaperService { + + @Autowired + private SysUserService sysUserService; + + @Autowired + private ExamService examService; + + @Autowired + private QuService quService; + + @Autowired + private QuAnswerService quAnswerService; + + @Autowired + private PaperService paperService; + + @Autowired + private PaperQuService paperQuService; + + @Autowired + private PaperQuAnswerService paperQuAnswerService; + + @Autowired + private UserBookService userBookService; + + @Autowired + private ExamRepoService examRepoService; + + @Autowired + private UserExamService userExamService; + + @Autowired + private JobService jobService; + + /** + * 选项标签列表,用于标识选择题的选项(A、B、C、D...) + */ + private static List ABC = Arrays.asList(new String[]{ + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K","L","M","N","O","P","Q","R","S","T","U","V","W","X" + ,"Y","Z" + }); + + /** + * 创建新试卷 + * 为用户创建一份新的考试试卷,包含题库组题、试卷保存和定时任务设置 + * + * @param userId 用户ID + * @param examId 考试规则ID + * @return 创建的试卷ID + * @throws ServiceException 当用户有未完成的考试或考试不存在时抛出异常 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public String createPaper(String userId, String examId) { + + // 校验是否有正在考试的试卷 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(Paper::getUserId, userId) + .eq(Paper::getState, PaperState.ING); + + int exists = this.count(wrapper); + + if (exists > 0) { + throw new ServiceException(ApiError.ERROR_20010002); + } + + // 查找考试 + ExamDTO exam = examService.findById(examId); + + if(exam == null){ + throw new ServiceException(1, "考试不存在!"); + } + + if(!ExamState.ENABLE.equals(exam.getState())){ + throw new ServiceException(1, "考试状态不正确!"); + } + + // 考试题目列表 + List quList = this.generateByRepo(examId); + + if(CollectionUtils.isEmpty(quList)){ + throw new ServiceException(1, "规则不正确,无对应的考题!"); + } + + //保存试卷内容 + Paper paper = this.savePaper(userId, exam, quList); + + // 强制交卷任务 + String jobName = JobPrefix.BREAK_EXAM + paper.getId(); + jobService.addCronJob(BreakExamJob.class, jobName, CronUtils.dateToCron(paper.getLimitTime()), paper.getId()); + + return paper.getId(); + } + + /** + * 获取试卷详情 + * 查询试卷基本信息并按题型分类返回题目列表 + * + * @param paperId 试卷ID + * @return 试卷详情响应对象 + */ + @Override + public ExamDetailRespDTO paperDetail(String paperId) { + + ExamDetailRespDTO respDTO = new ExamDetailRespDTO(); + + // 试题基本信息 + Paper paper = paperService.getById(paperId); + BeanMapper.copy(paper, respDTO); + + // 查找题目列表 + List list = paperQuService.listByPaper(paperId); + + List radioList = new ArrayList<>(); + List multiList = new ArrayList<>(); + List judgeList = new ArrayList<>(); + for(PaperQuDTO item: list){ + if(QuType.RADIO.equals(item.getQuType())){ + radioList.add(item); + } + if(QuType.MULTI.equals(item.getQuType())){ + multiList.add(item); + } + if(QuType.JUDGE.equals(item.getQuType())){ + judgeList.add(item); + } + } + + respDTO.setRadioList(radioList); + respDTO.setMultiList(multiList); + respDTO.setJudgeList(judgeList); + return respDTO; + } + + /** + * 获取考试结果 + * 查询试卷的最终结果,包含所有题目的详细信息和得分情况 + * + * @param paperId 试卷ID + * @return 考试结果响应对象 + */ + @Override + public ExamResultRespDTO paperResult(String paperId) { + + ExamResultRespDTO respDTO = new ExamResultRespDTO(); + + // 试题基本信息 + Paper paper = paperService.getById(paperId); + BeanMapper.copy(paper, respDTO); + + List quList = paperQuService.listForPaperResult(paperId); + respDTO.setQuList(quList); + + return respDTO; + } + + /** + * 查找题目详情 + * 查询指定题目的完整信息,包括题目内容和答案选项 + * + * @param paperId 试卷ID + * @param quId 题目ID + * @return 题目详情对象 + */ + @Override + public PaperQuDetailDTO findQuDetail(String paperId, String quId) { + + PaperQuDetailDTO respDTO = new PaperQuDetailDTO(); + // 问题 + Qu qu = quService.getById(quId); + + // 基本信息 + PaperQu paperQu = paperQuService.findByKey(paperId, quId); + BeanMapper.copy(paperQu, respDTO); + respDTO.setContent(qu.getContent()); + respDTO.setImage(qu.getImage()); + + // 答案列表 + List list = paperQuAnswerService.listForExam(paperId, quId); + respDTO.setAnswerList(list); + + return respDTO; + } + + /** + * 题库组题方式产生题目列表 + * 根据考试规则从题库中随机抽取题目,避免重复 + * + * @param examId 考试规则ID + * @return 试卷题目列表 + */ + private List generateByRepo(String examId){ + + // 查找规则指定的题库 + List list = examRepoService.listByExam(examId); + + //最终的题目列表 + List quList = new ArrayList<>(); + + //排除ID,避免题目重复 + List excludes = new ArrayList<>(); + excludes.add("none"); + + if (!CollectionUtils.isEmpty(list)) { + for (ExamRepoExtDTO item : list) { + + // 单选题 + if(item.getRadioCount() > 0){ + List radioList = quService.listByRandom(item.getRepoId(), QuType.RADIO, excludes, item.getRadioCount()); + for (Qu qu : radioList) { + PaperQu paperQu = this.processPaperQu(item, qu); + quList.add(paperQu); + excludes.add(qu.getId()); + } + } + + //多选题 + if(item.getMultiCount() > 0) { + List multiList = quService.listByRandom(item.getRepoId(), QuType.MULTI, excludes, + item.getMultiCount()); + for (Qu qu : multiList) { + PaperQu paperQu = this.processPaperQu(item, qu); + quList.add(paperQu); + excludes.add(qu.getId()); + } + } + + // 判断题 + if(item.getJudgeCount() > 0) { + List judgeList = quService.listByRandom(item.getRepoId(), QuType.JUDGE, excludes, + item.getJudgeCount()); + for (Qu qu : judgeList) { + PaperQu paperQu = this.processPaperQu(item, qu); + quList.add(paperQu); + excludes.add(qu.getId()); + } + } + } + } + return quList; + } + + /** + * 填充试题题目信息 + * 根据题库规则和题目信息创建试卷题目对象 + * + * @param repo 题库规则 + * @param qu 题目信息 + * @return 试卷题目对象 + */ + private PaperQu processPaperQu(ExamRepoDTO repo, Qu qu) { + + //保存试题信息 + PaperQu paperQu = new PaperQu(); + paperQu.setQuId(qu.getId()); + paperQu.setAnswered(false); + paperQu.setIsRight(false); + paperQu.setQuType(qu.getQuType()); + + if (QuType.RADIO.equals(qu.getQuType())) { + paperQu.setScore(repo.getRadioScore()); + paperQu.setActualScore(repo.getRadioScore()); + } + + if (QuType.MULTI.equals(qu.getQuType())) { + paperQu.setScore(repo.getMultiScore()); + paperQu.setActualScore(repo.getMultiScore()); + } + + if (QuType.JUDGE.equals(qu.getQuType())) { + paperQu.setScore(repo.getJudgeScore()); + paperQu.setActualScore(repo.getJudgeScore()); + } + + return paperQu; + } + + /** + * 保存试卷 + * 创建试卷基本信息并设置考试时间限制 + * + * @param userId 用户ID + * @param exam 考试规则 + * @param quList 题目列表 + * @return 保存的试卷对象 + */ + private Paper savePaper(String userId, ExamDTO exam, List quList) { + + // 查找用户 + SysUser user = sysUserService.getById(userId); + + //保存试卷基本信息 + Paper paper = new Paper(); + paper.setDepartId(user.getDepartId()); + paper.setExamId(exam.getId()); + paper.setTitle(exam.getTitle()); + paper.setTotalScore(exam.getTotalScore()); + paper.setTotalTime(exam.getTotalTime()); + paper.setUserScore(0); + paper.setUserId(userId); + paper.setCreateTime(new Date()); + paper.setUpdateTime(new Date()); + paper.setQualifyScore(exam.getQualifyScore()); + paper.setState(PaperState.ING); + paper.setHasSaq(false); + + // 截止时间 + Calendar cl = Calendar.getInstance(); + cl.setTimeInMillis(System.currentTimeMillis()); + cl.add(Calendar.MINUTE, exam.getTotalTime()); + paper.setLimitTime(cl.getTime()); + + paperService.save(paper); + + if (!CollectionUtils.isEmpty(quList)) { + this.savePaperQu(paper.getId(), quList); + } + + return paper; + } + + /** + * 保存试卷试题列表 + * 批量保存试卷题目和对应的答案选项 + * + * @param paperId 试卷ID + * @param quList 题目列表 + */ + private void savePaperQu(String paperId, List quList){ + + List batchQuList = new ArrayList<>(); + List batchAnswerList = new ArrayList<>(); + + int sort = 0; + for (PaperQu item : quList) { + + item.setPaperId(paperId); + item.setSort(sort); + item.setId(IdWorker.getIdStr()); + + //回答列表 + List answerList = quAnswerService.listAnswerByRandom(item.getQuId()); + + if (!CollectionUtils.isEmpty(answerList)) { + + int ii = 0; + for (QuAnswer answer : answerList) { + PaperQuAnswer paperQuAnswer = new PaperQuAnswer(); + paperQuAnswer.setId(UUID.randomUUID().toString()); + paperQuAnswer.setPaperId(paperId); + paperQuAnswer.setQuId(answer.getQuId()); + paperQuAnswer.setAnswerId(answer.getId()); + paperQuAnswer.setChecked(false); + paperQuAnswer.setSort(ii); + paperQuAnswer.setAbc(ABC.get(ii)); + paperQuAnswer.setIsRight(answer.getIsRight()); + ii++; + batchAnswerList.add(paperQuAnswer); + } + } + + batchQuList.add(item); + sort++; + } + + //添加问题 + paperQuService.saveBatch(batchQuList); + + //批量添加问题答案 + paperQuAnswerService.saveBatch(batchAnswerList); + } + + /** + * 填写答案 + * 处理用户提交的题目答案,更新答题状态和正确性判断 + * + * @param reqDTO 答题请求对象 + * @throws ServiceException 当请求数据异常时抛出 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void fillAnswer(PaperAnswerDTO reqDTO) { + + // 未作答 + if(CollectionUtils.isEmpty(reqDTO.getAnswers()) + && StringUtils.isBlank(reqDTO.getAnswer())){ + return; + } + + //查找答案列表 + List list = paperQuAnswerService.listForFill(reqDTO.getPaperId(), reqDTO.getQuId()); + + //是否正确 + boolean right = true; + + //更新正确答案 + for (PaperQuAnswer item : list) { + + if (reqDTO.getAnswers().contains(item.getId())) { + item.setChecked(true); + } else { + item.setChecked(false); + } + + //有一个对不上就是错的 + if (item.getIsRight()!=null && !item.getIsRight().equals(item.getChecked())) { + right = false; + } + paperQuAnswerService.updateById(item); + } + + //修改为已回答 + PaperQu qu = new PaperQu(); + qu.setQuId(reqDTO.getQuId()); + qu.setPaperId(reqDTO.getPaperId()); + qu.setIsRight(right); + qu.setAnswer(reqDTO.getAnswer()); + qu.setAnswered(true); + + paperQuService.updateByKey(qu); + } + + /** + * 手动交卷 + * 处理试卷提交,计算分数,更新状态,并处理相关业务逻辑 + * + * @param paperId 试卷ID + * @throws ServiceException 当试卷状态不正确时抛出 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void handExam(String paperId) { + + //获取试卷信息 + Paper paper = paperService.getById(paperId); + + //如果不是正常的,抛出异常 + if(!PaperState.ING.equals(paper.getState())){ + throw new ServiceException(1, "试卷状态不正确!"); + } + + // 客观分 + int objScore = paperQuService.sumObjective(paperId); + paper.setObjScore(objScore); + paper.setUserScore(objScore); + + // 主观分,因为要阅卷,所以给0 + paper.setSubjScore(0); + + // 待阅卷 + if(paper.getHasSaq()) { + paper.setState(PaperState.WAIT_OPT); + }else { + + // 同步保存考试成绩 + userExamService.joinResult(paper.getUserId(), paper.getExamId(), objScore, objScore>=paper.getQualifyScore()); + + paper.setState(PaperState.FINISHED); + } + paper.setUpdateTime(new Date()); + + //计算考试时长 + Calendar cl = Calendar.getInstance(); + cl.setTimeInMillis(System.currentTimeMillis()); + int userTime = (int)((System.currentTimeMillis() - paper.getCreateTime().getTime()) / 1000 / 60); + if(userTime == 0){ + userTime = 1; + } + paper.setUserTime(userTime); + + //更新试卷 + paperService.updateById(paper); + + // 终止定时任务 + String name = JobPrefix.BREAK_EXAM + paperId; + jobService.deleteJob(name, JobGroup.SYSTEM); + + //把打错的问题加入错题本 + List list = paperQuService.listByPaper(paperId); + for(PaperQuDTO qu: list){ + // 主观题和对的都不加入错题库 + if(qu.getIsRight()){ + continue; + } + //加入错题本 + new Thread(() -> userBookService.addBook(paper.getExamId(), qu.getQuId())).run(); + } + } + + /** + * 分页查询试卷列表 + * + * @param reqDTO 分页请求参数 + * @return 分页的试卷列表响应对象 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + return baseMapper.paging(reqDTO.toPage(), reqDTO.getParams()); + } + + /** + * 检查考试进度 + * 查询用户是否有正在进行的考试 + * + * @param userId 用户ID + * @return 进行中的试卷信息,如果没有则返回null + */ + @Override + public PaperDTO checkProcess(String userId) { + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(Paper::getUserId, userId) + .eq(Paper::getState, PaperState.ING); + + Paper paper = this.getOne(wrapper, false); + + if (paper != null) { + return BeanMapper.map(paper, PaperDTO.class); + } + + return null; + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/controller/QuController.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/controller/QuController.java new file mode 100644 index 0000000..33884fd --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/controller/QuController.java @@ -0,0 +1,342 @@ +package com.yf.exam.modules.qu.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.google.common.collect.Lists; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.BaseIdReqDTO; +import com.yf.exam.core.api.dto.BaseIdRespDTO; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.core.utils.excel.ExportExcel; +import com.yf.exam.core.utils.excel.ImportExcel; +import com.yf.exam.modules.qu.dto.QuDTO; +import com.yf.exam.modules.qu.dto.export.QuExportDTO; +import com.yf.exam.modules.qu.dto.ext.QuDetailDTO; +import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; +import com.yf.exam.modules.qu.entity.Qu; +import com.yf.exam.modules.qu.service.QuService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** +*

+* 问题题目控制器 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:25 +*/ +@Api(tags={"问题题目"}) +@RestController +@RequestMapping("/exam/api/qu/qu") +public class QuController extends BaseController { + + @Autowired + private QuService baseService; + + /** + * 添加或修改题目 + * 支持题目的新增和修改操作,包含题目内容、选项、答案等完整信息 + * 需要管理员权限才能操作 + * + * @param reqDTO 题目详情请求对象,包含题目的完整信息 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "添加或修改") + @RequestMapping(value = "/save", method = {RequestMethod.POST}) + public ApiRest save(@RequestBody QuDetailDTO reqDTO) { + baseService.save(reqDTO); + return super.success(); + } + + /** + * 批量删除题目 + * 根据ID列表批量删除题目及其相关数据 + * 需要管理员权限才能操作 + * + * @param reqDTO 批量删除请求对象,包含要删除的题目ID列表 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "批量删除") + @RequestMapping(value = "/delete", method = {RequestMethod.POST}) + public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { + //根据ID删除 + baseService.delete(reqDTO.getIds()); + return super.success(); + } + + /** + * 查找题目详情 + * 根据题目ID查询题目的完整信息,包括内容、选项、答案等 + * + * @param reqDTO 基础ID请求对象,包含要查询的题目ID + * @return 题目详情响应对象 + */ + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/detail", method = {RequestMethod.POST}) + public ApiRest detail(@RequestBody BaseIdReqDTO reqDTO) { + QuDetailDTO dto = baseService.detail(reqDTO.getId()); + return super.success(dto); + } + + /** + * 分页查找题目 + * 根据查询条件分页查询题目列表 + * 需要管理员权限才能操作 + * + * @param reqDTO 分页查询请求对象,包含分页参数和查询条件 + * @return 分页的题目列表 + */ + @RequiresRoles("sa") + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = {RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + //分页查询并转换 + IPage page = baseService.paging(reqDTO); + + return super.success(page); + } + + /** + * 导出题目到Excel文件 + * 将题目数据导出为Excel格式,支持按条件筛选导出 + * 需要管理员权限才能操作 + * + * @param response HTTP响应对象 + * @param reqDTO 查询条件请求对象 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ResponseBody + @RequestMapping(value = "/export") + public ApiRest exportFile(HttpServletResponse response, @RequestBody QuQueryReqDTO reqDTO) { + + // 导出文件名 + String fileName = "导出的试题-" + System.currentTimeMillis() + ".xlsx"; + + try { + + int no = 0; + String quId = ""; + List list = baseService.listForExport(reqDTO); + for (QuExportDTO item : list) { + if (!quId.equals(item.getQId())) { + quId = item.getQId(); + no += 1; + } else { + item.setQuType("0"); + item.setQContent(""); + item.setQAnalysis(""); + item.setRepoList(null); + item.setQImage(""); + item.setQVideo(""); + } + item.setNo(String.valueOf(no)); + } + new ExportExcel("试题", QuExportDTO.class).setDataList(list).write(response, fileName).dispose(); + return super.success(); + } catch (Exception e) { + return failure(e.getMessage()); + } + } + + /** + * 从Excel导入题目 + * 通过Excel文件批量导入题目数据 + * 需要管理员权限才能操作 + * + * @param file Excel文件 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ResponseBody + @RequestMapping(value = "/import") + public ApiRest importFile(@RequestParam("file") MultipartFile file) { + + try { + + ImportExcel ei = new ImportExcel(file, 1, 0); + List list = ei.getDataList(QuExportDTO.class); + + // 校验数据 + this.checkExcel(list); + + // 导入数据条数 + baseService.importExcel(list); + + // 导入成功 + return super.success(); + + } catch (IOException e) { + + } catch (InvalidFormatException e) { + + } catch (IllegalAccessException e) { + + } catch (InstantiationException e) { + + } + + return super.failure(); + } + + /** + * 校验Excel数据 + * 对导入的Excel数据进行格式和内容校验 + * + * @param list 导入的题目数据列表 + * @throws ServiceException 当数据校验不通过时抛出异常 + */ + private void checkExcel(List list) throws ServiceException { + + // 约定第三行开始导入 + int line = 3; + StringBuffer sb = new StringBuffer(); + + if (CollectionUtils.isEmpty(list)) { + throw new ServiceException(1, "您导入的数据似乎是一个空表格!"); + } + + Integer quNo = null; + for (QuExportDTO item : list) { + + System.out.println(item.getNo()); + if (StringUtils.isBlank(item.getNo())) { + line++; + continue; + } + + System.out.println(item.getQContent()); + Integer no; + + try { + no = Integer.parseInt(item.getNo()); + } catch (Exception e) { + line++; + continue; + } + + if (no == null) { + sb.append("第" + line + "行,题目序号不能为空!
"); + } + + if (quNo == null || !quNo.equals(no)) { + + if (item.getQuType() == null) { + sb.append("第" + line + "行,题目类型不能为空
"); + } + + if (StringUtils.isBlank(item.getQContent())) { + sb.append("第" + line + "行,题目内容不能为空
"); + } + + if (CollectionUtils.isEmpty(item.getRepoList())) { + sb.append("第" + line + "行,题目必须包含一个题库
"); + } + } + + if (StringUtils.isBlank(item.getAIsRight())) { + sb.append("第" + line + "行,选项是否正确不能为空
"); + } + + if (StringUtils.isBlank(item.getAContent()) && StringUtils.isBlank(item.getAImage())) { + sb.append("第" + line + "行,选项内容和选项图片必须有一个不为空
"); + } + + quNo = no; + line++; + } + + // 存在错误 + if (!"".equals(sb.toString())) { + throw new ServiceException(1, sb.toString()); + } + } + + /** + * 下载导入题目数据模板 + * 提供标准化的Excel模板文件,用于规范题目数据导入 + * + * @param response HTTP响应对象 + * @return 操作结果 + */ + @ResponseBody + @RequestMapping(value = "import/template") + public ApiRest importFileTemplate(HttpServletResponse response) { + try { + String fileName = "试题导入模板.xlsx"; + List list = Lists.newArrayList(); + + QuExportDTO l1 = new QuExportDTO(); + l1.setNo("正式导入,请删除此说明行:数字,相同的数字表示同一题的序列"); + l1.setQContent("问题内容"); + l1.setQAnalysis("整个问题的解析"); + l1.setQuType("只能填写1、2、3、4;1表示单选题,2表示多选题,3表示判断题,4表示主观题"); + l1.setQImage("题目图片,完整URL,多个用逗号隔开,限制10个"); + l1.setQVideo("题目视频,完整URL,只限一个"); + l1.setAImage("答案图片,完整URL,只限一个"); + l1.setRepoList(Arrays.asList(new String[]{"已存在题库的ID,多个用逗号隔开,题库ID错误无法导入"})); + l1.setAContent("候选答案1"); + l1.setAIsRight("只能填写0或1,0表示否,1表示是"); + l1.setAAnalysis("这个项是正确的"); + + + QuExportDTO l2 = new QuExportDTO(); + l2.setQContent("找出以下可以被2整除的数(多选)"); + l2.setQAnalysis("最基本的数学题,不做过多解析"); + l2.setQuType("2"); + l2.setNo("1"); + l2.setAIsRight("1"); + l2.setAContent("数字:2"); + l2.setAAnalysis("2除以2=1,对的"); + + QuExportDTO l3 = new QuExportDTO(); + l3.setNo("1"); + l3.setAIsRight("0"); + l3.setAContent("数字:3"); + l3.setAAnalysis("3除以2=1.5,不能被整除"); + + QuExportDTO l4 = new QuExportDTO(); + l4.setNo("1"); + l4.setAIsRight("1"); + l4.setAContent("数字:6"); + l4.setAAnalysis("6除以2=3,对的"); + + + + list.add(l1); + list.add(l2); + list.add(l3); + list.add(l4); + + new ExportExcel("试题数据", QuExportDTO.class, 1).setDataList(list).write(response, fileName).dispose(); + return super.success(); + } catch (Exception e) { + return super.failure("导入模板下载失败!失败信息:"+e.getMessage()); + } + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuAnswerDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuAnswerDTO.java new file mode 100644 index 0000000..02efd1f --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuAnswerDTO.java @@ -0,0 +1,66 @@ +package com.yf.exam.modules.qu.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 候选答案请求类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@ApiModel(value="候选答案", description="候选答案") +public class QuAnswerDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 答案唯一标识 + * 用于区分不同的候选答案项 + */ + @ApiModelProperty(value = "答案ID", required=true) + private String id; + + /** + * 关联的问题ID + * 表示此答案属于哪个问题 + */ + @ApiModelProperty(value = "问题ID", required=true) + private String quId; + + /** + * 答案正确性标识 + * true: 表示此答案是正确答案 + * false: 表示此答案是错误答案 + */ + @ApiModelProperty(value = "是否正确", required=true) + private Boolean isRight; + + /** + * 答案对应的图片URL或路径 + * 用于存储选项相关的图片资源 + */ + @ApiModelProperty(value = "选项图片", required=true) + private String image; + + /** + * 答案的文本内容 + * 存储候选答案的具体文字描述 + */ + @ApiModelProperty(value = "答案内容", required=true) + private String content; + + /** + * 答案的解析说明 + * 对答案进行解释说明,帮助理解为什么正确或错误 + */ + @ApiModelProperty(value = "答案分析", required=true) + private String analysis; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuDTO.java new file mode 100644 index 0000000..daabb8f --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuDTO.java @@ -0,0 +1,88 @@ +package com.yf.exam.modules.qu.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** +*

+* 问题题目请求类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@ApiModel(value="问题题目", description="问题题目") +public class QuDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 题目唯一标识 + * 用于唯一识别每个题目实体 + */ + @ApiModelProperty(value = "题目ID", required=true) + private String id; + + /** + * 题目类型编码 + * 用于区分不同题型,如单选题、多选题、判断题等 + */ + @ApiModelProperty(value = "题目类型", required=true) + private Integer quType; + + /** + * 题目难度等级 + * 1: 普通难度 + * 2: 较难级别 + */ + @ApiModelProperty(value = "1普通,2较难", required=true) + private Integer level; + + /** + * 题目配图URL或路径 + * 用于存储题目相关的图片资源,如图表、示意图等 + */ + @ApiModelProperty(value = "题目图片", required=true) + private String image; + + /** + * 题目正文内容 + * 存储题目的主要文本内容,包括题干和问题描述 + */ + @ApiModelProperty(value = "题目内容", required=true) + private String content; + + /** + * 题目创建时间 + * 记录题目的初始创建时间戳 + */ + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + /** + * 题目最后更新时间 + * 记录题目最近一次修改的时间戳 + */ + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + + /** + * 题目备注信息 + * 用于存储题目的附加说明、教学提示或其他备注内容 + */ + @ApiModelProperty(value = "题目备注", required=true) + private String remark; + + /** + * 题目整体解析 + * 对题目进行全面的解析说明,包括解题思路、方法技巧等 + */ + @ApiModelProperty(value = "整题解析", required=true) + private String analysis; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuRepoDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuRepoDTO.java new file mode 100644 index 0000000..c17a171 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/QuRepoDTO.java @@ -0,0 +1,58 @@ +package com.yf.exam.modules.qu.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 试题题库请求类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@ApiModel(value="试题题库", description="试题题库") +public class QuRepoDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 试题题库关联关系唯一标识 + * 用于唯一标识题目与题库的关联记录 + */ + private String id; + + /** + * 试题ID + * 关联具体的题目实体 + */ + @ApiModelProperty(value = "试题", required=true) + private String quId; + + /** + * 题库ID + * 关联具体的题库实体 + */ + @ApiModelProperty(value = "归属题库", required=true) + private String repoId; + + /** + * 题目类型 + * 记录题目的类型编码,如单选题、多选题等 + */ + @ApiModelProperty(value = "题目类型", required=true) + private Integer quType; + + /** + * 排序序号 + * 用于控制题目在题库中的显示顺序 + * 数值越小排序越靠前 + */ + @ApiModelProperty(value = "排序", required=true) + private Integer sort; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/export/QuExportDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/export/QuExportDTO.java new file mode 100644 index 0000000..995a1b4 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/export/QuExportDTO.java @@ -0,0 +1,106 @@ +package com.yf.exam.modules.qu.dto.export; + +import com.yf.exam.core.utils.excel.annotation.ExcelField; +import com.yf.exam.core.utils.excel.fieldtype.ListType; +import lombok.Data; + +import java.util.List; + +/** + * 用于导出的数据结构 + * 该类定义了题目导出到Excel时的数据结构,包含题目基本信息和答案选项信息 + * 使用ExcelField注解定义Excel列的标题、排序、对齐方式等属性 + * @author bool + */ +@Data +public class QuExportDTO { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * 题目ID + * 用于内部标识题目,不在Excel中显示 + */ + private String qId; + + /** + * 题目序号 + * 用于标识题目的顺序,相同的序号表示同一题目的不同选项 + */ + @ExcelField(title="题目序号", align=2, sort=1) + private String no; + + /** + * 题目类型 + * 标识题目的类型:1-单选题,2-多选题,3-判断题,4-主观题 + */ + @ExcelField(title="题目类型", align=2, sort=2) + private String quType; + + /** + * 题目内容 + * 题目的正文内容,即题目描述 + */ + @ExcelField(title="题目内容", align=2, sort=3) + private String qContent; + + /** + * 整体解析 + * 对整个题目的解析说明,帮助理解题目 + */ + @ExcelField(title="整体解析", align=2, sort=4) + private String qAnalysis; + + /** + * 题目图片 + * 题目相关的图片URL,多个URL用逗号隔开,最多10个 + */ + @ExcelField(title="题目图片", align=2, sort=5) + private String qImage; + + /** + * 题目视频 + * 题目相关的视频URL,只能有一个 + */ + @ExcelField(title="题目视频", align=2, sort=6) + private String qVideo; + + /** + * 所属题库 + * 题目所属的题库ID列表,多个题库用逗号隔开 + * 使用ListType字段类型处理列表数据 + */ + @ExcelField(title="所属题库", align=2, sort=7, fieldType = ListType.class) + private List repoList; + + /** + * 是否正确项 + * 标识该选项是否为正确答案:0-错误,1-正确 + */ + @ExcelField(title="是否正确项", align=2, sort=8) + private String aIsRight; + + /** + * 选项内容 + * 选择题选项的文本内容 + */ + @ExcelField(title="选项内容", align=2, sort=9) + private String aContent; + + /** + * 选项解析 + * 对单个选项的解析说明 + */ + @ExcelField(title="选项解析", align=2, sort=10) + private String aAnalysis; + + /** + * 选项图片 + * 选项相关的图片URL,只能有一个 + */ + @ExcelField(title="选项图片", align=2, sort=11) + private String aImage; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/export/QuImportDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/export/QuImportDTO.java new file mode 100644 index 0000000..22e48aa --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/export/QuImportDTO.java @@ -0,0 +1,63 @@ +package com.yf.exam.modules.qu.dto.export; + +import com.yf.exam.modules.qu.dto.QuAnswerDTO; +import lombok.Data; + +import java.util.List; + +/** + * 用于导入的数据结构 + * 该类定义了从外部数据源(如Excel)导入题目时的数据结构 + * 包含题目的基本信息和答案选项列表,用于数据转换和业务处理 + * @author bool + */ +@Data +public class QuImportDTO { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * 题目类型 + * 标识题目的类型,如:1-单选题,2-多选题,3-判断题,4-主观题 + * 用于确定题目的分类和评分规则 + */ + private String quType; + + /** + * 题目内容 + * 题目的正文描述,即题目本身的内容 + * 这是题目的核心信息,不能为空 + */ + private String qContent; + + /** + * 整体解析 + * 对整个题目的解析说明,帮助理解题目意图和解题思路 + * 可用于学习阶段的题目解析展示 + */ + private String qAnalysis; + + /** + * 题目图片 + * 题目相关的图片资源URL,用于图文并茂的题目展示 + * 支持多个图片URL,通常用逗号分隔 + */ + private String qImage; + + /** + * 题库名称 + * 题目所属的题库名称,用于关联题目到具体的题库分类 + * 在导入过程中会根据题库名称查找或创建对应的题库 + */ + private String repoName; + + /** + * 答案选项列表 + * 包含题目的所有备选答案信息,每个答案包含内容、正确性、解析等 + * 对于选择题,包含多个选项;对于判断题,通常包含两个选项 + */ + private List answerList; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/ext/QuDetailDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/ext/QuDetailDTO.java new file mode 100644 index 0000000..d39a1b2 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/ext/QuDetailDTO.java @@ -0,0 +1,46 @@ +package com.yf.exam.modules.qu.dto.ext; + +import com.yf.exam.modules.qu.dto.QuAnswerDTO; +import com.yf.exam.modules.qu.dto.QuDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** +*

+* 问题题目详情请求类 +*

+* 继承自QuDTO,扩展了题目详情相关的数据,用于题目管理和展示的完整信息传输 +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@ApiModel(value="问题题目详情", description="问题题目详情") +public class QuDetailDTO extends QuDTO { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * 备选项列表 + * 包含题目的所有备选答案信息,每个答案包含内容、正确性标识、解析等 + * 对于选择题,包含多个选项;对于判断题,通常包含两个选项(正确/错误) + * 该字段在请求中为必需数据,不能为空 + */ + @ApiModelProperty(value = "备选项列表", required=true) + private List answerList; + + /** + * 题库列表 + * 题目所属的题库ID集合,用于关联题目到多个题库分类 + * 一个题目可以同时属于多个题库,便于题目的分类管理和检索 + * 该字段在请求中为必需数据,不能为空 + */ + @ApiModelProperty(value = "题库列表", required=true) + private List repoIds; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/request/QuQueryReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/request/QuQueryReqDTO.java new file mode 100644 index 0000000..b11faf9 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/request/QuQueryReqDTO.java @@ -0,0 +1,60 @@ +package com.yf.exam.modules.qu.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** +*

+* 问题题目查询请求类 +*

+* 用于题目查询的请求参数封装,支持按多种条件筛选题目 +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@ApiModel(value="题目查询请求类", description="题目查询请求类") +public class QuQueryReqDTO implements Serializable { + + /** + * 序列化版本UID,用于保证序列化时的版本一致性 + */ + private static final long serialVersionUID = 1L; + + /** + * 题目类型 + * 用于按题目类型筛选,如:1-单选题,2-多选题,3-判断题,4-主观题 + * 为空时表示不按题目类型筛选 + */ + @ApiModelProperty(value = "题目类型") + private Integer quType; + + /** + * 归属题库 + * 题库ID列表,用于筛选属于指定题库的题目 + * 支持多题库筛选,题目只要属于其中任意一个题库即符合条件 + * 为空时表示不按题库筛选 + */ + @ApiModelProperty(value = "归属题库") + private List repoIds; + + /** + * 题目内容 + * 用于按题目内容关键词进行模糊搜索 + * 支持对题目正文内容的全文检索,为空时表示不按内容筛选 + */ + @ApiModelProperty(value = "题目内容") + private String content; + + /** + * 排除ID列表 + * 需要排除的题目ID集合,用于在查询结果中排除指定的题目 + * 常用于随机抽题时避免重复题目等场景 + */ + @ApiModelProperty(value = "排除ID列表") + private List excludes; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/request/QuRepoBatchReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/request/QuRepoBatchReqDTO.java new file mode 100644 index 0000000..f4444dc --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/dto/request/QuRepoBatchReqDTO.java @@ -0,0 +1,46 @@ +package com.yf.exam.modules.qu.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + *

+ * 问题题目请求类 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 13:23 + */ +@Data +@ApiModel(value = "试题题库批量操作类", description = "试题题库批量操作类") +public class QuRepoBatchReqDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 题目ID列表 + * 用于批量操作时指定需要操作的题目ID集合 + */ + @ApiModelProperty(value = "题目ID", required = true) + private List quIds; + + /** + * 题库ID列表 + * 用于指定题目需要关联或移除的题库集合 + */ + @ApiModelProperty(value = "题库ID", required = true) + private List repoIds; + + /** + * 操作类型标识 + * true: 表示从题库中移除题目 + * false: 表示向题库中新增题目 + */ + @ApiModelProperty(value = "是否移除,否就新增;是就移除", required = true) + private Boolean remove; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/Qu.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/Qu.java new file mode 100644 index 0000000..3dcaf10 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/Qu.java @@ -0,0 +1,85 @@ +package com.yf.exam.modules.qu.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +import java.util.Date; + +/** +*

+* 问题题目实体类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@TableName("el_qu") +public class Qu extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 题目ID + * 使用雪花算法分配ID,保证分布式系统下的唯一性 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 题目类型 + * 存储题目类型的数字编码,对应数据库表中的qu_type字段 + */ + @TableField("qu_type") + private Integer quType; + + /** + * 题目难度等级 + * 1: 普通难度 + * 2: 较难级别 + */ + private Integer level; + + /** + * 题目配图URL或路径 + * 存储题目相关的图片资源地址,支持图文混合的题目形式 + */ + private String image; + + /** + * 题目正文内容 + * 存储题目的主要文本内容,包括题干和问题描述 + */ + private String content; + + /** + * 题目创建时间 + * 记录题目的初始创建时间,对应数据库表中的create_time字段 + */ + @TableField("create_time") + private Date createTime; + + /** + * 题目最后更新时间 + * 记录题目最近一次修改的时间,对应数据库表中的update_time字段 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 题目备注信息 + * 用于存储题目的附加说明、教学提示或其他备注内容 + */ + private String remark; + + /** + * 题目整体解析 + * 对题目进行全面的解析说明,包括解题思路、方法技巧等 + */ + private String analysis; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/QuAnswer.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/QuAnswer.java new file mode 100644 index 0000000..5b2973a --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/QuAnswer.java @@ -0,0 +1,65 @@ +package com.yf.exam.modules.qu.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 候选答案实体类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@TableName("el_qu_answer") +public class QuAnswer extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 答案ID + * 使用雪花算法分配ID,保证分布式系统下的唯一性 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 关联的问题ID + * 表示此答案属于哪个问题,对应数据库表中的qu_id字段 + */ + @TableField("qu_id") + private String quId; + + /** + * 答案正确性标识 + * true: 表示此答案是正确答案 + * false: 表示此答案是错误答案 + * 对应数据库表中的is_right字段 + */ + @TableField("is_right") + private Boolean isRight; + + /** + * 选项图片URL或路径 + * 用于存储选项相关的图片资源,支持图文混合的选项形式 + */ + private String image; + + /** + * 答案的文本内容 + * 存储候选答案的具体文字描述 + */ + private String content; + + /** + * 答案的解析说明 + * 对答案进行解释说明,帮助理解为什么正确或错误 + */ + private String analysis; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/QuRepo.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/QuRepo.java new file mode 100644 index 0000000..ba24552 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/entity/QuRepo.java @@ -0,0 +1,60 @@ +package com.yf.exam.modules.qu.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 试题题库实体类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@TableName("el_qu_repo") +public class QuRepo extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 试题题库关联关系ID + * 使用雪花算法分配ID,保证分布式系统下的唯一性 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 试题ID + * 关联具体的题目实体,对应数据库表中的qu_id字段 + */ + @TableField("qu_id") + private String quId; + + /** + * 题库ID + * 关联具体的题库实体,对应数据库表中的repo_id字段 + */ + @TableField("repo_id") + private String repoId; + + /** + * 题目类型 + * 记录题目的类型编码,如单选题、多选题等 + * 对应数据库表中的qu_type字段 + */ + @TableField("qu_type") + private Integer quType; + + /** + * 排序序号 + * 用于控制题目在题库中的显示顺序 + * 数值越小排序越靠前 + */ + private Integer sort; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/enums/QuType.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/enums/QuType.java new file mode 100644 index 0000000..a585ce8 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/enums/QuType.java @@ -0,0 +1,30 @@ +package com.yf.exam.modules.qu.enums; + + +/** + * 题目类型枚举接口 + * 定义系统中所有题目类型的常量值 + * @author bool + * @date 2019-10-30 13:11 + */ +public interface QuType { + + /** + * 单选题 + * 只有一个正确答案的选择题 + */ + Integer RADIO = 1; + + /** + * 多选题 + * 有多个正确答案的选择题 + */ + Integer MULTI = 2; + + /** + * 判断题 + * 只有对错两种选项的题目 + */ + Integer JUDGE = 3; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuAnswerMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuAnswerMapper.java new file mode 100644 index 0000000..5f37ba0 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuAnswerMapper.java @@ -0,0 +1,16 @@ +package com.yf.exam.modules.qu.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.qu.entity.QuAnswer; + +/** +*

+* 候选答案Mapper接口 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +public interface QuAnswerMapper extends BaseMapper { + +} diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuMapper.java new file mode 100644 index 0000000..923b7fe --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuMapper.java @@ -0,0 +1,55 @@ +package com.yf.exam.modules.qu.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yf.exam.modules.qu.dto.QuDTO; +import com.yf.exam.modules.qu.dto.export.QuExportDTO; +import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; +import com.yf.exam.modules.qu.entity.Qu; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** +*

+* 问题题目Mapper接口 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +public interface QuMapper extends BaseMapper { + + /** + * 随机抽取题库的数据 + * 根据题库ID、题目类型等条件随机抽取指定数量的题目 + * @param repoId 题库ID,指定要从哪个题库中抽取题目 + * @param quType 题目类型,按题目类型进行筛选(如单选题、多选题等) + * @param excludes 要排除的题目ID列表,避免重复抽取已选中的题目 + * @param size 需要抽取的题目数量 + * @return 随机抽取的题目列表 + */ + List listByRandom(@Param("repoId") String repoId, + @Param("quType") Integer quType, + @Param("excludes") List excludes, + @Param("size") Integer size); + + /** + * 查找导出列表 + * 获取符合查询条件的题目列表,用于数据导出功能 + * @param query 查询条件对象,包含各种筛选条件 + * @return 导出格式的题目数据列表 + */ + List listForExport(@Param("query") QuQueryReqDTO query); + + /** + * 分页查找题目 + * 根据查询条件进行分页查询,返回分页结果 + * @param page 分页参数对象,包含当前页、每页大小等信息 + * @param query 查询条件对象,包含各种筛选条件 + * @return 分页结果,包含题目数据和分页信息 + */ + IPage paging(Page page, @Param("query") QuQueryReqDTO query); + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuRepoMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuRepoMapper.java new file mode 100644 index 0000000..62356dc --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/mapper/QuRepoMapper.java @@ -0,0 +1,16 @@ +package com.yf.exam.modules.qu.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.qu.entity.QuRepo; + +/** +*

+* 试题题库Mapper接口 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +public interface QuRepoMapper extends BaseMapper { + +} diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuAnswerService.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuAnswerService.java new file mode 100644 index 0000000..6cd1a3f --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuAnswerService.java @@ -0,0 +1,52 @@ +package com.yf.exam.modules.qu.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.qu.dto.QuAnswerDTO; +import com.yf.exam.modules.qu.entity.QuAnswer; + +import java.util.List; + +/** +*

+* 候选答案业务类 +* 负责候选答案的增删改查、随机排序、批量保存等业务操作 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +public interface QuAnswerService extends IService { + + /** + * 分页查询数据 + * @param reqDTO 分页查询请求参数,包含分页信息和查询条件 + * @return 分页结果,包含候选答案数据列表和分页信息 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 根据题目ID查询答案并随机排序 + * 主要用于考试时打乱选项顺序,防止作弊 + * @param quId 题目ID + * @return 随机排序后的候选答案实体列表 + */ + List listAnswerByRandom(String quId); + + /** + * 根据问题ID查找所有候选答案 + * 用于题目编辑、详情展示等场景 + * @param quId 题目ID + * @return 候选答案数据传输对象列表 + */ + List listByQu(String quId); + + /** + * 保存题目的所有候选答案 + * 包含新增、更新、删除的完整保存逻辑,会智能识别需要操作的数据 + * @param quId 题目ID + * @param list 候选答案数据传输对象列表 + */ + void saveAll(String quId, List list); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuRepoService.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuRepoService.java new file mode 100644 index 0000000..dd4c9e3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuRepoService.java @@ -0,0 +1,62 @@ +package com.yf.exam.modules.qu.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.qu.dto.QuRepoDTO; +import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO; +import com.yf.exam.modules.qu.entity.QuRepo; + +import java.util.List; + +/** +*

+* 试题题库业务类 +* 负责题目与题库关联关系的管理,包括题目的题库分配、批量操作、查询等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +public interface QuRepoService extends IService { + + /** + * 分页查询数据 + * @param reqDTO 分页查询请求参数,包含分页信息和查询条件 + * @return 分页结果,包含试题题库关联数据列表和分页信息 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 保存题目的所有题库关联 + * 采用全量更新策略,先删除原有关联再保存新的关联 + * @param quId 题目ID + * @param quType 题目类型 + * @param ids 题库ID列表 + */ + void saveAll(String quId, Integer quType, List ids); + + /** + * 根据题目ID查找其所属的所有题库 + * @param quId 题目ID + * @return 题库ID列表 + */ + List listByQu(String quId); + + /** + * 根据题库ID查找包含的所有题目 + * @param repoId 题库ID + * @param quType 题目类型(可空,为空时查询所有类型) + * @param rand 是否随机排序,true为随机排序,false为按排序号升序 + * @return 题目ID列表 + */ + List listByRepo(String repoId, Integer quType, boolean rand); + + /** + * 批量操作题目与题库的关联关系 + * 支持批量添加题目到题库或从题库批量移除题目 + * @param reqDTO 批量操作请求参数 + */ + void batchAction(QuRepoBatchReqDTO reqDTO); + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuService.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuService.java new file mode 100644 index 0000000..73075e8 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/QuService.java @@ -0,0 +1,83 @@ +package com.yf.exam.modules.qu.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.qu.dto.QuDTO; +import com.yf.exam.modules.qu.dto.export.QuExportDTO; +import com.yf.exam.modules.qu.dto.ext.QuDetailDTO; +import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; +import com.yf.exam.modules.qu.entity.Qu; + +import java.util.List; + +/** +*

+* 问题题目业务类 +* 负责题目的完整业务逻辑处理,包括题目的增删改查、导入导出、随机抽题等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +public interface QuService extends IService { + + /** + * 分页查询题目数据 + * @param reqDTO 分页查询请求参数,包含分页信息和查询条件 + * @return 分页结果,包含题目数据列表和分页信息 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 批量删除题目及相关联数据 + * 会同步删除题目的候选答案和题库关联关系,保证数据一致性 + * @param ids 要删除的题目ID列表 + */ + void delete(List ids); + + /** + * 从指定题库中随机抽取题目 + * 用于组卷时的随机抽题功能,支持排除已选题目 + * @param repoId 题库ID + * @param quType 题目类型(可空,为空时抽取所有类型) + * @param excludes 要排除的题目ID列表,避免重复抽取 + * @param size 需要抽取的题目数量 + * @return 随机抽取的题目实体列表 + */ + List listByRandom(String repoId, + Integer quType, + List excludes, + Integer size); + + /** + * 获取题目的完整详情信息 + * 包含题目基本信息、候选答案列表和所属题库信息 + * @param id 题目ID + * @return 题目详情数据传输对象 + */ + QuDetailDTO detail(String id); + + /** + * 保存题目完整信息(新增或更新) + * 包含题目基本信息、候选答案和题库关联的完整保存 + * @param reqDTO 题目详情数据传输对象 + */ + void save(QuDetailDTO reqDTO); + + /** + * 查找导出格式的题目列表 + * 用于题目数据的导出功能,返回专门用于导出的数据传输对象 + * @param query 查询条件对象 + * @return 导出格式的题目数据列表 + */ + List listForExport(QuQueryReqDTO query); + + /** + * 导入Excel格式的题目数据 + * 支持从Excel文件批量导入题目和答案数据 + * @param dtoList Excel解析后的题目数据列表 + * @return 成功导入的题目数量 + */ + int importExcel(List dtoList); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuAnswerServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuAnswerServiceImpl.java new file mode 100644 index 0000000..b98099d --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuAnswerServiceImpl.java @@ -0,0 +1,150 @@ +package com.yf.exam.modules.qu.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.qu.dto.QuAnswerDTO; +import com.yf.exam.modules.qu.entity.QuAnswer; +import com.yf.exam.modules.qu.mapper.QuAnswerMapper; +import com.yf.exam.modules.qu.service.QuAnswerService; +import com.yf.exam.modules.qu.utils.ImageCheckUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** +*

+* 候选答案服务实现类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Service +public class QuAnswerServiceImpl extends ServiceImpl implements QuAnswerService { + + @Autowired + private ImageCheckUtils imageCheckUtils; + + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + //获得数据 + IPage page = this.page(query, wrapper); + //转换结果:通过JSON序列化和反序列化实现实体类到DTO的转换 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + @Override + public List listAnswerByRandom(String quId) { + //创建查询条件:按问题ID查询,并随机排序 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(QuAnswer::getQuId, quId); + //使用数据库的随机函数排序,实现答案选项的随机化 + wrapper.last(" ORDER BY RAND() "); + + return this.list(wrapper); + } + + @Override + public List listByQu(String quId) { + //创建查询条件:按问题ID查询所有关联的答案 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(QuAnswer::getQuId, quId); + + List list = this.list(wrapper); + if(!CollectionUtils.isEmpty(list)){ + //使用BeanMapper将实体列表转换为DTO列表 + return BeanMapper.mapList(list, QuAnswerDTO.class); + } + + return null; + } + + + /** + * 查找指定问题下已存在的答案ID列表 + * 用于在保存时识别哪些答案需要更新,哪些需要删除 + * @param quId 问题ID + * @return 已存在的答案ID列表 + */ + public List findExistsList(String quId) { + //返回结果 + List ids = new ArrayList<>(); + + QueryWrapper wrapper = new QueryWrapper(); + wrapper.lambda().eq(QuAnswer::getQuId, quId); + List list = this.list(wrapper); + + if (!CollectionUtils.isEmpty(list)) { + for (QuAnswer item : list) { + ids.add(item.getId()); + } + } + return ids; + } + + @Override + public void saveAll(String quId, List list) { + + //最终要保存的列表 + List saveList = new ArrayList<>(); + + //查询已存在的答案ID列表 + List ids = this.findExistsList(quId); + + if(!CollectionUtils.isEmpty(list)){ + for(QuAnswerDTO item: list){ + + // 校验图片地址的合法性 + imageCheckUtils.checkImage(item.getImage(), "选项图片地址错误!"); + + //标签ID + String id = item.getId(); + QuAnswer answer = new QuAnswer(); + //使用BeanMapper进行对象属性拷贝 + BeanMapper.copy(item, answer); + answer.setQuId(quId); + + //如果当前答案ID已存在,则从待删除列表中移除(表示需要更新而非删除) + if(ids.contains(id)){ + ids.remove(id); + } + + saveList.add(answer); + } + + //批量保存或更新答案列表 + if(!CollectionUtils.isEmpty(saveList)) { + this.saveOrUpdateBatch(saveList); + } + + //删除已从列表中移除的答案(ids中剩余的就是需要删除的) + if(!ids.isEmpty()){ + this.removeByIds(ids); + } + }else{ + //如果传入的答案列表为空,则删除该问题下的所有答案 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(QuAnswer::getQuId, quId); + this.remove(wrapper); + } + } + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuRepoServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuRepoServiceImpl.java new file mode 100644 index 0000000..8947ad2 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuRepoServiceImpl.java @@ -0,0 +1,175 @@ +package com.yf.exam.modules.qu.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.qu.dto.QuRepoDTO; +import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO; +import com.yf.exam.modules.qu.entity.Qu; +import com.yf.exam.modules.qu.entity.QuRepo; +import com.yf.exam.modules.qu.mapper.QuMapper; +import com.yf.exam.modules.qu.mapper.QuRepoMapper; +import com.yf.exam.modules.qu.service.QuRepoService; +import com.yf.exam.modules.repo.service.RepoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** +*

+* 试题题库服务实现类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Service +public class QuRepoServiceImpl extends ServiceImpl implements QuRepoService { + + @Autowired + private QuMapper quMapper; + + @Autowired + private RepoService repoService; + + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + //获得数据 + IPage page = this.page(query, wrapper); + //转换结果:通过JSON序列化和反序列化实现实体类到DTO的转换 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + @Override + public void saveAll(String quId, Integer quType, List repoIds) { + // 先删除该题目原有的所有题库关联 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(QuRepo::getQuId, quId); + this.remove(wrapper); + + // 保存全部新的题库关联 + if(!CollectionUtils.isEmpty(repoIds)){ + List list = new ArrayList<>(); + for(String repoId: repoIds){ + QuRepo ref = new QuRepo(); + ref.setQuId(quId); + ref.setRepoId(repoId); + ref.setQuType(quType); // 记录题目类型,便于查询时按类型筛选 + list.add(ref); + } + this.saveBatch(list); + + // 对每个涉及的题库重新排序 + for(String repoId: repoIds){ + this.sortRepo(repoId); + } + } + } + + @Override + public List listByQu(String quId) { + // 查询指定题目关联的所有题库ID + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(QuRepo::getQuId, quId); + List list = this.list(wrapper); + List repoIds = new ArrayList<>(); + if(!CollectionUtils.isEmpty(list)){ + for(QuRepo item: list){ + repoIds.add(item.getRepoId()); + } + } + return repoIds; + } + + @Override + public List listByRepo(String repoId, Integer quType, boolean rand) { + // 查询指定题库下的题目ID列表 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(QuRepo::getRepoId, repoId); + + // 按题目类型筛选 + if(quType != null){ + wrapper.lambda().eq(QuRepo::getQuType, quType); + } + + // 排序方式:随机排序或按排序号升序 + if(rand){ + wrapper.orderByAsc(" RAND() "); // 随机排序,用于随机组卷 + }else{ + wrapper.lambda().orderByAsc(QuRepo::getSort); // 按排序号升序 + } + + List list = this.list(wrapper); + List quIds = new ArrayList<>(); + if(!CollectionUtils.isEmpty(list)){ + for(QuRepo item: list){ + quIds.add(item.getQuId()); + } + } + return quIds; + } + + @Override + public void batchAction(QuRepoBatchReqDTO reqDTO) { + + // 移除操作:从指定题库中批量移除题目 + if(reqDTO.getRemove() != null && reqDTO.getRemove()){ + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .in(QuRepo::getRepoId, reqDTO.getRepoIds()) // 多个题库 + .in(QuRepo::getQuId, reqDTO.getQuIds()); // 多个题目 + this.remove(wrapper); + }else{ + // 新增操作:将题目批量添加到指定题库 + for(String quId : reqDTO.getQuIds()){ + Qu q = quMapper.selectById(quId); // 查询题目信息,获取题目类型 + this.saveAll(quId, q.getQuType(), reqDTO.getRepoIds()); + } + } + + // 对涉及的每个题库重新排序 + for(String repoId: reqDTO.getRepoIds()){ + this.sortRepo(repoId); + } + } + + /** + * 对指定题库中的题目进行重新排序 + * 将题库中的所有题目按当前顺序重新设置排序号,确保排序连续 + * @param repoId 题库ID + */ + private void sortRepo(String repoId){ + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(QuRepo::getRepoId, repoId); + + List list = this.list(wrapper); + if(CollectionUtils.isEmpty(list)){ + return; + } + + // 重新设置排序号,从1开始连续递增 + int sort = 1; + for(QuRepo item: list){ + item.setSort(sort); + sort++; + } + // 批量更新排序号 + this.updateBatchById(list); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuServiceImpl.java new file mode 100644 index 0000000..6070c79 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/service/impl/QuServiceImpl.java @@ -0,0 +1,296 @@ +package com.yf.exam.modules.qu.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.ability.upload.config.UploadConfig; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.qu.dto.QuAnswerDTO; +import com.yf.exam.modules.qu.dto.QuDTO; +import com.yf.exam.modules.qu.dto.export.QuExportDTO; +import com.yf.exam.modules.qu.dto.ext.QuDetailDTO; +import com.yf.exam.modules.qu.dto.request.QuQueryReqDTO; +import com.yf.exam.modules.qu.entity.Qu; +import com.yf.exam.modules.qu.entity.QuAnswer; +import com.yf.exam.modules.qu.entity.QuRepo; +import com.yf.exam.modules.qu.enums.QuType; +import com.yf.exam.modules.qu.mapper.QuMapper; +import com.yf.exam.modules.qu.service.QuAnswerService; +import com.yf.exam.modules.qu.service.QuRepoService; +import com.yf.exam.modules.qu.service.QuService; +import com.yf.exam.modules.qu.utils.ImageCheckUtils; +import com.yf.exam.modules.repo.service.RepoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * 题目服务实现类 + *

+ * + * @author 聪明笨狗 + * @since 2020-05-25 10:17 + */ +@Service +public class QuServiceImpl extends ServiceImpl implements QuService { + + @Autowired + private QuAnswerService quAnswerService; + + @Autowired + private QuRepoService quRepoService; + + @Autowired + private ImageCheckUtils imageCheckUtils; + + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + Page page = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + //转换结果:调用自定义的Mapper分页查询方法 + IPage pageData = baseMapper.paging(page, reqDTO.getParams()); + return pageData; + } + + /** + * 批量删除题目及相关联的数据 + * 使用事务保证数据一致性 + * @param ids 要删除的题目ID列表 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(List ids) { + // 移除题目主表数据 + this.removeByIds(ids); + + // 移除关联的候选答案 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().in(QuAnswer::getQuId, ids); + quAnswerService.remove(wrapper); + + // 移除题库绑定关系 + QueryWrapper wrapper1 = new QueryWrapper<>(); + wrapper1.lambda().in(QuRepo::getQuId, ids); + quRepoService.remove(wrapper1); + } + + @Override + public List listByRandom(String repoId, Integer quType, List excludes, Integer size) { + // 调用Mapper的随机查询方法,从指定题库中随机抽取题目 + return baseMapper.listByRandom(repoId, quType, excludes, size); + } + + @Override + public QuDetailDTO detail(String id) { + // 构建题目详情响应对象 + QuDetailDTO respDTO = new QuDetailDTO(); + // 查询题目基本信息 + Qu qu = this.getById(id); + BeanMapper.copy(qu, respDTO); + + // 查询题目的候选答案列表 + List answerList = quAnswerService.listByQu(id); + respDTO.setAnswerList(answerList); + + // 查询题目所属的题库ID列表 + List repoIds = quRepoService.listByQu(id); + respDTO.setRepoIds(repoIds); + + return respDTO; + } + + /** + * 保存题目信息(新增或更新) + * 包含题目基本信息、候选答案和题库关联的完整保存 + * 使用事务保证数据一致性 + * @param reqDTO 题目详情数据传输对象 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void save(QuDetailDTO reqDTO) { + + // 校验题目数据的完整性 + this.checkData(reqDTO, ""); + + Qu qu = new Qu(); + BeanMapper.copy(reqDTO, qu); + + // 校验题干图片地址的合法性 + imageCheckUtils.checkImage(qu.getImage(), "题干图片地址错误!"); + + // 保存或更新题目基本信息 + this.saveOrUpdate(qu); + + // 保存题目的候选答案列表 + quAnswerService.saveAll(qu.getId(), reqDTO.getAnswerList()); + + // 保存题目与题库的关联关系 + quRepoService.saveAll(qu.getId(), qu.getQuType(), reqDTO.getRepoIds()); + } + + @Override + public List listForExport(QuQueryReqDTO query) { + // 调用Mapper的导出查询方法,获取导出格式的题目数据 + return baseMapper.listForExport(query); + } + + @Override + public int importExcel(List dtoList) { + + // 根据题目序号分组存储选项信息 + Map> anMap = new HashMap<>(16); + + // 存储题目本体信息,key为题目序号 + Map quMap = new HashMap<>(16); + + // 数据分组处理:将Excel中的行数据按题目序号分组 + for (QuExportDTO item : dtoList) { + + // 跳过空白的序号行 + if (StringUtils.isEmpty(item.getNo())) { + continue; + } + + Integer key; + // 转换序号为整数 + try { + key = Integer.parseInt(item.getNo()); + } catch (Exception e) { + continue; + } + + // 如果已经有该题目的记录,将当前行作为选项添加到对应列表 + if (anMap.containsKey(key)) { + anMap.get(key).add(item); + } else { + // 如果是新题目,创建新的选项列表并添加当前行 + List subList = new ArrayList<>(); + subList.add(item); + anMap.put(key, subList); + quMap.put(key, item); + } + } + + int count = 0; + try { + // 循环处理每个题目,插入数据库 + for (Integer key : quMap.keySet()) { + + QuExportDTO im = quMap.get(key); + + // 构建题目基本信息 + QuDetailDTO qu = new QuDetailDTO(); + qu.setContent(im.getQContent()); // 题目内容 + qu.setAnalysis(im.getQAnalysis()); // 题目解析 + qu.setQuType(Integer.parseInt(im.getQuType())); // 题目类型 + qu.setCreateTime(new Date()); // 创建时间 + + // 处理并设置题目的候选答案列表 + List answerList = this.processAnswerList(anMap.get(key)); + qu.setAnswerList(answerList); + + // 设置题目所属的题库ID列表 + qu.setRepoIds(im.getRepoList()); + + // 保存题目完整信息 + this.save(qu); + count++; + } + + } catch (ServiceException e) { + e.printStackTrace(); + throw new ServiceException(1, "导入出现问题,行:" + count + "," + e.getMessage()); + } + + return count; + } + + /** + * 处理回答列表,将导入的DTO转换为答案DTO列表 + * @param importList 导入的题目选项数据列表 + * @return 转换后的候选答案DTO列表 + */ + private List processAnswerList(List importList) { + + List list = new ArrayList<>(16); + for (QuExportDTO item : importList) { + QuAnswerDTO a = new QuAnswerDTO(); + a.setIsRight("1".equals(item.getAIsRight())); // 设置答案正确性 + a.setContent(item.getAContent()); // 设置答案内容 + a.setAnalysis(item.getAAnalysis()); // 设置答案解析 + a.setId(""); // 初始化ID为空 + list.add(a); + } + return list; + } + + /** + * 校验题目数据的完整性 + * 包括题目内容、题库选择、选项设置等的验证 + * @param qu 题目详情对象 + * @param no 题目序号(用于错误提示) + * @throws ServiceException 当数据校验不通过时抛出 + */ + public void checkData(QuDetailDTO qu, String no) { + + // 校验题目内容不能为空 + if (StringUtils.isEmpty(qu.getContent())) { + throw new ServiceException(1, no + "题目内容不能为空!"); + } + + // 校验至少选择一个题库 + if (CollectionUtils.isEmpty(qu.getRepoIds())) { + throw new ServiceException(1, no + "至少要选择一个题库!"); + } + + List answers = qu.getAnswerList(); + + // 校验客观题至少要包含一个备选答案 + if (CollectionUtils.isEmpty(answers)) { + throw new ServiceException(1, no + "客观题至少要包含一个备选答案!"); + } + + int trueCount = 0; + for (QuAnswerDTO a : answers) { + + // 校验必须定义选项是否正确项 + if (a.getIsRight() == null) { + throw new ServiceException(1, no + "必须定义选项是否正确项!"); + } + + // 校验选项内容不为空 + if (StringUtils.isEmpty(a.getContent())) { + throw new ServiceException(1, no + "选项内容不为空!"); + } + + // 统计正确答案数量 + if (a.getIsRight()) { + trueCount += 1; + } + } + + // 校验至少要包含一个正确项 + if (trueCount == 0) { + throw new ServiceException(1, no + "至少要包含一个正确项!"); + } + + // 校验单选题不能包含多个正确项 + if (qu.getQuType().equals(QuType.RADIO) && trueCount > 1) { + throw new ServiceException(1, no + "单选题不能包含多个正确项!"); + } + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/qu/utils/ImageCheckUtils.java b/exam-api1/src/main/java/com/yf/exam/modules/qu/utils/ImageCheckUtils.java new file mode 100644 index 0000000..c8eb916 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/qu/utils/ImageCheckUtils.java @@ -0,0 +1,39 @@ +package com.yf.exam.modules.qu.utils; + +import com.yf.exam.ability.upload.config.UploadConfig; +import com.yf.exam.core.exception.ServiceException; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 图片校验工具类 + * 负责校验题目和答案中图片地址的合法性和安全性 + * 防止非法图片地址和跨站脚本攻击 + */ +@Component +public class ImageCheckUtils { + + @Autowired + private UploadConfig conf; + + /** + * 进行图片地址安全性校验! + * 校验图片地址是否来自配置的合法域名,防止非法图片和XSS攻击 + * @param image 待校验的图片地址 + * @param throwMsg 校验不通过时抛出的异常信息 + */ + public void checkImage(String image, String throwMsg) { + + // 如果图片地址为空,直接返回,不进行校验 + if(StringUtils.isBlank(image)){ + return; + } + + // 校验图片地址是否以配置的合法URL开头 + // 确保图片来自可信的域名,防止恶意图片地址 + if(!image.startsWith(conf.getUrl())){ + throw new ServiceException(throwMsg); + } + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/repo/controller/RepoController.java b/exam-api1/src/main/java/com/yf/exam/modules/repo/controller/RepoController.java new file mode 100644 index 0000000..4180ad5 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/repo/controller/RepoController.java @@ -0,0 +1,124 @@ +package com.yf.exam.modules.repo.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.BaseIdReqDTO; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO; +import com.yf.exam.modules.qu.service.QuRepoService; +import com.yf.exam.modules.repo.dto.RepoDTO; +import com.yf.exam.modules.repo.dto.request.RepoReqDTO; +import com.yf.exam.modules.repo.dto.response.RepoRespDTO; +import com.yf.exam.modules.repo.entity.Repo; +import com.yf.exam.modules.repo.service.RepoService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** +*

+* 题库控制器 +* 负责题库的增删改查、题目批量操作等API接口 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:25 +*/ +@Api(tags={"题库"}) +@RestController +@RequestMapping("/exam/api/repo") +public class RepoController extends BaseController { + + @Autowired + private RepoService baseService; + + @Autowired + private QuRepoService quRepoService; + + /** + * 添加或修改题库 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 题库数据传输对象 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "添加或修改") + @RequestMapping(value = "/save", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody RepoDTO reqDTO) { + baseService.save(reqDTO); + return super.success(); + } + + /** + * 批量删除题库 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 包含要删除的题库ID列表 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "批量删除") + @RequestMapping(value = "/delete", method = { RequestMethod.POST}) + public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { + //根据ID删除 + baseService.removeByIds(reqDTO.getIds()); + return super.success(); + } + + /** + * 查找题库详情 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 包含题库ID + * @return 题库详情数据 + */ + @RequiresRoles("sa") + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/detail", method = { RequestMethod.POST}) + public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) { + Repo entity = baseService.getById(reqDTO.getId()); + RepoDTO dto = new RepoDTO(); + BeanUtils.copyProperties(entity, dto); + return super.success(dto); + } + + /** + * 分页查找题库列表 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 分页查询请求参数 + * @return 分页的题库响应数据 + */ + @RequiresRoles("sa") + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + //分页查询并转换 + IPage page = baseService.paging(reqDTO); + + return super.success(page); + } + + /** + * 批量操作题目与题库的关联关系 + * 支持批量添加题目到题库或从题库批量移除题目 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 批量操作请求参数 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "批量操作", notes = "批量加入或从题库移除") + @RequestMapping(value = "/batch-action", method = { RequestMethod.POST}) + public ApiRest batchAction(@RequestBody QuRepoBatchReqDTO reqDTO) { + + //调用题目题库关联服务执行批量操作 + quRepoService.batchAction(reqDTO); + return super.success(); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/RepoDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/RepoDTO.java new file mode 100644 index 0000000..26f9453 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/RepoDTO.java @@ -0,0 +1,67 @@ +package com.yf.exam.modules.repo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** +*

+* 题库数据传输类 +* 用于题库信息的传输,包含题库的基本信息和元数据 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@ApiModel(value="题库", description="题库") +public class RepoDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 题库唯一标识 + * 用于系统内唯一识别每个题库 + */ + @ApiModelProperty(value = "题库ID", required=true) + private String id; + + /** + * 题库编号 + * 题库的业务编号,可用于快速识别和检索 + */ + @ApiModelProperty(value = "题库编号", required=true) + private String code; + + /** + * 题库名称 + * 题库的显示名称,用于界面展示和用户识别 + */ + @ApiModelProperty(value = "题库名称", required=true) + private String title; + + /** + * 题库备注信息 + * 用于存储题库的附加说明、使用说明或其他备注内容 + */ + @ApiModelProperty(value = "题库备注", required=true) + private String remark; + + /** + * 题库创建时间 + * 记录题库的初始创建时间戳 + */ + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + /** + * 题库最后更新时间 + * 记录题库最近一次修改的时间戳 + */ + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/request/RepoReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/request/RepoReqDTO.java new file mode 100644 index 0000000..fed27c7 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/request/RepoReqDTO.java @@ -0,0 +1,39 @@ +package com.yf.exam.modules.repo.dto.request; + +import com.yf.exam.modules.repo.dto.RepoDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** +*

+* 题库分页请求类 +* 用于题库分页查询时的请求参数传递,扩展了基础题库DTO的查询条件 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@ApiModel(value="题库分页请求类", description="题库分页请求类") +public class RepoReqDTO extends RepoDTO { + + private static final long serialVersionUID = 1L; + + /** + * 需要排除的题库ID列表 + * 用于查询时排除指定的题库,常用于避免查询到已选择的题库 + */ + @ApiModelProperty(value = "排除题库ID", required=true) + private List excludes; + + /** + * 题库标题或名称 + * 用于按题库名称进行模糊搜索查询 + */ + @ApiModelProperty(value = "题库标题", required=true) + private String title; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/response/RepoRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/response/RepoRespDTO.java new file mode 100644 index 0000000..80649f2 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/repo/dto/response/RepoRespDTO.java @@ -0,0 +1,44 @@ +package com.yf.exam.modules.repo.dto.response; + +import com.yf.exam.modules.repo.dto.RepoDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** +*

+* 题库分页响应类 +* 用于题库分页查询时的响应数据返回,扩展了题库统计信息 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@ApiModel(value="题库分页响应类", description="题库分页响应类") +public class RepoRespDTO extends RepoDTO { + + private static final long serialVersionUID = 1L; + + /** + * 多选题数量统计 + * 表示该题库中包含的多选题的总数量 + */ + @ApiModelProperty(value = "多选题数量", required=true) + private Integer multiCount; + + /** + * 单选题数量统计 + * 表示该题库中包含的单选题的总数量 + */ + @ApiModelProperty(value = "单选题数量", required=true) + private Integer radioCount; + + /** + * 判断题数量统计 + * 表示该题库中包含的判断题的总数量 + */ + @ApiModelProperty(value = "判断题数量", required=true) + private Integer judgeCount; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/repo/entity/Repo.java b/exam-api1/src/main/java/com/yf/exam/modules/repo/entity/Repo.java new file mode 100644 index 0000000..bc88646 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/repo/entity/Repo.java @@ -0,0 +1,66 @@ +package com.yf.exam.modules.repo.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +import java.util.Date; + +/** +*

+* 题库实体类 +* 对应数据库中的题库表,存储题库的基本信息 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Data +@TableName("el_repo") +public class Repo extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 题库ID + * 使用雪花算法分配ID,保证分布式系统下的唯一性 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 题库编号 + * 题库的业务编码,可用于快速识别和检索题库 + */ + private String code; + + /** + * 题库名称 + * 题库的显示名称,用于界面展示和用户识别 + */ + private String title; + + /** + * 题库备注信息 + * 用于存储题库的附加说明、使用说明或其他备注内容 + */ + private String remark; + + /** + * 题库创建时间 + * 记录题库的初始创建时间,对应数据库表中的create_time字段 + */ + @TableField("create_time") + private Date createTime; + + /** + * 题库最后更新时间 + * 记录题库最近一次修改的时间,对应数据库表中的update_time字段 + */ + @TableField("update_time") + private Date updateTime; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/repo/mapper/RepoMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/repo/mapper/RepoMapper.java new file mode 100644 index 0000000..d3af01e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/repo/mapper/RepoMapper.java @@ -0,0 +1,31 @@ +package com.yf.exam.modules.repo.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yf.exam.modules.repo.dto.request.RepoReqDTO; +import com.yf.exam.modules.repo.dto.response.RepoRespDTO; +import com.yf.exam.modules.repo.entity.Repo; +import org.apache.ibatis.annotations.Param; + +/** +*

+* 题库Mapper接口 +* 负责题库数据的数据库访问操作,包含基础CRUD和自定义分页查询 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +public interface RepoMapper extends BaseMapper { + + /** + * 分页查询题库信息 + * 自定义分页查询方法,返回包含统计信息的题库响应数据 + * @param page 分页参数对象,包含当前页、每页大小等分页信息 + * @param query 查询条件对象,包含题库名称、排除列表等查询条件 + * @return 分页结果,包含题库基本信息和题目类型统计信息 + */ + IPage paging(Page page, @Param("query") RepoReqDTO query); + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/repo/service/RepoService.java b/exam-api1/src/main/java/com/yf/exam/modules/repo/service/RepoService.java new file mode 100644 index 0000000..c67e541 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/repo/service/RepoService.java @@ -0,0 +1,37 @@ +package com.yf.exam.modules.repo.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.repo.dto.RepoDTO; +import com.yf.exam.modules.repo.dto.request.RepoReqDTO; +import com.yf.exam.modules.repo.dto.response.RepoRespDTO; +import com.yf.exam.modules.repo.entity.Repo; + +/** +*

+* 题库业务类 +* 负责题库的业务逻辑处理,包括题库的分页查询和保存操作 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +public interface RepoService extends IService { + + /** + * 分页查询题库数据 + * 返回包含统计信息的题库分页数据 + * @param reqDTO 分页查询请求参数,包含分页信息和查询条件 + * @return 分页结果,包含题库基本信息和题目类型统计信息 + */ + IPage paging(PagingReqDTO reqDTO); + + + /** + * 保存题库信息 + * 支持题库的新增和更新操作 + * @param reqDTO 题库数据传输对象 + */ + void save(RepoDTO reqDTO); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/repo/service/impl/RepoServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/repo/service/impl/RepoServiceImpl.java new file mode 100644 index 0000000..4e8c858 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/repo/service/impl/RepoServiceImpl.java @@ -0,0 +1,44 @@ +package com.yf.exam.modules.repo.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.repo.dto.RepoDTO; +import com.yf.exam.modules.repo.dto.request.RepoReqDTO; +import com.yf.exam.modules.repo.dto.response.RepoRespDTO; +import com.yf.exam.modules.repo.entity.Repo; +import com.yf.exam.modules.repo.mapper.RepoMapper; +import com.yf.exam.modules.repo.service.RepoService; +import org.springframework.stereotype.Service; + +/** +*

+* 题库服务实现类 +* 负责题库的业务逻辑处理,包括题库的分页查询和保存操作 +*

+* +* @author 聪明笨狗 +* @since 2020-05-25 13:23 +*/ +@Service +public class RepoServiceImpl extends ServiceImpl implements RepoService { + + @Override + public IPage paging(PagingReqDTO reqDTO) { + // 调用Mapper的自定义分页查询方法 + // 将分页请求转换为MyBatis-Plus的分页对象,并传递查询参数 + return baseMapper.paging(reqDTO.toPage(), reqDTO.getParams()); + } + + @Override + public void save(RepoDTO reqDTO) { + + // 创建题库实体对象 + Repo entity = new Repo(); + // 使用BeanMapper将DTO属性拷贝到实体对象 + BeanMapper.copy(reqDTO, entity); + // 执行保存或更新操作 + this.saveOrUpdate(entity); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/config/controller/SysConfigController.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/controller/SysConfigController.java new file mode 100644 index 0000000..9cb9b9c --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/controller/SysConfigController.java @@ -0,0 +1,74 @@ +package com.yf.exam.modules.sys.config.controller; + +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.BaseIdRespDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.qu.utils.ImageCheckUtils; +import com.yf.exam.modules.sys.config.dto.SysConfigDTO; +import com.yf.exam.modules.sys.config.entity.SysConfig; +import com.yf.exam.modules.sys.config.service.SysConfigService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** +*

+* 通用配置控制器 +* 负责系统全局配置的管理,包括系统LOGO等通用设置的保存和查询 +*

+* +* @author 聪明笨狗 +* @since 2020-04-17 09:12 +*/ +@Api(tags={"通用配置"}) +@RestController +@RequestMapping("/exam/api/sys/config") +public class SysConfigController extends BaseController { + + @Autowired + private SysConfigService baseService; + + @Autowired + private ImageCheckUtils imageCheckUtils; + + /** + * 添加或修改系统配置 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 系统配置数据传输对象 + * @return 包含配置ID的操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "添加或修改") + @RequestMapping(value = "/save", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody SysConfigDTO reqDTO) { + + //复制参数:将DTO对象转换为实体对象 + SysConfig entity = new SysConfig(); + BeanMapper.copy(reqDTO, entity); + + // 校验系统LOGO图片地址的安全性 + imageCheckUtils.checkImage(entity.getBackLogo(), "系统LOGO地址错误!"); + + // 保存或更新系统配置 + baseService.saveOrUpdate(entity); + return super.success(new BaseIdRespDTO(entity.getId())); + } + + /** + * 查找系统配置详情 + * 获取当前系统的全局配置信息 + * @return 系统配置详情数据 + */ + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/detail", method = { RequestMethod.POST}) + public ApiRest find() { + SysConfigDTO dto = baseService.find(); + return super.success(dto); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/config/dto/SysConfigDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/dto/SysConfigDTO.java new file mode 100644 index 0000000..f989e80 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/dto/SysConfigDTO.java @@ -0,0 +1,62 @@ +package com.yf.exam.modules.sys.config.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 通用配置请求类 +* 用于系统全局配置信息的传输,包含系统名称、LOGO、版权等基础配置 +*

+* +* @author 聪明笨狗 +* @since 2020-04-17 09:12 +*/ +@Data +@ApiModel(value="通用配置", description="通用配置") +public class SysConfigDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 配置记录唯一标识 + * 用于系统内唯一识别配置记录,通常对应数据库主键 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 系统显示名称 + * 用于在系统界面中显示的系统名称,如网站标题、登录页标题等 + */ + @ApiModelProperty(value = "系统名称") + private String siteName; + + /** + * 前端系统LOGO图片地址 + * 用于前端页面展示的系统LOGO,通常是用户可见的界面LOGO + * 存储图片的URL地址或文件路径 + */ + @ApiModelProperty(value = "前端LOGO") + private String frontLogo; + + /** + * 后台管理系统LOGO图片地址 + * 用于后台管理界面展示的LOGO,通常是管理员操作界面的LOGO + * 存储图片的URL地址或文件路径 + */ + @ApiModelProperty(value = "后台LOGO") + private String backLogo; + + /** + * 系统版权信息 + * 用于在系统页脚或其他位置显示的版权声明信息 + * 通常包含公司名称、年份、版权声明等内容 + */ + @ApiModelProperty(value = "版权信息") + private String copyRight; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/config/entity/SysConfig.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/entity/SysConfig.java new file mode 100644 index 0000000..2082304 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/entity/SysConfig.java @@ -0,0 +1,67 @@ +package com.yf.exam.modules.sys.config.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 通用配置实体类 +* 对应数据库中的系统配置表,存储系统的全局配置信息 +*

+* +* @author 聪明笨狗 +* @since 2020-04-17 09:12 +*/ +@Data +@TableName("sys_config") +public class SysConfig extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 配置记录唯一标识 + * 使用雪花算法分配ID,保证分布式系统下的唯一性 + * 对应数据库表中的id字段,作为主键 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 系统显示名称 + * 用于在系统界面中显示的系统名称,如网站标题等 + * 对应数据库表中的site_name字段 + */ + @TableField("site_name") + private String siteName; + + /** + * 前端系统LOGO图片地址 + * 用于前端用户界面展示的系统LOGO + * 存储图片的URL地址或文件路径 + * 对应数据库表中的front_logo字段 + */ + @TableField("front_logo") + private String frontLogo; + + /** + * 后台管理系统LOGO图片地址 + * 用于后台管理界面展示的系统LOGO + * 存储图片的URL地址或文件路径 + * 对应数据库表中的back_logo字段 + */ + @TableField("back_logo") + private String backLogo; + + /** + * 系统版权信息 + * 用于在系统页脚或其他位置显示的版权声明信息 + * 通常包含公司名称、年份、版权声明等内容 + * 对应数据库表中的copy_right字段 + */ + @TableField("copy_right") + private String copyRight; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/config/mapper/SysConfigMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/mapper/SysConfigMapper.java new file mode 100644 index 0000000..3e521e6 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/mapper/SysConfigMapper.java @@ -0,0 +1,17 @@ +package com.yf.exam.modules.sys.config.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.sys.config.entity.SysConfig; + +/** +*

+* 通用配置Mapper接口 +* 负责系统配置数据的数据库访问操作,提供对系统全局配置的CRUD功能 +*

+* +* @author 聪明笨狗 +* @since 2020-04-17 09:12 +*/ +public interface SysConfigMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/config/service/SysConfigService.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/service/SysConfigService.java new file mode 100644 index 0000000..d1f6666 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/service/SysConfigService.java @@ -0,0 +1,25 @@ +package com.yf.exam.modules.sys.config.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.modules.sys.config.dto.SysConfigDTO; +import com.yf.exam.modules.sys.config.entity.SysConfig; + +/** +*

+* 通用配置业务类 +* 负责系统全局配置信息的业务逻辑处理,包括配置信息的查询和转换 +*

+* +* @author 聪明笨狗 +* @since 2020-04-17 09:12 +*/ +public interface SysConfigService extends IService { + + /** + * 查找系统配置信息 + * 获取系统的全局配置信息,包括系统名称、LOGO、版权信息等 + * 通常系统配置表为单记录表,此方法返回第一条配置记录 + * @return 系统配置数据传输对象,包含系统的所有全局配置信息 + */ + SysConfigDTO find(); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/config/service/impl/SysConfigServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..91a31be --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/config/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,45 @@ +package com.yf.exam.modules.sys.config.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.sys.config.dto.SysConfigDTO; +import com.yf.exam.modules.sys.config.entity.SysConfig; +import com.yf.exam.modules.sys.config.mapper.SysConfigMapper; +import com.yf.exam.modules.sys.config.service.SysConfigService; +import org.springframework.stereotype.Service; + +/** +*

+* 系统配置服务实现类 +* 负责系统全局配置信息的业务逻辑处理,包括配置信息的查询和转换 +*

+* +* @author 聪明笨狗 +* @since 2020-04-17 09:12 +*/ +@Service +public class SysConfigServiceImpl extends ServiceImpl implements SysConfigService { + + @Override + public SysConfigDTO find() { + + // 创建查询条件包装器,限制只查询一条记录 + // 由于系统配置通常是单记录表,所以只需要查询第一条记录 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.last(" LIMIT 1"); + + // 执行查询操作,获取系统配置实体 + // 第二个参数false表示如果查询到多条记录不抛出异常 + SysConfig entity = this.getOne(wrapper, false); + + // 创建配置数据传输对象 + SysConfigDTO dto = new SysConfigDTO(); + + // 使用BeanMapper将实体对象属性拷贝到DTO对象 + BeanMapper.copy(entity, dto); + + // 返回配置数据传输对象 + return dto; + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/controller/SysDepartController.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/controller/SysDepartController.java new file mode 100644 index 0000000..6abead8 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/controller/SysDepartController.java @@ -0,0 +1,158 @@ +package com.yf.exam.modules.sys.depart.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.BaseIdReqDTO; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; +import com.yf.exam.modules.sys.depart.dto.request.DepartSortReqDTO; +import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; +import com.yf.exam.modules.sys.depart.entity.SysDepart; +import com.yf.exam.modules.sys.depart.service.SysDepartService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** +*

+* 部门信息控制器 +* 负责部门管理的所有RESTful API接口,包括部门的增删改查、树形结构展示和排序功能 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +@Api(tags={"部门信息"}) +@RestController +@RequestMapping("/exam/api/sys/depart") +public class SysDepartController extends BaseController { + + @Autowired + private SysDepartService baseService; + + /** + * 添加或修改部门信息 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 部门数据传输对象,包含部门的基本信息 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "添加或修改") + @RequestMapping(value = "/save", method = { RequestMethod.POST}) + public ApiRest save(@RequestBody SysDepartDTO reqDTO) { + baseService.save(reqDTO); + return super.success(); + } + + /** + * 批量删除部门 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 包含要删除的部门ID列表 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "批量删除") + @RequestMapping(value = "/delete", method = { RequestMethod.POST}) + public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { + //根据ID删除 + baseService.removeByIds(reqDTO.getIds()); + return super.success(); + } + + /** + * 查找部门详情 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 包含部门ID + * @return 部门详情数据 + */ + @RequiresRoles("sa") + @ApiOperation(value = "查找详情") + @RequestMapping(value = "/detail", method = { RequestMethod.POST}) + public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) { + SysDepart entity = baseService.getById(reqDTO.getId()); + SysDepartDTO dto = new SysDepartDTO(); + BeanUtils.copyProperties(entity, dto); + return super.success(dto); + } + + /** + * 分页查找部门列表 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 分页查询请求参数 + * @return 分页的部门树形数据 + */ + @RequiresRoles("sa") + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + //分页查询并转换 + IPage page = baseService.paging(reqDTO); + + return super.success(page); + } + + /** + * 查找部门列表,每次最多返回200条数据 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 部门查询条件 + * @return 部门数据列表 + */ + @RequiresRoles("sa") + @ApiOperation(value = "查找列表") + @RequestMapping(value = "/list", method = { RequestMethod.POST}) + public ApiRest> list(@RequestBody SysDepartDTO reqDTO) { + + //分页查询并转换 + QueryWrapper wrapper = new QueryWrapper<>(); + + //转换并返回 + List list = baseService.list(wrapper); + + //转换数据 + List dtoList = BeanMapper.mapList(list, SysDepartDTO.class); + + return super.success(dtoList); + } + + + /** + * 获取部门树形结构数据 + * 需要超级管理员(sa)角色权限 + * @return 部门树形结构列表 + */ + @RequiresRoles("sa") + @ApiOperation(value = "树列表") + @RequestMapping(value = "/tree", method = { RequestMethod.POST}) + public ApiRest> tree() { + List dtoList = baseService.findTree(); + return super.success(dtoList); + } + + + /** + * 部门排序 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 部门排序请求参数,包含部门ID和新的排序值 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "分类排序") + @RequestMapping(value = "/sort", method = { RequestMethod.POST}) + public ApiRest sort(@RequestBody DepartSortReqDTO reqDTO) { + baseService.sort(reqDTO.getId(), reqDTO.getSort()); + return super.success(); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/SysDepartDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/SysDepartDTO.java new file mode 100644 index 0000000..0fa2acf --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/SysDepartDTO.java @@ -0,0 +1,71 @@ +package com.yf.exam.modules.sys.depart.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 部门信息数据传输类 +* 用于部门信息的传输,包含部门的基本信息、层级关系和排序信息 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +@Data +@ApiModel(value="部门信息", description="部门信息") +public class SysDepartDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 部门唯一标识 + * 用于系统内唯一识别每个部门 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 部门类型 + * 1: 公司 - 最高层级的组织单位 + * 2: 部门 - 公司下的子部门 + * 用于区分组织架构中的不同层级类型 + */ + @ApiModelProperty(value = "部门类型,1公司2部门", required=true) + private Integer deptType; + + /** + * 上级部门ID + * 指向当前部门的直接上级部门 + * 用于构建部门的父子层级关系,根部门的parentId通常为0或空 + */ + @ApiModelProperty(value = "所属上级", required=true) + private String parentId; + + /** + * 部门名称 + * 部门的显示名称,用于界面展示和用户识别 + */ + @ApiModelProperty(value = "部门名称", required=true) + private String deptName; + + /** + * 部门编码 + * 部门的业务编码,可用于快速识别、检索和权限控制 + * 通常遵循一定的编码规则,如按层级编号 + */ + @ApiModelProperty(value = "部门编码", required=true) + private String deptCode; + + /** + * 排序序号 + * 用于控制同级部门中的显示顺序 + * 数值越小排序越靠前,支持手动调整部门顺序 + */ + @ApiModelProperty(value = "排序", required=true) + private Integer sort; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/request/DepartSortReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/request/DepartSortReqDTO.java new file mode 100644 index 0000000..22d6422 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/request/DepartSortReqDTO.java @@ -0,0 +1,40 @@ +package com.yf.exam.modules.sys.depart.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 部门排序请求类 +* 用于部门排序操作的请求参数传输,支持部门的上升和下降排序调整 +*

+* +* @author 聪明笨狗 +* @since 2020-03-14 10:37 +*/ +@Data +@ApiModel(value="部门排序请求类", description="部门排序请求类") +public class DepartSortReqDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 部门唯一标识 + * 需要进行排序调整的目标部门ID + * 通过此ID定位到具体的部门记录 + */ + @ApiModelProperty(value = "部门ID") + private String id; + + /** + * 排序操作类型 + * 0: 下降 - 将部门在列表中的位置向下移动 + * 1: 上升 - 将部门在列表中的位置向上移动 + * 用于控制部门在组织结构中的显示顺序 + */ + @ApiModelProperty(value = "排序操作类型,0下降,1上升") + private Integer sort; +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/response/SysDepartTreeDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/response/SysDepartTreeDTO.java new file mode 100644 index 0000000..1372375 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/dto/response/SysDepartTreeDTO.java @@ -0,0 +1,34 @@ +package com.yf.exam.modules.sys.depart.dto.response; + +import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** +*

+* 部门树结构响应类 +* 用于返回部门树形结构数据的响应传输对象,支持无限层级的部门树展示 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +@Data +@ApiModel(value="部门树结构响应类", description="部门树结构响应类") +public class SysDepartTreeDTO extends SysDepartDTO { + + private static final long serialVersionUID = 1L; + + /** + * 子部门列表 + * 存储当前部门下的所有子部门,形成树形结构 + * 支持无限层级嵌套,实现完整的部门组织结构树 + */ + @ApiModelProperty(value = "子部门列表", required=true) + private List children; + + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/entity/SysDepart.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/entity/SysDepart.java new file mode 100644 index 0000000..57ee6e1 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/entity/SysDepart.java @@ -0,0 +1,76 @@ +package com.yf.exam.modules.sys.depart.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 部门信息实体类 +* 对应数据库中的部门表,存储部门的组织结构信息和基本属性 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +@Data +@TableName("sys_depart") +public class SysDepart extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 部门唯一标识 + * 使用雪花算法分配ID,保证分布式系统下的唯一性 + * 对应数据库表中的id字段,作为主键 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 部门类型 + * 1: 公司 - 最高层级的组织单位 + * 2: 部门 - 公司下的子部门 + * 对应数据库表中的dept_type字段 + */ + @TableField("dept_type") + private Integer deptType; + + /** + * 上级部门ID + * 指向当前部门的直接上级部门 + * 用于构建部门的父子层级关系,根部门的parentId通常为0或空 + * 对应数据库表中的parent_id字段 + */ + @TableField("parent_id") + private String parentId; + + /** + * 部门名称 + * 部门的显示名称,用于界面展示和用户识别 + * 对应数据库表中的dept_name字段 + */ + @TableField("dept_name") + private String deptName; + + /** + * 部门编码 + * 部门的业务编码,可用于快速识别、检索和权限控制 + * 通常遵循一定的编码规则,如按层级编号 + * 对应数据库表中的dept_code字段 + */ + @TableField("dept_code") + private String deptCode; + + /** + * 排序序号 + * 用于控制同级部门中的显示顺序 + * 数值越小排序越靠前,支持手动调整部门顺序 + * 对应数据库表中的sort字段 + */ + private Integer sort; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/mapper/SysDepartMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/mapper/SysDepartMapper.java new file mode 100644 index 0000000..44645ff --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/mapper/SysDepartMapper.java @@ -0,0 +1,30 @@ +package com.yf.exam.modules.sys.depart.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; +import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; +import com.yf.exam.modules.sys.depart.entity.SysDepart; +import org.apache.ibatis.annotations.Param; + +/** +*

+* 部门信息Mapper接口 +* 负责部门数据的数据库访问操作,包含基础CRUD和自定义的树形分页查询 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +public interface SysDepartMapper extends BaseMapper { + + /** + * 部门树形结构分页查询 + * 自定义分页查询方法,返回包含树形结构信息的部门数据 + * @param page 分页参数对象,包含当前页、每页大小等分页信息 + * @param query 查询条件对象,包含部门名称、部门类型等筛选条件 + * @return 分页结果,包含部门树形结构数据和分页信息 + */ + IPage paging(Page page, @Param("query") SysDepartDTO query); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/service/SysDepartService.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/service/SysDepartService.java new file mode 100644 index 0000000..488f1f7 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/service/SysDepartService.java @@ -0,0 +1,69 @@ +package com.yf.exam.modules.sys.depart.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; +import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; +import com.yf.exam.modules.sys.depart.entity.SysDepart; + +import java.util.List; + +/** +*

+* 部门信息业务类 +* 负责部门管理的完整业务逻辑,包括部门树形结构处理、排序、编码生成和层级关系管理 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +public interface SysDepartService extends IService { + + /** + * 保存部门信息 + * 支持部门的新增和更新操作,新增时自动生成部门编码和排序号 + * @param reqDTO 部门数据传输对象,包含部门的基本信息和层级关系 + */ + void save(SysDepartDTO reqDTO); + + /** + * 分页查询部门数据 + * 返回包含树形结构信息的部门分页数据,便于前端展示和管理 + * @param reqDTO 分页查询请求参数,包含分页信息和查询条件 + * @return 分页结果,包含部门树形结构数据和分页信息 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 查找完整的部门树形结构 + * 构建完整的部门组织架构树,包含所有层级的部门关系 + * @return 部门树形结构列表,从根部门开始递归包含所有子部门 + */ + List findTree(); + + /** + * 根据部门ID列表查找部门树 + * 构建指定部门及其相关上级部门的树形结构,用于权限控制等场景 + * @param ids 部门ID列表,指定要包含在树形结构中的部门 + * @return 部门树形结构列表,包含指定部门及其必要的上级部门 + */ + List findTree(List ids); + + /** + * 部门排序调整 + * 调整部门在同级部门中的显示顺序,支持上升和下降操作 + * @param id 要调整排序的部门ID + * @param sort 排序操作类型,0表示下降,1表示上升 + */ + void sort(String id, Integer sort); + + + /** + * 获取某个部门ID下的所有子部门ID + * 递归查询指定部门的所有下级部门,包括直接子部门和间接子部门 + * @param id 部门ID,指定要查询的根部门 + * @return 部门ID列表,包含指定部门及其所有下级部门的ID + */ + List listAllSubIds( String id); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/service/impl/SysDepartServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/service/impl/SysDepartServiceImpl.java new file mode 100644 index 0000000..e519622 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/depart/service/impl/SysDepartServiceImpl.java @@ -0,0 +1,313 @@ +package com.yf.exam.modules.sys.depart.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; +import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; +import com.yf.exam.modules.sys.depart.entity.SysDepart; +import com.yf.exam.modules.sys.depart.mapper.SysDepartMapper; +import com.yf.exam.modules.sys.depart.service.SysDepartService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** +*

+* 部门信息业务实现类 +* 负责部门管理的完整业务逻辑实现,包括部门树形结构处理、排序、编码生成等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-09-02 17:25 +*/ +@Service +public class SysDepartServiceImpl extends ServiceImpl implements SysDepartService { + + + /** + * 0标识为顶级分类 + * 用于表示根级部门的父ID,构建部门树形结构的根节点 + */ + private static final String ROOT_TAG = "0"; + + + @Override + public void save(SysDepartDTO reqDTO) { + + // 新增部门时生成部门编码和排序号,更新时保持不变 + if(StringUtils.isBlank(reqDTO.getId())) { + this.fillCode(reqDTO); + }else{ + // 更新操作时,不允许修改排序和部门编码 + reqDTO.setSort(null); + reqDTO.setDeptCode(null); + } + + // 将DTO转换为实体并保存 + SysDepart entity = new SysDepart(); + BeanMapper.copy(reqDTO, entity); + this.saveOrUpdate(entity); + } + + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 创建分页对象 + Page query = new Page(reqDTO.getCurrent(), reqDTO.getSize()); + + // 获取请求参数 + SysDepartDTO params = reqDTO.getParams(); + + // 调用Mapper的分页查询方法并返回结果 + IPage pageData = baseMapper.paging(query, params); + + return pageData; + } + + + @Override + public List findTree() { + // 查找完整的部门树形结构 + return this.findTree(null); + } + + @Override + public List findTree(List ids) { + + // 创建查询条件,按排序号升序排列 + QueryWrapper wrapper = new QueryWrapper(); + wrapper.lambda().orderByAsc(SysDepart::getSort); + + // 如果传入了部门ID列表,需要查询这些部门及其所有上级部门 + if(!CollectionUtils.isEmpty(ids)){ + + List fullIds = new ArrayList<>(); + for(String id: ids){ + // 递归获取指定部门的所有上级部门ID + this.cycleAllParent(fullIds, id); + } + + // 根据完整的部门ID列表进行查询 + if(!CollectionUtils.isEmpty(fullIds)){ + wrapper.lambda().in(SysDepart::getId, fullIds); + } + } + + // 查询部门列表并转换为DTO + List list = this.list(wrapper); + List dtoList = BeanMapper.mapList(list, SysDepartTreeDTO.class); + + // 构建部门父子关系映射表,key为父部门ID,value为子部门列表 + Map> map = new HashMap<>(16); + + for(SysDepartTreeDTO item: dtoList){ + + // 如果父部门ID已存在映射中,将当前部门添加到对应子部门列表 + if(map.containsKey(item.getParentId())){ + map.get(item.getParentId()).add(item); + continue; + } + + // 创建新的子部门列表并添加到映射表 + List a = new ArrayList<>(); + a.add(item); + map.put(item.getParentId(), a); + } + + // 获取顶级部门列表(父ID为0的部门) + List topList = map.get(ROOT_TAG); + if(!CollectionUtils.isEmpty(topList)){ + // 为每个顶级部门递归填充子部门 + for(SysDepartTreeDTO item: topList){ + this.fillChildren(map, item); + } + } + + return topList; + } + + @Override + public void sort(String id, Integer sort) { + + // 获取要排序的部门信息 + SysDepart depart = this.getById(id); + SysDepart exchange = null; + + QueryWrapper wrapper = new QueryWrapper<>(); + // 限定在同级部门中进行排序 + wrapper.lambda() + .eq(SysDepart::getParentId, depart.getParentId()); + wrapper.last("LIMIT 1"); + + // 上升操作:与排序值较小的部门交换位置 + if(sort == 0){ + // 查询当前部门前面一个部门(排序值较小) + wrapper.lambda() + .lt(SysDepart::getSort, depart.getSort()) + .orderByDesc(SysDepart::getSort); + exchange = this.getOne(wrapper, false); + } + + // 下降操作:与排序值较大的部门交换位置 + if(sort == 1){ + // 查询当前部门后面一个部门(排序值较大) + wrapper.lambda() + .gt(SysDepart::getSort, depart.getSort()) + .orderByAsc(SysDepart::getSort); + exchange = this.getOne(wrapper, false); + } + + // 如果找到要交换的部门,交换两者的排序值 + if(exchange!=null) { + SysDepart a = new SysDepart(); + a.setId(id); + a.setSort(exchange.getSort()); + SysDepart b = new SysDepart(); + b.setId(exchange.getId()); + b.setSort(depart.getSort()); + this.updateById(a); + this.updateById(b); + } + } + + /** + * 生成部门编码和排序号 + * 部门编码规则:父部门编码 + 格式化后的排序号 + * @param reqDTO 部门数据传输对象 + */ + private void fillCode(SysDepartDTO reqDTO){ + + // 部门编码前缀,继承自父部门 + String code = ""; + + // 如果有父部门且不是根部门,获取父部门编码 + if(StringUtils.isNotBlank(reqDTO.getParentId()) + && !ROOT_TAG.equals(reqDTO.getParentId())){ + SysDepart parent = this.getById(reqDTO.getParentId()); + code = parent.getDeptCode(); + } + + QueryWrapper wrapper = new QueryWrapper<>(); + + // 查询同级部门中排序最大的部门 + wrapper.lambda() + .eq(SysDepart::getParentId, reqDTO.getParentId()) + .orderByDesc(SysDepart::getSort); + wrapper.last("LIMIT 1"); + SysDepart depart = this.getOne(wrapper, false); + + // 设置排序号和部门编码 + if(depart !=null){ + // 在最大排序号基础上加1 + code += this.formatCode(depart.getSort()+1); + reqDTO.setSort(depart.getSort()+1); + }else{ + // 第一个子部门,排序号为1 + code += this.formatCode(1); + reqDTO.setSort(1); + } + + reqDTO.setDeptCode(code); + } + + /** + * 格式化排序号为部门编码格式 + * 编码规则:A + 两位数字(不足两位前面补0) + * @param sort 排序号 + * @return 格式化后的编码字符串 + */ + private String formatCode(Integer sort){ + if(sort < 10){ + return "A0"+sort; + } + return "A"+sort; + } + + /** + * 递归填充子部门数据 + * 构建完整的部门树形结构 + * @param map 部门父子关系映射表 + * @param item 当前部门节点 + */ + private void fillChildren(Map> map, SysDepartTreeDTO item){ + + // 如果当前部门有子部门,递归设置子部门 + if(map.containsKey(item.getId())){ + + List children = map.get(item.getId()); + if(!CollectionUtils.isEmpty(children)){ + // 递归填充子部门的子部门 + for(SysDepartTreeDTO sub: children){ + this.fillChildren(map, sub); + } + } + // 设置子部门列表 + item.setChildren(children); + } + } + + + @Override + public List listAllSubIds( String id){ + // 获取指定部门及其所有下级部门的ID列表 + List ids = new ArrayList<>(); + this.cycleAllSubs(ids, id); + return ids; + } + + + /** + * 递归获取指定部门的所有下级部门ID + * 包括直接子部门和间接子部门 + * @param list 存储部门ID的列表 + * @param id 当前部门ID + */ + private void cycleAllSubs(List list, String id){ + + // 添加当前部门ID + list.add(id); + + // 查询当前部门的直接子部门 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(SysDepart::getParentId, id) + .orderByDesc(SysDepart::getSort); + List subList = this.list(wrapper); + + // 递归处理每个子部门 + if(!CollectionUtils.isEmpty(subList)){ + for(SysDepart item: subList){ + this.cycleAllSubs(list, item.getId()); + } + } + } + + /** + * 递归获取指定部门的所有上级部门ID + * 包括直接上级部门和间接上级部门 + * @param list 存储部门ID的列表 + * @param id 当前部门ID + */ + private void cycleAllParent(List list, String id){ + + // 添加当前部门ID + list.add(id); + SysDepart depart = this.getById(id); + + // 如果存在上级部门且不是根部门,继续递归 + if(StringUtils.isNotBlank(depart.getParentId()) + && !ROOT_TAG.equals(depart.getParentId())){ + this.cycleAllParent(list, depart.getParentId()); + } + + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/system/mapper/SysDictMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/system/mapper/SysDictMapper.java new file mode 100644 index 0000000..3b2db33 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/system/mapper/SysDictMapper.java @@ -0,0 +1,31 @@ +package com.yf.exam.modules.sys.system.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** +*

+* 系统字典Mapper接口 +* 负责系统字典数据的动态查询,提供灵活的数据字典查询功能 +*

+* +* @author 聪明笨狗 +* @since 2020-08-22 13:46 +*/ +@Mapper +public interface SysDictMapper { + + /** + * 动态查找数据字典值 + * 根据指定的表名、字段名和条件动态查询字典数据 + * @param table 表名,指定要查询的数据表 + * @param text 显示文本字段名,用于返回给前端的显示内容 + * @param key 条件字段名,用于WHERE条件查询的字段 + * @param value 条件字段值,用于WHERE条件查询的值 + * @return 字典显示文本值,如果未找到则返回null + */ + String findDict(@Param("table") String table, + @Param("text") String text, + @Param("key") String key, + @Param("value") String value); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/system/service/SysDictService.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/system/service/SysDictService.java new file mode 100644 index 0000000..f28b570 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/system/service/SysDictService.java @@ -0,0 +1,24 @@ +package com.yf.exam.modules.sys.system.service; + +/** + * 数据字典服务接口 + * 提供统一的数据字典查询服务,支持动态表名和字段名的字典数据查询 + * @author bool + */ +public interface SysDictService { + + /** + * 动态查找数据字典值 + * 根据指定的表名、字段名和条件值查询对应的字典显示文本 + * 用于将代码值转换为用户友好的显示名称 + * @param table 表名,指定要查询的数据表,如"sys_user"、"sys_depart"等 + * @param text 显示文本字段名,查询结果中要返回的显示内容字段,通常是名称、标题等 + * @param key 条件字段名,用于WHERE条件查询的字段,通常是ID、编码等唯一标识字段 + * @param value 条件字段值,用于WHERE条件查询的值,与条件字段名对应 + * @return 字典显示文本值,如果未找到匹配记录则返回null + */ + String findDict(String table, + String text, + String key, + String value); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/system/service/impl/SysDictServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/system/service/impl/SysDictServiceImpl.java new file mode 100644 index 0000000..a65358f --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/system/service/impl/SysDictServiceImpl.java @@ -0,0 +1,24 @@ +package com.yf.exam.modules.sys.system.service.impl; + +import com.yf.exam.modules.sys.system.mapper.SysDictMapper; +import com.yf.exam.modules.sys.system.service.SysDictService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 系统字典服务实现类 + * 负责系统字典数据的业务逻辑处理,提供统一的数据字典查询服务 + * @author bool + */ +@Service +public class SysDictServiceImpl implements SysDictService { + + @Autowired + private SysDictMapper sysDictMapper; + + @Override + public String findDict(String table, String text, String key, String value) { + // 直接调用Mapper层的字典查询方法,实现字典值的动态查询 + return sysDictMapper.findDict(table, text, key, value); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/controller/SysRoleController.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/controller/SysRoleController.java new file mode 100644 index 0000000..f10f0d3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/controller/SysRoleController.java @@ -0,0 +1,80 @@ +package com.yf.exam.modules.sys.user.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.modules.sys.user.dto.SysRoleDTO; +import com.yf.exam.modules.sys.user.entity.SysRole; +import com.yf.exam.modules.sys.user.service.SysRoleService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + *

+ * 管理用户控制器 + * 负责系统角色管理的RESTful API接口,包括角色列表查询和分页查询功能 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +@Api(tags = {"管理用户"}) +@RestController +@RequestMapping("/exam/api/sys/role") +public class SysRoleController extends BaseController { + + @Autowired + private SysRoleService baseService; + + + + + /** + * 分页查找角色列表 + * 需要超级管理员(sa)角色权限 + * @param reqDTO 分页查询请求参数,包含分页信息和查询条件 + * @return 分页的角色数据列表 + */ + @RequiresRoles("sa") + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + //分页查询并转换 + IPage page = baseService.paging(reqDTO); + return super.success(page); + } + /** + * 查找角色列表,每次最多返回200条数据 + * 需要超级管理员(sa)角色权限 + * 用于角色选择器、下拉列表等需要完整角色数据的场景 + * @return 角色数据列表 + */ + @RequiresRoles("sa") + @ApiOperation(value = "查找列表") + @RequestMapping(value = "/list", method = { RequestMethod.POST}) + public ApiRest> list() { + + //分页查询并转换 ,创建查询条件包装器,不设置条件查询所有角色 + QueryWrapper wrapper = new QueryWrapper<>(); + + //转换并返回,查询角色列表 + List list = baseService.list(wrapper); + + //转换数据,使用BeanMapper将实体列表转换为DTO列表 + List dtoList = BeanMapper.mapList(list, SysRoleDTO.class); + + return super.success(dtoList); + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/controller/SysUserController.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/controller/SysUserController.java new file mode 100644 index 0000000..7a3128d --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/controller/SysUserController.java @@ -0,0 +1,194 @@ +package com.yf.exam.modules.sys.user.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.BaseStateReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.sys.user.dto.SysUserDTO; +import com.yf.exam.modules.sys.user.dto.request.SysUserLoginReqDTO; +import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO; +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +import com.yf.exam.modules.sys.user.entity.SysUser; +import com.yf.exam.modules.sys.user.service.SysUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + *

+ * 管理用户控制器 + * 负责系统用户管理的所有RESTful API接口,包括用户登录、注册、信息管理和权限控制等功能 + *

+ * + * @author 聪明笨狗 + * @since 2020-04-13 16:57 + */ +@Api(tags = {"管理用户"}) +@RestController +@RequestMapping("/exam/api/sys/user") +public class SysUserController extends BaseController { + + @Autowired + private SysUserService baseService; + + /** + * 用户登录 + * 支持跨域访问的用户登录接口,验证用户名和密码后返回登录结果和token + * @return 包含用户信息和token的登录响应数据 + */ + @CrossOrigin + @ApiOperation(value = "用户登录") + @RequestMapping(value = "/login", method = {RequestMethod.POST}) + public ApiRest login(@RequestBody SysUserLoginReqDTO reqDTO) { + SysUserLoginDTO respDTO = baseService.login(reqDTO.getUsername(), reqDTO.getPassword()); + return super.success(respDTO); + } + + /** + * 用户登录 + * 用户登出接口,通过token标识当前会话并执行登出操作 + * @return 操作结果 + */ + @CrossOrigin + @ApiOperation(value = "用户登录") + @RequestMapping(value = "/logout", method = {RequestMethod.POST}) + public ApiRest logout(HttpServletRequest request) { + String token = request.getHeader("token"); + System.out.println("+++++当前会话为:"+token); + baseService.logout(token); + return super.success(); + } + + /** + * 获取会话 + * 根据token获取当前用户的会话信息,包括用户基本信息和权限信息 + * @return 用户会话信息 + */ + @ApiOperation(value = "获取会话") + @RequestMapping(value = "/info", method = {RequestMethod.POST}) + public ApiRest info(@RequestParam("token") String token) { + SysUserLoginDTO respDTO = baseService.token(token); + return success(respDTO); + } + + /** + * 修改用户资料 + * 用户修改自己的基本信息,如姓名、联系方式等 + * @return 操作结果 + */ + @ApiOperation(value = "修改用户资料") + @RequestMapping(value = "/update", method = {RequestMethod.POST}) + public ApiRest update(@RequestBody SysUserDTO reqDTO) { + baseService.update(reqDTO); + return success(); + } + + + /** + * 保存或修改系统用户 + * 需要超级管理员权限的用户管理功能,用于创建或修改系统用户信息 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "保存或修改") + @RequestMapping(value = "/save", method = {RequestMethod.POST}) + public ApiRest save(@RequestBody SysUserSaveReqDTO reqDTO) { + baseService.save(reqDTO); + return success(); + } + + + /** + * 批量删除 + * 需要超级管理员权限的批量删除用户功能 + * @param reqDTO 包含要删除的用户ID列表 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "批量删除") + @RequestMapping(value = "/delete", method = { RequestMethod.POST}) + public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) { + //根据ID删除 + baseService.removeByIds(reqDTO.getIds()); + return super.success(); + } + + /** + * 分页查找 + * 需要超级管理员权限的用户分页查询功能,支持条件查询和分页参数 + * @param reqDTO 分页查询请求参数 + * @return 分页的用户数据列表 + */ + @RequiresRoles("sa") + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + //分页查询并转换 + IPage page = baseService.paging(reqDTO); + return super.success(page); + } + + /** + * 修改状态 + * 需要超级管理员权限的用户状态修改功能,可以批量启用或禁用用户 + * 特别保护admin超级管理员账户不被禁用 + * @param reqDTO 包含用户ID列表和要设置的状态值 + * @return 操作结果 + */ + @RequiresRoles("sa") + @ApiOperation(value = "修改状态") + @RequestMapping(value = "/state", method = { RequestMethod.POST}) + public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) { + + // 条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .in(SysUser::getId, reqDTO.getIds()) + .ne(SysUser::getUserName, "admin"); + + + SysUser record = new SysUser(); + record.setState(reqDTO.getState()); + baseService.update(record, wrapper); + + return super.success(); + } + + + /** + * 保存或修改系统用户 + * 学员自主注册接口,新用户可以通过此接口注册系统账号 + * @return 注册成功后的用户登录信息 + */ + @ApiOperation(value = "学员注册") + @RequestMapping(value = "/reg", method = {RequestMethod.POST}) + public ApiRest reg(@RequestBody SysUserDTO reqDTO) { + SysUserLoginDTO respDTO = baseService.reg(reqDTO); + return success(respDTO); + } + + /** + * 快速注册,如果手机号存在则登录,不存在就注册 + * 便捷的快速注册登录接口,根据手机号自动判断是登录还是注册 + * @return 登录或注册成功后的用户信息 + */ + @ApiOperation(value = "快速注册") + @RequestMapping(value = "/quick-reg", method = {RequestMethod.POST}) + public ApiRest quick(@RequestBody SysUserDTO reqDTO) { + SysUserLoginDTO respDTO = baseService.quickReg(reqDTO); + return success(respDTO); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysRoleDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysRoleDTO.java new file mode 100644 index 0000000..b85de0c --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysRoleDTO.java @@ -0,0 +1,39 @@ +package com.yf.exam.modules.sys.user.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 角色请求类 +* 用于系统角色信息的传输,包含角色的基本属性定义 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@ApiModel(value="角色", description="角色") +public class SysRoleDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 角色唯一标识 + * 用于系统内唯一识别每个角色,通常由系统自动生成 + */ + @ApiModelProperty(value = "角色ID", required=true) + private String id; + + /** + * 角色名称 + * 角色的显示名称,用于界面展示和用户识别 + * 应具有描述性,便于理解角色的权限范围 + */ + @ApiModelProperty(value = "角色名称", required=true) + private String roleName; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserDTO.java new file mode 100644 index 0000000..af2da6f --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserDTO.java @@ -0,0 +1,95 @@ +package com.yf.exam.modules.sys.user.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** +*

+* 管理用户请求类 +* 用于系统管理用户信息的传输,包含用户的基本信息、权限信息和状态信息 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@ApiModel(value="管理用户", description="管理用户") +public class SysUserDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户唯一标识 + * 系统内唯一识别用户的ID,用于用户的精确识别和关联操作 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 登录用户名 + * 用户在系统中的登录账号,需要保证唯一性,用于身份认证 + */ + @ApiModelProperty(value = "用户名", required=true) + private String userName; + + /** + * 用户真实姓名 + * 用户的真实中文姓名,用于界面展示和身份识别 + */ + @ApiModelProperty(value = "真实姓名", required=true) + private String realName; + + /** + * 登录密码 + * 用户的登录密码,需要进行加密存储和传输 + */ + @ApiModelProperty(value = "密码", required=true) + private String password; + + /** + * 密码加密盐值 + * 用于密码加密的随机盐值,增强密码安全性,防止彩虹表攻击 + */ + @ApiModelProperty(value = "密码盐", required=true) + private String salt; + + /** + * 角色ID列表 + * 用户所属角色的ID集合,以字符串形式存储,用于权限控制 + */ + @ApiModelProperty(value = "角色列表", required=true) + private String roleIds; + + /** + * 所属部门ID + * 用户所在部门的唯一标识,用于组织架构管理和部门权限控制 + */ + @ApiModelProperty(value = "部门ID", required=true) + private String departId; + + /** + * 用户创建时间 + * 记录用户账号的创建时间戳,用于审计和版本管理 + */ + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + /** + * 用户信息更新时间 + * 记录用户信息的最后修改时间戳,用于追踪数据变更 + */ + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + + /** + * 用户状态 + * 标识用户账号的当前状态:0-禁用,1-启用,用于账号的状态管理 + */ + @ApiModelProperty(value = "状态", required=true) + private Integer state; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserRoleDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserRoleDTO.java new file mode 100644 index 0000000..1d6c560 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserRoleDTO.java @@ -0,0 +1,45 @@ +package com.yf.exam.modules.sys.user.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 用户角色请求类 +* 用于用户与角色关联关系的数据传输,建立用户和角色之间的多对多关系 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@ApiModel(value="用户角色", description="用户角色") +public class SysUserRoleDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户角色关联关系唯一标识 + * 用于唯一标识用户与角色之间的关联记录 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 用户唯一标识 + * 关联的用户ID,指向具体的用户实体 + */ + @ApiModelProperty(value = "用户ID", required=true) + private String userId; + + /** + * 角色唯一标识 + * 关联的角色ID,指向具体的角色实体 + */ + @ApiModelProperty(value = "角色ID", required=true) + private String roleId; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserLoginReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserLoginReqDTO.java new file mode 100644 index 0000000..0951609 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserLoginReqDTO.java @@ -0,0 +1,40 @@ +package com.yf.exam.modules.sys.user.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 管理员登录请求类 +* 用于系统管理员登录时的请求参数传输,包含用户名和密码等认证信息 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@ApiModel(value="管理员登录请求类", description="管理员登录请求类") +public class SysUserLoginReqDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 登录用户名 + * 系统管理员的登录账号,用于身份识别和系统登录验证 + * 通常是管理员在系统中注册的唯一标识符 + */ + @ApiModelProperty(value = "用户名", required=true) + private String username; + + /** + * 登录密码 + * 系统管理员的登录密码,用于身份认证和安全性验证 + * 密码需要进行加密传输和存储,确保系统安全性 + */ + @ApiModelProperty(value = "密码", required=true) + private String password; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserSaveReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserSaveReqDTO.java new file mode 100644 index 0000000..ad4907d --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserSaveReqDTO.java @@ -0,0 +1,78 @@ +package com.yf.exam.modules.sys.user.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** +*

+* 管理员保存请求类 +* 用于系统管理员用户的新增和修改操作的请求参数传输,包含用户基本信息和权限配置 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@ApiModel(value="管理员保存请求类", description="管理员保存请求类") +public class SysUserSaveReqDTO implements Serializable { + + /** + * 用户唯一标识 + * 用于识别和定位用户记录,新增时为空,修改时传递已有用户ID + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 登录用户名 + * 用户的系统登录账号,需要保证在系统中的唯一性 + * 用于用户登录系统时的身份识别 + */ + @ApiModelProperty(value = "用户名", required=true) + private String userName; + + /** + * 用户头像地址 + * 用户个人头像的图片URL或文件路径 + * 用于在系统界面中展示用户的个性化头像 + */ + @ApiModelProperty(value = "头像", required=true) + private String avatar; + + /** + * 用户真实姓名 + * 用户的真实中文姓名,用于系统内部识别和对外展示 + * 便于管理员识别用户真实身份 + */ + @ApiModelProperty(value = "真实姓名", required=true) + private String realName; + + /** + * 登录密码 + * 用户的系统登录密码,新增用户时必须提供 + * 修改用户信息时,如果提供新密码则会更新,不提供则保持原密码 + */ + @ApiModelProperty(value = "密码", required=true) + private String password; + + /** + * 所属部门ID + * 用户所属的组织部门唯一标识 + * 用于建立用户与部门之间的隶属关系,支持组织架构管理 + */ + @ApiModelProperty(value = "部门", required=true) + private String departId; + + /** + * 用户角色列表 + * 用户所拥有的系统角色ID集合 + * 用于配置用户的系统权限和功能访问范围 + */ + @ApiModelProperty(value = "角色列表", required=true) + private List roles; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserTokenReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserTokenReqDTO.java new file mode 100644 index 0000000..0f26549 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserTokenReqDTO.java @@ -0,0 +1,34 @@ +package com.yf.exam.modules.sys.user.dto.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** +*

+* 会话检查请求类 +* 用于用户会话验证和令牌检查的请求参数传输 +* 通过token令牌来验证用户登录状态和会话有效性 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@ApiModel(value="会话检查请求类", description="会话检查请求类") +public class SysUserTokenReqDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户会话令牌 + * 用户登录成功后获取的访问令牌,用于标识和验证用户会话 + * 在每次请求中携带此令牌以维持登录状态和权限验证 + * 注意:Swagger注解中value值为"用户名"可能是文档描述需要 + */ + @ApiModelProperty(value = "用户名", required=true) + private String token; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/response/SysUserLoginDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/response/SysUserLoginDTO.java new file mode 100644 index 0000000..2ee1d9e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/dto/response/SysUserLoginDTO.java @@ -0,0 +1,96 @@ +package com.yf.exam.modules.sys.user.dto.response; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** +*

+* 管理用户请求类 +* 用户登录成功后返回的响应数据传输对象,包含用户基本信息、权限信息和会话令牌 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@ApiModel(value="管理用户登录响应类", description="管理用户登录响应类") +public class SysUserLoginDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户唯一标识 + * 系统内唯一识别用户的ID,用于后续的用户相关操作 + */ + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 登录用户名 + * 用户在系统中的登录账号名称 + */ + @ApiModelProperty(value = "用户名", required=true) + private String userName; + + /** + * 用户真实姓名 + * 用户的真实中文姓名,用于界面展示和身份识别 + */ + @ApiModelProperty(value = "真实姓名", required=true) + private String realName; + + /** + * 角色ID字符串 + * 用户所属角色的ID集合,以字符串形式存储,便于权限验证 + */ + @ApiModelProperty(value = "角色列表", required=true) + private String roleIds; + + /** + * 所属部门ID + * 用户所在部门的唯一标识,用于组织架构管理 + */ + @ApiModelProperty(value = "部门ID", required=true) + private String departId; + + /** + * 用户账号创建时间 + * 记录用户账号的创建时间戳 + */ + @ApiModelProperty(value = "创建时间", required=true) + private Date createTime; + + /** + * 用户信息最后更新时间 + * 记录用户信息的最后修改时间戳 + */ + @ApiModelProperty(value = "更新时间", required=true) + private Date updateTime; + + /** + * 用户状态 + * 标识用户账号的当前状态:0-禁用,1-启用 + */ + @ApiModelProperty(value = "状态", required=true) + private Integer state; + + /** + * 角色名称列表 + * 用户所属角色的名称集合,用于界面展示用户权限信息 + */ + @ApiModelProperty(value = "角色列表", required=true) + private List roles; + + /** + * 登录会话令牌 + * 用户登录成功后生成的访问令牌,用于后续API请求的身份验证 + */ + @ApiModelProperty(value = "登录令牌", required=true) + private String token; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysRole.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysRole.java new file mode 100644 index 0000000..d4c3eb8 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysRole.java @@ -0,0 +1,41 @@ +package com.yf.exam.modules.sys.user.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 角色实体类 +* 对应数据库中的角色表,存储系统角色定义和权限分组信息 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@TableName("sys_role") +public class SysRole extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 角色ID + * 使用雪花算法分配ID,保证分布式系统下的唯一性 + * 对应数据库表中的id字段,作为主键 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 角色名称 + * 角色的显示名称,用于界面展示和权限识别 + * 对应数据库表中的role_name字段 + */ + @TableField("role_name") + private String roleName; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysUser.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysUser.java new file mode 100644 index 0000000..673cc3f --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysUser.java @@ -0,0 +1,104 @@ +package com.yf.exam.modules.sys.user.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +import java.util.Date; + +/** +*

+* 管理用户实体类 +* 对应数据库中的系统用户表,存储系统管理用户的基本信息、权限配置和状态信息 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@TableName("sys_user") +public class SysUser extends Model { + + private static final long serialVersionUID = 1L; + + /** + * ID + * 用户唯一标识,使用雪花算法分配ID,保证分布式系统下的唯一性 + * 对应数据库表中的id字段,作为主键 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 用户名 + * 用户的系统登录账号,需要保证唯一性,用于身份认证 + * 对应数据库表中的user_name字段 + */ + @TableField("user_name") + private String userName; + + /** + * 真实姓名 + * 用户的真实中文姓名,用于界面展示和身份识别 + * 对应数据库表中的real_name字段 + */ + @TableField("real_name") + private String realName; + + /** + * 密码 + * 用户的登录密码,需要进行加密存储,确保系统安全性 + * 对应数据库表中的password字段 + */ + private String password; + + /** + * 密码盐 + * 用于密码加密的随机盐值,增强密码安全性,防止彩虹表攻击 + * 对应数据库表中的salt字段 + */ + private String salt; + + /** + * 角色列表 + * 用户所属角色的ID集合,以字符串形式存储,用于权限控制 + * 对应数据库表中的role_ids字段 + */ + @TableField("role_ids") + private String roleIds; + + /** + * 部门ID + * 用户所在部门的唯一标识,用于组织架构管理和部门权限控制 + * 对应数据库表中的depart_id字段 + */ + @TableField("depart_id") + private String departId; + + /** + * 创建时间 + * 用户账号的创建时间戳,用于审计和版本管理 + * 对应数据库表中的create_time字段 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + * 用户信息的最后修改时间戳,用于追踪数据变更 + * 对应数据库表中的update_time字段 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 状态 + * 用户账号的当前状态标识:0-禁用,1-启用,用于账号状态管理 + * 对应数据库表中的state字段 + */ + private Integer state; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysUserRole.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysUserRole.java new file mode 100644 index 0000000..5944014 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/entity/SysUserRole.java @@ -0,0 +1,49 @@ +package com.yf.exam.modules.sys.user.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +/** +*

+* 用户角色实体类 +* 对应数据库中的用户角色关联表,建立用户与角色之间的多对多关系映射 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Data +@TableName("sys_user_role") +public class SysUserRole extends Model { + + private static final long serialVersionUID = 1L; + + /** + * ID + * 用户角色关联关系唯一标识,使用雪花算法分配ID,保证分布式系统下的唯一性 + * 对应数据库表中的id字段,作为主键 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 用户ID + * 关联的用户唯一标识,指向sys_user表的主键,建立用户实体的关联 + * 对应数据库表中的user_id字段 + */ + @TableField("user_id") + private String userId; + + /** + * 角色ID + * 关联的角色唯一标识,指向sys_role表的主键,建立角色实体的关联 + * 对应数据库表中的role_id字段 + */ + @TableField("role_id") + private String roleId; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysRoleMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysRoleMapper.java new file mode 100644 index 0000000..a56c5d7 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysRoleMapper.java @@ -0,0 +1,16 @@ +package com.yf.exam.modules.sys.user.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.sys.user.entity.SysRole; + +/** +*

+* 角色Mapper +* 负责系统角色数据的数据库访问操作,提供对角色表的CRUD功能 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +public interface SysRoleMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysUserMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysUserMapper.java new file mode 100644 index 0000000..9031a7b --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysUserMapper.java @@ -0,0 +1,17 @@ +package com.yf.exam.modules.sys.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.sys.user.entity.SysUser; + +/** +*

+* 管理用户Mapper +* 负责系统用户数据的数据库访问操作,提供对用户表的CRUD功能 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +public interface SysUserMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysUserRoleMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..34d35c2 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/mapper/SysUserRoleMapper.java @@ -0,0 +1,17 @@ +package com.yf.exam.modules.sys.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.sys.user.entity.SysUserRole; + +/** +*

+* 用户角色Mapper +* 负责用户角色关联关系的数据库访问操作,提供对用户角色关联表的CRUD功能 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +public interface SysUserRoleMapper extends BaseMapper { + +} diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysRoleService.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysRoleService.java new file mode 100644 index 0000000..0a46a83 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysRoleService.java @@ -0,0 +1,40 @@ +package com.yf.exam.modules.sys.user.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.modules.sys.user.dto.SysRoleDTO; +import com.yf.exam.modules.sys.user.entity.SysRole; +import com.yf.exam.core.api.dto.PagingReqDTO; + +/** +*

+* 角色业务类 +* 定义系统角色管理的核心业务接口,包括角色信息的增删改查等操作 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +public interface SysRoleService extends IService { + + /** + * 分页查询角色列表 + * 根据查询条件对角色数据进行分页查询,支持按角色名称、角色编码等条件过滤 + * + * @param reqDTO 分页请求参数对象,包含以下信息: + * - current: 当前页码 + * - size: 每页记录数 + * - params: 查询条件参数,可包含角色名称、角色状态等过滤条件 + * @return 分页的角色数据列表,包含角色基本信息及分页信息 + * + * @example + * PagingReqDTO reqDTO = new PagingReqDTO<>(); + * reqDTO.setCurrent(1); + * reqDTO.setSize(10); + * SysRoleDTO params = new SysRoleDTO(); + * params.setRoleName("管理员"); + * reqDTO.setParams(params); + * IPage result = sysRoleService.paging(reqDTO); + */ + IPage paging(PagingReqDTO reqDTO); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysUserRoleService.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysUserRoleService.java new file mode 100644 index 0000000..e5b5b27 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysUserRoleService.java @@ -0,0 +1,108 @@ +package com.yf.exam.modules.sys.user.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.modules.sys.user.dto.SysUserRoleDTO; +import com.yf.exam.modules.sys.user.entity.SysUserRole; +import com.yf.exam.core.api.dto.PagingReqDTO; + +import java.util.List; + +/** +*

+* 用户角色业务类 +* 定义用户与角色关联关系的核心业务接口,包括用户角色分配、查询和权限验证等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +public interface SysUserRoleService extends IService { + + /** + * 分页查询用户角色关联关系 + * 支持按用户ID、角色ID等条件对用户角色关系进行分页查询 + * + * @param reqDTO 分页请求参数对象,包含以下信息: + * - current: 当前页码 + * - size: 每页记录数 + * - params: 查询条件参数,可包含用户ID、角色ID等过滤条件 + * @return 分页的用户角色关联数据列表,包含关联关系信息及分页信息 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 根据用户ID查询该用户拥有的所有角色ID列表 + * 用于获取指定用户的所有角色标识,通常用于权限验证和角色展示 + * + * @param userId 用户ID,不能为空 + * @return 用户拥有的角色ID列表,如果用户没有任何角色则返回空列表 + * + * @example + * List roles = sysUserRoleService.listRoles("123456"); + * // 返回结果可能是: ["student", "teacher"] 或空列表 [] + */ + List listRoles(String userId); + + /** + * 保存用户的角色关系(先删除原有角色,再添加新角色) + * 此操作会先清除用户现有的所有角色,然后批量添加新的角色关系 + * 注意:此操作具有事务性,确保数据一致性 + * + * @param userId 用户ID,不能为空 + * @param ids 新的角色ID列表,可以为空(表示清除所有角色) + * @return 保存的角色ID字符串,以逗号分隔,便于存储和展示 + * + * @example + * List newRoles = Arrays.asList("student", "teacher"); + * String roleIds = sysUserRoleService.saveRoles("123456", newRoles); + * // 返回结果: "student,teacher" + */ + String saveRoles(String userId, List ids); + + /** + * 判断用户是否具有学生角色 + * 通过查询用户角色关联表中是否存在"student"角色记录来判断 + * + * @param userId 用户ID,不能为空 + * @return true-用户具有学生角色,false-用户不具有学生角色 + * + * @example + * boolean isStudent = sysUserRoleService.isStudent("123456"); + * if(isStudent) { + * // 执行学生相关逻辑 + * } + */ + boolean isStudent(String userId); + + /** + * 判断用户是否具有教师角色 + * 通过查询用户角色关联表中是否存在"teacher"角色记录来判断 + * + * @param userId 用户ID,不能为空 + * @return true-用户具有教师角色,false-用户不具有教师角色 + * + * @example + * boolean isTeacher = sysUserRoleService.isTeacher("123456"); + * if(isTeacher) { + * // 执行教师相关逻辑 + * } + */ + boolean isTeacher(String userId); + + /** + * 判断用户是否具有管理员角色 + * 通过查询用户角色关联表中是否存在"sa"(系统管理员)角色记录来判断 + * 管理员通常具有系统的最高权限 + * + * @param userId 用户ID,不能为空 + * @return true-用户具有管理员角色,false-用户不具有管理员角色 + * + * @example + * boolean isAdmin = sysUserRoleService.isAdmin("123456"); + * if(isAdmin) { + * // 执行管理员相关逻辑 + * } + */ + boolean isAdmin(String userId); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysUserService.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysUserService.java new file mode 100644 index 0000000..4d43046 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/SysUserService.java @@ -0,0 +1,136 @@ +package com.yf.exam.modules.sys.user.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.modules.sys.user.dto.SysUserDTO; +import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO; +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +import com.yf.exam.modules.sys.user.entity.SysUser; +import com.yf.exam.core.api.dto.PagingReqDTO; + +/** +*

+* 管理用户业务类 +* 定义系统用户管理的核心业务接口,包括用户认证、注册、信息管理等操作 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +public interface SysUserService extends IService { + + /** + * 分页查询用户列表 + * 支持按用户名、真实姓名等条件对用户数据进行分页查询和过滤 + * + * @param reqDTO 分页请求参数对象,包含以下信息: + * - current: 当前页码 + * - size: 每页记录数 + * - params: 查询条件参数,可包含用户名、真实姓名等过滤条件 + * @return 分页的用户数据列表,包含用户基本信息及分页信息 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 用户登录认证 + * 验证用户名和密码的正确性,并返回包含token的用户登录信息 + * + * @param userName 用户名,不能为空 + * @param password 密码,明文密码 + * @return 用户登录响应信息,包含用户基本信息、角色列表和访问token + * @throws com.yf.exam.core.exception.ServiceException 当用户名不存在、密码错误或用户被禁用时抛出异常 + * + * @example + * SysUserLoginDTO result = sysUserService.login("admin", "123456"); + * String token = result.getToken(); // 获取访问令牌 + */ + SysUserLoginDTO login(String userName, String password); + + /** + * 根据token验证并获取用户信息 + * 验证JWT token的有效性,并返回对应的用户登录信息 + * + * @param token JWT访问令牌 + * @return 用户登录响应信息,包含用户基本信息和角色列表 + * @throws com.yf.exam.core.exception.ServiceException 当token无效、用户不存在或被禁用时抛出异常 + * + * @example + * SysUserLoginDTO userInfo = sysUserService.token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."); + */ + SysUserLoginDTO token(String token); + + /** + * 用户退出登录 + * 使当前用户的token失效,清除用户会话信息 + * + * @param token 需要失效的JWT访问令牌 + * + * @example + * sysUserService.logout("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."); + */ + void logout(String token); + + /** + * 修改当前登录用户的资料 + * 主要用于用户修改自己的密码等个人信息 + * + * @param reqDTO 用户更新请求参数,包含需要修改的用户信息 + * + * @example + * SysUserDTO updateDTO = new SysUserDTO(); + * updateDTO.setPassword("newPassword123"); + * sysUserService.update(updateDTO); + */ + void update(SysUserDTO reqDTO); + + /** + * 保存或添加系统用户(管理员操作) + * 用于创建新用户或修改现有用户信息,包含角色分配功能 + * + * @param reqDTO 用户保存请求参数,包含用户基本信息和角色列表 + * @throws com.yf.exam.core.exception.ServiceException 当未分配用户角色时抛出异常 + * + * @example + * SysUserSaveReqDTO saveDTO = new SysUserSaveReqDTO(); + * saveDTO.setUserName("newuser"); + * saveDTO.setRealName("新用户"); + * saveDTO.setPassword("password123"); + * saveDTO.setRoles(Arrays.asList("student", "teacher")); + * sysUserService.save(saveDTO); + */ + void save(SysUserSaveReqDTO reqDTO); + + /** + * 用户注册 + * 用于新用户自主注册账号,默认分配学生角色 + * + * @param reqDTO 用户注册请求参数,包含用户名、密码、真实姓名等信息 + * @return 注册成功后的用户登录信息,包含token和用户基本信息 + * @throws com.yf.exam.core.exception.ServiceException 当用户名已存在时抛出异常 + * + * @example + * SysUserDTO regDTO = new SysUserDTO(); + * regDTO.setUserName("student001"); + * regDTO.setPassword("123456"); + * regDTO.setRealName("张三"); + * SysUserLoginDTO result = sysUserService.reg(regDTO); + */ + SysUserLoginDTO reg(SysUserDTO reqDTO); + + /** + * 快速注册 + * 如果用户名已存在则直接登录,否则执行注册流程 + * 适用于需要快速创建或获取用户账号的场景 + * + * @param reqDTO 用户注册请求参数 + * @return 用户登录信息,包含token和用户基本信息 + * + * @example + * SysUserDTO quickRegDTO = new SysUserDTO(); + * quickRegDTO.setUserName("quickuser"); + * quickRegDTO.setPassword("123456"); + * quickRegDTO.setRealName("快速用户"); + * SysUserLoginDTO result = sysUserService.quickReg(quickRegDTO); + */ + SysUserLoginDTO quickReg(SysUserDTO reqDTO); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysRoleServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..aad719d --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,43 @@ +package com.yf.exam.modules.sys.user.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.modules.sys.user.dto.SysRoleDTO; +import com.yf.exam.modules.sys.user.entity.SysRole; +import com.yf.exam.modules.sys.user.mapper.SysRoleMapper; +import com.yf.exam.modules.sys.user.service.SysRoleService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import org.springframework.stereotype.Service; + +/** +*

+* 语言设置 服务实现类 +* 负责系统角色管理的业务逻辑实现,包括角色的分页查询和数据转换 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Service +public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { + + @Override + public IPage paging(PagingReqDTO reqDTO) { + + //创建分页对象 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + //查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + + //获得数据 + IPage page = this.page(query, wrapper); + //转换结果 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserRoleServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserRoleServiceImpl.java new file mode 100644 index 0000000..cf2225e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserRoleServiceImpl.java @@ -0,0 +1,172 @@ +package com.yf.exam.modules.sys.user.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.modules.sys.user.dto.SysUserRoleDTO; +import com.yf.exam.modules.sys.user.entity.SysUserRole; +import com.yf.exam.modules.sys.user.mapper.SysUserRoleMapper; +import com.yf.exam.modules.sys.user.service.SysUserRoleService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** +*

+* 用户角色服务实现类 +* 负责用户角色关联关系的业务逻辑实现,包括用户角色分配、查询和权限验证等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Service +public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService { + + /** + * 分页查询用户角色关系列表 + * @param reqDTO 分页请求参数,包含当前页码、每页大小和查询条件 + * @return 分页的用户角色关系数据 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 创建分页对象,设置当前页和每页大小 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + // 创建查询条件包装器 + QueryWrapper wrapper = new QueryWrapper<>(); + + // 执行分页查询,获取用户角色关系数据 + IPage page = this.page(query, wrapper); + + // 将查询结果转换为DTO对象:先序列化为JSON字符串,再反序列化为目标类型 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + /** + * 根据用户ID查询该用户拥有的所有角色ID列表 + * @param userId 用户ID + * @return 用户拥有的角色ID列表 + */ + @Override + public List listRoles(String userId) { + + // 创建查询条件:按用户ID过滤 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId); + + // 查询用户角色关系列表 + List list = this.list(wrapper); + List roles = new ArrayList<>(); + + // 如果查询结果不为空,提取所有角色ID + if(!CollectionUtils.isEmpty(list)){ + for(SysUserRole item: list){ + roles.add(item.getRoleId()); + } + } + + return roles; + } + + /** + * 保存用户的角色关系(先删除原有角色,再添加新角色) + * @param userId 用户ID + * @param ids 新的角色ID列表 + * @return 保存的角色ID字符串,以逗号分隔 + */ + @Override + public String saveRoles(String userId, List ids) { + + // 先删除该用户的全部现有角色关系 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId); + this.remove(wrapper); + + // 如果新的角色列表不为空,批量插入新的角色关系 + if(!CollectionUtils.isEmpty(ids)){ + + List list = new ArrayList<>(); + String roleIds = null; // 用于拼接角色ID字符串 + + // 遍历角色ID列表,创建用户角色关系对象 + for(String item: ids){ + SysUserRole role = new SysUserRole(); + role.setRoleId(item); + role.setUserId(userId); + list.add(role); + + // 拼接角色ID字符串 + if(StringUtils.isEmpty(roleIds)){ + roleIds = item; + }else{ + roleIds+=","+item; + } + } + + // 批量保存用户角色关系 + this.saveBatch(list); + return roleIds; + } + + return ""; // 如果没有角色,返回空字符串 + } + + /** + * 判断用户是否具有学生角色 + * @param userId 用户ID + * @return true-是学生角色,false-不是学生角色 + */ + @Override + public boolean isStudent(String userId) { + + // 查询条件:用户ID为指定用户且角色ID为"student" + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId) + .eq(SysUserRole::getRoleId, "student"); + + // 如果查询结果数量大于0,说明用户具有学生角色 + return this.count(wrapper) > 0; + } + + /** + * 判断用户是否具有教师角色 + * @param userId 用户ID + * @return true-是教师角色,false-不是教师角色 + */ + @Override + public boolean isTeacher(String userId) { + // 查询条件:用户ID为指定用户且角色ID为"teacher" + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId) + .eq(SysUserRole::getRoleId, "teacher"); + + // 如果查询结果数量大于0,说明用户具有教师角色 + return this.count(wrapper) > 0; + } + + /** + * 判断用户是否具有管理员角色 + * @param userId 用户ID + * @return true-是管理员角色,false-不是管理员角色 + */ + @Override + public boolean isAdmin(String userId) { + // 查询条件:用户ID为指定用户且角色ID为"sa"(系统管理员) + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUserRole::getUserId, userId) + .eq(SysUserRole::getRoleId, "sa"); + + // 如果查询结果数量大于0,说明用户具有管理员角色 + return this.count(wrapper) > 0; + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..66335c8 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/sys/user/service/impl/SysUserServiceImpl.java @@ -0,0 +1,328 @@ +package com.yf.exam.modules.sys.user.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.core.enums.CommonState; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.core.utils.BeanMapper; +import com.yf.exam.core.utils.passwd.PassHandler; +import com.yf.exam.core.utils.passwd.PassInfo; +import com.yf.exam.ability.shiro.jwt.JwtUtils; +import com.yf.exam.modules.sys.user.dto.SysUserDTO; +import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO; +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +import com.yf.exam.modules.sys.user.entity.SysUser; +import com.yf.exam.modules.sys.user.mapper.SysUserMapper; +import com.yf.exam.modules.sys.user.service.SysUserRoleService; +import com.yf.exam.modules.sys.user.service.SysUserService; +import com.yf.exam.modules.user.UserUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.SecurityUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** +*

+* 系统用户服务实现类 +* 负责用户管理、登录认证、注册等核心业务逻辑的实现 +*

+* +* @author 聪明笨狗 +* @since 2020-04-13 16:57 +*/ +@Service +public class SysUserServiceImpl extends ServiceImpl implements SysUserService { + + // 注入用户角色服务,用于处理用户与角色的关联关系 + @Autowired + private SysUserRoleService sysUserRoleService; + + /** + * 分页查询用户列表 + * @param reqDTO 分页请求参数,包含分页信息和查询条件 + * @return 分页的用户数据 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 创建分页对象,设置当前页码和每页大小 + IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + // 创建查询条件包装器 + QueryWrapper wrapper = new QueryWrapper<>(); + + // 获取查询参数 + SysUserDTO params = reqDTO.getParams(); + + // 如果存在查询参数,构建查询条件 + if(params!=null){ + // 按用户名模糊查询 + if(!StringUtils.isBlank(params.getUserName())){ + wrapper.lambda().like(SysUser::getUserName, params.getUserName()); + } + + // 按真实姓名模糊查询 + if(!StringUtils.isBlank(params.getRealName())){ + wrapper.lambda().like(SysUser::getRealName, params.getRealName()); + } + } + + // 执行分页查询 + IPage page = this.page(query, wrapper); + + // 将查询结果转换为DTO对象:先序列化为JSON字符串,再反序列化为目标类型 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + /** + * 用户登录认证 + * @param userName 用户名 + * @param password 密码 + * @return 登录响应信息,包含用户信息和token + * @throws ServiceException 当用户名不存在、用户被禁用或密码错误时抛出异常 + */ + @Override + public SysUserLoginDTO login(String userName, String password) { + + // 构建查询条件:按用户名精确查询 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getUserName, userName); + + // 查询用户信息 + SysUser user = this.getOne(wrapper, false); + + // 用户不存在 + if(user == null){ + throw new ServiceException(ApiError.ERROR_90010002); + } + + // 检查用户状态是否被禁用 + if(user.getState().equals(CommonState.ABNORMAL)){ + throw new ServiceException(ApiError.ERROR_90010005); + } + + // 验证密码:使用盐值对输入密码进行加密后与数据库存储的密码比较 + boolean check = PassHandler.checkPass(password,user.getSalt(), user.getPassword()); + if(!check){ + throw new ServiceException(ApiError.ERROR_90010002); + } + + // 登录成功,生成token并返回用户信息 + return this.setToken(user); + } + + /** + * 根据token验证用户身份并获取用户信息 + * @param token JWT token + * @return 用户登录信息 + * @throws ServiceException 当token无效、用户不存在或被禁用时抛出异常 + */ + @Override + public SysUserLoginDTO token(String token) { + + // 从token中解析出用户名 + String username = JwtUtils.getUsername(token); + + // 验证token的有效性 + boolean check = JwtUtils.verify(token, username); + + if(!check){ + throw new ServiceException(ApiError.ERROR_90010002); + } + + // 根据用户名查询用户信息 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getUserName, username); + + SysUser user = this.getOne(wrapper, false); + if(user == null){ + throw new ServiceException(ApiError.ERROR_10010002); + } + + // 检查用户状态 + if(user.getState().equals(CommonState.ABNORMAL)){ + throw new ServiceException(ApiError.ERROR_90010005); + } + + // 验证通过,返回用户信息 + return this.setToken(user); + } + + /** + * 用户登出 + * @param token 用户token + */ + @Override + public void logout(String token) { + // 使用Shiro安全框架退出当前会话 + SecurityUtils.getSubject().logout(); + } + + /** + * 更新用户信息(主要用于修改密码) + * @param reqDTO 用户更新请求参数 + */ + @Override + public void update(SysUserDTO reqDTO) { + + // 获取新密码 + String pass = reqDTO.getPassword(); + + // 如果提供了新密码,则更新密码 + if(!StringUtils.isBlank(pass)){ + // 生成加密后的密码和盐值 + PassInfo passInfo = PassHandler.buildPassword(pass); + + // 获取当前登录用户 + SysUser user = this.getById(UserUtils.getUserId()); + user.setPassword(passInfo.getPassword()); + user.setSalt(passInfo.getSalt()); + + // 更新用户信息 + this.updateById(user); + } + } + + /** + * 保存或更新用户信息(管理员操作) + * @param reqDTO 用户保存请求参数,包含用户基本信息和角色信息 + * @throws ServiceException 当未分配角色时抛出异常 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void save(SysUserSaveReqDTO reqDTO) { + + // 获取用户角色列表 + List roles = reqDTO.getRoles(); + + // 验证必须分配至少一个角色 + if(CollectionUtils.isEmpty(roles)){ + throw new ServiceException(ApiError.ERROR_90010003); + } + + // 复制基本信息到用户实体 + SysUser user = new SysUser(); + BeanMapper.copy(reqDTO, user); + + // 如果是新增用户,生成用户ID + if(StringUtils.isBlank(user.getId())){ + user.setId(IdWorker.getIdStr()); + } + + // 如果提供了密码,进行密码加密处理 + if(!StringUtils.isBlank(reqDTO.getPassword())){ + PassInfo pass = PassHandler.buildPassword(reqDTO.getPassword()); + user.setPassword(pass.getPassword()); + user.setSalt(pass.getSalt()); + } + + // 保存用户角色关系,并获取角色ID字符串 + String roleIds = sysUserRoleService.saveRoles(user.getId(), roles); + user.setRoleIds(roleIds); + + // 保存或更新用户信息 + this.saveOrUpdate(user); + } + + /** + * 用户注册 + * @param reqDTO 用户注册请求参数 + * @return 注册成功后的用户登录信息 + * @throws ServiceException 当用户名已存在时抛出异常 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public SysUserLoginDTO reg(SysUserDTO reqDTO) { + + // 检查用户名是否已存在 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getUserName, reqDTO.getUserName()); + + int count = this.count(wrapper); + + if(count > 0){ + throw new ServiceException(1, "用户名已存在,换一个吧!"); + } + + // 创建新用户 + SysUser user = new SysUser(); + user.setId(IdWorker.getIdStr()); + user.setUserName(reqDTO.getUserName()); + user.setRealName(reqDTO.getRealName()); + + // 密码加密处理 + PassInfo passInfo = PassHandler.buildPassword(reqDTO.getPassword()); + user.setPassword(passInfo.getPassword()); + user.setSalt(passInfo.getSalt()); + + // 默认分配学生角色 + List roles = new ArrayList<>(); + roles.add("student"); + String roleIds = sysUserRoleService.saveRoles(user.getId(), roles); + user.setRoleIds(roleIds); + + // 保存用户信息 + this.save(user); + + // 生成token并返回登录信息 + return this.setToken(user); + } + + /** + * 快速注册:如果用户已存在则直接登录,否则执行注册 + * @param reqDTO 用户注册请求参数 + * @return 用户登录信息 + */ + @Override + public SysUserLoginDTO quickReg(SysUserDTO reqDTO) { + + // 查询用户是否已存在 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(SysUser::getUserName, reqDTO.getUserName()); + wrapper.last(" LIMIT 1 "); + SysUser user = this.getOne(wrapper); + + // 用户已存在,直接生成token登录 + if(user!=null){ + return this.setToken(user); + } + + // 用户不存在,执行注册流程 + return this.reg(reqDTO); + } + + /** + * 生成用户token并构建登录响应信息 + * @param user 用户实体对象 + * @return 用户登录响应信息,包含用户基本信息和token + */ + private SysUserLoginDTO setToken(SysUser user){ + + // 创建登录响应对象并复制用户基本信息 + SysUserLoginDTO respDTO = new SysUserLoginDTO(); + BeanMapper.copy(user, respDTO); + + // 生成JWT token + String token = JwtUtils.sign(user.getUserName()); + respDTO.setToken(token); + + // 查询用户角色列表并设置到响应对象中 + List roles = sysUserRoleService.listRoles(user.getId()); + respDTO.setRoles(roles); + + return respDTO; + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/UserUtils.java b/exam-api1/src/main/java/com/yf/exam/modules/user/UserUtils.java new file mode 100644 index 0000000..4477ecb --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/UserUtils.java @@ -0,0 +1,56 @@ +package com.yf.exam.modules.user; + +import com.yf.exam.core.api.ApiError; +import com.yf.exam.core.exception.ServiceException; +import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO; +import org.apache.shiro.SecurityUtils; + +/** + * 用户静态工具类 + * @author bool + */ +public class UserUtils { + + + /** + * 获取当前登录用户的ID + * @param throwable + * @return + */ + public static String getUserId(boolean throwable){ + try { + return ((SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal()).getId(); + }catch (Exception e){ + if(throwable){ + throw new ServiceException(ApiError.ERROR_10010002); + } + return null; + } + } + + /** + * 获取当前登录用户的ID + * @param throwable + * @return + */ + public static boolean isAdmin(boolean throwable){ + try { + SysUserLoginDTO dto = ((SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal()); + return dto.getRoles().contains("sa"); + }catch (Exception e){ + if(throwable){ + throw new ServiceException(ApiError.ERROR_10010002); + } + } + + return false; + } + + /** + * 获取当前登录用户的ID,默认是会抛异常的 + * @return + */ + public static String getUserId(){ + return getUserId(true); + } +} diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/book/controller/UserBookController.java b/exam-api1/src/main/java/com/yf/exam/modules/user/book/controller/UserBookController.java new file mode 100644 index 0000000..096ae80 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/book/controller/UserBookController.java @@ -0,0 +1,105 @@ +package com.yf.exam.modules.user.book.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.BaseIdRespDTO; +import com.yf.exam.core.api.dto.BaseIdsReqDTO; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.user.book.dto.UserBookDTO; +import com.yf.exam.modules.user.book.service.UserBookService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** +*

+* 错题本控制器 +* 提供用户错题本的相关操作接口,包括错题删除、分页查询和下一题查询等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-05-27 17:56 +*/ +@Api(tags={"错题本"}) +@RestController +@RequestMapping("/exam/api/user/wrong-book") +public class UserBookController extends BaseController { + + @Autowired + private UserBookService baseService; + + /** + * 批量删除错题记录 + * 根据ID列表批量删除用户错题本中的题目记录 + * + * @param reqDTO 包含要删除的错题ID列表的请求参数 + * @return 操作结果响应 + * + * @example + * { + * "ids": ["123", "456", "789"] + * } + */ + @ApiOperation(value = "批量删除") + @RequestMapping(value = "/delete", method = { RequestMethod.POST}) + public ApiRest delete(@RequestBody BaseIdsReqDTO reqDTO) { + // 根据ID列表批量删除错题记录 + baseService.removeByIds(reqDTO.getIds()); + return super.success(); + } + + /** + * 分页查询错题列表 + * 根据查询条件分页获取用户的错题本记录,支持按考试、题目类型等条件过滤 + * + * @param reqDTO 分页请求参数,包含分页信息和查询条件 + * @return 分页的错题数据 + * + * @example + * { + * "current": 1, + * "size": 10, + * "params": { + * "examId": "exam123", + * "quType": 1 + * } + * } + */ + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + // 分页查询错题记录并转换为DTO对象 + IPage page = baseService.paging(reqDTO); + + return super.success(page); + } + + /** + * 查找下一道错题 + * 在当前错题的基础上查找下一道错题,用于错题练习的连续做题功能 + * 每次最多返回200条数据,避免数据量过大 + * + * @param reqDTO 包含考试ID和当前题目ID的请求参数 + * @return 下一道错题的ID + * + * @example + * { + * "examId": "exam123", + * "quId": "current_question_id" + * } + */ + @ApiOperation(value = "查找列表") + @RequestMapping(value = "/next", method = { RequestMethod.POST}) + public ApiRest nextQu(@RequestBody UserBookDTO reqDTO) { + // 查找下一道错题的ID + String quId = baseService.findNext(reqDTO.getExamId(), reqDTO.getQuId()); + // 返回下一题ID的响应对象 + return super.success(new BaseIdRespDTO(quId)); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/book/dto/UserBookDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/user/book/dto/UserBookDTO.java new file mode 100644 index 0000000..907c896 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/book/dto/UserBookDTO.java @@ -0,0 +1,76 @@ +package com.yf.exam.modules.user.book.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** +*

+* 错题本请求类 +*

+* +* @author 聪明笨狗 +* @since 2020-05-27 17:56 +*/ +@Data +@ApiModel(value="错题本", description="错题本") +public class UserBookDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + + @ApiModelProperty(value = "ID", required=true) + private String id; + + /** + * 考试ID,关联具体的考试信息 + */ + @ApiModelProperty(value = "考试ID", required=true) + private String examId; + + /** + * 用户ID,标识错题所属的用户 + */ + @ApiModelProperty(value = "用户ID", required=true) + private String userId; + + /** + * 题目ID,关联具体的题目信息 + */ + @ApiModelProperty(value = "题目ID", required=true) + private String quId; + + /** + * 错题加入时间,记录题目首次被加入错题本的时间 + */ + @ApiModelProperty(value = "加入时间", required=true) + private Date createTime; + + /** + * 最近错误时间,记录该题目最近一次被做错的时间 + */ + @ApiModelProperty(value = "最近错误时间", required=true) + private Date updateTime; + + /** + * 错误次数统计,记录该题目被做错的总次数 + */ + @ApiModelProperty(value = "错误时间", required=true) + private Integer wrongCount; + + /** + * 题目标题,用于在错题本中显示题目内容 + */ + @ApiModelProperty(value = "题目标题", required=true) + private String title; + + /** + * 错题序号,用于错题本中的题目排序 + */ + @ApiModelProperty(value = "错题序号", required=true) + private Integer sort; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/book/entity/UserBook.java b/exam-api1/src/main/java/com/yf/exam/modules/user/book/entity/UserBook.java new file mode 100644 index 0000000..d123792 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/book/entity/UserBook.java @@ -0,0 +1,85 @@ +package com.yf.exam.modules.user.book.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; + +import java.util.Date; + +/** +*

+* 错题本实体类 +* 对应数据库表 el_user_book,用于存储用户的错题记录信息 +*

+* +* @author 聪明笨狗 +* @since 2020-05-27 17:56 +*/ +@Data +@TableName("el_user_book") +public class UserBook extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 错题记录唯一标识 + * 使用雪花算法分配ID,保证分布式环境下的唯一性 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 关联的考试ID + * 标识该错题属于哪次考试,对应考试表的ID字段 + */ + @TableField("exam_id") + private String examId; + + /** + * 用户标识 + * 记录错题所属的用户,对应用户表的ID字段 + */ + @TableField("user_id") + private String userId; + /** + * 题目标识 + * 关联具体的题目信息,对应题目表的ID字段 + */ + @TableField("qu_id") + private String quId; + /** + * 错题创建时间 + * 记录该题目第一次被加入错题本的时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 最近错误时间 + * 记录该题目最近一次被做错的时间,用于错题复习排序 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 错误次数统计 + * 累计记录该题目被做错的次数,用于识别高频错题 + */ + @TableField("wrong_count") + private Integer wrongCount; + + /** + * 题目标题 + * 题目的主要内容或标题,用于错题列表展示 + */ + private String title; + /** + * 错题排序序号 + * 用于错题本中的题目排序,可以按加入时间、错误次数等规则排序 + */ + private Integer sort; + +} diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/book/mapper/UserBookMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/user/book/mapper/UserBookMapper.java new file mode 100644 index 0000000..b81d875 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/book/mapper/UserBookMapper.java @@ -0,0 +1,18 @@ +package com.yf.exam.modules.user.book.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yf.exam.modules.user.book.entity.UserBook; + +/** +*

+* 错题本Mapper +* 用户错题本数据访问层接口,继承MyBatis-Plus的BaseMapper,提供基本的CRUD操作 +* 对应数据库表:el_user_book +*

+* +* @author 聪明笨狗 +* @since 2020-05-27 17:56 +*/ +public interface UserBookMapper extends BaseMapper { + +} diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/book/service/UserBookService.java b/exam-api1/src/main/java/com/yf/exam/modules/user/book/service/UserBookService.java new file mode 100644 index 0000000..fcb60b3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/book/service/UserBookService.java @@ -0,0 +1,59 @@ +package com.yf.exam.modules.user.book.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.user.book.dto.UserBookDTO; +import com.yf.exam.modules.user.book.entity.UserBook; + +/** +*

+* 错题本业务类 +* 定义错题本管理的核心业务接口,包括错题添加、查询和导航等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-05-27 17:56 +*/ +public interface UserBookService extends IService { + + /** + * 分页查询错题列表 + * 根据查询条件对用户的错题记录进行分页查询,支持按标题和考试ID过滤 + * + * @param reqDTO 分页请求参数对象,包含以下信息: + * - current: 当前页码 + * - size: 每页记录数 + * - params: 查询条件参数,可包含题目标题、考试ID等过滤条件 + * @return 分页的错题数据列表,包含错题基本信息及分页信息 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 添加题目到错题本 + * 将用户做错的题目添加到错题本中,如果题目已存在则增加错误次数 + * + * @param examId 考试ID,标识题目所属的考试 + * @param quId 题目ID,需要添加到错题本的题目标识 + * + * @example + * userBookService.addBook("exam123", "question456"); + */ + void addBook(String examId, String quId); + + /** + * 查找下一道错题 + * 在当前错题的基础上,按排序号查找下一道错题,用于错题练习的连续导航 + * + * @param examId 考试ID,限定在指定考试范围内查找 + * @param quId 当前题目ID,基于此题目查找下一题 + * @return 下一道错题的题目ID,如果没有下一题则返回null + * + * @example + * String nextQuId = userBookService.findNext("exam123", "currentQuestionId"); + * if(nextQuId != null) { + * // 加载下一道错题 + * } + */ + String findNext(String examId, String quId); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/book/service/impl/UserBookServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/user/book/service/impl/UserBookServiceImpl.java new file mode 100644 index 0000000..8cb24eb --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/book/service/impl/UserBookServiceImpl.java @@ -0,0 +1,179 @@ +package com.yf.exam.modules.user.book.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.qu.entity.Qu; +import com.yf.exam.modules.qu.service.QuService; +import com.yf.exam.modules.user.UserUtils; +import com.yf.exam.modules.user.book.dto.UserBookDTO; +import com.yf.exam.modules.user.book.entity.UserBook; +import com.yf.exam.modules.user.book.mapper.UserBookMapper; +import com.yf.exam.modules.user.book.service.UserBookService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +/** +*

+* 错题本服务实现类 +* 负责用户错题本的业务逻辑实现,包括错题添加、查询和排序等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-05-27 17:56 +*/ +@Service +public class UserBookServiceImpl extends ServiceImpl implements UserBookService { + + @Autowired + private QuService quService; + + /** + * 分页查询用户的错题列表 + * @param reqDTO 分页请求参数,包含分页信息和查询条件 + * @return 分页的错题数据 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 创建分页对象,设置当前页码和每页大小 + Page query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize()); + + // 创建查询条件包装器 + QueryWrapper wrapper = new QueryWrapper<>(); + // 只查找当前登录用户的错题记录 + wrapper.lambda().eq(UserBook::getUserId, UserUtils.getUserId(true)); + + // 获取查询参数 + UserBookDTO params = reqDTO.getParams(); + if(params!=null){ + // 按题目标题模糊查询 + if(!StringUtils.isEmpty(params.getTitle())){ + wrapper.lambda().like(UserBook::getTitle, params.getTitle()); + } + + // 按考试ID精确查询 + if(!StringUtils.isEmpty(params.getExamId())){ + wrapper.lambda().eq(UserBook::getExamId, params.getExamId()); + } + } + + // 执行分页查询 + IPage page = this.page(query, wrapper); + // 将查询结果转换为DTO对象:先序列化为JSON字符串,再反序列化为目标类型 + IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){}); + return pageData; + } + + /** + * 添加题目到错题本 + * 如果题目已存在错题本中,则增加错误次数;否则创建新的错题记录 + * @param examId 考试ID + * @param quId 题目ID + */ + @Override + public void addBook(String examId, String quId) { + + // 构建查询条件:用户ID、考试ID、题目ID + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(UserBook::getUserId, UserUtils.getUserId()) + .eq(UserBook::getExamId, examId) + .eq(UserBook::getQuId, quId); + + // 查找是否已存在该错题记录 + UserBook book = this.getOne(wrapper, false); + + // 获取题目信息 + Qu qu = quService.getById(quId); + + // 如果错题记录不存在,创建新的记录 + if (book == null) { + book = new UserBook(); + book.setExamId(examId); + book.setUserId(UserUtils.getUserId()); + book.setTitle(qu.getContent()); // 使用题目内容作为标题 + book.setQuId(quId); + book.setWrongCount(1); // 初始错误次数为1 + // 查找当前考试下用户错题的最大排序值,新错题排在最后 + Integer maxSort = this.findMaxSort(examId, UserUtils.getUserId()); + book.setSort(maxSort+1); + + this.save(book); + } else { + // 错题记录已存在,增加错误次数 + book.setWrongCount(book.getWrongCount()+1); + this.updateById(book); + } + } + + /** + * 查找下一道错题 + * 在当前错题的基础上,按排序号查找下一道错题(排序号较小的题目) + * @param examId 考试ID + * @param quId 当前题目ID + * @return 下一道错题的题目ID,如果没有下一题则返回null + */ + @Override + public String findNext(String examId, String quId) { + + // 默认设置一个较大的排序值 + Integer sort = 999999; + + // 如果提供了当前题目ID,查询其排序号 + if(!StringUtils.isEmpty(quId)){ + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(UserBook::getUserId, UserUtils.getUserId()) + .eq(UserBook::getExamId, examId) + .eq(UserBook::getQuId, quId); + wrapper.last(" ORDER BY `sort` DESC"); + + UserBook last = this.getOne(wrapper, false); + if(last!=null){ + sort = last.getSort(); // 获取当前题目的排序号 + } + } + + // 查找排序号小于当前题目排序号的下一道错题 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(UserBook::getUserId, UserUtils.getUserId()) + .eq(UserBook::getExamId, examId) + .lt(UserBook::getSort, sort); // 查找排序号更小的题目 + wrapper.last(" ORDER BY `sort` DESC"); // 按排序号降序排列,取最接近的一个 + + UserBook next = this.getOne(wrapper, false); + if(next != null){ + return next.getQuId(); // 返回下一道错题的题目ID + } + + return null; // 没有找到下一题 + } + + /** + * 查找指定考试和用户下错题的最大排序号 + * @param examId 考试ID + * @param userId 用户ID + * @return 最大排序号,如果没有记录则返回0 + */ + private Integer findMaxSort(String examId, String userId){ + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .eq(UserBook::getExamId, examId) + .eq(UserBook::getUserId, userId); + wrapper.last(" ORDER BY `sort` DESC"); // 按排序号降序排列,取第一个 + + UserBook book = this.getOne(wrapper, false); + if(book == null){ + return 0; // 没有错题记录,返回0 + } + return book.getSort(); // 返回最大排序号 + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/exam/controller/UserExamController.java b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/controller/UserExamController.java new file mode 100644 index 0000000..f3f1192 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/controller/UserExamController.java @@ -0,0 +1,88 @@ +package com.yf.exam.modules.user.exam.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yf.exam.core.api.ApiRest; +import com.yf.exam.core.api.controller.BaseController; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; +import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; +import com.yf.exam.modules.user.exam.service.UserExamService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** +*

+* 考试记录控制器 +* 提供用户考试记录的相关操作接口,包括考试记录的分页查询等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-09-21 15:13 +*/ +@Api(tags={"考试记录"}) +@RestController +@RequestMapping("/exam/api/user/exam") +public class UserExamController extends BaseController { + + @Autowired + private UserExamService baseService; + + /** + * 分页查询考试记录(管理员视角) + * 管理员可以查看所有用户的考试记录,用于统计分析和管理 + * + * @param reqDTO 分页请求参数,包含分页信息和查询条件 + * @return 分页的考试记录数据 + * + * @example + * { + * "current": 1, + * "size": 10, + * "params": { + * "userName": "张三", + * "examName": "期末考试" + * } + * } + */ + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/paging", method = { RequestMethod.POST}) + public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) { + + // 分页查询考试记录并转换为响应DTO + IPage page = baseService.paging(reqDTO); + + return super.success(page); + } + + /** + * 分页查询我的考试记录(用户视角) + * 用户只能查看自己的考试记录,用于个人学习进度跟踪 + * + * @param reqDTO 分页请求参数,包含分页信息和查询条件 + * @return 分页的当前用户考试记录数据 + * + * @example + * { + * "current": 1, + * "size": 10, + * "params": { + * "examName": "模拟考试", + * "state": 1 + * } + * } + */ + @ApiOperation(value = "分页查找") + @RequestMapping(value = "/my-paging", method = { RequestMethod.POST}) + public ApiRest> myPaging(@RequestBody PagingReqDTO reqDTO) { + + // 分页查询当前用户的考试记录并转换为响应DTO + IPage page = baseService.myPaging(reqDTO); + + return super.success(page); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/UserExamDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/UserExamDTO.java new file mode 100644 index 0000000..0d870b3 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/UserExamDTO.java @@ -0,0 +1,81 @@ +package com.yf.exam.modules.user.exam.dto; + +import com.yf.exam.core.annon.Dict; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.util.Date; + +import java.io.Serializable; + +/** +*

+* 考试记录数据传输类 +* 用于在系统内部传输用户考试记录数据,包含考试基本信息、成绩统计和时间信息 +*

+* +* @author 聪明笨狗 +* @since 2020-09-21 15:13 +*/ +@Data +@ApiModel(value="考试记录", description="考试记录") +public class UserExamDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 考试记录唯一标识 + */ + private String id; + + /** + * 用户标识 + * 关联参加考试的用户ID + */ + @ApiModelProperty(value = "用户ID", required=true) + private String userId; + + /** + * 考试标识 + * 关联具体的考试信息,使用字典注解实现考试名称的自动转换 + */ + @Dict(dictTable = "el_exam", dicText = "title", dicCode = "id") + @ApiModelProperty(value = "考试ID", required=true) + private String examId; + + /** + * 考试尝试次数 + * 记录用户参加该考试的总次数 + */ + @ApiModelProperty(value = "考试次数", required=true) + private Integer tryCount; + + /** + * 最高得分 + * 记录用户在该考试中获得的最高分数 + */ + @ApiModelProperty(value = "最高分数", required=true) + private Integer maxScore; + + /** + * 是否通过考试 + * 标识用户是否已达到该考试的及格标准 + */ + @ApiModelProperty(value = "是否通过", required=true) + private Boolean passed; + + /** + * 记录创建时间 + * 用户第一次参加该考试的时间 + */ + @ApiModelProperty(value = "创建时间") + private Date createTime; + + /** + * 记录更新时间 + * 最近一次参加该考试或更新成绩的时间 + */ + @ApiModelProperty(value = "更新时间") + private Date updateTime; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/request/UserExamReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/request/UserExamReqDTO.java new file mode 100644 index 0000000..8d24a16 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/request/UserExamReqDTO.java @@ -0,0 +1,37 @@ +package com.yf.exam.modules.user.exam.dto.request; + +import com.yf.exam.modules.user.exam.dto.UserExamDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** +*

+* 考试记录数据传输类 +* 用于考试记录查询请求的数据传输,继承自UserExamDTO并扩展了查询相关的字段 +*

+* +* @author 聪明笨狗 +* @since 2020-09-21 15:13 +*/ +@Data +@ApiModel(value="考试记录", description="考试记录") +public class UserExamReqDTO extends UserExamDTO { + + private static final long serialVersionUID = 1L; + + /** + * 考试名称 + * 用于按考试名称进行模糊查询,便于用户快速找到特定考试记录 + */ + @ApiModelProperty(value = "考试名称", required=true) + private String title; + + /** + * 人员真实姓名 + * 用于按用户真实姓名进行查询,便于管理员查找特定用户的考试记录 + */ + @ApiModelProperty(value = "人员名称", required=true) + private String realName; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/response/UserExamRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/response/UserExamRespDTO.java new file mode 100644 index 0000000..ac9a6ee --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/dto/response/UserExamRespDTO.java @@ -0,0 +1,38 @@ +package com.yf.exam.modules.user.exam.dto.response; + +import com.yf.exam.modules.user.exam.dto.UserExamDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** +*

+* 考试记录数据传输类 +* 用于考试记录查询响应的数据传输,继承自UserExamDTO并扩展了展示相关的字段 +* 包含考试名称和人员姓名等展示信息,便于前端页面显示 +*

+* +* @author 聪明笨狗 +* @since 2020-09-21 15:13 +*/ +@Data +@ApiModel(value="考试记录", description="考试记录") +public class UserExamRespDTO extends UserExamDTO { + + private static final long serialVersionUID = 1L; + + /** + * 考试名称 + * 用于在考试记录列表中显示考试的具体名称,提升用户体验 + */ + @ApiModelProperty(value = "考试名称", required=true) + private String title; + + /** + * 人员真实姓名 + * 用于在考试记录列表中显示用户的真实姓名,便于识别和查找 + */ + @ApiModelProperty(value = "人员名称", required=true) + private String realName; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/exam/entity/UserExam.java b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/entity/UserExam.java new file mode 100644 index 0000000..e31bb0a --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/entity/UserExam.java @@ -0,0 +1,81 @@ +package com.yf.exam.modules.user.exam.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; +import java.util.Date; + +/** +*

+* 考试记录实体类 +* 对应数据库表 el_user_exam,用于存储用户的考试记录和成绩统计信息 +*

+* +* @author 聪明笨狗 +* @since 2020-09-21 15:13 +*/ +@Data +@TableName("el_user_exam") +public class UserExam extends Model { + + private static final long serialVersionUID = 1L; + + /** + * 考试记录唯一标识 + * 使用雪花算法分配ID,保证分布式环境下的唯一性 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 用户标识 + * 关联参加考试的用户,对应用户表的ID字段 + */ + @TableField("user_id") + private String userId; + + /** + * 考试标识 + * 关联具体的考试信息,对应考试表的ID字段 + */ + @TableField("exam_id") + private String examId; + + /** + * 考试尝试次数 + * 记录用户参加该考试的总次数,用于统计学习进度 + */ + @TableField("try_count") + private Integer tryCount; + + /** + * 最高得分 + * 记录用户在该考试中获得的最高分数,用于成绩追踪 + */ + @TableField("max_score") + private Integer maxScore; + + /** + * 是否通过考试 + * 标识用户是否已达到该考试的及格标准,用于资格判断 + */ + private Boolean passed; + + /** + * 记录创建时间 + * 用户第一次参加该考试的时间,自动记录 + */ + @TableField("create_time") + private Date createTime; + + /** + * 记录更新时间 + * 最近一次参加该考试或更新成绩的时间,自动更新 + */ + @TableField("update_time") + private Date updateTime; + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/exam/mapper/UserExamMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/mapper/UserExamMapper.java new file mode 100644 index 0000000..b327baf --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/mapper/UserExamMapper.java @@ -0,0 +1,33 @@ +package com.yf.exam.modules.user.exam.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; +import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; +import com.yf.exam.modules.user.exam.entity.UserExam; +import org.apache.ibatis.annotations.Param; + +/** +*

+* 考试记录Mapper +* 用户考试记录数据访问层接口,继承MyBatis-Plus的BaseMapper并提供自定义分页查询方法 +* 对应数据库表:el_user_exam +*

+* +* @author 聪明笨狗 +* @since 2020-09-21 15:13 +*/ +public interface UserExamMapper extends BaseMapper { + + /** + * 分页查询考试记录 + * 自定义分页查询方法,支持多表关联查询和复杂条件过滤 + * + * @param page 分页对象,包含分页参数信息 + * @param query 查询条件对象,包含考试名称、用户姓名等过滤条件 + * @return 分页的考试记录响应数据,包含考试基本信息和关联的用户、考试信息 + */ + IPage paging(Page page, @Param("query") UserExamReqDTO query); + +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/exam/service/UserExamService.java b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/service/UserExamService.java new file mode 100644 index 0000000..407350e --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/service/UserExamService.java @@ -0,0 +1,58 @@ +package com.yf.exam.modules.user.exam.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; +import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; +import com.yf.exam.modules.user.exam.entity.UserExam; + +/** +*

+* 考试记录业务类 +* 定义用户考试记录管理的核心业务接口,包括考试记录查询和成绩统计等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-09-21 15:13 +*/ +public interface UserExamService extends IService { + + /** + * 分页查询考试记录(管理员视角) + * 管理员可以查看所有用户的考试记录,支持按考试名称、用户姓名等条件过滤 + * + * @param reqDTO 分页请求参数对象,包含以下信息: + * - current: 当前页码 + * - size: 每页记录数 + * - params: 查询条件参数,可包含考试名称、用户姓名等过滤条件 + * @return 分页的考试记录数据列表,包含考试基本信息和关联的用户、考试信息 + */ + IPage paging(PagingReqDTO reqDTO); + + /** + * 分页查询我的考试记录(用户视角) + * 用户只能查看自己的考试记录,自动设置当前用户ID为查询条件 + * + * @param reqDTO 分页请求参数对象,包含以下信息: + * - current: 当前页码 + * - size: 每页记录数 + * - params: 查询条件参数,可包含考试名称等过滤条件 + * @return 分页的当前用户考试记录数据列表 + */ + IPage myPaging(PagingReqDTO reqDTO); + + /** + * 记录用户考试结果 + * 在用户完成考试后保存或更新考试记录,包括最高分数、通过状态和考试次数统计 + * + * @param userId 用户ID,标识参加考试的用户 + * @param examId 考试ID,标识具体的考试 + * @param score 本次考试得分,用于更新最高分数 + * @param passed 是否通过考试,用于更新通过状态 + * + * @example + * userExamService.joinResult("user123", "exam456", 85, true); + */ + void joinResult(String userId, String examId, Integer score, boolean passed); +} \ No newline at end of file diff --git a/exam-api1/src/main/java/com/yf/exam/modules/user/exam/service/impl/UserExamServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/service/impl/UserExamServiceImpl.java new file mode 100644 index 0000000..02214b2 --- /dev/null +++ b/exam-api1/src/main/java/com/yf/exam/modules/user/exam/service/impl/UserExamServiceImpl.java @@ -0,0 +1,114 @@ +package com.yf.exam.modules.user.exam.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yf.exam.core.api.dto.PagingReqDTO; +import com.yf.exam.modules.user.UserUtils; +import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; +import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; +import com.yf.exam.modules.user.exam.entity.UserExam; +import com.yf.exam.modules.user.exam.mapper.UserExamMapper; +import com.yf.exam.modules.user.exam.service.UserExamService; +import org.springframework.stereotype.Service; + +import java.util.Date; + +/** +*

+* 考试记录业务实现类 +* 负责用户考试记录的业务逻辑实现,包括考试记录查询、成绩统计和更新等功能 +*

+* +* @author 聪明笨狗 +* @since 2020-09-21 15:13 +*/ +@Service +public class UserExamServiceImpl extends ServiceImpl implements UserExamService { + + /** + * 分页查询考试记录(管理员视角) + * 管理员可以查看所有用户的考试记录,支持按条件过滤 + * @param reqDTO 分页请求参数,包含分页信息和查询条件 + * @return 分页的考试记录数据 + */ + @Override + public IPage paging(PagingReqDTO reqDTO) { + + // 调用自定义Mapper方法进行分页查询并返回结果 + IPage pageData = baseMapper.paging(reqDTO.toPage(), reqDTO.getParams()); + return pageData; + } + + /** + * 分页查询我的考试记录(用户视角) + * 用户只能查看自己的考试记录,自动设置当前用户ID为查询条件 + * @param reqDTO 分页请求参数,包含分页信息和查询条件 + * @return 分页的当前用户考试记录数据 + */ + @Override + public IPage myPaging(PagingReqDTO reqDTO) { + + // 获取查询参数,如果为空则创建新对象 + UserExamReqDTO params = reqDTO.getParams(); + if(params==null){ + params = new UserExamReqDTO(); + } + + // 设置当前用户ID为查询条件,确保用户只能查看自己的考试记录 + params.setUserId(UserUtils.getUserId()); + + // 调用自定义Mapper方法进行分页查询并返回结果 + IPage pageData = baseMapper.paging(reqDTO.toPage(), params); + return pageData; + } + + /** + * 记录用户考试结果 + * 保存或更新用户的考试记录,包括最高分数、通过状态和考试次数 + * @param userId 用户ID + * @param examId 考试ID + * @param score 本次考试得分 + * @param passed 是否通过考试 + */ + @Override + public void joinResult(String userId, String examId, Integer score, boolean passed) { + + // 构建查询条件:用户ID和考试ID + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(UserExam::getUserId, userId) + .eq(UserExam::getExamId, examId); + + // 查找是否已存在该用户的考试记录 + UserExam record = this.getOne(wrapper, false); + + // 如果不存在考试记录,创建新的记录 + if(record == null){ + record = new UserExam(); + record.setCreateTime(new Date()); + record.setUpdateTime(new Date()); + record.setUserId(userId); + record.setExamId(examId); + record.setMaxScore(score); + record.setPassed(passed); + record.setTryCount(1); // 第一次参加考试 + this.save(record); + return; + } + + // 更新已存在的考试记录 + // 增加考试次数 + record.setTryCount(record.getTryCount()+1); + record.setUpdateTime(new Date()); + + // 如果本次分数高于历史最高分,更新最高分和通过状态 + // 修复了低分数不加入统计的问题,确保每次考试都会被记录 + if(record.getMaxScore() < score){ + record.setMaxScore(score); + record.setPassed(passed); + } + + // 更新考试记录 + this.updateById(record); + } +} \ No newline at end of file diff --git a/exam-api1/src/main/resources/application-dev.yml b/exam-api1/src/main/resources/application-dev.yml new file mode 100644 index 0000000..f58c358 --- /dev/null +++ b/exam-api1/src/main/resources/application-dev.yml @@ -0,0 +1,71 @@ +# 开发环境配置文件 +spring: + # 数据库配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/yf_exam_lite?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: root + password: root@123 + # druid相关配置 + druid: + max-active: 5000 + initial-size: 20 + min-idle: 5 + async-init: true + # 监控统计 + filters: stat,wall + filter: + stat: + log-slow-sql: true + slow-sql-millis: 5000 + wall: + config: + create-table-allow: false + alter-table-allow: false + drop-table-allow: false + truncate-allow: false + + # 定时任务配置 + quartz: + # 数据库方式 + job-store-type: jdbc + # quartz 相关属性配置 + properties: + org: + quartz: + scheduler: + instanceName: eamScheduler + instanceId: AUTO + jobStore: + class: org.quartz.impl.jdbcjobstore.JobStoreTX + driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate + tablePrefix: QRTZ_ + isClustered: true + clusterCheckinInterval: 10000 + useProperties: false + threadPool: + class: org.quartz.simpl.SimpleThreadPool + threadCount: 10 + threadPriority: 5 + threadsInheritContextClassLoaderOfInitializingThread: true + +# 文件上传配置 +conf: + upload: + # 物理文件存储位置,以/结束,windows已正斜杠,如:d:/exam-upload/ + dir: /Users/van/Documents/work/upload/ + # 访问地址,注意不要去除/upload/file/,此节点为虚拟标识符 + # 如:http://localhost:8101/upload/file/exam.jpg,对应物理文件为:/data/upload/exam.jpg + url: http://localhost:1201/upload/file/ + # 允许上传的文件后缀 + allow-extensions: jpg,jpeg,png + +# 开启文档 +swagger: + enable: true + +logging: + level: + root: debug + path: logs/${spring.application.name}/ diff --git a/exam-api1/src/main/resources/application-local.yml b/exam-api1/src/main/resources/application-local.yml new file mode 100644 index 0000000..f5e82aa --- /dev/null +++ b/exam-api1/src/main/resources/application-local.yml @@ -0,0 +1,85 @@ +# 独立配置文件,可以拿到jar外面跑 +spring: + application: + name: yf-exam-lite + profiles: + active: dev + main: + allow-bean-definition-overriding: true + # 数据库配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/yf_exam_lite?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: root + password: root + # druid相关配置 + druid: + max-active: 5000 + initial-size: 20 + min-idle: 5 + async-init: true + # 监控统计 + filters: stat,wall + filter: + stat: + log-slow-sql: true + slow-sql-millis: 5000 + wall: + config: + create-table-allow: false + alter-table-allow: false + drop-table-allow: false + truncate-allow: false + + # 定时任务配置 + quartz: + # 数据库方式 + job-store-type: jdbc + # quartz 相关属性配置 + properties: + org: + quartz: + scheduler: + instanceName: examScheduler + instanceId: AUTO + jobStore: + class: org.quartz.impl.jdbcjobstore.JobStoreTX + driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate + tablePrefix: QRTZ_ + isClustered: true + clusterCheckinInterval: 10000 + useProperties: false + threadPool: + class: org.quartz.simpl.SimpleThreadPool + threadCount: 10 + threadPriority: 5 + threadsInheritContextClassLoaderOfInitializingThread: true + +server: + port: 8101 + # 启用服务端压缩 + compression: + enabled: true + min-response-size: 10 + mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css + +# 文件上传配置 +conf: + upload: + # 物理文件存储位置,以/结束,windows已正斜杠,如:d:/exam-upload/ + dir: /data/upload/ + # 访问地址,注意不要去除/upload/file/,此节点为虚拟标识符 + # 如:http://localhost:8101/upload/file/exam.jpg,对应物理文件为:/data/upload/exam.jpg + url: http://localhost:8101/upload/file/ + # 允许上传的文件后缀 + allow-extensions: jpg,jpeg,png + +# 开启文档 +swagger: + enable: true + +logging: + level: + root: debug + path: logs/${spring.application.name}/ diff --git a/exam-api1/src/main/resources/application.yml b/exam-api1/src/main/resources/application.yml new file mode 100644 index 0000000..839f049 --- /dev/null +++ b/exam-api1/src/main/resources/application.yml @@ -0,0 +1,15 @@ +spring: + application: + name: yf-exam-lite + profiles: + active: dev + main: + allow-bean-definition-overriding: true +server: + port: 8101 + # 启用服务端压缩 + compression: + enabled: true + min-response-size: 10 + mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css + diff --git a/exam-api1/src/main/resources/mapper/exam/ExamDepartMapper.xml b/exam-api1/src/main/resources/mapper/exam/ExamDepartMapper.xml new file mode 100644 index 0000000..7a9a90b --- /dev/null +++ b/exam-api1/src/main/resources/mapper/exam/ExamDepartMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + `id`,`exam_id`,`depart_id` + + + diff --git a/exam-api1/src/main/resources/mapper/exam/ExamMapper.xml b/exam-api1/src/main/resources/mapper/exam/ExamMapper.xml new file mode 100644 index 0000000..f580479 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/exam/ExamMapper.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + `id`,`title`,`content`,`open_type`,`join_type`,`level`,`state`,`time_limit`,`start_time`,`end_time`,`create_time`,`update_time`,`total_score`,`total_time`,`qualify_score` + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/exam/ExamRepoMapper.xml b/exam-api1/src/main/resources/mapper/exam/ExamRepoMapper.xml new file mode 100644 index 0000000..bc06691 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/exam/ExamRepoMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + `id`,`exam_id`,`repo_id`,`radio_count`,`radio_score`,`multi_count`,`multi_score`,`judge_count`,`judge_score` + + + + + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/paper/PaperMapper.xml b/exam-api1/src/main/resources/mapper/paper/PaperMapper.xml new file mode 100644 index 0000000..8653ab1 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/paper/PaperMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`user_id`,`depart_id`,`exam_id`,`title`,`total_time`,`user_time`,`total_score`,`qualify_score`,`obj_score`,`subj_score`,`user_score`,`has_saq`,`state`,`create_time`,`update_time`,`limit_time` + + + + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/paper/PaperQuAnswerMapper.xml b/exam-api1/src/main/resources/mapper/paper/PaperQuAnswerMapper.xml new file mode 100644 index 0000000..bd4f89b --- /dev/null +++ b/exam-api1/src/main/resources/mapper/paper/PaperQuAnswerMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + `id`,`paper_id`,`answer_id`,`qu_id`,`is_right`,`checked`,`sort`,`abc` + + + + + + + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/paper/PaperQuMapper.xml b/exam-api1/src/main/resources/mapper/paper/PaperQuMapper.xml new file mode 100644 index 0000000..0498b71 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/paper/PaperQuMapper.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + `id`,`paper_id`,`qu_id`,`qu_type`,`answered`,`answer`,`sort`,`score`,`actual_score`,`is_right` + + + + + + + + + + + + + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/qu/QuAnswerMapper.xml b/exam-api1/src/main/resources/mapper/qu/QuAnswerMapper.xml new file mode 100644 index 0000000..a5cfd35 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/qu/QuAnswerMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + `id`,`qu_id`,`is_right`,`image`,`content`,`analysis` + + + diff --git a/exam-api1/src/main/resources/mapper/qu/QuMapper.xml b/exam-api1/src/main/resources/mapper/qu/QuMapper.xml new file mode 100644 index 0000000..20ed746 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/qu/QuMapper.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + `id`,`qu_type`,`level`,`image`,`content`,`create_time`,`update_time`,`remark`,`analysis` + + + + + + + + + + + + + + + + + + + + + + + + + + AND q.qu_type = #{query.quType} + + + AND po.repo_id IN + #{repoId} + + + AND q.content LIKE CONCAT('%',#{query.content},'%') + + + AND q.id NOT IN + + #{quId} + + + + + + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/qu/QuRepoMapper.xml b/exam-api1/src/main/resources/mapper/qu/QuRepoMapper.xml new file mode 100644 index 0000000..3353d6c --- /dev/null +++ b/exam-api1/src/main/resources/mapper/qu/QuRepoMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + `id`,`qu_id`,`repo_id`,`qu_type`,`sort` + + + + diff --git a/exam-api1/src/main/resources/mapper/repo/RepoMapper.xml b/exam-api1/src/main/resources/mapper/repo/RepoMapper.xml new file mode 100644 index 0000000..d026b31 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/repo/RepoMapper.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + `id`,`code`,`title`,`radio_count`,`multi_count`,`judge_count`,`remark`,`create_time`,`update_time` + + + + + + + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/sys/depart/SysDepartMapper.xml b/exam-api1/src/main/resources/mapper/sys/depart/SysDepartMapper.xml new file mode 100644 index 0000000..2bae742 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/sys/depart/SysDepartMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + `id`,`dept_type`,`parent_id`,`dept_name`,`dept_code`,`sort` + + + + + + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/sys/system/SysDictMapper.xml b/exam-api1/src/main/resources/mapper/sys/system/SysDictMapper.xml new file mode 100644 index 0000000..194ff86 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/sys/system/SysDictMapper.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/exam-api1/src/main/resources/mapper/sys/user/SysRoleMapper.xml b/exam-api1/src/main/resources/mapper/sys/user/SysRoleMapper.xml new file mode 100644 index 0000000..f79fbf6 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/sys/user/SysRoleMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + `id`,`role_name` + + + diff --git a/exam-api1/src/main/resources/mapper/sys/user/SysUserMapper.xml b/exam-api1/src/main/resources/mapper/sys/user/SysUserMapper.xml new file mode 100644 index 0000000..7d9c81f --- /dev/null +++ b/exam-api1/src/main/resources/mapper/sys/user/SysUserMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + `id`,`user_name`,`real_name`,`password`,`salt`,`role_ids`,`depart_id`,`create_time`,`update_time`,`state` + + + diff --git a/exam-api1/src/main/resources/mapper/sys/user/SysUserRoleMapper.xml b/exam-api1/src/main/resources/mapper/sys/user/SysUserRoleMapper.xml new file mode 100644 index 0000000..f2ea833 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/sys/user/SysUserRoleMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + `id`,`user_id`,`role_id` + + + diff --git a/exam-api1/src/main/resources/mapper/user/UserBookMapper.xml b/exam-api1/src/main/resources/mapper/user/UserBookMapper.xml new file mode 100644 index 0000000..17d6775 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/user/UserBookMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + `id`,`exam_id`,`user_id`,`qu_id`,`create_time`,`update_time`,`wrong_count`,`title`,`sort` + + + diff --git a/exam-api1/src/main/resources/mapper/user/UserExamMapper.xml b/exam-api1/src/main/resources/mapper/user/UserExamMapper.xml new file mode 100644 index 0000000..d123ee7 --- /dev/null +++ b/exam-api1/src/main/resources/mapper/user/UserExamMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + `id`,`user_id`,`exam_id`,`try_count`,`max_score`,`passed`,`create_time`,`update_time` + + + + + + + + + + + diff --git a/exam-api1/src/main/resources/static/favicon.png b/exam-api1/src/main/resources/static/favicon.png new file mode 100644 index 0000000..01f150e Binary files /dev/null and b/exam-api1/src/main/resources/static/favicon.png differ diff --git a/exam-api1/src/main/resources/static/index.html b/exam-api1/src/main/resources/static/index.html new file mode 100644 index 0000000..3862b9d --- /dev/null +++ b/exam-api1/src/main/resources/static/index.html @@ -0,0 +1,14 @@ + + + + + + + + + 云帆考试培训系统 + + +
+ + diff --git a/exam-api1/src/main/resources/static/readMe.txt b/exam-api1/src/main/resources/static/readMe.txt new file mode 100644 index 0000000..a581872 --- /dev/null +++ b/exam-api1/src/main/resources/static/readMe.txt @@ -0,0 +1,2 @@ +为了方便单文件运行,可以把前端打包文件dist目录的内容复制到本目录下面,这样就可以直接运行jar就包含前端了,不需要单独再部署前端了。 +访问地址为:http://localhost:8101 \ No newline at end of file diff --git a/exam-api1/src/main/resources/static/static/fonts/element-icons.535877f5.woff b/exam-api1/src/main/resources/static/static/fonts/element-icons.535877f5.woff new file mode 100644 index 0000000..02b9a25 Binary files /dev/null and b/exam-api1/src/main/resources/static/static/fonts/element-icons.535877f5.woff differ diff --git a/exam-api1/src/main/resources/static/static/fonts/element-icons.732389de.ttf b/exam-api1/src/main/resources/static/static/fonts/element-icons.732389de.ttf new file mode 100644 index 0000000..91b74de Binary files /dev/null and b/exam-api1/src/main/resources/static/static/fonts/element-icons.732389de.ttf differ diff --git a/exam-api1/src/main/resources/static/static/img/401.089007e7.gif b/exam-api1/src/main/resources/static/static/img/401.089007e7.gif new file mode 100644 index 0000000..cd6e0d9 Binary files /dev/null and b/exam-api1/src/main/resources/static/static/img/401.089007e7.gif differ diff --git a/exam-api1/src/main/resources/static/static/img/404.a57b6f31.png b/exam-api1/src/main/resources/static/static/img/404.a57b6f31.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/exam-api1/src/main/resources/static/static/img/404.a57b6f31.png differ diff --git a/exam-api1/src/main/resources/static/static/img/404_cloud.0f4bc32b.png b/exam-api1/src/main/resources/static/static/img/404_cloud.0f4bc32b.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/exam-api1/src/main/resources/static/static/img/404_cloud.0f4bc32b.png differ diff --git a/exam-api1/src/main/resources/static/static/img/contact.22828125.png b/exam-api1/src/main/resources/static/static/img/contact.22828125.png new file mode 100644 index 0000000..ce3e761 Binary files /dev/null and b/exam-api1/src/main/resources/static/static/img/contact.22828125.png differ diff --git a/exam-api1/src/main/resources/static/static/img/h5.ac62244a.png b/exam-api1/src/main/resources/static/static/img/h5.ac62244a.png new file mode 100644 index 0000000..41478fd Binary files /dev/null and b/exam-api1/src/main/resources/static/static/img/h5.ac62244a.png differ diff --git a/exam-api1/src/main/resources/static/static/img/login-bg.5825f033.svg b/exam-api1/src/main/resources/static/static/img/login-bg.5825f033.svg new file mode 100644 index 0000000..89c2597 --- /dev/null +++ b/exam-api1/src/main/resources/static/static/img/login-bg.5825f033.svg @@ -0,0 +1,69 @@ + + + + Group 21 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exam-api1/src/main/resources/static/static/img/mp.1a3d1b7d.jpg b/exam-api1/src/main/resources/static/static/img/mp.1a3d1b7d.jpg new file mode 100644 index 0000000..db5c422 Binary files /dev/null and b/exam-api1/src/main/resources/static/static/img/mp.1a3d1b7d.jpg differ diff --git a/exam-api1/src/main/resources/static/static/js/0.js b/exam-api1/src/main/resources/static/static/js/0.js new file mode 100644 index 0000000..b1c22bd --- /dev/null +++ b/exam-api1/src/main/resources/static/static/js/0.js @@ -0,0 +1,219 @@ +(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{ + +/***/ "./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/FileUpload/index.vue?vue&type=script&lang=js&": +/*!*************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/FileUpload/index.vue?vue&type=script&lang=js& ***! + \*************************************************************************************************************************************************************************************************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _local__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./local */ \"./src/components/FileUpload/local.vue\");\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n name: 'FileUpload',\n components: {\n FileUploadLocal: _local__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n props: {\n value: String,\n accept: {\n type: String,\n default: '*'\n },\n tips: String,\n listType: {\n type: String,\n default: 'picture'\n }\n },\n data: function data() {\n return {\n fileUrl: ''\n };\n },\n watch: {\n // 检测查询变化\n value: {\n handler: function handler() {\n this.fillValue();\n }\n },\n // 检测查询变化\n fileUrl: {\n handler: function handler() {\n this.$emit('input', this.fileUrl);\n }\n }\n },\n mounted: function mounted() {},\n created: function created() {\n this.fillValue();\n },\n methods: {\n fillValue: function fillValue() {\n this.fileUrl = this.value;\n }\n }\n});\n\n//# sourceURL=webpack:///./src/components/FileUpload/index.vue?./node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/FileUpload/local.vue?vue&type=script&lang=js&": +/*!*************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/FileUpload/local.vue?vue&type=script&lang=js& ***! + \*************************************************************************************************************************************************************************************************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es6_number_constructor__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es6.number.constructor */ \"./node_modules/core-js/modules/es6.number.constructor.js\");\n/* harmony import */ var core_js_modules_es6_number_constructor__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_number_constructor__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _utils_auth__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @/utils/auth */ \"./src/utils/auth.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n name: 'FileUploadLocal',\n props: {\n value: String,\n accept: String,\n tips: String,\n listType: String,\n limit: {\n type: Number,\n default: 1\n }\n },\n data: function data() {\n return {\n server: \"\".concat(\"\", \"/common/api/file/upload\"),\n fileList: [],\n fileUrl: '',\n header: {}\n };\n },\n watch: {\n // 检测查询变化\n value: {\n handler: function handler() {\n this.fillValue();\n }\n }\n },\n created: function created() {\n this.fillValue();\n this.header = {\n token: Object(_utils_auth__WEBPACK_IMPORTED_MODULE_1__[\"getToken\"])()\n };\n },\n methods: {\n fillValue: function fillValue() {\n this.fileList = [];\n this.fileUrl = this.value;\n\n if (this.fileUrl) {\n this.fileList = [{\n name: this.fileUrl,\n url: this.fileUrl\n }];\n }\n },\n // 文件超出个数限制时的钩子\n handleExceed: function handleExceed() {\n this.$message.warning(\"\\u6BCF\\u6B21\\u53EA\\u80FD\\u4E0A\\u4F20 \".concat(this.limit, \" \\u4E2A\\u6587\\u4EF6\"));\n },\n // 删除文件之前的钩子\n beforeRemove: function beforeRemove() {\n return this.$confirm(\"\\u786E\\u5B9A\\u79FB\\u9664\\u6587\\u4EF6\\u5417\\uFF1F\");\n },\n // 文件列表移除文件时的钩子\n handleRemove: function handleRemove() {\n this.$emit('input', '');\n this.fileList = [];\n },\n // 文件上传成功时的钩子\n handleSuccess: function handleSuccess(response) {\n if (response.code === 1) {\n this.$message({\n type: 'error',\n message: response.msg\n });\n this.fileList = [];\n return;\n }\n\n this.$emit('input', response.data.url);\n }\n }\n});\n\n//# sourceURL=webpack:///./src/components/FileUpload/local.vue?./node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/qu/qu/form.vue?vue&type=script&lang=js&": +/*!**************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/views/qu/qu/form.vue?vue&type=script&lang=js& ***! + \**************************************************************************************************************************************************************************************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/web.dom.iterable */ \"./node_modules/core-js/modules/web.dom.iterable.js\");\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _api_qu_qu__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @/api/qu/qu */ \"./src/api/qu/qu.js\");\n/* harmony import */ var _components_RepoSelect__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @/components/RepoSelect */ \"./src/components/RepoSelect/index.vue\");\n/* harmony import */ var _components_FileUpload__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @/components/FileUpload */ \"./src/components/FileUpload/index.vue\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n name: 'QuDetail',\n components: {\n FileUpload: _components_FileUpload__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n RepoSelect: _components_RepoSelect__WEBPACK_IMPORTED_MODULE_2__[\"default\"]\n },\n data: function data() {\n return {\n quTypeDisabled: false,\n itemImage: true,\n levels: [{\n value: 1,\n label: '普通'\n }, {\n value: 2,\n label: '较难'\n }],\n quTypes: [{\n value: 1,\n label: '单选题'\n }, {\n value: 2,\n label: '多选题'\n }, {\n value: 3,\n label: '判断题'\n }],\n postForm: {\n repoIds: [],\n tagList: [],\n answerList: []\n },\n rules: {\n content: [{\n required: true,\n message: '题目内容不能为空!'\n }],\n quType: [{\n required: true,\n message: '题目类型不能为空!'\n }],\n level: [{\n required: true,\n message: '必须选择难度等级!'\n }],\n repoIds: [{\n required: true,\n message: '至少要选择一个题库!'\n }]\n }\n };\n },\n created: function created() {\n var id = this.$route.params.id;\n\n if (typeof id !== 'undefined') {\n this.quTypeDisabled = true;\n this.fetchData(id);\n }\n },\n methods: {\n handleTypeChange: function handleTypeChange(v) {\n this.postForm.answerList = [];\n\n if (v === 3) {\n this.postForm.answerList.push({\n isRight: true,\n content: '正确',\n analysis: ''\n });\n this.postForm.answerList.push({\n isRight: false,\n content: '错误',\n analysis: ''\n });\n }\n\n if (v === 1 || v === 2) {\n this.postForm.answerList.push({\n isRight: false,\n content: '',\n analysis: ''\n });\n this.postForm.answerList.push({\n isRight: false,\n content: '',\n analysis: ''\n });\n this.postForm.answerList.push({\n isRight: false,\n content: '',\n analysis: ''\n });\n this.postForm.answerList.push({\n isRight: false,\n content: '',\n analysis: ''\n });\n }\n },\n // 添加子项\n handleAdd: function handleAdd() {\n this.postForm.answerList.push({\n isRight: false,\n content: '',\n analysis: ''\n });\n },\n removeItem: function removeItem(index) {\n this.postForm.answerList.splice(index, 1);\n },\n fetchData: function fetchData(id) {\n var _this = this;\n\n Object(_api_qu_qu__WEBPACK_IMPORTED_MODULE_1__[\"fetchDetail\"])(id).then(function (response) {\n _this.postForm = response.data;\n });\n },\n submitForm: function submitForm() {\n var _this2 = this;\n\n console.log(JSON.stringify(this.postForm));\n var rightCount = 0;\n this.postForm.answerList.forEach(function (item) {\n if (item.isRight) {\n rightCount += 1;\n }\n });\n\n if (this.postForm.quType === 1) {\n if (rightCount !== 1) {\n this.$message({\n message: '单选题答案只能有一个',\n type: 'warning'\n });\n return;\n }\n }\n\n if (this.postForm.quType === 2) {\n if (rightCount < 2) {\n this.$message({\n message: '多选题至少要有两个正确答案!',\n type: 'warning'\n });\n return;\n }\n }\n\n if (this.postForm.quType === 3) {\n if (rightCount !== 1) {\n this.$message({\n message: '判断题只能有一个正确项!',\n type: 'warning'\n });\n return;\n }\n }\n\n this.$refs.postForm.validate(function (valid) {\n if (!valid) {\n return;\n }\n\n Object(_api_qu_qu__WEBPACK_IMPORTED_MODULE_1__[\"saveData\"])(_this2.postForm).then(function (response) {\n _this2.postForm = response.data;\n\n _this2.$notify({\n title: '成功',\n message: '试题保存成功!',\n type: 'success',\n duration: 2000\n });\n\n _this2.$router.push({\n name: 'ListQu'\n });\n });\n });\n },\n onCancel: function onCancel() {\n this.$router.push({\n name: 'ListQu'\n });\n }\n }\n});\n\n//# sourceURL=webpack:///./src/views/qu/qu/form.vue?./node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/FileUpload/index.vue?vue&type=template&id=211f81e0&": +/*!*********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"9323b05c-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/FileUpload/index.vue?vue&type=template&id=211f81e0& ***! + \*********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function () {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n [\n _c(\"file-upload-local\", {\n attrs: {\n accept: _vm.accept,\n tips: _vm.tips,\n \"list-type\": _vm.listType,\n },\n model: {\n value: _vm.fileUrl,\n callback: function ($$v) {\n _vm.fileUrl = $$v\n },\n expression: \"fileUrl\",\n },\n }),\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./src/components/FileUpload/index.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%229323b05c-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/FileUpload/local.vue?vue&type=template&id=5087fdae&": +/*!*********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"9323b05c-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/FileUpload/local.vue?vue&type=template&id=5087fdae& ***! + \*********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function () {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"content\" },\n [\n _c(\n \"el-upload\",\n {\n attrs: {\n action: _vm.server,\n accept: _vm.accept,\n \"before-remove\": _vm.beforeRemove,\n \"on-remove\": _vm.handleRemove,\n \"on-success\": _vm.handleSuccess,\n \"on-exceed\": _vm.handleExceed,\n drag: _vm.listType !== \"picture\",\n limit: _vm.limit,\n headers: _vm.header,\n \"file-list\": _vm.fileList,\n \"list-type\": _vm.listType,\n },\n model: {\n value: _vm.fileUrl,\n callback: function ($$v) {\n _vm.fileUrl = $$v\n },\n expression: \"fileUrl\",\n },\n },\n [\n _vm.listType === \"picture\"\n ? _c(\"el-button\", { attrs: { size: \"small\", type: \"primary\" } }, [\n _vm._v(\"点击上传\"),\n ])\n : _vm._e(),\n _vm.listType !== \"picture\"\n ? _c(\"i\", { staticClass: \"el-icon-upload\" })\n : _vm._e(),\n _vm.listType !== \"picture\"\n ? _c(\"div\", { staticClass: \"el-upload__text\" }, [\n _vm._v(\" 将文件拖到此处,或 \"),\n _c(\"em\", [_vm._v(\"点击上传\")]),\n ])\n : _vm._e(),\n _vm.tips\n ? _c(\n \"div\",\n {\n staticClass: \"el-upload__tip\",\n attrs: { slot: \"tip\" },\n slot: \"tip\",\n },\n [_vm._v(_vm._s(_vm.tips))]\n )\n : _vm._e(),\n ],\n 1\n ),\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./src/components/FileUpload/local.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%229323b05c-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/qu/qu/form.vue?vue&type=template&id=4fe7c07e&scoped=true&": +/*!**********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"9323b05c-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/views/qu/qu/form.vue?vue&type=template&id=4fe7c07e&scoped=true& ***! + \**********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function () {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"app-container\" },\n [\n _c(\n \"el-form\",\n {\n ref: \"postForm\",\n attrs: {\n model: _vm.postForm,\n rules: _vm.rules,\n \"label-position\": \"left\",\n \"label-width\": \"150px\",\n },\n },\n [\n _c(\n \"el-card\",\n [\n _c(\n \"el-form-item\",\n { attrs: { label: \"题目类型 \", prop: \"quType\" } },\n [\n _c(\n \"el-select\",\n {\n staticClass: \"filter-item\",\n attrs: { disabled: _vm.quTypeDisabled },\n on: { change: _vm.handleTypeChange },\n model: {\n value: _vm.postForm.quType,\n callback: function ($$v) {\n _vm.$set(_vm.postForm, \"quType\", $$v)\n },\n expression: \"postForm.quType\",\n },\n },\n _vm._l(_vm.quTypes, function (item) {\n return _c(\"el-option\", {\n key: item.value,\n attrs: { label: item.label, value: item.value },\n })\n }),\n 1\n ),\n ],\n 1\n ),\n _c(\n \"el-form-item\",\n { attrs: { label: \"难度等级 \", prop: \"level\" } },\n [\n _c(\n \"el-select\",\n {\n staticClass: \"filter-item\",\n model: {\n value: _vm.postForm.level,\n callback: function ($$v) {\n _vm.$set(_vm.postForm, \"level\", $$v)\n },\n expression: \"postForm.level\",\n },\n },\n _vm._l(_vm.levels, function (item) {\n return _c(\"el-option\", {\n key: item.value,\n attrs: { label: item.label, value: item.value },\n })\n }),\n 1\n ),\n ],\n 1\n ),\n _c(\n \"el-form-item\",\n { attrs: { label: \"归属题库\", prop: \"repoIds\" } },\n [\n _c(\"repo-select\", {\n attrs: { multi: true },\n model: {\n value: _vm.postForm.repoIds,\n callback: function ($$v) {\n _vm.$set(_vm.postForm, \"repoIds\", $$v)\n },\n expression: \"postForm.repoIds\",\n },\n }),\n ],\n 1\n ),\n _c(\n \"el-form-item\",\n { attrs: { label: \"题目内容\", prop: \"content\" } },\n [\n _c(\"el-input\", {\n attrs: { type: \"textarea\" },\n model: {\n value: _vm.postForm.content,\n callback: function ($$v) {\n _vm.$set(_vm.postForm, \"content\", $$v)\n },\n expression: \"postForm.content\",\n },\n }),\n ],\n 1\n ),\n _c(\n \"el-form-item\",\n { attrs: { label: \"试题图片\" } },\n [\n _c(\"file-upload\", {\n attrs: { accept: \".jpg,.jepg,.png\" },\n model: {\n value: _vm.postForm.image,\n callback: function ($$v) {\n _vm.$set(_vm.postForm, \"image\", $$v)\n },\n expression: \"postForm.image\",\n },\n }),\n ],\n 1\n ),\n _c(\n \"el-form-item\",\n { attrs: { label: \"整题解析\", prop: \"oriPrice\" } },\n [\n _c(\"el-input\", {\n attrs: { precision: 1, max: 999999, type: \"textarea\" },\n model: {\n value: _vm.postForm.analysis,\n callback: function ($$v) {\n _vm.$set(_vm.postForm, \"analysis\", $$v)\n },\n expression: \"postForm.analysis\",\n },\n }),\n ],\n 1\n ),\n ],\n 1\n ),\n _vm.postForm.quType !== 4\n ? _c(\n \"div\",\n {\n staticClass: \"filter-container\",\n staticStyle: { \"margin-top\": \"25px\" },\n },\n [\n _c(\n \"el-button\",\n {\n staticClass: \"filter-item\",\n attrs: {\n type: \"primary\",\n icon: \"el-icon-plus\",\n size: \"small\",\n plain: \"\",\n },\n on: { click: _vm.handleAdd },\n },\n [_vm._v(\" 添加 \")]\n ),\n _c(\n \"el-table\",\n {\n staticStyle: { width: \"100%\" },\n attrs: { data: _vm.postForm.answerList, border: true },\n },\n [\n _c(\"el-table-column\", {\n attrs: {\n label: \"是否答案\",\n width: \"120\",\n align: \"center\",\n },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function (scope) {\n return [\n _c(\n \"el-checkbox\",\n {\n model: {\n value: scope.row.isRight,\n callback: function ($$v) {\n _vm.$set(scope.row, \"isRight\", $$v)\n },\n expression: \"scope.row.isRight\",\n },\n },\n [_vm._v(\"答案\")]\n ),\n ]\n },\n },\n ],\n null,\n false,\n 1650073960\n ),\n }),\n _vm.itemImage\n ? _c(\"el-table-column\", {\n attrs: {\n label: \"选项图片\",\n width: \"120px\",\n align: \"center\",\n },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function (scope) {\n return [\n _c(\"file-upload\", {\n attrs: { accept: \".jpg,.jepg,.png\" },\n model: {\n value: scope.row.image,\n callback: function ($$v) {\n _vm.$set(scope.row, \"image\", $$v)\n },\n expression: \"scope.row.image\",\n },\n }),\n ]\n },\n },\n ],\n null,\n false,\n 2051426284\n ),\n })\n : _vm._e(),\n _c(\"el-table-column\", {\n attrs: { label: \"答案内容\" },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function (scope) {\n return [\n _c(\"el-input\", {\n attrs: { type: \"textarea\" },\n model: {\n value: scope.row.content,\n callback: function ($$v) {\n _vm.$set(scope.row, \"content\", $$v)\n },\n expression: \"scope.row.content\",\n },\n }),\n ]\n },\n },\n ],\n null,\n false,\n 924406712\n ),\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"答案解析\" },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function (scope) {\n return [\n _c(\"el-input\", {\n attrs: { type: \"textarea\" },\n model: {\n value: scope.row.analysis,\n callback: function ($$v) {\n _vm.$set(scope.row, \"analysis\", $$v)\n },\n expression: \"scope.row.analysis\",\n },\n }),\n ]\n },\n },\n ],\n null,\n false,\n 3792987939\n ),\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"操作\",\n align: \"center\",\n width: \"100px\",\n },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function (scope) {\n return [\n _c(\"el-button\", {\n attrs: {\n type: \"danger\",\n icon: \"el-icon-delete\",\n circle: \"\",\n },\n on: {\n click: function ($event) {\n return _vm.removeItem(scope.$index)\n },\n },\n }),\n ]\n },\n },\n ],\n null,\n false,\n 1518468532\n ),\n }),\n ],\n 1\n ),\n ],\n 1\n )\n : _vm._e(),\n _c(\n \"div\",\n { staticStyle: { \"margin-top\": \"20px\" } },\n [\n _c(\n \"el-button\",\n { attrs: { type: \"primary\" }, on: { click: _vm.submitForm } },\n [_vm._v(\"保存\")]\n ),\n _c(\n \"el-button\",\n { attrs: { type: \"info\" }, on: { click: _vm.onCancel } },\n [_vm._v(\"返回\")]\n ),\n ],\n 1\n ),\n ],\n 1\n ),\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./src/views/qu/qu/form.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%229323b05c-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/core-js/modules/es6.array.find.js": +/*!********************************************************!*\ + !*** ./node_modules/core-js/modules/es6.array.find.js ***! + \********************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\n// 22.1.3.8 Array.prototype.find(predicate, thisArg = undefined)\nvar $export = __webpack_require__(/*! ./_export */ \"./node_modules/core-js/modules/_export.js\");\nvar $find = __webpack_require__(/*! ./_array-methods */ \"./node_modules/core-js/modules/_array-methods.js\")(5);\nvar KEY = 'find';\nvar forced = true;\n// Shouldn't skip holes\nif (KEY in []) Array(1)[KEY](function () { forced = false; });\n$export($export.P + $export.F * forced, 'Array', {\n find: function find(callbackfn /* , that = undefined */) {\n return $find(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);\n }\n});\n__webpack_require__(/*! ./_add-to-unscopables */ \"./node_modules/core-js/modules/_add-to-unscopables.js\")(KEY);\n\n\n//# sourceURL=webpack:///./node_modules/core-js/modules/es6.array.find.js?"); + +/***/ }), + +/***/ "./src/api/qu/qu.js": +/*!**************************!*\ + !*** ./src/api/qu/qu.js ***! + \**************************/ +/*! exports provided: fetchDetail, saveData, exportExcel, importTemplate, importExcel */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"fetchDetail\", function() { return fetchDetail; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"saveData\", function() { return saveData; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"exportExcel\", function() { return exportExcel; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"importTemplate\", function() { return importTemplate; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"importExcel\", function() { return importExcel; });\n/* harmony import */ var _utils_request__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @/utils/request */ \"./src/utils/request.js\");\n\n/**\n * 题库详情\n * @param data\n */\n\nfunction fetchDetail(id) {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"post\"])('/exam/api/qu/qu/detail', {\n id: id\n });\n}\n/**\n * 保存题库\n * @param data\n */\n\nfunction saveData(data) {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"post\"])('/exam/api/qu/qu/save', data);\n}\n/**\n * 导出\n * @param data\n */\n\nfunction exportExcel(data) {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"download\"])('/exam/api/qu/qu/export', data, '导出的数据.xlsx');\n}\n/**\n * 导入模板\n * @param data\n */\n\nfunction importTemplate() {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"download\"])('/exam/api/qu/qu/import/template', {}, 'qu-import-template.xlsx');\n}\n/**\n * 导出\n * @param data\n */\n\nfunction importExcel(file) {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"upload\"])('/exam/api/qu/qu/import', file);\n}\n\n//# sourceURL=webpack:///./src/api/qu/qu.js?"); + +/***/ }), + +/***/ "./src/api/qu/repo.js": +/*!****************************!*\ + !*** ./src/api/qu/repo.js ***! + \****************************/ +/*! exports provided: fetchDetail, saveData, fetchPaging, batchAction */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"fetchDetail\", function() { return fetchDetail; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"saveData\", function() { return saveData; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"fetchPaging\", function() { return fetchPaging; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"batchAction\", function() { return batchAction; });\n/* harmony import */ var _utils_request__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @/utils/request */ \"./src/utils/request.js\");\n\n/**\n * 题库详情\n * @param data\n */\n\nfunction fetchDetail(data) {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"post\"])('/exam/api/repo/detail', data);\n}\n/**\n * 保存题库\n * @param data\n */\n\nfunction saveData(data) {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"post\"])('/exam/api/repo/save', data);\n}\n/**\n * 保存题库\n * @param data\n */\n\nfunction fetchPaging(data) {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"post\"])('/exam/api/repo/paging', data);\n}\n/**\n * 题库批量操作\n * @param data\n */\n\nfunction batchAction(data) {\n return Object(_utils_request__WEBPACK_IMPORTED_MODULE_0__[\"post\"])('/exam/api/repo/batch-action', data);\n}\n\n//# sourceURL=webpack:///./src/api/qu/repo.js?"); + +/***/ }), + +/***/ "./src/components/FileUpload/index.vue": +/*!*********************************************!*\ + !*** ./src/components/FileUpload/index.vue ***! + \*********************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _index_vue_vue_type_template_id_211f81e0___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.vue?vue&type=template&id=211f81e0& */ \"./src/components/FileUpload/index.vue?vue&type=template&id=211f81e0&\");\n/* harmony import */ var _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.vue?vue&type=script&lang=js& */ \"./src/components/FileUpload/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _index_vue_vue_type_template_id_211f81e0___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _index_vue_vue_type_template_id_211f81e0___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/components/FileUpload/index.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/components/FileUpload/index.vue?"); + +/***/ }), + +/***/ "./src/components/FileUpload/index.vue?vue&type=script&lang=js&": +/*!**********************************************************************!*\ + !*** ./src/components/FileUpload/index.vue?vue&type=script&lang=js& ***! + \**********************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../node_modules/babel-loader/lib!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/FileUpload/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/components/FileUpload/index.vue?"); + +/***/ }), + +/***/ "./src/components/FileUpload/index.vue?vue&type=template&id=211f81e0&": +/*!****************************************************************************!*\ + !*** ./src/components/FileUpload/index.vue?vue&type=template&id=211f81e0& ***! + \****************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_211f81e0___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=template&id=211f81e0& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/FileUpload/index.vue?vue&type=template&id=211f81e0&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_211f81e0___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_211f81e0___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/components/FileUpload/index.vue?"); + +/***/ }), + +/***/ "./src/components/FileUpload/local.vue": +/*!*********************************************!*\ + !*** ./src/components/FileUpload/local.vue ***! + \*********************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _local_vue_vue_type_template_id_5087fdae___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./local.vue?vue&type=template&id=5087fdae& */ \"./src/components/FileUpload/local.vue?vue&type=template&id=5087fdae&\");\n/* harmony import */ var _local_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./local.vue?vue&type=script&lang=js& */ \"./src/components/FileUpload/local.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _local_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _local_vue_vue_type_template_id_5087fdae___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _local_vue_vue_type_template_id_5087fdae___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/components/FileUpload/local.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/components/FileUpload/local.vue?"); + +/***/ }), + +/***/ "./src/components/FileUpload/local.vue?vue&type=script&lang=js&": +/*!**********************************************************************!*\ + !*** ./src/components/FileUpload/local.vue?vue&type=script&lang=js& ***! + \**********************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_local_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../node_modules/babel-loader/lib!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./local.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/FileUpload/local.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_local_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/components/FileUpload/local.vue?"); + +/***/ }), + +/***/ "./src/components/FileUpload/local.vue?vue&type=template&id=5087fdae&": +/*!****************************************************************************!*\ + !*** ./src/components/FileUpload/local.vue?vue&type=template&id=5087fdae& ***! + \****************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_local_vue_vue_type_template_id_5087fdae___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./local.vue?vue&type=template&id=5087fdae& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/FileUpload/local.vue?vue&type=template&id=5087fdae&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_local_vue_vue_type_template_id_5087fdae___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_local_vue_vue_type_template_id_5087fdae___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/components/FileUpload/local.vue?"); + +/***/ }), + +/***/ "./src/views/qu/qu/form.vue": +/*!**********************************!*\ + !*** ./src/views/qu/qu/form.vue ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _form_vue_vue_type_template_id_4fe7c07e_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./form.vue?vue&type=template&id=4fe7c07e&scoped=true& */ \"./src/views/qu/qu/form.vue?vue&type=template&id=4fe7c07e&scoped=true&\");\n/* harmony import */ var _form_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./form.vue?vue&type=script&lang=js& */ \"./src/views/qu/qu/form.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _form_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _form_vue_vue_type_template_id_4fe7c07e_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _form_vue_vue_type_template_id_4fe7c07e_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"4fe7c07e\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/views/qu/qu/form.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/views/qu/qu/form.vue?"); + +/***/ }), + +/***/ "./src/views/qu/qu/form.vue?vue&type=script&lang=js&": +/*!***********************************************************!*\ + !*** ./src/views/qu/qu/form.vue?vue&type=script&lang=js& ***! + \***********************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_form_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./form.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/qu/qu/form.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_form_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/views/qu/qu/form.vue?"); + +/***/ }), + +/***/ "./src/views/qu/qu/form.vue?vue&type=template&id=4fe7c07e&scoped=true&": +/*!*****************************************************************************!*\ + !*** ./src/views/qu/qu/form.vue?vue&type=template&id=4fe7c07e&scoped=true& ***! + \*****************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_form_vue_vue_type_template_id_4fe7c07e_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./form.vue?vue&type=template&id=4fe7c07e&scoped=true& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/qu/qu/form.vue?vue&type=template&id=4fe7c07e&scoped=true&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_form_vue_vue_type_template_id_4fe7c07e_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_form_vue_vue_type_template_id_4fe7c07e_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/views/qu/qu/form.vue?"); + +/***/ }) + +}]); \ No newline at end of file diff --git a/exam-api1/src/main/resources/static/static/js/1.js b/exam-api1/src/main/resources/static/static/js/1.js new file mode 100644 index 0000000..46609e8 --- /dev/null +++ b/exam-api1/src/main/resources/static/static/js/1.js @@ -0,0 +1,133 @@ +(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],{ + +/***/ "./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/DepartTreeSelect/index.vue?vue&type=script&lang=js&": +/*!*******************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/DepartTreeSelect/index.vue?vue&type=script&lang=js& ***! + \*******************************************************************************************************************************************************************************************************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n name: 'DepartTree',\n // 设置绑定参数\n model: {\n prop: 'value',\n event: 'selected'\n },\n props: {\n // 接收绑定参数\n value: String,\n // 输入框宽度\n width: String,\n // 选项数据\n options: {\n type: Array,\n required: true\n },\n // 输入框占位符\n placeholder: {\n type: String,\n required: false,\n default: '请选择'\n },\n // 树节点配置选项\n props: {\n type: Object,\n required: false,\n default: function _default() {\n return {\n parent: 'parentId',\n value: 'rowGuid',\n label: 'areaName',\n children: 'children'\n };\n }\n }\n },\n data: function data() {\n return {\n // 树状菜单显示状态\n showStatus: false,\n // 菜单宽度\n treeWidth: 'auto',\n // 输入框显示值\n labelModel: '',\n // 实际请求传值\n valueModel: '0'\n };\n },\n computed: {\n // 是否为树状结构数据\n dataType: function dataType() {\n var jsonStr = JSON.stringify(this.options);\n return jsonStr.indexOf(this.props.children) !== -1;\n },\n // 若非树状结构,则转化为树状结构数据\n data: function data() {\n return this.dataType ? this.options : this.switchTree();\n }\n },\n watch: {\n labelModel: function labelModel(val) {\n if (!val) {\n this.valueModel = '';\n }\n\n this.$refs.tree.filter(val);\n },\n value: function value(val) {\n this.labelModel = this.queryTree(this.data, val);\n }\n },\n created: function created() {\n var _this = this;\n\n // 检测输入框原有值并显示对应 label\n if (this.value) {\n this.labelModel = this.queryTree(this.data, this.value);\n } // 获取输入框宽度同步至树状菜单宽度\n\n\n this.$nextTick(function () {\n _this.treeWidth = \"\".concat((_this.width || _this.$refs.input.$refs.input.clientWidth) - 24, \"px\");\n });\n },\n methods: {\n // 单击节点\n onClickNode: function onClickNode(node) {\n this.labelModel = node[this.props.label];\n this.valueModel = node[this.props.value];\n this.onCloseTree();\n },\n // 偏平数组转化为树状层级结构\n switchTree: function switchTree() {\n return this.cleanChildren(this.buildTree(this.options, '0'));\n },\n // 隐藏树状菜单\n onCloseTree: function onCloseTree() {\n this.$refs.popover.showPopper = false;\n },\n // 显示时触发\n onShowPopover: function onShowPopover() {\n this.showStatus = true;\n this.$refs.tree.filter(false);\n },\n // 隐藏时触发\n onHidePopover: function onHidePopover() {\n this.showStatus = false;\n this.$emit('selected', this.valueModel);\n },\n // 树节点过滤方法\n filterNode: function filterNode(query, data) {\n if (!query) return true;\n return data[this.props.label].indexOf(query) !== -1;\n },\n // 搜索树状数据中的 ID\n queryTree: function queryTree(tree, id) {\n var stark = [];\n stark = stark.concat(tree);\n\n while (stark.length) {\n var temp = stark.shift();\n\n if (temp[this.props.children]) {\n stark = stark.concat(temp[this.props.children]);\n }\n\n if (temp[this.props.value] === id) {\n return temp[this.props.label];\n }\n }\n\n return '';\n },\n // 将一维的扁平数组转换为多层级对象\n buildTree: function buildTree(data) {\n var _this2 = this;\n\n var id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '0';\n\n var fa = function fa(parentId) {\n var temp = [];\n\n for (var i = 0; i < data.length; i++) {\n var n = data[i];\n\n if (n[_this2.props.parent] === parentId) {\n n.children = fa(n.rowGuid);\n temp.push(n);\n }\n }\n\n return temp;\n };\n\n return fa(id);\n },\n // 清除空 children项\n cleanChildren: function cleanChildren(data) {\n var fa = function fa(list) {\n list.map(function (e) {\n if (e.children.length) {\n fa(e.children);\n } else {\n delete e.children;\n }\n\n return e;\n });\n return list;\n };\n\n return fa(data);\n }\n }\n});\n\n//# sourceURL=webpack:///./src/components/DepartTreeSelect/index.vue?./node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/DepartTreeSelect/index.vue?vue&type=template&id=1392eafe&": +/*!***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"9323b05c-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/DepartTreeSelect/index.vue?vue&type=template&id=1392eafe& ***! + \***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function () {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"el-popover\",\n {\n ref: \"popover\",\n attrs: { placement: \"bottom-start\", trigger: \"click\" },\n on: { show: _vm.onShowPopover, hide: _vm.onHidePopover },\n },\n [\n _c(\"el-tree\", {\n ref: \"tree\",\n staticClass: \"select-tree\",\n style: \"min-width: \" + _vm.treeWidth,\n attrs: {\n data: _vm.data,\n props: _vm.props,\n \"expand-on-click-node\": false,\n \"filter-node-method\": _vm.filterNode,\n placeholder: \"选择部门\",\n \"check-strictly\": false,\n \"highlight-current\": \"\",\n \"default-expand-all\": \"\",\n },\n on: { \"node-click\": _vm.onClickNode },\n }),\n _c(\"el-input\", {\n ref: \"input\",\n class: { rotate: _vm.showStatus },\n style: \"width: \" + _vm.width + \"px\",\n attrs: {\n slot: \"reference\",\n placeholder: _vm.placeholder,\n clearable: \"\",\n \"suffix-icon\": \"el-icon-arrow-down\",\n },\n slot: \"reference\",\n model: {\n value: _vm.labelModel,\n callback: function ($$v) {\n _vm.labelModel = $$v\n },\n expression: \"labelModel\",\n },\n }),\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./src/components/DepartTreeSelect/index.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%229323b05c-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/DepartTreeSelect/index.vue?vue&type=style&index=0&lang=css&": +/*!*************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js??ref--7-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--7-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/DepartTreeSelect/index.vue?vue&type=style&index=0&lang=css& ***! + \*************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.el-input.el-input--suffix {\\n cursor: pointer;\\n overflow: hidden;\\n}\\n.el-input.el-input--suffix.rotate .el-input__suffix {\\n -webkit-transform: rotate(180deg);\\n transform: rotate(180deg);\\n}\\n.select-tree {\\n max-height: 350px;\\n overflow-y: scroll;\\n}\\n/* 菜单滚动条 */\\n.select-tree::-webkit-scrollbar {\\n z-index: 11;\\n width: 6px;\\n}\\n.select-tree::-webkit-scrollbar-track,\\n.select-tree::-webkit-scrollbar-corner {\\n background: #fff;\\n}\\n.select-tree::-webkit-scrollbar-thumb {\\n border-radius: 5px;\\n width: 6px;\\n background: #b4bccc;\\n}\\n.select-tree::-webkit-scrollbar-track-piece {\\n background: #fff;\\n width: 6px;\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack:///./src/components/DepartTreeSelect/index.vue?./node_modules/css-loader/dist/cjs.js??ref--7-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--7-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options"); + +/***/ }), + +/***/ "./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/DepartTreeSelect/index.vue?vue&type=style&index=0&lang=css&": +/*!***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/vue-style-loader??ref--7-oneOf-1-0!./node_modules/css-loader/dist/cjs.js??ref--7-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--7-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/DepartTreeSelect/index.vue?vue&type=style&index=0&lang=css& ***! + \***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("// style-loader: Adds some css to the DOM by adding a \"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/admin.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/agreement.svg": +/*!*************************************!*\ + !*** ./src/icons/svg/agreement.svg ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-agreement\",\n \"use\": \"icon-agreement-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/agreement.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/bug.svg": +/*!*******************************!*\ + !*** ./src/icons/svg/bug.svg ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-bug\",\n \"use\": \"icon-bug-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/bug.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/chart.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/chart.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-chart\",\n \"use\": \"icon-chart-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/chart.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/clipboard.svg": +/*!*************************************!*\ + !*** ./src/icons/svg/clipboard.svg ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-clipboard\",\n \"use\": \"icon-clipboard-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/clipboard.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/component.svg": +/*!*************************************!*\ + !*** ./src/icons/svg/component.svg ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-component\",\n \"use\": \"icon-component-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/component.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/configure.svg": +/*!*************************************!*\ + !*** ./src/icons/svg/configure.svg ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-configure\",\n \"use\": \"icon-configure-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/configure.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/dashboard.svg": +/*!*************************************!*\ + !*** ./src/icons/svg/dashboard.svg ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-dashboard\",\n \"use\": \"icon-dashboard-usage\",\n \"viewBox\": \"0 0 128 100\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/dashboard.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/documentation.svg": +/*!*****************************************!*\ + !*** ./src/icons/svg/documentation.svg ***! + \*****************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-documentation\",\n \"use\": \"icon-documentation-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/documentation.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/drag.svg": +/*!********************************!*\ + !*** ./src/icons/svg/drag.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-drag\",\n \"use\": \"icon-drag-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/drag.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/edit.svg": +/*!********************************!*\ + !*** ./src/icons/svg/edit.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-edit\",\n \"use\": \"icon-edit-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/edit.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/education.svg": +/*!*************************************!*\ + !*** ./src/icons/svg/education.svg ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-education\",\n \"use\": \"icon-education-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/education.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/email.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/email.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-email\",\n \"use\": \"icon-email-usage\",\n \"viewBox\": \"0 0 128 96\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/email.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/example.svg": +/*!***********************************!*\ + !*** ./src/icons/svg/example.svg ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-example\",\n \"use\": \"icon-example-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/example.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/excel.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/excel.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-excel\",\n \"use\": \"icon-excel-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/excel.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/exit-fullscreen.svg": +/*!*******************************************!*\ + !*** ./src/icons/svg/exit-fullscreen.svg ***! + \*******************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-exit-fullscreen\",\n \"use\": \"icon-exit-fullscreen-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/exit-fullscreen.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/eye-open.svg": +/*!************************************!*\ + !*** ./src/icons/svg/eye-open.svg ***! + \************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-eye-open\",\n \"use\": \"icon-eye-open-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/eye-open.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/eye.svg": +/*!*******************************!*\ + !*** ./src/icons/svg/eye.svg ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-eye\",\n \"use\": \"icon-eye-usage\",\n \"viewBox\": \"0 0 128 64\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/eye.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/fire.svg": +/*!********************************!*\ + !*** ./src/icons/svg/fire.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-fire\",\n \"use\": \"icon-fire-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\\n \\n \\n\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/fire.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/form.svg": +/*!********************************!*\ + !*** ./src/icons/svg/form.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-form\",\n \"use\": \"icon-form-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/form.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/fullscreen.svg": +/*!**************************************!*\ + !*** ./src/icons/svg/fullscreen.svg ***! + \**************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-fullscreen\",\n \"use\": \"icon-fullscreen-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/fullscreen.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/guide.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/guide.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-guide\",\n \"use\": \"icon-guide-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/guide.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/hot.svg": +/*!*******************************!*\ + !*** ./src/icons/svg/hot.svg ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-hot\",\n \"use\": \"icon-hot-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/hot.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/icon.svg": +/*!********************************!*\ + !*** ./src/icons/svg/icon.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-icon\",\n \"use\": \"icon-icon-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/icon.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/international.svg": +/*!*****************************************!*\ + !*** ./src/icons/svg/international.svg ***! + \*****************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-international\",\n \"use\": \"icon-international-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/international.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/language.svg": +/*!************************************!*\ + !*** ./src/icons/svg/language.svg ***! + \************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-language\",\n \"use\": \"icon-language-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/language.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/link.svg": +/*!********************************!*\ + !*** ./src/icons/svg/link.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-link\",\n \"use\": \"icon-link-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/link.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/list.svg": +/*!********************************!*\ + !*** ./src/icons/svg/list.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-list\",\n \"use\": \"icon-list-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/list.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/lock.svg": +/*!********************************!*\ + !*** ./src/icons/svg/lock.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-lock\",\n \"use\": \"icon-lock-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/lock.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/log.svg": +/*!*******************************!*\ + !*** ./src/icons/svg/log.svg ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-log\",\n \"use\": \"icon-log-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/log.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/map.svg": +/*!*******************************!*\ + !*** ./src/icons/svg/map.svg ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-map\",\n \"use\": \"icon-map-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/map.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/message.svg": +/*!***********************************!*\ + !*** ./src/icons/svg/message.svg ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-message\",\n \"use\": \"icon-message-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/message.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/money.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/money.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-money\",\n \"use\": \"icon-money-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/money.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/nested.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/nested.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-nested\",\n \"use\": \"icon-nested-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/nested.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/notify.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/notify.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-notify\",\n \"use\": \"icon-notify-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\\n \\n \\n\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/notify.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/paper.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/paper.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-paper\",\n \"use\": \"icon-paper-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\\n \\n \\n \\n \\n \\n\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/paper.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/password.svg": +/*!************************************!*\ + !*** ./src/icons/svg/password.svg ***! + \************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-password\",\n \"use\": \"icon-password-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/password.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/pdf.svg": +/*!*******************************!*\ + !*** ./src/icons/svg/pdf.svg ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-pdf\",\n \"use\": \"icon-pdf-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/pdf.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/people.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/people.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-people\",\n \"use\": \"icon-people-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/people.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/peoples.svg": +/*!***********************************!*\ + !*** ./src/icons/svg/peoples.svg ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-peoples\",\n \"use\": \"icon-peoples-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/peoples.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/qq.svg": +/*!******************************!*\ + !*** ./src/icons/svg/qq.svg ***! + \******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-qq\",\n \"use\": \"icon-qq-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/qq.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/repo.svg": +/*!********************************!*\ + !*** ./src/icons/svg/repo.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-repo\",\n \"use\": \"icon-repo-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\\n \\n \\n\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/repo.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/results.svg": +/*!***********************************!*\ + !*** ./src/icons/svg/results.svg ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-results\",\n \"use\": \"icon-results-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/results.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/review.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/review.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-review\",\n \"use\": \"icon-review-usage\",\n \"viewBox\": \"0 0 1047 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/review.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/role.svg": +/*!********************************!*\ + !*** ./src/icons/svg/role.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-role\",\n \"use\": \"icon-role-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/role.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/search.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/search.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-search\",\n \"use\": \"icon-search-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/search.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/settings .svg": +/*!*************************************!*\ + !*** ./src/icons/svg/settings .svg ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-settings \",\n \"use\": \"icon-settings -usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/settings_.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/shopping.svg": +/*!************************************!*\ + !*** ./src/icons/svg/shopping.svg ***! + \************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-shopping\",\n \"use\": \"icon-shopping-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/shopping.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/size.svg": +/*!********************************!*\ + !*** ./src/icons/svg/size.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-size\",\n \"use\": \"icon-size-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/size.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/skill.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/skill.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-skill\",\n \"use\": \"icon-skill-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/skill.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/star.svg": +/*!********************************!*\ + !*** ./src/icons/svg/star.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-star\",\n \"use\": \"icon-star-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/star.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/statis.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/statis.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-statis\",\n \"use\": \"icon-statis-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/statis.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/stats-dots.svg": +/*!**************************************!*\ + !*** ./src/icons/svg/stats-dots.svg ***! + \**************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-stats-dots\",\n \"use\": \"icon-stats-dots-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/stats-dots.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/stats2.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/stats2.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-stats2\",\n \"use\": \"icon-stats2-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/stats2.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/study.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/study.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-study\",\n \"use\": \"icon-study-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\\n \\n \\n\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/study.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/study1.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/study1.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-study1\",\n \"use\": \"icon-study1-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\\n \\n \\n\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/study1.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/support.svg": +/*!***********************************!*\ + !*** ./src/icons/svg/support.svg ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-support\",\n \"use\": \"icon-support-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/support.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/tab.svg": +/*!*******************************!*\ + !*** ./src/icons/svg/tab.svg ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-tab\",\n \"use\": \"icon-tab-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/tab.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/table.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/table.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-table\",\n \"use\": \"icon-table-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/table.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/test.svg": +/*!********************************!*\ + !*** ./src/icons/svg/test.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-test\",\n \"use\": \"icon-test-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/test.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/theme.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/theme.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-theme\",\n \"use\": \"icon-theme-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/theme.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/topic.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/topic.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-topic\",\n \"use\": \"icon-topic-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/topic.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/training.svg": +/*!************************************!*\ + !*** ./src/icons/svg/training.svg ***! + \************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-training\",\n \"use\": \"icon-training-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\\n \\n \\n\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/training.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/tree-table.svg": +/*!**************************************!*\ + !*** ./src/icons/svg/tree-table.svg ***! + \**************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-tree-table\",\n \"use\": \"icon-tree-table-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/tree-table.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/tree.svg": +/*!********************************!*\ + !*** ./src/icons/svg/tree.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-tree\",\n \"use\": \"icon-tree-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/tree.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/user.svg": +/*!********************************!*\ + !*** ./src/icons/svg/user.svg ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-user\",\n \"use\": \"icon-user-usage\",\n \"viewBox\": \"0 0 130 130\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/user.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/water.svg": +/*!*********************************!*\ + !*** ./src/icons/svg/water.svg ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-water\",\n \"use\": \"icon-water-usage\",\n \"viewBox\": \"0 0 1024 1024\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/water.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/wechat.svg": +/*!**********************************!*\ + !*** ./src/icons/svg/wechat.svg ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-wechat\",\n \"use\": \"icon-wechat-usage\",\n \"viewBox\": \"0 0 128 110\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/wechat.svg?"); + +/***/ }), + +/***/ "./src/icons/svg/zip.svg": +/*!*******************************!*\ + !*** ./src/icons/svg/zip.svg ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! svg-baker-runtime/browser-symbol */ \"./node_modules/svg-baker-runtime/browser-symbol.js\");\n/* harmony import */ var svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! svg-sprite-loader/runtime/browser-sprite.build */ \"./node_modules/svg-sprite-loader/runtime/browser-sprite.build.js\");\n/* harmony import */ var svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1__);\n\n\nvar symbol = new svg_baker_runtime_browser_symbol__WEBPACK_IMPORTED_MODULE_0___default.a({\n \"id\": \"icon-zip\",\n \"use\": \"icon-zip-usage\",\n \"viewBox\": \"0 0 128 128\",\n \"content\": \"\"\n});\nvar result = svg_sprite_loader_runtime_browser_sprite_build__WEBPACK_IMPORTED_MODULE_1___default.a.add(symbol);\n/* harmony default export */ __webpack_exports__[\"default\"] = (symbol);\n\n//# sourceURL=webpack:///./src/icons/svg/zip.svg?"); + +/***/ }), + +/***/ "./src/layout/components/AppMain.vue": +/*!*******************************************!*\ + !*** ./src/layout/components/AppMain.vue ***! + \*******************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _AppMain_vue_vue_type_template_id_078753dd_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AppMain.vue?vue&type=template&id=078753dd&scoped=true& */ \"./src/layout/components/AppMain.vue?vue&type=template&id=078753dd&scoped=true&\");\n/* harmony import */ var _AppMain_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./AppMain.vue?vue&type=script&lang=js& */ \"./src/layout/components/AppMain.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _AppMain_vue_vue_type_style_index_0_id_078753dd_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AppMain.vue?vue&type=style&index=0&id=078753dd&lang=scss&scoped=true& */ \"./src/layout/components/AppMain.vue?vue&type=style&index=0&id=078753dd&lang=scss&scoped=true&\");\n/* harmony import */ var _AppMain_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AppMain.vue?vue&type=style&index=1&lang=scss& */ \"./src/layout/components/AppMain.vue?vue&type=style&index=1&lang=scss&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"])(\n _AppMain_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _AppMain_vue_vue_type_template_id_078753dd_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _AppMain_vue_vue_type_template_id_078753dd_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"078753dd\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/AppMain.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/AppMain.vue?"); + +/***/ }), + +/***/ "./src/layout/components/AppMain.vue?vue&type=script&lang=js&": +/*!********************************************************************!*\ + !*** ./src/layout/components/AppMain.vue?vue&type=script&lang=js& ***! + \********************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../node_modules/babel-loader/lib!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./AppMain.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/AppMain.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/AppMain.vue?"); + +/***/ }), + +/***/ "./src/layout/components/AppMain.vue?vue&type=style&index=0&id=078753dd&lang=scss&scoped=true&": +/*!*****************************************************************************************************!*\ + !*** ./src/layout/components/AppMain.vue?vue&type=style&index=0&id=078753dd&lang=scss&scoped=true& ***! + \*****************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_0_id_078753dd_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./AppMain.vue?vue&type=style&index=0&id=078753dd&lang=scss&scoped=true& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/AppMain.vue?vue&type=style&index=0&id=078753dd&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_0_id_078753dd_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_0_id_078753dd_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_0_id_078753dd_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_0_id_078753dd_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/components/AppMain.vue?"); + +/***/ }), + +/***/ "./src/layout/components/AppMain.vue?vue&type=style&index=1&lang=scss&": +/*!*****************************************************************************!*\ + !*** ./src/layout/components/AppMain.vue?vue&type=style&index=1&lang=scss& ***! + \*****************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./AppMain.vue?vue&type=style&index=1&lang=scss& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/AppMain.vue?vue&type=style&index=1&lang=scss&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/components/AppMain.vue?"); + +/***/ }), + +/***/ "./src/layout/components/AppMain.vue?vue&type=template&id=078753dd&scoped=true&": +/*!**************************************************************************************!*\ + !*** ./src/layout/components/AppMain.vue?vue&type=template&id=078753dd&scoped=true& ***! + \**************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_template_id_078753dd_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./AppMain.vue?vue&type=template&id=078753dd&scoped=true& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/AppMain.vue?vue&type=template&id=078753dd&scoped=true&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_template_id_078753dd_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_AppMain_vue_vue_type_template_id_078753dd_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/AppMain.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Navbar.vue": +/*!******************************************!*\ + !*** ./src/layout/components/Navbar.vue ***! + \******************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _Navbar_vue_vue_type_template_id_d16d6306_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Navbar.vue?vue&type=template&id=d16d6306&scoped=true& */ \"./src/layout/components/Navbar.vue?vue&type=template&id=d16d6306&scoped=true&\");\n/* harmony import */ var _Navbar_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Navbar.vue?vue&type=script&lang=js& */ \"./src/layout/components/Navbar.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _Navbar_vue_vue_type_style_index_0_id_d16d6306_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Navbar.vue?vue&type=style&index=0&id=d16d6306&lang=scss&scoped=true& */ \"./src/layout/components/Navbar.vue?vue&type=style&index=0&id=d16d6306&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(\n _Navbar_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _Navbar_vue_vue_type_template_id_d16d6306_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _Navbar_vue_vue_type_template_id_d16d6306_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"d16d6306\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/Navbar.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/Navbar.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Navbar.vue?vue&type=script&lang=js&": +/*!*******************************************************************!*\ + !*** ./src/layout/components/Navbar.vue?vue&type=script&lang=js& ***! + \*******************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../node_modules/babel-loader/lib!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./Navbar.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Navbar.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/Navbar.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Navbar.vue?vue&type=style&index=0&id=d16d6306&lang=scss&scoped=true&": +/*!****************************************************************************************************!*\ + !*** ./src/layout/components/Navbar.vue?vue&type=style&index=0&id=d16d6306&lang=scss&scoped=true& ***! + \****************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_style_index_0_id_d16d6306_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./Navbar.vue?vue&type=style&index=0&id=d16d6306&lang=scss&scoped=true& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Navbar.vue?vue&type=style&index=0&id=d16d6306&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_style_index_0_id_d16d6306_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_style_index_0_id_d16d6306_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_style_index_0_id_d16d6306_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_style_index_0_id_d16d6306_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/components/Navbar.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Navbar.vue?vue&type=template&id=d16d6306&scoped=true&": +/*!*************************************************************************************!*\ + !*** ./src/layout/components/Navbar.vue?vue&type=template&id=d16d6306&scoped=true& ***! + \*************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_template_id_d16d6306_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../node_modules/vue-loader/lib??vue-loader-options!./Navbar.vue?vue&type=template&id=d16d6306&scoped=true& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Navbar.vue?vue&type=template&id=d16d6306&scoped=true&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_template_id_d16d6306_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Navbar_vue_vue_type_template_id_d16d6306_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/Navbar.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Settings/index.vue": +/*!**************************************************!*\ + !*** ./src/layout/components/Settings/index.vue ***! + \**************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _index_vue_vue_type_template_id_126b135a_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.vue?vue&type=template&id=126b135a&scoped=true& */ \"./src/layout/components/Settings/index.vue?vue&type=template&id=126b135a&scoped=true&\");\n/* harmony import */ var _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.vue?vue&type=script&lang=js& */ \"./src/layout/components/Settings/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _index_vue_vue_type_style_index_0_id_126b135a_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./index.vue?vue&type=style&index=0&id=126b135a&lang=scss&scoped=true& */ \"./src/layout/components/Settings/index.vue?vue&type=style&index=0&id=126b135a&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(\n _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _index_vue_vue_type_template_id_126b135a_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _index_vue_vue_type_template_id_126b135a_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"126b135a\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/Settings/index.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/Settings/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Settings/index.vue?vue&type=script&lang=js&": +/*!***************************************************************************!*\ + !*** ./src/layout/components/Settings/index.vue?vue&type=script&lang=js& ***! + \***************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Settings/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/Settings/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Settings/index.vue?vue&type=style&index=0&id=126b135a&lang=scss&scoped=true&": +/*!************************************************************************************************************!*\ + !*** ./src/layout/components/Settings/index.vue?vue&type=style&index=0&id=126b135a&lang=scss&scoped=true& ***! + \************************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_126b135a_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=style&index=0&id=126b135a&lang=scss&scoped=true& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Settings/index.vue?vue&type=style&index=0&id=126b135a&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_126b135a_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_126b135a_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_126b135a_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_126b135a_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/components/Settings/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Settings/index.vue?vue&type=template&id=126b135a&scoped=true&": +/*!*********************************************************************************************!*\ + !*** ./src/layout/components/Settings/index.vue?vue&type=template&id=126b135a&scoped=true& ***! + \*********************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_126b135a_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=template&id=126b135a&scoped=true& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Settings/index.vue?vue&type=template&id=126b135a&scoped=true&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_126b135a_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_126b135a_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/Settings/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/FixiOSBug.js": +/*!****************************************************!*\ + !*** ./src/layout/components/Sidebar/FixiOSBug.js ***! + \****************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n computed: {\n device: function device() {\n return this.$store.state.app.device;\n }\n },\n mounted: function mounted() {\n // In order to fix the click on menu on the ios device will trigger the mouseleave bug\n // https://github.com/PanJiaChen/vue-element-admin/issues/1135\n this.fixBugIniOS();\n },\n methods: {\n fixBugIniOS: function fixBugIniOS() {\n var _this = this;\n\n var $subMenu = this.$refs.subMenu;\n\n if ($subMenu) {\n var handleMouseleave = $subMenu.handleMouseleave;\n\n $subMenu.handleMouseleave = function (e) {\n if (_this.device === 'mobile') {\n return;\n }\n\n handleMouseleave(e);\n };\n }\n }\n }\n});\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/FixiOSBug.js?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Item.vue": +/*!************************************************!*\ + !*** ./src/layout/components/Sidebar/Item.vue ***! + \************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _Item_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Item.vue?vue&type=script&lang=js& */ \"./src/layout/components/Sidebar/Item.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\nvar render, staticRenderFns\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(\n _Item_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"],\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/Sidebar/Item.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Item.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Item.vue?vue&type=script&lang=js&": +/*!*************************************************************************!*\ + !*** ./src/layout/components/Sidebar/Item.vue?vue&type=script&lang=js& ***! + \*************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Item_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./Item.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/Item.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Item_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Item.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Link.vue": +/*!************************************************!*\ + !*** ./src/layout/components/Sidebar/Link.vue ***! + \************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _Link_vue_vue_type_template_id_32e8ab1a___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Link.vue?vue&type=template&id=32e8ab1a& */ \"./src/layout/components/Sidebar/Link.vue?vue&type=template&id=32e8ab1a&\");\n/* harmony import */ var _Link_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Link.vue?vue&type=script&lang=js& */ \"./src/layout/components/Sidebar/Link.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _Link_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _Link_vue_vue_type_template_id_32e8ab1a___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _Link_vue_vue_type_template_id_32e8ab1a___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/Sidebar/Link.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Link.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Link.vue?vue&type=script&lang=js&": +/*!*************************************************************************!*\ + !*** ./src/layout/components/Sidebar/Link.vue?vue&type=script&lang=js& ***! + \*************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Link_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./Link.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/Link.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Link_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Link.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Link.vue?vue&type=template&id=32e8ab1a&": +/*!*******************************************************************************!*\ + !*** ./src/layout/components/Sidebar/Link.vue?vue&type=template&id=32e8ab1a& ***! + \*******************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Link_vue_vue_type_template_id_32e8ab1a___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./Link.vue?vue&type=template&id=32e8ab1a& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/Link.vue?vue&type=template&id=32e8ab1a&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Link_vue_vue_type_template_id_32e8ab1a___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Link_vue_vue_type_template_id_32e8ab1a___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Link.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Logo.vue": +/*!************************************************!*\ + !*** ./src/layout/components/Sidebar/Logo.vue ***! + \************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _Logo_vue_vue_type_template_id_6494804b_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Logo.vue?vue&type=template&id=6494804b&scoped=true& */ \"./src/layout/components/Sidebar/Logo.vue?vue&type=template&id=6494804b&scoped=true&\");\n/* harmony import */ var _Logo_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Logo.vue?vue&type=script&lang=js& */ \"./src/layout/components/Sidebar/Logo.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _Logo_vue_vue_type_style_index_0_id_6494804b_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Logo.vue?vue&type=style&index=0&id=6494804b&lang=scss&scoped=true& */ \"./src/layout/components/Sidebar/Logo.vue?vue&type=style&index=0&id=6494804b&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(\n _Logo_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _Logo_vue_vue_type_template_id_6494804b_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _Logo_vue_vue_type_template_id_6494804b_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"6494804b\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/Sidebar/Logo.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Logo.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Logo.vue?vue&type=script&lang=js&": +/*!*************************************************************************!*\ + !*** ./src/layout/components/Sidebar/Logo.vue?vue&type=script&lang=js& ***! + \*************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./Logo.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/Logo.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Logo.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Logo.vue?vue&type=style&index=0&id=6494804b&lang=scss&scoped=true&": +/*!**********************************************************************************************************!*\ + !*** ./src/layout/components/Sidebar/Logo.vue?vue&type=style&index=0&id=6494804b&lang=scss&scoped=true& ***! + \**********************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_style_index_0_id_6494804b_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./Logo.vue?vue&type=style&index=0&id=6494804b&lang=scss&scoped=true& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/Logo.vue?vue&type=style&index=0&id=6494804b&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_style_index_0_id_6494804b_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_style_index_0_id_6494804b_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_style_index_0_id_6494804b_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_style_index_0_id_6494804b_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Logo.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/Logo.vue?vue&type=template&id=6494804b&scoped=true&": +/*!*******************************************************************************************!*\ + !*** ./src/layout/components/Sidebar/Logo.vue?vue&type=template&id=6494804b&scoped=true& ***! + \*******************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_template_id_6494804b_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./Logo.vue?vue&type=template&id=6494804b&scoped=true& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/Logo.vue?vue&type=template&id=6494804b&scoped=true&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_template_id_6494804b_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Logo_vue_vue_type_template_id_6494804b_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/Logo.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/SidebarItem.vue": +/*!*******************************************************!*\ + !*** ./src/layout/components/Sidebar/SidebarItem.vue ***! + \*******************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _SidebarItem_vue_vue_type_template_id_2d2bbdc2___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./SidebarItem.vue?vue&type=template&id=2d2bbdc2& */ \"./src/layout/components/Sidebar/SidebarItem.vue?vue&type=template&id=2d2bbdc2&\");\n/* harmony import */ var _SidebarItem_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SidebarItem.vue?vue&type=script&lang=js& */ \"./src/layout/components/Sidebar/SidebarItem.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _SidebarItem_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _SidebarItem_vue_vue_type_template_id_2d2bbdc2___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _SidebarItem_vue_vue_type_template_id_2d2bbdc2___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/Sidebar/SidebarItem.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/SidebarItem.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/SidebarItem.vue?vue&type=script&lang=js&": +/*!********************************************************************************!*\ + !*** ./src/layout/components/Sidebar/SidebarItem.vue?vue&type=script&lang=js& ***! + \********************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_SidebarItem_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./SidebarItem.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/SidebarItem.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_SidebarItem_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/SidebarItem.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/SidebarItem.vue?vue&type=template&id=2d2bbdc2&": +/*!**************************************************************************************!*\ + !*** ./src/layout/components/Sidebar/SidebarItem.vue?vue&type=template&id=2d2bbdc2& ***! + \**************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_SidebarItem_vue_vue_type_template_id_2d2bbdc2___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./SidebarItem.vue?vue&type=template&id=2d2bbdc2& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/SidebarItem.vue?vue&type=template&id=2d2bbdc2&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_SidebarItem_vue_vue_type_template_id_2d2bbdc2___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_SidebarItem_vue_vue_type_template_id_2d2bbdc2___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/SidebarItem.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/index.vue": +/*!*************************************************!*\ + !*** ./src/layout/components/Sidebar/index.vue ***! + \*************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _index_vue_vue_type_template_id_33ec43fc___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.vue?vue&type=template&id=33ec43fc& */ \"./src/layout/components/Sidebar/index.vue?vue&type=template&id=33ec43fc&\");\n/* harmony import */ var _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.vue?vue&type=script&lang=js& */ \"./src/layout/components/Sidebar/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _index_vue_vue_type_template_id_33ec43fc___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _index_vue_vue_type_template_id_33ec43fc___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/Sidebar/index.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/index.vue?vue&type=script&lang=js&": +/*!**************************************************************************!*\ + !*** ./src/layout/components/Sidebar/index.vue?vue&type=script&lang=js& ***! + \**************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/Sidebar/index.vue?vue&type=template&id=33ec43fc&": +/*!********************************************************************************!*\ + !*** ./src/layout/components/Sidebar/index.vue?vue&type=template&id=33ec43fc& ***! + \********************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_33ec43fc___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=template&id=33ec43fc& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/Sidebar/index.vue?vue&type=template&id=33ec43fc&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_33ec43fc___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_33ec43fc___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/Sidebar/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/ScrollPane.vue": +/*!*******************************************************!*\ + !*** ./src/layout/components/TagsView/ScrollPane.vue ***! + \*******************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ScrollPane_vue_vue_type_template_id_be6b7bae_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ScrollPane.vue?vue&type=template&id=be6b7bae&scoped=true& */ \"./src/layout/components/TagsView/ScrollPane.vue?vue&type=template&id=be6b7bae&scoped=true&\");\n/* harmony import */ var _ScrollPane_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ScrollPane.vue?vue&type=script&lang=js& */ \"./src/layout/components/TagsView/ScrollPane.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _ScrollPane_vue_vue_type_style_index_0_id_be6b7bae_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ScrollPane.vue?vue&type=style&index=0&id=be6b7bae&lang=scss&scoped=true& */ \"./src/layout/components/TagsView/ScrollPane.vue?vue&type=style&index=0&id=be6b7bae&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(\n _ScrollPane_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _ScrollPane_vue_vue_type_template_id_be6b7bae_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _ScrollPane_vue_vue_type_template_id_be6b7bae_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"be6b7bae\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/TagsView/ScrollPane.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/TagsView/ScrollPane.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/ScrollPane.vue?vue&type=script&lang=js&": +/*!********************************************************************************!*\ + !*** ./src/layout/components/TagsView/ScrollPane.vue?vue&type=script&lang=js& ***! + \********************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./ScrollPane.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/TagsView/ScrollPane.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/TagsView/ScrollPane.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/ScrollPane.vue?vue&type=style&index=0&id=be6b7bae&lang=scss&scoped=true&": +/*!*****************************************************************************************************************!*\ + !*** ./src/layout/components/TagsView/ScrollPane.vue?vue&type=style&index=0&id=be6b7bae&lang=scss&scoped=true& ***! + \*****************************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_style_index_0_id_be6b7bae_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./ScrollPane.vue?vue&type=style&index=0&id=be6b7bae&lang=scss&scoped=true& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/TagsView/ScrollPane.vue?vue&type=style&index=0&id=be6b7bae&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_style_index_0_id_be6b7bae_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_style_index_0_id_be6b7bae_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_style_index_0_id_be6b7bae_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_style_index_0_id_be6b7bae_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/components/TagsView/ScrollPane.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/ScrollPane.vue?vue&type=template&id=be6b7bae&scoped=true&": +/*!**************************************************************************************************!*\ + !*** ./src/layout/components/TagsView/ScrollPane.vue?vue&type=template&id=be6b7bae&scoped=true& ***! + \**************************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_template_id_be6b7bae_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./ScrollPane.vue?vue&type=template&id=be6b7bae&scoped=true& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/TagsView/ScrollPane.vue?vue&type=template&id=be6b7bae&scoped=true&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_template_id_be6b7bae_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_ScrollPane_vue_vue_type_template_id_be6b7bae_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/TagsView/ScrollPane.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/index.vue": +/*!**************************************************!*\ + !*** ./src/layout/components/TagsView/index.vue ***! + \**************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _index_vue_vue_type_template_id_fac8ca64_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.vue?vue&type=template&id=fac8ca64&scoped=true& */ \"./src/layout/components/TagsView/index.vue?vue&type=template&id=fac8ca64&scoped=true&\");\n/* harmony import */ var _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.vue?vue&type=script&lang=js& */ \"./src/layout/components/TagsView/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _index_vue_vue_type_style_index_0_id_fac8ca64_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./index.vue?vue&type=style&index=0&id=fac8ca64&lang=scss&scoped=true& */ \"./src/layout/components/TagsView/index.vue?vue&type=style&index=0&id=fac8ca64&lang=scss&scoped=true&\");\n/* harmony import */ var _index_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./index.vue?vue&type=style&index=1&lang=scss& */ \"./src/layout/components/TagsView/index.vue?vue&type=style&index=1&lang=scss&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"])(\n _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _index_vue_vue_type_template_id_fac8ca64_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _index_vue_vue_type_template_id_fac8ca64_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"fac8ca64\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/components/TagsView/index.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/components/TagsView/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/index.vue?vue&type=script&lang=js&": +/*!***************************************************************************!*\ + !*** ./src/layout/components/TagsView/index.vue?vue&type=script&lang=js& ***! + \***************************************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../../../node_modules/babel-loader/lib!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/TagsView/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/components/TagsView/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/index.vue?vue&type=style&index=0&id=fac8ca64&lang=scss&scoped=true&": +/*!************************************************************************************************************!*\ + !*** ./src/layout/components/TagsView/index.vue?vue&type=style&index=0&id=fac8ca64&lang=scss&scoped=true& ***! + \************************************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_fac8ca64_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=style&index=0&id=fac8ca64&lang=scss&scoped=true& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/TagsView/index.vue?vue&type=style&index=0&id=fac8ca64&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_fac8ca64_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_fac8ca64_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_fac8ca64_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_fac8ca64_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/components/TagsView/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/index.vue?vue&type=style&index=1&lang=scss&": +/*!************************************************************************************!*\ + !*** ./src/layout/components/TagsView/index.vue?vue&type=style&index=1&lang=scss& ***! + \************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=style&index=1&lang=scss& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/TagsView/index.vue?vue&type=style&index=1&lang=scss&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_1_lang_scss___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/components/TagsView/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/TagsView/index.vue?vue&type=template&id=fac8ca64&scoped=true&": +/*!*********************************************************************************************!*\ + !*** ./src/layout/components/TagsView/index.vue?vue&type=template&id=fac8ca64&scoped=true& ***! + \*********************************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_fac8ca64_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=template&id=fac8ca64&scoped=true& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/components/TagsView/index.vue?vue&type=template&id=fac8ca64&scoped=true&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_fac8ca64_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_fac8ca64_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/components/TagsView/index.vue?"); + +/***/ }), + +/***/ "./src/layout/components/index.js": +/*!****************************************!*\ + !*** ./src/layout/components/index.js ***! + \****************************************/ +/*! exports provided: AppMain, Navbar, Settings, Sidebar, TagsView */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _AppMain__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AppMain */ \"./src/layout/components/AppMain.vue\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"AppMain\", function() { return _AppMain__WEBPACK_IMPORTED_MODULE_0__[\"default\"]; });\n\n/* harmony import */ var _Navbar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Navbar */ \"./src/layout/components/Navbar.vue\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"Navbar\", function() { return _Navbar__WEBPACK_IMPORTED_MODULE_1__[\"default\"]; });\n\n/* harmony import */ var _Settings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Settings */ \"./src/layout/components/Settings/index.vue\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"Settings\", function() { return _Settings__WEBPACK_IMPORTED_MODULE_2__[\"default\"]; });\n\n/* harmony import */ var _Sidebar_index_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./Sidebar/index.vue */ \"./src/layout/components/Sidebar/index.vue\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"Sidebar\", function() { return _Sidebar_index_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"]; });\n\n/* harmony import */ var _TagsView_index_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./TagsView/index.vue */ \"./src/layout/components/TagsView/index.vue\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"TagsView\", function() { return _TagsView_index_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"]; });\n\n\n\n\n\n\n\n//# sourceURL=webpack:///./src/layout/components/index.js?"); + +/***/ }), + +/***/ "./src/layout/index.vue": +/*!******************************!*\ + !*** ./src/layout/index.vue ***! + \******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _index_vue_vue_type_template_id_13877386_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.vue?vue&type=template&id=13877386&scoped=true& */ \"./src/layout/index.vue?vue&type=template&id=13877386&scoped=true&\");\n/* harmony import */ var _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.vue?vue&type=script&lang=js& */ \"./src/layout/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _index_vue_vue_type_style_index_0_id_13877386_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./index.vue?vue&type=style&index=0&id=13877386&lang=scss&scoped=true& */ \"./src/layout/index.vue?vue&type=style&index=0&id=13877386&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(\n _index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _index_vue_vue_type_template_id_13877386_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _index_vue_vue_type_template_id_13877386_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"13877386\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/layout/index.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./src/layout/index.vue?"); + +/***/ }), + +/***/ "./src/layout/index.vue?vue&type=script&lang=js&": +/*!*******************************************************!*\ + !*** ./src/layout/index.vue?vue&type=script&lang=js& ***! + \*******************************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/cache-loader/dist/cjs.js??ref--13-0!../../node_modules/babel-loader/lib!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=script&lang=js& */ \"./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/index.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_cache_loader_dist_cjs_js_ref_13_0_node_modules_babel_loader_lib_index_js_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[\"default\"]); \n\n//# sourceURL=webpack:///./src/layout/index.vue?"); + +/***/ }), + +/***/ "./src/layout/index.vue?vue&type=style&index=0&id=13877386&lang=scss&scoped=true&": +/*!****************************************************************************************!*\ + !*** ./src/layout/index.vue?vue&type=style&index=0&id=13877386&lang=scss&scoped=true& ***! + \****************************************************************************************/ +/*! no static exports found */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_13877386_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-style-loader??ref--9-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--9-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src??ref--9-oneOf-1-2!../../node_modules/sass-loader/dist/cjs.js??ref--9-oneOf-1-3!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=style&index=0&id=13877386&lang=scss&scoped=true& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/sass-loader/dist/cjs.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/index.vue?vue&type=style&index=0&id=13877386&lang=scss&scoped=true&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_13877386_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_13877386_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_13877386_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_9_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_9_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_9_oneOf_1_2_node_modules_sass_loader_dist_cjs_js_ref_9_oneOf_1_3_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_style_index_0_id_13877386_lang_scss_scoped_true___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack:///./src/layout/index.vue?"); + +/***/ }), + +/***/ "./src/layout/index.vue?vue&type=template&id=13877386&scoped=true&": +/*!*************************************************************************!*\ + !*** ./src/layout/index.vue?vue&type=template&id=13877386&scoped=true& ***! + \*************************************************************************/ +/*! exports provided: render, staticRenderFns */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_13877386_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/cache-loader/dist/cjs.js?{\"cacheDirectory\":\"node_modules/.cache/vue-loader\",\"cacheIdentifier\":\"9323b05c-vue-loader-template\"}!../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib??vue-loader-options!./index.vue?vue&type=template&id=13877386&scoped=true& */ \"./node_modules/cache-loader/dist/cjs.js?{\\\"cacheDirectory\\\":\\\"node_modules/.cache/vue-loader\\\",\\\"cacheIdentifier\\\":\\\"9323b05c-vue-loader-template\\\"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/layout/index.vue?vue&type=template&id=13877386&scoped=true&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_13877386_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return _node_modules_cache_loader_dist_cjs_js_cacheDirectory_node_modules_cache_vue_loader_cacheIdentifier_9323b05c_vue_loader_template_node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_cache_loader_dist_cjs_js_ref_1_0_node_modules_vue_loader_lib_index_js_vue_loader_options_index_vue_vue_type_template_id_13877386_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"]; });\n\n\n\n//# sourceURL=webpack:///./src/layout/index.vue?"); + +/***/ }), + +/***/ "./src/layout/mixin/ResizeHandler.js": +/*!*******************************************!*\ + !*** ./src/layout/mixin/ResizeHandler.js ***! + \*******************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _store__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @/store */ \"./src/store/index.js\");\n\nvar _document = document,\n body = _document.body;\nvar WIDTH = 992; // refer to Bootstrap's responsive design\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n watch: {\n $route: function $route(route) {\n if (this.device === 'mobile' && this.sidebar.opened) {\n _store__WEBPACK_IMPORTED_MODULE_0__[\"default\"].dispatch('app/closeSideBar', {\n withoutAnimation: false\n });\n }\n }\n },\n beforeMount: function beforeMount() {\n window.addEventListener('resize', this.$_resizeHandler);\n },\n beforeDestroy: function beforeDestroy() {\n window.removeEventListener('resize', this.$_resizeHandler);\n },\n mounted: function mounted() {\n var isMobile = this.$_isMobile();\n\n if (isMobile) {\n _store__WEBPACK_IMPORTED_MODULE_0__[\"default\"].dispatch('app/toggleDevice', 'mobile');\n _store__WEBPACK_IMPORTED_MODULE_0__[\"default\"].dispatch('app/closeSideBar', {\n withoutAnimation: true\n });\n }\n },\n methods: {\n // use $_ for mixins properties\n // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential\n $_isMobile: function $_isMobile() {\n var rect = body.getBoundingClientRect();\n return rect.width - 1 < WIDTH;\n },\n $_resizeHandler: function $_resizeHandler() {\n if (!document.hidden) {\n var isMobile = this.$_isMobile();\n _store__WEBPACK_IMPORTED_MODULE_0__[\"default\"].dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop');\n\n if (isMobile) {\n _store__WEBPACK_IMPORTED_MODULE_0__[\"default\"].dispatch('app/closeSideBar', {\n withoutAnimation: true\n });\n }\n }\n }\n }\n});\n\n//# sourceURL=webpack:///./src/layout/mixin/ResizeHandler.js?"); + +/***/ }), + +/***/ "./src/main.js": +/*!*********************!*\ + !*** ./src/main.js ***! + \*********************/ +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es6_object_keys__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es6.object.keys */ \"./node_modules/core-js/modules/es6.object.keys.js\");\n/* harmony import */ var core_js_modules_es6_object_keys__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_object_keys__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/web.dom.iterable */ \"./node_modules/core-js/modules/web.dom.iterable.js\");\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_array_iterator_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node_modules/core-js/modules/es6.array.iterator.js */ \"./node_modules/core-js/modules/es6.array.iterator.js\");\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_array_iterator_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_array_iterator_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_promise_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./node_modules/core-js/modules/es6.promise.js */ \"./node_modules/core-js/modules/es6.promise.js\");\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_promise_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_promise_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_object_assign_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./node_modules/core-js/modules/es6.object.assign.js */ \"./node_modules/core-js/modules/es6.object.assign.js\");\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_object_assign_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es6_object_assign_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es7_promise_finally_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./node_modules/core-js/modules/es7.promise.finally.js */ \"./node_modules/core-js/modules/es7.promise.finally.js\");\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es7_promise_finally_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_core_js_modules_es7_promise_finally_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.runtime.esm.js\");\n/* harmony import */ var js_cookie__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! js-cookie */ \"./node_modules/js-cookie/src/js.cookie.js\");\n/* harmony import */ var js_cookie__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(js_cookie__WEBPACK_IMPORTED_MODULE_7__);\n/* harmony import */ var normalize_css_normalize_css__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! normalize.css/normalize.css */ \"./node_modules/normalize.css/normalize.css\");\n/* harmony import */ var normalize_css_normalize_css__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(normalize_css_normalize_css__WEBPACK_IMPORTED_MODULE_8__);\n/* harmony import */ var element_ui__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! element-ui */ \"./node_modules/element-ui/lib/element-ui.common.js\");\n/* harmony import */ var element_ui__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(element_ui__WEBPACK_IMPORTED_MODULE_9__);\n/* harmony import */ var _styles_element_variables_scss__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./styles/element-variables.scss */ \"./src/styles/element-variables.scss\");\n/* harmony import */ var _styles_element_variables_scss__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(_styles_element_variables_scss__WEBPACK_IMPORTED_MODULE_10__);\n/* harmony import */ var _styles_index_scss__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @/styles/index.scss */ \"./src/styles/index.scss\");\n/* harmony import */ var _styles_index_scss__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(_styles_index_scss__WEBPACK_IMPORTED_MODULE_11__);\n/* harmony import */ var _App__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./App */ \"./src/App.vue\");\n/* harmony import */ var _store__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./store */ \"./src/store/index.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./router */ \"./src/router/index.js\");\n/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./icons */ \"./src/icons/index.js\");\n/* harmony import */ var _permission__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./permission */ \"./src/permission.js\");\n/* harmony import */ var _utils_error_log__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./utils/error-log */ \"./src/utils/error-log.js\");\n/* harmony import */ var _filters__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./filters */ \"./src/filters/index.js\");\n\n\n\n\n\n\n\n\n // a modern alternative to CSS resets\n\n\n\n\n\n\n\n // icon\n\n // permission control\n\n // error log\n\n // Element UI\n\nvue__WEBPACK_IMPORTED_MODULE_6__[\"default\"].use(element_ui__WEBPACK_IMPORTED_MODULE_9___default.a, {\n size: js_cookie__WEBPACK_IMPORTED_MODULE_7___default.a.get('size') || 'medium' // set element-ui default size\n\n}); // 注册全局过滤器\n\nObject.keys(_filters__WEBPACK_IMPORTED_MODULE_18__).forEach(function (key) {\n vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"].filter(key, _filters__WEBPACK_IMPORTED_MODULE_18__[key]);\n});\nvue__WEBPACK_IMPORTED_MODULE_6__[\"default\"].config.productionTip = false; // 环境标识\n\nvue__WEBPACK_IMPORTED_MODULE_6__[\"default\"].prototype.$demo = \"demo\" === 'demo';\nnew vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"]({\n el: '#app',\n router: _router__WEBPACK_IMPORTED_MODULE_14__[\"default\"],\n store: _store__WEBPACK_IMPORTED_MODULE_13__[\"default\"],\n render: function render(h) {\n return h(_App__WEBPACK_IMPORTED_MODULE_12__[\"default\"]);\n }\n});\n\n//# sourceURL=webpack:///./src/main.js?"); + +/***/ }), + +/***/ "./src/permission.js": +/*!***************************!*\ + !*** ./src/permission.js ***! + \***************************/ +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime-corejs2/helpers/esm/objectSpread2.js */ \"./node_modules/@babel/runtime-corejs2/helpers/esm/objectSpread2.js\");\n/* harmony import */ var regenerator_runtime_runtime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! regenerator-runtime/runtime */ \"./node_modules/regenerator-runtime/runtime.js\");\n/* harmony import */ var regenerator_runtime_runtime__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(regenerator_runtime_runtime__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node_modules/@babel/runtime-corejs2/helpers/esm/asyncToGenerator.js */ \"./node_modules/@babel/runtime-corejs2/helpers/esm/asyncToGenerator.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./router */ \"./src/router/index.js\");\n/* harmony import */ var _store__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./store */ \"./src/store/index.js\");\n/* harmony import */ var element_ui__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! element-ui */ \"./node_modules/element-ui/lib/element-ui.common.js\");\n/* harmony import */ var element_ui__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(element_ui__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var nprogress__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! nprogress */ \"./node_modules/nprogress/nprogress.js\");\n/* harmony import */ var nprogress__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(nprogress__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var nprogress_nprogress_css__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! nprogress/nprogress.css */ \"./node_modules/nprogress/nprogress.css\");\n/* harmony import */ var nprogress_nprogress_css__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(nprogress_nprogress_css__WEBPACK_IMPORTED_MODULE_7__);\n/* harmony import */ var _utils_auth__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @/utils/auth */ \"./src/utils/auth.js\");\n/* harmony import */ var _utils_get_page_title__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @/utils/get-page-title */ \"./src/utils/get-page-title.js\");\n\n\n\n\n\n\n // progress bar\n\n // progress bar style\n\n // get token from cookie\n\n\nnprogress__WEBPACK_IMPORTED_MODULE_6___default.a.configure({\n showSpinner: false\n}); // NProgress Configuration\n\nvar whiteList = ['/login', '/register']; // no redirect whitelist\n\n_router__WEBPACK_IMPORTED_MODULE_3__[\"default\"].beforeEach( /*#__PURE__*/function () {\n var _ref = Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])( /*#__PURE__*/regeneratorRuntime.mark(function _callee(to, from, next) {\n var siteData, hasToken, hasRoles, _yield$store$dispatch, roles, accessRoutes;\n\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n // start progress bar\n nprogress__WEBPACK_IMPORTED_MODULE_6___default.a.start(); // 获取网站基本信息\n\n siteData = _store__WEBPACK_IMPORTED_MODULE_4__[\"default\"].getters.siteData;\n\n if (siteData.siteName) {\n _context.next = 6;\n break;\n }\n\n _context.next = 5;\n return _store__WEBPACK_IMPORTED_MODULE_4__[\"default\"].dispatch('settings/getSite');\n\n case 5:\n siteData = _context.sent;\n\n case 6:\n // 页面标题\n document.title = Object(_utils_get_page_title__WEBPACK_IMPORTED_MODULE_9__[\"default\"])(siteData.siteName, to.meta.title); // 本地token\n\n hasToken = Object(_utils_auth__WEBPACK_IMPORTED_MODULE_8__[\"getToken\"])();\n\n if (!hasToken) {\n _context.next = 40;\n break;\n }\n\n if (!(to.path === '/login')) {\n _context.next = 14;\n break;\n }\n\n next({\n path: '/'\n });\n nprogress__WEBPACK_IMPORTED_MODULE_6___default.a.done();\n _context.next = 38;\n break;\n\n case 14:\n hasRoles = _store__WEBPACK_IMPORTED_MODULE_4__[\"default\"].getters.roles && _store__WEBPACK_IMPORTED_MODULE_4__[\"default\"].getters.roles.length > 0;\n\n if (!hasRoles) {\n _context.next = 19;\n break;\n }\n\n next();\n _context.next = 38;\n break;\n\n case 19:\n _context.prev = 19;\n _context.next = 22;\n return _store__WEBPACK_IMPORTED_MODULE_4__[\"default\"].dispatch('user/getInfo');\n\n case 22:\n _yield$store$dispatch = _context.sent;\n roles = _yield$store$dispatch.roles;\n _context.next = 26;\n return _store__WEBPACK_IMPORTED_MODULE_4__[\"default\"].dispatch('permission/generateRoutes', roles);\n\n case 26:\n accessRoutes = _context.sent;\n _router__WEBPACK_IMPORTED_MODULE_3__[\"default\"].addRoutes(accessRoutes);\n next(Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])({}, to), {}, {\n replace: true\n }));\n _context.next = 38;\n break;\n\n case 31:\n _context.prev = 31;\n _context.t0 = _context[\"catch\"](19);\n _context.next = 35;\n return _store__WEBPACK_IMPORTED_MODULE_4__[\"default\"].dispatch('user/resetToken');\n\n case 35:\n element_ui__WEBPACK_IMPORTED_MODULE_5__[\"Message\"].error(_context.t0 || 'Has Error');\n next(\"/login?redirect=\".concat(to.path));\n nprogress__WEBPACK_IMPORTED_MODULE_6___default.a.done();\n\n case 38:\n _context.next = 41;\n break;\n\n case 40:\n // 排除白名单\n if (whiteList.indexOf(to.path) !== -1) {\n next();\n } else {\n // 跳转到登录页面\n next(\"/login?redirect=\".concat(to.path));\n nprogress__WEBPACK_IMPORTED_MODULE_6___default.a.done();\n }\n\n case 41:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[19, 31]]);\n }));\n\n return function (_x, _x2, _x3) {\n return _ref.apply(this, arguments);\n };\n}());\n_router__WEBPACK_IMPORTED_MODULE_3__[\"default\"].afterEach(function () {\n // finish progress bar\n nprogress__WEBPACK_IMPORTED_MODULE_6___default.a.done();\n});\n\n//# sourceURL=webpack:///./src/permission.js?"); + +/***/ }), + +/***/ "./src/router/index.js": +/*!*****************************!*\ + !*** ./src/router/index.js ***! + \*****************************/ +/*! exports provided: constantRoutes, asyncRoutes, resetRouter, default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"constantRoutes\", function() { return constantRoutes; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"asyncRoutes\", function() { return asyncRoutes; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"resetRouter\", function() { return resetRouter; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.runtime.esm.js\");\n/* harmony import */ var vue_router__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vue-router */ \"./node_modules/vue-router/dist/vue-router.esm.js\");\n/* harmony import */ var _layout__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @/layout */ \"./src/layout/index.vue\");\n/* harmony import */ var _views_login_components_LoginLayout__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @/views/login/components/LoginLayout */ \"./src/views/login/components/LoginLayout.vue\");\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(vue_router__WEBPACK_IMPORTED_MODULE_1__[\"default\"]); // 主要框架\n\n // 登录框架\n\n\nvar constantRoutes = [{\n path: '/redirect',\n component: _layout__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n hidden: true,\n children: [{\n path: '/redirect/:path*',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 27).then(__webpack_require__.bind(null, /*! @/views/redirect/index */ \"./src/views/redirect/index.vue\"));\n }\n }]\n}, {\n path: '/login',\n component: _views_login_components_LoginLayout__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n hidden: true,\n children: [{\n path: '/login',\n name: 'Login',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 25).then(__webpack_require__.bind(null, /*! @/views/login/index */ \"./src/views/login/index.vue\"));\n },\n hidden: true\n }, {\n path: '/register',\n name: 'Register',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 26).then(__webpack_require__.bind(null, /*! @/views/login/register */ \"./src/views/login/register.vue\"));\n },\n hidden: true\n }]\n}, {\n path: '/404',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 12).then(__webpack_require__.bind(null, /*! @/views/error-page/404 */ \"./src/views/error-page/404.vue\"));\n },\n hidden: true\n}, {\n path: '/401',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 16).then(__webpack_require__.bind(null, /*! @/views/error-page/401 */ \"./src/views/error-page/401.vue\"));\n },\n hidden: true\n}, {\n path: '/',\n component: _layout__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n redirect: '/dashboard',\n children: [{\n path: 'dashboard',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 11).then(__webpack_require__.bind(null, /*! @/views/dashboard/index */ \"./src/views/dashboard/index.vue\"));\n },\n name: 'Dashboard',\n meta: {\n title: '控制台',\n icon: 'dashboard',\n affix: true\n }\n }, {\n path: 'qu/view/:id',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 18).then(__webpack_require__.bind(null, /*! @/views/qu/qu/view */ \"./src/views/qu/qu/view.vue\"));\n },\n name: 'ViewQu',\n meta: {\n title: '题目详情',\n noCache: true,\n activeMenu: '/manage/qu'\n },\n hidden: true\n }]\n}, {\n path: '/profile',\n component: _layout__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n redirect: '/profile/index',\n hidden: true,\n children: [{\n path: 'index',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 4).then(__webpack_require__.bind(null, /*! @/views/profile/index */ \"./src/views/profile/index.vue\"));\n },\n name: 'Profile',\n meta: {\n title: '个人资料',\n icon: 'user',\n noCache: true\n }\n }]\n}];\n/**\n * asyncRoutes\n * the routes that need to be dynamically loaded based on user roles\n */\n\nvar asyncRoutes = [{\n path: '/exam/start/:id',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 5).then(__webpack_require__.bind(null, /*! @/views/paper/exam/exam */ \"./src/views/paper/exam/exam.vue\"));\n },\n name: 'StartExam',\n meta: {\n title: '开始考试'\n },\n hidden: true\n}, {\n path: '/my',\n component: _layout__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n redirect: '/my/exam',\n name: 'Online',\n meta: {\n title: '在线考试',\n icon: 'list',\n roles: ['student', 'sa']\n },\n children: [{\n path: 'exam',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(19)]).then(__webpack_require__.bind(null, /*! @/views/paper/exam/list */ \"./src/views/paper/exam/list.vue\"));\n },\n name: 'ExamOnline',\n meta: {\n title: '在线考试',\n noCache: true,\n icon: 'guide'\n }\n }, {\n path: 'exam/prepare/:examId',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 13).then(__webpack_require__.bind(null, /*! @/views/paper/exam/preview */ \"./src/views/paper/exam/preview.vue\"));\n },\n name: 'PreExam',\n meta: {\n title: '准备考试',\n noCache: true,\n activeMenu: '/my/exam'\n },\n hidden: true\n }, {\n path: 'exam/result/:id',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 17).then(__webpack_require__.bind(null, /*! @/views/paper/exam/result */ \"./src/views/paper/exam/result.vue\"));\n },\n name: 'ShowExam',\n meta: {\n title: '考试结果',\n noCache: true,\n activeMenu: '/online/exam'\n },\n hidden: true\n }, {\n path: 'exam/records',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(7)]).then(__webpack_require__.bind(null, /*! @/views/user/exam/my */ \"./src/views/user/exam/my.vue\"));\n },\n name: 'ListMyExam',\n meta: {\n title: '我的成绩',\n noCache: true,\n icon: 'results'\n }\n }, {\n path: 'book/list/:examId',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(24)]).then(__webpack_require__.bind(null, /*! @/views/user/book */ \"./src/views/user/book/index.vue\"));\n },\n name: 'BookList',\n meta: {\n title: '考试错题',\n noCache: true,\n activeMenu: '/my/exam/records'\n },\n hidden: true\n }, {\n path: 'book/training/:examId',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 15).then(__webpack_require__.bind(null, /*! @/views/user/book/train */ \"./src/views/user/book/train.vue\"));\n },\n name: 'BookTraining',\n meta: {\n title: '错题训练',\n noCache: true,\n activeMenu: '/my/exam/records'\n },\n hidden: true\n }]\n}, {\n path: '/exam',\n component: _layout__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n redirect: '/exam/repo',\n name: 'Manage',\n meta: {\n title: '考试管理',\n icon: 'example',\n roles: ['sa', 'teacher']\n },\n children: [{\n path: 'repo',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(21)]).then(__webpack_require__.bind(null, /*! @/views/qu/repo */ \"./src/views/qu/repo/index.vue\"));\n },\n name: 'ListRepo',\n meta: {\n title: '题库管理',\n noCache: true,\n icon: 'repo'\n }\n }, {\n path: 'repo/add',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 3).then(__webpack_require__.bind(null, /*! @/views/qu/repo/form */ \"./src/views/qu/repo/form.vue\"));\n },\n name: 'AddRepo',\n meta: {\n title: '添加题库',\n noCache: true,\n activeMenu: '/exam/repo'\n },\n hidden: true\n }, {\n path: 'repo/update/:id',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 3).then(__webpack_require__.bind(null, /*! @/views/qu/repo/form */ \"./src/views/qu/repo/form.vue\"));\n },\n name: 'UpdateRepo',\n meta: {\n title: '题库详情',\n noCache: true,\n activeMenu: '/exam/repo'\n },\n hidden: true\n }, {\n path: 'qu',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(14)]).then(__webpack_require__.bind(null, /*! @/views/qu/qu */ \"./src/views/qu/qu/index.vue\"));\n },\n name: 'ListQu',\n meta: {\n title: '试题管理',\n noCache: true,\n icon: 'support'\n }\n }, {\n path: 'qu/add',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(0)]).then(__webpack_require__.bind(null, /*! @/views/qu/qu/form */ \"./src/views/qu/qu/form.vue\"));\n },\n name: 'AddQu',\n meta: {\n title: '添加试题',\n noCache: true,\n activeMenu: '/exam/qu'\n },\n hidden: true\n }, {\n path: 'qu/update/:id',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(0)]).then(__webpack_require__.bind(null, /*! @/views/qu/qu/form */ \"./src/views/qu/qu/form.vue\"));\n },\n name: 'UpdateQu',\n meta: {\n title: '修改试题',\n noCache: true,\n activeMenu: '/exam/qu'\n },\n hidden: true\n }, {\n path: 'exam',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(20)]).then(__webpack_require__.bind(null, /*! @/views/exam/exam */ \"./src/views/exam/exam/index.vue\"));\n },\n name: 'ListExam',\n meta: {\n title: '考试管理',\n noCache: true,\n icon: 'log'\n }\n }, {\n path: 'exam/add',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(2)]).then(__webpack_require__.bind(null, /*! @/views/exam/exam/form */ \"./src/views/exam/exam/form.vue\"));\n },\n name: 'AddExam',\n meta: {\n title: '添加考试',\n noCache: true,\n activeMenu: '/exam/exam'\n },\n hidden: true\n }, {\n path: 'exam/update/:id',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(2)]).then(__webpack_require__.bind(null, /*! @/views/exam/exam/form */ \"./src/views/exam/exam/form.vue\"));\n },\n name: 'UpdateExam',\n meta: {\n title: '修改考试',\n noCache: true,\n activeMenu: '/exam/exam'\n },\n hidden: true\n }, {\n path: 'exam/users/:examId',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(6)]).then(__webpack_require__.bind(null, /*! @/views/user/exam */ \"./src/views/user/exam/index.vue\"));\n },\n name: 'ListExamUser',\n meta: {\n title: '考试人员',\n noCache: true,\n activeMenu: '/exam/exam'\n },\n hidden: true\n }, {\n path: 'exam/paper/:examId',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(1), __webpack_require__.e(9)]).then(__webpack_require__.bind(null, /*! @/views/paper/paper */ \"./src/views/paper/paper/index.vue\"));\n },\n name: 'ListPaper',\n meta: {\n title: '考试记录',\n noCache: true,\n activeMenu: '/exam/exam'\n },\n hidden: true\n }]\n}, {\n path: '/sys',\n component: _layout__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n redirect: '/sys/config',\n name: 'Sys',\n meta: {\n title: '系统设置',\n icon: 'configure',\n roles: ['sa']\n },\n children: [{\n path: 'config',\n component: function component() {\n return __webpack_require__.e(/*! import() */ 8).then(__webpack_require__.bind(null, /*! @/views/sys/config */ \"./src/views/sys/config/index.vue\"));\n },\n name: 'SysConfig',\n meta: {\n title: '系统配置',\n icon: 'theme'\n }\n }, {\n path: 'depart',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(22)]).then(__webpack_require__.bind(null, /*! @/views/sys/depart */ \"./src/views/sys/depart/index.vue\"));\n },\n name: 'SysDepart',\n meta: {\n title: '部门管理',\n icon: 'tree'\n }\n }, {\n path: 'role',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(23)]).then(__webpack_require__.bind(null, /*! @/views/sys/role */ \"./src/views/sys/role/index.vue\"));\n },\n name: 'SysRole',\n meta: {\n title: '角色管理',\n icon: 'role'\n }\n }, {\n path: 'user',\n component: function component() {\n return Promise.all(/*! import() */[__webpack_require__.e(\"chunk-commons\"), __webpack_require__.e(1), __webpack_require__.e(10)]).then(__webpack_require__.bind(null, /*! @/views/sys/user */ \"./src/views/sys/user/index.vue\"));\n },\n name: 'SysUser',\n meta: {\n title: '用户管理',\n icon: 'admin'\n }\n }]\n}, // 404 page must be placed at the end !!!\n{\n path: '*',\n redirect: '/dashboard',\n hidden: true\n}];\n\nvar createRouter = function createRouter() {\n return new vue_router__WEBPACK_IMPORTED_MODULE_1__[\"default\"]({\n // mode: 'history', // require service support\n scrollBehavior: function scrollBehavior() {\n return {\n y: 0\n };\n },\n routes: constantRoutes\n });\n};\n\nvar router = createRouter();\nfunction resetRouter() {\n var newRouter = createRouter();\n router.matcher = newRouter.matcher; // reset router\n}\n/* harmony default export */ __webpack_exports__[\"default\"] = (router);\n\n//# sourceURL=webpack:///./src/router/index.js?"); + +/***/ }), + +/***/ "./src/settings.js": +/*!*************************!*\ + !*** ./src/settings.js ***! + \*************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +eval("module.exports = {\n title: '云帆考试培训系统',\n\n /**\n * @type {boolean} true | false\n * @description Whether show the settings right-panel\n */\n showSettings: false,\n\n /**\n * @type {boolean} true | false\n * @description Whether need tagsView\n */\n tagsView: true,\n\n /**\n * @type {boolean} true | false\n * @description Whether fix the header\n */\n fixedHeader: false,\n\n /**\n * @type {boolean} true | false\n * @description Whether show the logo in sidebar\n */\n sidebarLogo: true,\n\n /**\n * @type {string | array} 'production' | ['production', 'development']\n * @description Need show err logs component.\n * The default is only used in the production env\n * If you want to also use it in dev, you can pass ['production', 'development']\n */\n errorLog: 'production'\n};\n\n//# sourceURL=webpack:///./src/settings.js?"); + +/***/ }), + +/***/ "./src/store/getters.js": +/*!******************************!*\ + !*** ./src/store/getters.js ***! + \******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es6_function_name__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es6.function.name */ \"./node_modules/core-js/modules/es6.function.name.js\");\n/* harmony import */ var core_js_modules_es6_function_name__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_function_name__WEBPACK_IMPORTED_MODULE_0__);\n\nvar getters = {\n sidebar: function sidebar(state) {\n return state.app.sidebar;\n },\n size: function size(state) {\n return state.app.size;\n },\n device: function device(state) {\n return state.app.device;\n },\n visitedViews: function visitedViews(state) {\n return state.tagsView.visitedViews;\n },\n cachedViews: function cachedViews(state) {\n return state.tagsView.cachedViews;\n },\n token: function token(state) {\n return state.user.token;\n },\n avatar: function avatar(state) {\n return state.user.avatar;\n },\n userId: function userId(state) {\n return state.user.userId;\n },\n name: function name(state) {\n return state.user.name;\n },\n realName: function realName(state) {\n return state.user.realName;\n },\n introduction: function introduction(state) {\n return state.user.introduction;\n },\n roles: function roles(state) {\n return state.user.roles;\n },\n permission_routes: function permission_routes(state) {\n return state.permission.routes;\n },\n errorLogs: function errorLogs(state) {\n return state.errorLog.logs;\n },\n siteData: function siteData(state) {\n return state.settings.siteData;\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (getters);\n\n//# sourceURL=webpack:///./src/store/getters.js?"); + +/***/ }), + +/***/ "./src/store/index.js": +/*!****************************!*\ + !*** ./src/store/index.js ***! + \****************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es6_regexp_replace__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es6.regexp.replace */ \"./node_modules/core-js/modules/es6.regexp.replace.js\");\n/* harmony import */ var core_js_modules_es6_regexp_replace__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_regexp_replace__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/web.dom.iterable */ \"./node_modules/core-js/modules/web.dom.iterable.js\");\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.runtime.esm.js\");\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _getters__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./getters */ \"./src/store/getters.js\");\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].use(vuex__WEBPACK_IMPORTED_MODULE_3__[\"default\"]); // https://webpack.js.org/guides/dependency-management/#requirecontext\n\nvar modulesFiles = __webpack_require__(\"./src/store/modules sync recursive \\\\.js$\"); // you do not need `import app from './modules/app'`\n// it will auto require all vuex module from modules file\n\n\nvar modules = modulesFiles.keys().reduce(function (modules, modulePath) {\n // set './app.js' => 'app'\n var moduleName = modulePath.replace(/^\\.\\/(.*)\\.\\w+$/, '$1');\n var value = modulesFiles(modulePath);\n modules[moduleName] = value.default;\n return modules;\n}, {});\nvar store = new vuex__WEBPACK_IMPORTED_MODULE_3__[\"default\"].Store({\n modules: modules,\n getters: _getters__WEBPACK_IMPORTED_MODULE_4__[\"default\"]\n});\n/* harmony default export */ __webpack_exports__[\"default\"] = (store);\n\n//# sourceURL=webpack:///./src/store/index.js?"); + +/***/ }), + +/***/ "./src/store/modules sync recursive \\.js$": +/*!**************************************!*\ + !*** ./src/store/modules sync \.js$ ***! + \**************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("var map = {\n\t\"./app.js\": \"./src/store/modules/app.js\",\n\t\"./errorLog.js\": \"./src/store/modules/errorLog.js\",\n\t\"./permission.js\": \"./src/store/modules/permission.js\",\n\t\"./settings.js\": \"./src/store/modules/settings.js\",\n\t\"./tagsView.js\": \"./src/store/modules/tagsView.js\",\n\t\"./user.js\": \"./src/store/modules/user.js\"\n};\n\n\nfunction webpackContext(req) {\n\tvar id = webpackContextResolve(req);\n\treturn __webpack_require__(id);\n}\nfunction webpackContextResolve(req) {\n\tif(!__webpack_require__.o(map, req)) {\n\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\te.code = 'MODULE_NOT_FOUND';\n\t\tthrow e;\n\t}\n\treturn map[req];\n}\nwebpackContext.keys = function webpackContextKeys() {\n\treturn Object.keys(map);\n};\nwebpackContext.resolve = webpackContextResolve;\nmodule.exports = webpackContext;\nwebpackContext.id = \"./src/store/modules sync recursive \\\\.js$\";\n\n//# sourceURL=webpack:///./src/store/modules_sync_\\.js$?"); + +/***/ }), + +/***/ "./src/store/modules/app.js": +/*!**********************************!*\ + !*** ./src/store/modules/app.js ***! + \**********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var js_cookie__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! js-cookie */ \"./node_modules/js-cookie/src/js.cookie.js\");\n/* harmony import */ var js_cookie__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(js_cookie__WEBPACK_IMPORTED_MODULE_0__);\n\nvar state = {\n sidebar: {\n opened: js_cookie__WEBPACK_IMPORTED_MODULE_0___default.a.get('sidebarStatus') ? !!+js_cookie__WEBPACK_IMPORTED_MODULE_0___default.a.get('sidebarStatus') : true,\n withoutAnimation: false\n },\n device: 'desktop',\n size: js_cookie__WEBPACK_IMPORTED_MODULE_0___default.a.get('size') || 'medium'\n};\nvar mutations = {\n TOGGLE_SIDEBAR: function TOGGLE_SIDEBAR(state) {\n state.sidebar.opened = !state.sidebar.opened;\n state.sidebar.withoutAnimation = false;\n\n if (state.sidebar.opened) {\n js_cookie__WEBPACK_IMPORTED_MODULE_0___default.a.set('sidebarStatus', 1);\n } else {\n js_cookie__WEBPACK_IMPORTED_MODULE_0___default.a.set('sidebarStatus', 0);\n }\n },\n CLOSE_SIDEBAR: function CLOSE_SIDEBAR(state, withoutAnimation) {\n js_cookie__WEBPACK_IMPORTED_MODULE_0___default.a.set('sidebarStatus', 0);\n state.sidebar.opened = false;\n state.sidebar.withoutAnimation = withoutAnimation;\n },\n TOGGLE_DEVICE: function TOGGLE_DEVICE(state, device) {\n state.device = device;\n },\n SET_SIZE: function SET_SIZE(state, size) {\n state.size = size;\n js_cookie__WEBPACK_IMPORTED_MODULE_0___default.a.set('size', size);\n }\n};\nvar actions = {\n toggleSideBar: function toggleSideBar(_ref) {\n var commit = _ref.commit;\n commit('TOGGLE_SIDEBAR');\n },\n closeSideBar: function closeSideBar(_ref2, _ref3) {\n var commit = _ref2.commit;\n var withoutAnimation = _ref3.withoutAnimation;\n commit('CLOSE_SIDEBAR', withoutAnimation);\n },\n toggleDevice: function toggleDevice(_ref4, device) {\n var commit = _ref4.commit;\n commit('TOGGLE_DEVICE', device);\n },\n setSize: function setSize(_ref5, size) {\n var commit = _ref5.commit;\n commit('SET_SIZE', size);\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n namespaced: true,\n state: state,\n mutations: mutations,\n actions: actions\n});\n\n//# sourceURL=webpack:///./src/store/modules/app.js?"); + +/***/ }), + +/***/ "./src/store/modules/errorLog.js": +/*!***************************************!*\ + !*** ./src/store/modules/errorLog.js ***! + \***************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\nvar state = {\n logs: []\n};\nvar mutations = {\n ADD_ERROR_LOG: function ADD_ERROR_LOG(state, log) {\n state.logs.push(log);\n },\n CLEAR_ERROR_LOG: function CLEAR_ERROR_LOG(state) {\n state.logs.splice(0);\n }\n};\nvar actions = {\n addErrorLog: function addErrorLog(_ref, log) {\n var commit = _ref.commit;\n commit('ADD_ERROR_LOG', log);\n },\n clearErrorLog: function clearErrorLog(_ref2) {\n var commit = _ref2.commit;\n commit('CLEAR_ERROR_LOG');\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n namespaced: true,\n state: state,\n mutations: mutations,\n actions: actions\n});\n\n//# sourceURL=webpack:///./src/store/modules/errorLog.js?"); + +/***/ }), + +/***/ "./src/store/modules/permission.js": +/*!*****************************************!*\ + !*** ./src/store/modules/permission.js ***! + \*****************************************/ +/*! exports provided: filterAsyncRoutes, default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"filterAsyncRoutes\", function() { return filterAsyncRoutes; });\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime-corejs2/helpers/esm/objectSpread2.js */ \"./node_modules/@babel/runtime-corejs2/helpers/esm/objectSpread2.js\");\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/web.dom.iterable */ \"./node_modules/core-js/modules/web.dom.iterable.js\");\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var core_js_modules_es7_array_includes__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/es7.array.includes */ \"./node_modules/core-js/modules/es7.array.includes.js\");\n/* harmony import */ var core_js_modules_es7_array_includes__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es7_array_includes__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var core_js_modules_es6_string_includes__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! core-js/modules/es6.string.includes */ \"./node_modules/core-js/modules/es6.string.includes.js\");\n/* harmony import */ var core_js_modules_es6_string_includes__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_string_includes__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @/router */ \"./src/router/index.js\");\n\n\n\n\n\n/**\n * Use meta.role to determine if the current user has permission\n * @param roles\n * @param route\n */\n\nfunction hasPermission(roles, route) {\n if (route.meta && route.meta.roles) {\n return roles.some(function (role) {\n return route.meta.roles.includes(role);\n });\n } else {\n return true;\n }\n}\n/**\n * Filter asynchronous routing tables by recursion\n * @param routes asyncRoutes\n * @param roles\n */\n\n\nfunction filterAsyncRoutes(routes, roles) {\n var res = [];\n routes.forEach(function (route) {\n var tmp = Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])({}, route);\n\n if (hasPermission(roles, tmp)) {\n if (tmp.children) {\n tmp.children = filterAsyncRoutes(tmp.children, roles);\n }\n\n res.push(tmp);\n }\n });\n return res;\n}\nvar state = {\n routes: [],\n addRoutes: []\n};\nvar mutations = {\n SET_ROUTES: function SET_ROUTES(state, routes) {\n state.addRoutes = routes;\n state.routes = _router__WEBPACK_IMPORTED_MODULE_4__[\"constantRoutes\"].concat(routes);\n }\n};\nvar actions = {\n generateRoutes: function generateRoutes(_ref, roles) {\n var commit = _ref.commit;\n return new Promise(function (resolve) {\n var accessedRoutes = filterAsyncRoutes(_router__WEBPACK_IMPORTED_MODULE_4__[\"asyncRoutes\"], roles);\n commit('SET_ROUTES', accessedRoutes);\n resolve(accessedRoutes);\n });\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n namespaced: true,\n state: state,\n mutations: mutations,\n actions: actions\n});\n\n//# sourceURL=webpack:///./src/store/modules/permission.js?"); + +/***/ }), + +/***/ "./src/store/modules/settings.js": +/*!***************************************!*\ + !*** ./src/store/modules/settings.js ***! + \***************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _styles_element_variables_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @/styles/element-variables.scss */ \"./src/styles/element-variables.scss\");\n/* harmony import */ var _styles_element_variables_scss__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_styles_element_variables_scss__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _settings__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @/settings */ \"./src/settings.js\");\n/* harmony import */ var _settings__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_settings__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _api_sys_config_config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @/api/sys/config/config */ \"./src/api/sys/config/config.js\");\n\n\n\nvar showSettings = _settings__WEBPACK_IMPORTED_MODULE_1___default.a.showSettings,\n tagsView = _settings__WEBPACK_IMPORTED_MODULE_1___default.a.tagsView,\n fixedHeader = _settings__WEBPACK_IMPORTED_MODULE_1___default.a.fixedHeader,\n sidebarLogo = _settings__WEBPACK_IMPORTED_MODULE_1___default.a.sidebarLogo;\nvar state = {\n theme: _styles_element_variables_scss__WEBPACK_IMPORTED_MODULE_0___default.a.theme,\n showSettings: showSettings,\n tagsView: tagsView,\n fixedHeader: fixedHeader,\n sidebarLogo: sidebarLogo,\n siteData: {}\n};\nvar mutations = {\n CHANGE_SETTING: function CHANGE_SETTING(state, _ref) {\n var key = _ref.key,\n value = _ref.value;\n\n if (state.hasOwnProperty(key)) {\n state[key] = value;\n }\n },\n SET_SITE_DATA: function SET_SITE_DATA(state, siteData) {\n state.siteData = siteData;\n }\n};\nvar actions = {\n changeSetting: function changeSetting(_ref2, data) {\n var commit = _ref2.commit;\n commit('CHANGE_SETTING', data);\n },\n // 获取网站配置信息\n getSite: function getSite(_ref3) {\n var commit = _ref3.commit;\n return new Promise(function (resolve, reject) {\n Object(_api_sys_config_config__WEBPACK_IMPORTED_MODULE_2__[\"fetchDetail\"])({}).then(function (response) {\n var data = response.data;\n commit('SET_SITE_DATA', data);\n resolve(data);\n }).catch(function (error) {\n reject(error);\n });\n });\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n namespaced: true,\n state: state,\n mutations: mutations,\n actions: actions\n});\n\n//# sourceURL=webpack:///./src/store/modules/settings.js?"); + +/***/ }), + +/***/ "./src/store/modules/tagsView.js": +/*!***************************************!*\ + !*** ./src/store/modules/tagsView.js ***! + \***************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime-corejs2/helpers/esm/toConsumableArray.js */ \"./node_modules/@babel/runtime-corejs2/helpers/esm/toConsumableArray.js\");\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_slicedToArray_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./node_modules/@babel/runtime-corejs2/helpers/esm/slicedToArray.js */ \"./node_modules/@babel/runtime-corejs2/helpers/esm/slicedToArray.js\");\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/web.dom.iterable */ \"./node_modules/core-js/modules/web.dom.iterable.js\");\n/* harmony import */ var core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_dom_iterable__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_createForOfIteratorHelper_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./node_modules/@babel/runtime-corejs2/helpers/esm/createForOfIteratorHelper.js */ \"./node_modules/@babel/runtime-corejs2/helpers/esm/createForOfIteratorHelper.js\");\n/* harmony import */ var core_js_modules_es6_function_name__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! core-js/modules/es6.function.name */ \"./node_modules/core-js/modules/es6.function.name.js\");\n/* harmony import */ var core_js_modules_es6_function_name__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_function_name__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var core_js_modules_es7_array_includes__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! core-js/modules/es7.array.includes */ \"./node_modules/core-js/modules/es7.array.includes.js\");\n/* harmony import */ var core_js_modules_es7_array_includes__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es7_array_includes__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var core_js_modules_es6_string_includes__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! core-js/modules/es6.string.includes */ \"./node_modules/core-js/modules/es6.string.includes.js\");\n/* harmony import */ var core_js_modules_es6_string_includes__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_string_includes__WEBPACK_IMPORTED_MODULE_6__);\n\n\n\n\n\n\n\nvar state = {\n visitedViews: [],\n cachedViews: []\n};\nvar mutations = {\n ADD_VISITED_VIEW: function ADD_VISITED_VIEW(state, view) {\n if (state.visitedViews.some(function (v) {\n return v.path === view.path;\n })) return;\n state.visitedViews.push(Object.assign({}, view, {\n title: view.meta.title || 'no-name'\n }));\n },\n ADD_CACHED_VIEW: function ADD_CACHED_VIEW(state, view) {\n if (state.cachedViews.includes(view.name)) return;\n\n if (!view.meta.noCache) {\n state.cachedViews.push(view.name);\n }\n },\n DEL_VISITED_VIEW: function DEL_VISITED_VIEW(state, view) {\n var _iterator = Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_createForOfIteratorHelper_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(state.visitedViews.entries()),\n _step;\n\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var _step$value = Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_slicedToArray_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(_step.value, 2),\n i = _step$value[0],\n v = _step$value[1];\n\n if (v.path === view.path) {\n state.visitedViews.splice(i, 1);\n break;\n }\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n },\n DEL_CACHED_VIEW: function DEL_CACHED_VIEW(state, view) {\n var index = state.cachedViews.indexOf(view.name);\n index > -1 && state.cachedViews.splice(index, 1);\n },\n DEL_OTHERS_VISITED_VIEWS: function DEL_OTHERS_VISITED_VIEWS(state, view) {\n state.visitedViews = state.visitedViews.filter(function (v) {\n return v.meta.affix || v.path === view.path;\n });\n },\n DEL_OTHERS_CACHED_VIEWS: function DEL_OTHERS_CACHED_VIEWS(state, view) {\n var index = state.cachedViews.indexOf(view.name);\n\n if (index > -1) {\n state.cachedViews = state.cachedViews.slice(index, index + 1);\n } else {\n // if index = -1, there is no cached tags\n state.cachedViews = [];\n }\n },\n DEL_ALL_VISITED_VIEWS: function DEL_ALL_VISITED_VIEWS(state) {\n // keep affix tags\n var affixTags = state.visitedViews.filter(function (tag) {\n return tag.meta.affix;\n });\n state.visitedViews = affixTags;\n },\n DEL_ALL_CACHED_VIEWS: function DEL_ALL_CACHED_VIEWS(state) {\n state.cachedViews = [];\n },\n UPDATE_VISITED_VIEW: function UPDATE_VISITED_VIEW(state, view) {\n var _iterator2 = Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_createForOfIteratorHelper_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(state.visitedViews),\n _step2;\n\n try {\n for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {\n var v = _step2.value;\n\n if (v.path === view.path) {\n v = Object.assign(v, view);\n break;\n }\n }\n } catch (err) {\n _iterator2.e(err);\n } finally {\n _iterator2.f();\n }\n }\n};\nvar actions = {\n addView: function addView(_ref, view) {\n var dispatch = _ref.dispatch;\n dispatch('addVisitedView', view);\n dispatch('addCachedView', view);\n },\n addVisitedView: function addVisitedView(_ref2, view) {\n var commit = _ref2.commit;\n commit('ADD_VISITED_VIEW', view);\n },\n addCachedView: function addCachedView(_ref3, view) {\n var commit = _ref3.commit;\n commit('ADD_CACHED_VIEW', view);\n },\n delView: function delView(_ref4, view) {\n var dispatch = _ref4.dispatch,\n state = _ref4.state;\n return new Promise(function (resolve) {\n dispatch('delVisitedView', view);\n dispatch('delCachedView', view);\n resolve({\n visitedViews: Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.visitedViews),\n cachedViews: Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.cachedViews)\n });\n });\n },\n delVisitedView: function delVisitedView(_ref5, view) {\n var commit = _ref5.commit,\n state = _ref5.state;\n return new Promise(function (resolve) {\n commit('DEL_VISITED_VIEW', view);\n resolve(Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.visitedViews));\n });\n },\n delCachedView: function delCachedView(_ref6, view) {\n var commit = _ref6.commit,\n state = _ref6.state;\n return new Promise(function (resolve) {\n commit('DEL_CACHED_VIEW', view);\n resolve(Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.cachedViews));\n });\n },\n delOthersViews: function delOthersViews(_ref7, view) {\n var dispatch = _ref7.dispatch,\n state = _ref7.state;\n return new Promise(function (resolve) {\n dispatch('delOthersVisitedViews', view);\n dispatch('delOthersCachedViews', view);\n resolve({\n visitedViews: Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.visitedViews),\n cachedViews: Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.cachedViews)\n });\n });\n },\n delOthersVisitedViews: function delOthersVisitedViews(_ref8, view) {\n var commit = _ref8.commit,\n state = _ref8.state;\n return new Promise(function (resolve) {\n commit('DEL_OTHERS_VISITED_VIEWS', view);\n resolve(Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.visitedViews));\n });\n },\n delOthersCachedViews: function delOthersCachedViews(_ref9, view) {\n var commit = _ref9.commit,\n state = _ref9.state;\n return new Promise(function (resolve) {\n commit('DEL_OTHERS_CACHED_VIEWS', view);\n resolve(Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.cachedViews));\n });\n },\n delAllViews: function delAllViews(_ref10, view) {\n var dispatch = _ref10.dispatch,\n state = _ref10.state;\n return new Promise(function (resolve) {\n dispatch('delAllVisitedViews', view);\n dispatch('delAllCachedViews', view);\n resolve({\n visitedViews: Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.visitedViews),\n cachedViews: Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.cachedViews)\n });\n });\n },\n delAllVisitedViews: function delAllVisitedViews(_ref11) {\n var commit = _ref11.commit,\n state = _ref11.state;\n return new Promise(function (resolve) {\n commit('DEL_ALL_VISITED_VIEWS');\n resolve(Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.visitedViews));\n });\n },\n delAllCachedViews: function delAllCachedViews(_ref12) {\n var commit = _ref12.commit,\n state = _ref12.state;\n return new Promise(function (resolve) {\n commit('DEL_ALL_CACHED_VIEWS');\n resolve(Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_toConsumableArray_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(state.cachedViews));\n });\n },\n updateVisitedView: function updateVisitedView(_ref13, view) {\n var commit = _ref13.commit;\n commit('UPDATE_VISITED_VIEW', view);\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n namespaced: true,\n state: state,\n mutations: mutations,\n actions: actions\n});\n\n//# sourceURL=webpack:///./src/store/modules/tagsView.js?"); + +/***/ }), + +/***/ "./src/store/modules/user.js": +/*!***********************************!*\ + !*** ./src/store/modules/user.js ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var regenerator_runtime_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! regenerator-runtime/runtime */ \"./node_modules/regenerator-runtime/runtime.js\");\n/* harmony import */ var regenerator_runtime_runtime__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(regenerator_runtime_runtime__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./node_modules/@babel/runtime-corejs2/helpers/esm/asyncToGenerator.js */ \"./node_modules/@babel/runtime-corejs2/helpers/esm/asyncToGenerator.js\");\n/* harmony import */ var _api_user__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @/api/user */ \"./src/api/user.js\");\n/* harmony import */ var _utils_auth__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @/utils/auth */ \"./src/utils/auth.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @/router */ \"./src/router/index.js\");\n\n\n\n\n\nvar state = {\n token: Object(_utils_auth__WEBPACK_IMPORTED_MODULE_3__[\"getToken\"])(),\n userId: '',\n name: '',\n realName: '',\n avatar: '',\n introduction: '',\n roles: []\n};\nvar mutations = {\n SET_TOKEN: function SET_TOKEN(state, token) {\n state.token = token;\n },\n SET_INTRODUCTION: function SET_INTRODUCTION(state, introduction) {\n state.introduction = introduction;\n },\n SET_ID: function SET_ID(state, userId) {\n state.userId = userId;\n },\n SET_NAME: function SET_NAME(state, name) {\n state.name = name;\n },\n SET_REAL_NAME: function SET_REAL_NAME(state, realName) {\n state.realName = realName;\n },\n SET_AVATAR: function SET_AVATAR(state, avatar) {\n state.avatar = avatar;\n },\n SET_ROLES: function SET_ROLES(state, roles) {\n state.roles = roles;\n }\n};\nvar actions = {\n // user login\n login: function login(_ref, userInfo) {\n var commit = _ref.commit;\n var username = userInfo.username,\n password = userInfo.password;\n return new Promise(function (resolve, reject) {\n Object(_api_user__WEBPACK_IMPORTED_MODULE_2__[\"login\"])({\n username: username.trim(),\n password: password\n }).then(function (response) {\n var data = response.data;\n commit('SET_TOKEN', data.token);\n Object(_utils_auth__WEBPACK_IMPORTED_MODULE_3__[\"setToken\"])(data.token);\n resolve();\n }).catch(function (error) {\n reject(error);\n });\n });\n },\n reg: function reg(_ref2, userInfo) {\n var commit = _ref2.commit;\n var userName = userInfo.userName,\n realName = userInfo.realName,\n password = userInfo.password;\n return new Promise(function (resolve, reject) {\n Object(_api_user__WEBPACK_IMPORTED_MODULE_2__[\"reg\"])({\n userName: userName.trim(),\n realName: realName.trim(),\n password: password\n }).then(function (response) {\n var data = response.data;\n commit('SET_TOKEN', data.token);\n Object(_utils_auth__WEBPACK_IMPORTED_MODULE_3__[\"setToken\"])(data.token);\n resolve();\n }).catch(function (error) {\n reject(error);\n });\n });\n },\n // get user info\n getInfo: function getInfo(_ref3) {\n var commit = _ref3.commit,\n state = _ref3.state;\n return new Promise(function (resolve, reject) {\n Object(_api_user__WEBPACK_IMPORTED_MODULE_2__[\"getInfo\"])(state.token).then(function (response) {\n var data = response.data;\n\n if (!data) {\n reject('校验失败,请重新登录!.');\n }\n\n var id = data.id,\n roles = data.roles,\n userName = data.userName,\n realName = data.realName,\n avatar = data.avatar,\n introduction = data.introduction; // roles must be a non-empty array\n\n if (!roles || roles.length <= 0) {\n reject('用户角色不能为空!');\n }\n\n commit('SET_ID', id);\n commit('SET_ROLES', roles);\n commit('SET_REAL_NAME', realName);\n commit('SET_NAME', userName);\n commit('SET_AVATAR', avatar);\n commit('SET_INTRODUCTION', introduction);\n resolve(data);\n }).catch(function (error) {\n reject(error);\n });\n });\n },\n // user logout\n logout: function logout(_ref4) {\n var commit = _ref4.commit,\n state = _ref4.state,\n dispatch = _ref4.dispatch;\n return new Promise(function (resolve, reject) {\n Object(_api_user__WEBPACK_IMPORTED_MODULE_2__[\"logout\"])(state.token).then(function () {\n commit('SET_TOKEN', '');\n commit('SET_ROLES', []);\n Object(_utils_auth__WEBPACK_IMPORTED_MODULE_3__[\"removeToken\"])();\n Object(_router__WEBPACK_IMPORTED_MODULE_4__[\"resetRouter\"])(); // reset visited views and cached views\n // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485\n\n dispatch('tagsView/delAllViews', null, {\n root: true\n });\n resolve();\n }).catch(function (error) {\n reject(error);\n });\n });\n },\n // remove token\n resetToken: function resetToken(_ref5) {\n var commit = _ref5.commit;\n return new Promise(function (resolve) {\n commit('SET_TOKEN', '');\n commit('SET_ROLES', []);\n Object(_utils_auth__WEBPACK_IMPORTED_MODULE_3__[\"removeToken\"])();\n resolve();\n });\n },\n // dynamically modify permissions\n changeRoles: function changeRoles(_ref6, role) {\n var commit = _ref6.commit,\n dispatch = _ref6.dispatch;\n return new Promise( /*#__PURE__*/function () {\n var _ref7 = Object(_Users_van_Documents_yf_projects_yf_exam_lite_exam_vue_node_modules_babel_runtime_corejs2_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"])( /*#__PURE__*/regeneratorRuntime.mark(function _callee(resolve) {\n var token, _yield$dispatch, roles, accessRoutes;\n\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n token = role + '-token';\n commit('SET_TOKEN', token);\n Object(_utils_auth__WEBPACK_IMPORTED_MODULE_3__[\"setToken\"])(token);\n _context.next = 5;\n return dispatch('getInfo');\n\n case 5:\n _yield$dispatch = _context.sent;\n roles = _yield$dispatch.roles;\n Object(_router__WEBPACK_IMPORTED_MODULE_4__[\"resetRouter\"])(); // generate accessible routes map based on roles\n\n _context.next = 10;\n return dispatch('permission/generateRoutes', roles, {\n root: true\n });\n\n case 10:\n accessRoutes = _context.sent;\n // dynamically add accessible routes\n _router__WEBPACK_IMPORTED_MODULE_4__[\"default\"].addRoutes(accessRoutes); // reset visited views and cached views\n\n dispatch('tagsView/delAllViews', null, {\n root: true\n });\n resolve();\n\n case 14:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee);\n }));\n\n return function (_x) {\n return _ref7.apply(this, arguments);\n };\n }());\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n namespaced: true,\n state: state,\n mutations: mutations,\n actions: actions\n});\n\n//# sourceURL=webpack:///./src/store/modules/user.js?"); + +/***/ }), + +/***/ "./src/styles/element-variables.scss": +/*!*******************************************!*\ + !*** ./src/styles/element-variables.scss ***! + \*******************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +eval("// style-loader: Adds some css to the DOM by adding a