commit 29eb2681806cc0b1871010c60fec65818d00df58 Author: zzy <2450266535@qq.com> Date: Sun May 24 00:24:56 2026 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf068a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +target/ +.idea/ +springboot.iml + +file/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..846a7dd --- /dev/null +++ b/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + com.example + springboot + 0.0.1-SNAPSHOT + + + UTF-8 + UTF-8 + 21 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + com.mysql + mysql-connector-j + + + + com.baomidou + mybatis-plus-spring-boot3-starter + 3.5.5 + + + + org.projectlombok + lombok + + + + com.github.pagehelper + pagehelper-spring-boot-starter + 1.4.6 + + + org.mybatis + mybatis + + + + + + cn.hutool + hutool-all + 5.8.18 + + + + com.auth0 + java-jwt + 4.3.0 + + + + org.apache.poi + poi-ooxml + 4.1.2 + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/src/main/java/com/example/SpringbootApplication.java b/src/main/java/com/example/SpringbootApplication.java new file mode 100644 index 0000000..dc8a215 --- /dev/null +++ b/src/main/java/com/example/SpringbootApplication.java @@ -0,0 +1,18 @@ +package com.example; + + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@MapperScan("com.example.mapper") +@EnableScheduling +public class SpringbootApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringbootApplication.class, args); + } + +} diff --git a/src/main/java/com/example/common/Constants.java b/src/main/java/com/example/common/Constants.java new file mode 100644 index 0000000..2c2b1af --- /dev/null +++ b/src/main/java/com/example/common/Constants.java @@ -0,0 +1,9 @@ +package com.example.common; + +public interface Constants { + + String TOKEN = "token"; + + String USER_DEFAULT_PASSWORD = "123456"; + +} diff --git a/src/main/java/com/example/common/Result.java b/src/main/java/com/example/common/Result.java new file mode 100644 index 0000000..ac82767 --- /dev/null +++ b/src/main/java/com/example/common/Result.java @@ -0,0 +1,72 @@ +package com.example.common; + +public class Result { + private String code; + private String msg; + private Object data; + + private Result(Object data) { + this.data = data; + } + + public Result() { + } + + public static Result success() { + Result result = new Result(); + result.setCode("200"); + result.setMsg("请求成功"); + return result; + } + + public static Result success(Object data) { + Result result = success(); + result.setData(data); + return result; + } + + public static Result error() { + Result result = new Result(); + result.setCode("500"); + result.setMsg("请求失败"); + return result; + } + + public static Result error(String code, String msg) { + Result result = new Result(); + result.setCode(code); + result.setMsg(msg); + return result; + } + + public static Result error(String msg) { + Result result = new Result(); + result.setCode("500"); + result.setMsg(msg); + return result; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } +} diff --git a/src/main/java/com/example/common/config/CorsConfig.java b/src/main/java/com/example/common/config/CorsConfig.java new file mode 100644 index 0000000..9be21e6 --- /dev/null +++ b/src/main/java/com/example/common/config/CorsConfig.java @@ -0,0 +1,25 @@ +package com.example.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * 跨域配置 + */ +@Configuration +public class CorsConfig { + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址 + corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头 + corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法 + source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置 + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/common/config/JwtInterceptor.java b/src/main/java/com/example/common/config/JwtInterceptor.java new file mode 100644 index 0000000..6e7d347 --- /dev/null +++ b/src/main/java/com/example/common/config/JwtInterceptor.java @@ -0,0 +1,82 @@ +package com.example.common.config; + +import cn.hutool.core.util.ObjectUtil; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.example.common.Constants; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.*; +import com.example.exception.CustomException; +import com.example.service.*; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +/** + * jwt拦截器 + */ +@Component +public class JwtInterceptor implements HandlerInterceptor { + + private static final Logger log = LoggerFactory.getLogger(JwtInterceptor.class); + + @Resource + private AdminService adminService; + @Resource + private DoctorService doctorService; + @Resource + private PatientService patientService; + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + // 1. 从http请求的header中获取token + String token = request.getHeader(Constants.TOKEN); + if (ObjectUtil.isEmpty(token)) { + // 如果没拿到,从参数里再拿一次 + token = request.getParameter(Constants.TOKEN); + } + // 2. 开始执行认证 + if (ObjectUtil.isEmpty(token)) { + throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR); + } + Account account = null; + try { + // 解析token获取存储的数据 + String userRole = JWT.decode(token).getAudience().get(0); + String userId = userRole.split("-")[0]; + String role = userRole.split("-")[1]; + // 根据userId查询数据库 + if ("admin".equals(role)) { + account = adminService.selectById(Integer.valueOf(userId)); + } + if ("doctor".equals(role)) { + account = doctorService.selectById(Integer.valueOf(userId)); + } + if ("patient".equals(role)) { + account = patientService.selectById(Integer.valueOf(userId)); + } + + + } catch (Exception e) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + if (ObjectUtil.isNull(account)) { + throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR); + } + try { + // 用户密码加签验证 token + JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(account.getPassword())).build(); + jwtVerifier.verify(token); // 验证token + } catch (JWTVerificationException e) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/common/config/TokenUtils.java b/src/main/java/com/example/common/config/TokenUtils.java new file mode 100644 index 0000000..fbe82e9 --- /dev/null +++ b/src/main/java/com/example/common/config/TokenUtils.java @@ -0,0 +1,88 @@ +package com.example.common.config; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.example.entity.Account; +import com.example.service.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Date; + +@Component +public class TokenUtils { + + private static AdminService staticAdminService; + private static DoctorService staticDoctorService; + private static PatientService staticPatientService; + + private static final Logger log = LoggerFactory.getLogger(TokenUtils.class); + + @Resource + private AdminService adminService; + @Resource + private DoctorService doctorService; + @Resource + private PatientService patientService; + + @PostConstruct + public void setUserService() { + staticAdminService = adminService; + staticDoctorService = doctorService; + staticPatientService = patientService; + } + + /** + * 生成token + */ + public static String genToken(String userRole, String password) { + return JWT.create().withAudience(userRole) // 将 userId-role 保存到 token 里面,作为载荷 + .withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小时后token过期 + .sign(Algorithm.HMAC256(password)); // 以 password 作为 token 的密钥 + } + + /** + * 获取当前登录的用户信息 + */ + public static Account getCurrentUser() { + String token = null; + try { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + token = request.getHeader("token"); + if (StrUtil.isBlank(token)) { + token = request.getParameter("token"); + } + if (StrUtil.isBlank(token)) { + log.error("获取当前登录的token失败, token: {}", token); + return null; + } + // 解析token,获取用户的id + String userRole = JWT.decode(token).getAudience().get(0); + String userId = userRole.split("-")[0]; + String role = userRole.split("-")[1]; + Account account = null; + if ("admin".equals(role)) { + account = staticAdminService.selectById(Integer.valueOf(userId)); + } + if ("doctor".equals(role)) { + account = staticDoctorService.selectById(Integer.valueOf(userId)); + } + if ("patient".equals(role)) { + account = staticPatientService.selectById(Integer.valueOf(userId)); + } + + return account; + } catch (Exception e) { + log.error("获取当前登录的管理员信息失败, token={}", token, e); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/common/config/WebConfig.java b/src/main/java/com/example/common/config/WebConfig.java new file mode 100644 index 0000000..5a0627b --- /dev/null +++ b/src/main/java/com/example/common/config/WebConfig.java @@ -0,0 +1,24 @@ +package com.example.common.config; + +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Resource + private JwtInterceptor jwtInterceptor; + + // 加自定义拦截器JwtInterceptor,设置拦截规则 + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(jwtInterceptor).addPathPatterns("/**") + .excludePathPatterns("/") + .excludePathPatterns("/login", "/register", "/files/**", "/role/selectAll","/institution/selectAll") + .excludePathPatterns("/doctor") // 放行医生列表获取接口,用于患者注册时选择医生 + .excludePathPatterns("/ws/**", "/notification/**") // 放行WebSocket和通知相关的路径 + .excludePathPatterns("/eventInstitution/checkAndUpdateEventStatus"); // 放行事件状态检查接口 + } +} \ No newline at end of file diff --git a/src/main/java/com/example/common/enums/ResultCodeEnum.java b/src/main/java/com/example/common/enums/ResultCodeEnum.java new file mode 100644 index 0000000..56a803f --- /dev/null +++ b/src/main/java/com/example/common/enums/ResultCodeEnum.java @@ -0,0 +1,27 @@ +package com.example.common.enums; + +public enum ResultCodeEnum { + SUCCESS("200", "成功"), + + PARAM_ERROR("400", "参数异常"), + TOKEN_INVALID_ERROR("401", "无效的token"), + TOKEN_CHECK_ERROR("401", "token验证失败,请重新登录"), + PARAM_LOST_ERROR("4001", "参数缺失"), + NO_AUTH_ERROR("403", "没有操作权限"), + + SYSTEM_ERROR("500", "系统异常"), + USER_EXIST_ERROR("5001", "用户名已存在"), + USER_NOT_LOGIN("5002", "用户未登录"), + USER_ACCOUNT_ERROR("5003", "账号或密码错误"), + USER_NOT_EXIST_ERROR("5004", "用户不存在"), + PARAM_PASSWORD_ERROR("5005", "原密码输入错误"), + ; + + public String code; + public String msg; + + ResultCodeEnum(String code, String msg) { + this.code = code; + this.msg = msg; + } +} diff --git a/src/main/java/com/example/config/RestTemplateConfig.java b/src/main/java/com/example/config/RestTemplateConfig.java new file mode 100644 index 0000000..88e398a --- /dev/null +++ b/src/main/java/com/example/config/RestTemplateConfig.java @@ -0,0 +1,14 @@ +package com.example.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/config/WebSocketConfig.java b/src/main/java/com/example/config/WebSocketConfig.java new file mode 100644 index 0000000..8c55302 --- /dev/null +++ b/src/main/java/com/example/config/WebSocketConfig.java @@ -0,0 +1,20 @@ +package com.example.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * WebSocket配置类 + */ +@Configuration +public class WebSocketConfig { + + /** + * 注入ServerEndpointExporter,自动注册使用了@ServerEndpoint注解的bean + */ + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/AIChatController.java b/src/main/java/com/example/controller/AIChatController.java new file mode 100644 index 0000000..40a4a4d --- /dev/null +++ b/src/main/java/com/example/controller/AIChatController.java @@ -0,0 +1,56 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.service.AIChatService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * AI聊天控制器 + */ +@RestController +@RequestMapping("/aiChat") +public class AIChatController { + + @Resource + private AIChatService aiChatService; + + /** + * 发送消息到AI并获取回复 + */ + @PostMapping("/sendMessage") + public Result sendMessage(@RequestBody Map params) { + String message = params.get("message"); + + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "请先登录"); + } + + // 获取AI回复 + String aiReply = aiChatService.sendMessage(message); + + Map userMessage = new HashMap<>(); + userMessage.put("content", message); + userMessage.put("messageType", "user"); + userMessage.put("createTime", LocalDateTime.now()); + + Map aiMessage = new HashMap<>(); + aiMessage.put("content", aiReply); + aiMessage.put("messageType", "ai"); + aiMessage.put("createTime", LocalDateTime.now()); + + Map resultMap = new HashMap<>(); + resultMap.put("userMessage", userMessage); + resultMap.put("aiMessage", aiMessage); + + return Result.success(resultMap); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/AccountController.java b/src/main/java/com/example/controller/AccountController.java new file mode 100644 index 0000000..b2865d0 --- /dev/null +++ b/src/main/java/com/example/controller/AccountController.java @@ -0,0 +1,48 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.entity.Patient; +import com.example.service.PatientService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 账号相关接口 + */ +@RestController +@RequestMapping("/account") +public class AccountController { + + @Resource + private PatientService patientService; + + /** + * 获取所有患者列表(仅管理员可用) + * + * @param name 可选的患者姓名过滤 + * @return 患者列表 + */ + @GetMapping("/listPatients") + public Result listPatients(@RequestParam(required = false) String name) { + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !"admin".equals(currentUser.getRole())) { + return Result.error("401", "无权限,只有管理员可以访问此接口"); + } + + Patient searchParam = null; + if (name != null && !name.isEmpty()) { + searchParam = new Patient(); + searchParam.setName(name); + } + + List patients = patientService.selectAll(searchParam); + return Result.success(patients); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/AdminController.java b/src/main/java/com/example/controller/AdminController.java new file mode 100644 index 0000000..2ef7f62 --- /dev/null +++ b/src/main/java/com/example/controller/AdminController.java @@ -0,0 +1,93 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.entity.Admin; +import com.example.service.AdminService; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** +* 描述:管理员相关接口 +*/ +@RestController +@RequestMapping("/admin") +public class AdminController { + + @Resource + AdminService adminService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody Admin admin) { + + adminService.add(admin); + return Result.success(); + } + + /** + * 删除 + */ + @DeleteMapping("/delete/{id}") + public Result delete(@PathVariable Integer id) { + adminService.deleteById(id); + return Result.success(); + } + + /** + * 批量删除 + */ + @DeleteMapping("/delete/batch") + public Result delete(@RequestBody List ids) { + adminService.deleteBatch(ids); + return Result.success(); + } + + /** + * 更新 + */ + @PutMapping("/update") + public Result update(@RequestBody Admin admin) { + + adminService.updateById(admin); + return Result.success(); + } + + /** + * 查询单个 + */ + @GetMapping("/selectById/{id}") + public Result selectById(@PathVariable Integer id) { + Admin admin = adminService.selectById(id); + return Result.success(admin); + } + + /** + * 查询所有 + */ + @GetMapping("/selectAll") + public Result selectAll(Admin admin) { + List list = adminService.selectAll(admin); + return Result.success(list); + } + + /** + * 查询所有 + */ + @GetMapping("/selectPage") + public Result selectPage( + Admin admin, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + PageInfo pageInfo = adminService.selectPage(admin, pageNum, pageSize); + return Result.success(pageInfo); + } + + + + +} diff --git a/src/main/java/com/example/controller/AnnouncementController.java b/src/main/java/com/example/controller/AnnouncementController.java new file mode 100644 index 0000000..fcc84e7 --- /dev/null +++ b/src/main/java/com/example/controller/AnnouncementController.java @@ -0,0 +1,109 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.entity.Announcement; +import com.example.service.AnnouncementService; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 公告控制器 + */ +@RestController +@RequestMapping("/announcement") +public class AnnouncementController { + + @Resource + private AnnouncementService announcementService; + + /** + * 添加公告 + * @param announcement 公告信息 + * @return 结果 + */ + @PostMapping + public Result add(@RequestBody Announcement announcement) { + announcementService.add(announcement); + return Result.success(); + } + + /** + * 更新公告 + * @param announcement 公告信息 + * @return 结果 + */ + @PutMapping + public Result update(@RequestBody Announcement announcement) { + announcementService.update(announcement); + return Result.success(); + } + + /** + * 删除公告 + * @param id 公告ID + * @return 结果 + */ + @DeleteMapping("/{id}") + public Result delete(@PathVariable Integer id) { + announcementService.delete(id); + return Result.success(); + } + + /** + * 获取公告详情 + * @param id 公告ID + * @return 公告信息 + */ + @GetMapping("/{id}") + public Result getById(@PathVariable Integer id) { + Announcement announcement = announcementService.getById(id); + return Result.success(announcement); + } + + /** + * 获取所有已发布的公告(分页,所有用户可访问) + * @param pageNum 页码 + * @param pageSize 每页大小 + * @param title 标题关键字 + * @return 分页公告列表 + */ + @GetMapping("/page") + public Result getPublishedPage( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String title) { + PageInfo pageInfo = announcementService.getPublishedPage(pageNum, pageSize, title); + return Result.success(pageInfo); + } + + /** + * 获取所有公告(分页,包括草稿,仅管理员可用) + * @param pageNum 页码 + * @param pageSize 每页大小 + * @param title 标题关键字 + * @return 分页公告列表 + */ + @GetMapping("/adminPage") + public Result getAllForAdmin( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String title) { + PageInfo pageInfo = announcementService.getAllForAdmin(pageNum, pageSize, title); + return Result.success(pageInfo); + } + + /** + * 获取最新的几条已发布公告(用于首页展示) + * @param limit 限制条数 + * @return 公告列表 + */ + @GetMapping("/latest") + public Result getLatestPublished( + @RequestParam(defaultValue = "5") Integer limit) { + List list = announcementService.getLatestPublished(limit); + return Result.success(list); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/AssessmentReportController.java b/src/main/java/com/example/controller/AssessmentReportController.java new file mode 100644 index 0000000..8abaeb4 --- /dev/null +++ b/src/main/java/com/example/controller/AssessmentReportController.java @@ -0,0 +1,116 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.entity.AssessmentReport; +import com.example.service.AssessmentReportService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 评估报告控制器 + */ +@RestController +@RequestMapping("/assessmentReport") +public class AssessmentReportController { + + @Resource + private AssessmentReportService assessmentReportService; + + /** + * 手动生成评估报告 + */ + @PostMapping("/generate") + public Result generate(@RequestBody Map params) { + Integer patientId = params.get("patientId") != null ? Integer.valueOf(params.get("patientId").toString()) : null; + String reportPeriod = params.get("reportPeriod") != null ? params.get("reportPeriod").toString() : null; + + if (patientId == null) { + return Result.error("400", "缺少必要参数patientId"); + } + if (reportPeriod == null) { + return Result.error("400", "缺少必要参数reportPeriod"); + } + + AssessmentReport report = assessmentReportService.generateReport(patientId, reportPeriod); + return Result.success(report); + } + + /** + * 获取患者的评估报告列表 + */ + @GetMapping("/patient/{patientId}") + public Result getReportsByPatientId(@PathVariable Integer patientId) { + List reports = assessmentReportService.getReportsByPatientId(patientId); + return Result.success(reports); + } + + /** + * 获取医生负责的患者的评估报告列表 + */ + @GetMapping("/doctor") + public Result getReportsByDoctor() { + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "未登录"); + } + + List reports = assessmentReportService.getReportsByDoctorId(currentUser.getId()); + return Result.success(reports); + } + + /** + * 获取所有评估报告列表(管理员使用) + */ + @GetMapping("/admin") + public Result getAllReports() { + List reports = assessmentReportService.getAllReports(); + return Result.success(reports); + } + + /** + * 添加医生批注 + */ + @PutMapping("/comment/{reportId}") + public Result addComment(@PathVariable Integer reportId, @RequestParam(required = false) String comment) { + if (comment == null || comment.isEmpty()) { + return Result.error("400", "评论内容不能为空"); + } + assessmentReportService.addDoctorComment(reportId, comment); + return Result.success(); + } + + /** + * 更新报告状态为已读 + */ + @PutMapping("/read/{reportId}") + public Result markAsRead(@PathVariable Integer reportId) { + assessmentReportService.markAsRead(reportId); + return Result.success(); + } + + /** + * 获取评估报告详情 + */ + @GetMapping("/{reportId}") + public Result getReportById(@PathVariable Integer reportId) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "未登录"); + } + + // 通过ID获取报告 + AssessmentReport report = assessmentReportService.getReportById(reportId); + + if (report == null) { + return Result.error("404", "报告不存在"); + } + + return Result.success(report); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/ChatMessageController.java b/src/main/java/com/example/controller/ChatMessageController.java new file mode 100644 index 0000000..9327ff7 --- /dev/null +++ b/src/main/java/com/example/controller/ChatMessageController.java @@ -0,0 +1,48 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.entity.ChatMessage; +import com.example.service.ChatMessageService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 聊天消息控制器 + */ +@RestController +@RequestMapping("/chat") +public class ChatMessageController { + + @Resource + private ChatMessageService chatMessageService; + + /** + * 发送消息 + */ + @PostMapping("/send") + public Result send(@RequestBody ChatMessage chatMessage) { + ChatMessage message = chatMessageService.sendMessage(chatMessage); + return Result.success(message); + } + + /** + * 获取聊天历史记录 + */ + @GetMapping("/history") + public Result getChatHistory(@RequestParam Integer contactId, @RequestParam String contactType) { + List messages = chatMessageService.getChatHistory(contactId, contactType); + return Result.success(messages); + } + + /** + * 获取聊天联系人列表 + */ + @GetMapping("/contacts") + public Result getContactList() { + List> contacts = chatMessageService.getContactList(); + return Result.success(contacts); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/DoctorController.java b/src/main/java/com/example/controller/DoctorController.java new file mode 100644 index 0000000..0fc8556 --- /dev/null +++ b/src/main/java/com/example/controller/DoctorController.java @@ -0,0 +1,147 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.entity.Doctor; +import com.example.entity.DoctorPatient; +import com.example.service.DoctorPatientService; +import com.example.service.DoctorService; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 医生相关接口 + */ +@RestController +@RequestMapping("/doctor") +public class DoctorController { + + @Resource + private DoctorService doctorService; + + @Resource + private DoctorPatientService doctorPatientService; + + /** + * 添加医生 + */ + @PostMapping + public Result add(@RequestBody Doctor doctor) { + doctorService.add(doctor); + return Result.success(); + } + + /** + * 删除医生 + */ + @DeleteMapping("/{id}") + public Result delete(@PathVariable Integer id) { + doctorService.deleteById(id); + return Result.success(); + } + + /** + * 批量删除医生 + */ + @DeleteMapping("/batch") + public Result deleteBatch(@RequestBody List ids) { + doctorService.deleteBatch(ids); + return Result.success(); + } + + /** + * 修改医生 + */ + @PutMapping + public Result update(@RequestBody Doctor doctor) { + doctorService.updateById(doctor); + return Result.success(); + } + + /** + * 根据ID查询医生 + */ + @GetMapping("/{id}") + public Result getById(@PathVariable Integer id) { + Doctor doctor = doctorService.selectById(id); + return Result.success(doctor); + } + + /** + * 查询所有医生 + */ + @GetMapping + public Result getAll(Doctor doctor) { + List list = doctorService.selectAll(doctor); + return Result.success(list); + } + + /** + * 分页查询医生 + */ + @GetMapping("/page") + public Result page(Doctor doctor, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + PageInfo page = doctorService.selectPage(doctor, pageNum, pageSize); + return Result.success(page); + } + + /** + * 分页查询医生(与Admin页面保持一致的接口) + */ + @GetMapping("/selectPage") + public Result selectPage( + Doctor doctor, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + PageInfo pageInfo = doctorService.selectPage(doctor, pageNum, pageSize); + return Result.success(pageInfo); + } + + /** + * 获取当前医生负责的患者列表 + * 此接口是为了兼容之前的路径 + * @param name 患者姓名(可选) + * @return 患者列表 + */ + @GetMapping("/myPatients") + public Result myPatients(@RequestParam(required = false) String name) { + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !"doctor".equals(currentUser.getRole())) { + return Result.error("401", "无权限"); + } + + List list = doctorPatientService.selectByDoctorId(currentUser.getId(), name); + return Result.success(list); + } + + /** + * 更新医生个人资料 + */ + @PutMapping("/update") + public Result updateProfile(@RequestBody Doctor doctor) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !"doctor".equals(currentUser.getRole())) { + return Result.error("401", "无权限"); + } + + // 确保只能更新自己的信息 + if (!currentUser.getId().equals(doctor.getId())) { + return Result.error("403", "只能更新自己的信息"); + } + + // 更新个人资料 + doctorService.updateById(doctor); + + return Result.success(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/DoctorPatientController.java b/src/main/java/com/example/controller/DoctorPatientController.java new file mode 100644 index 0000000..61bea65 --- /dev/null +++ b/src/main/java/com/example/controller/DoctorPatientController.java @@ -0,0 +1,137 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.entity.DoctorPatient; +import com.example.entity.Patient; +import com.example.service.DoctorPatientService; +import com.example.service.PatientService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 医生与患者关系控制器 + */ +@RestController +@RequestMapping("/doctor-patient") +public class DoctorPatientController { + + @Resource + private DoctorPatientService doctorPatientService; + + @Resource + private PatientService patientService; + + /** + * 新增医生患者关系 + * @param doctorPatient 医生患者关系信息 + * @return 结果 + */ + @PostMapping + public Result add(@RequestBody DoctorPatient doctorPatient) { + doctorPatientService.add(doctorPatient); + return Result.success(); + } + + /** + * 更新医生患者关系 + * @param doctorPatient 医生患者关系信息 + * @return 结果 + */ + @PutMapping + public Result update(@RequestBody DoctorPatient doctorPatient) { + doctorPatientService.update(doctorPatient); + return Result.success(); + } + + /** + * 删除医生患者关系 + * @param id 关系ID + * @return 结果 + */ + @DeleteMapping("/{id}") + public Result delete(@PathVariable Integer id) { + doctorPatientService.delete(id); + return Result.success(); + } + + /** + * 结束医生患者关系 + * @param id 关系ID + * @return 结果 + */ + @PutMapping("/end/{id}") + public Result endRelationship(@PathVariable Integer id) { + doctorPatientService.endRelationship(id); + return Result.success(); + } + + /** + * 获取当前医生的患者列表 + * @param name 患者姓名(可选) + * @return 患者列表 + */ + @GetMapping("/doctor/patients") + public Result getPatientsByDoctor(@RequestParam(required = false) String name) { + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !"doctor".equals(currentUser.getRole())) { + return Result.error("401", "无权限"); + } + + List list = doctorPatientService.selectByDoctorId(currentUser.getId(), name); + return Result.success(list); + } + + /** + * 获取当前患者的医生列表 + * @param name 医生姓名(可选) + * @return 医生列表 + */ + @GetMapping("/patient/doctors") + public Result getDoctorsByPatient(@RequestParam(required = false) String name) { + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !"patient".equals(currentUser.getRole())) { + return Result.error("401", "无权限"); + } + + List list = doctorPatientService.selectByPatientId(currentUser.getId(), name); + return Result.success(list); + } + + /** + * 根据医生ID获取其患者列表(管理员使用) + * @param doctorId 医生ID + * @param name 患者姓名(可选) + * @return 患者列表 + */ + @GetMapping("/admin/doctor/{doctorId}/patients") + public Result getPatientsByDoctorForAdmin(@PathVariable Integer doctorId, @RequestParam(required = false) String name) { + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !"admin".equals(currentUser.getRole())) { + return Result.error("401", "无权限"); + } + + List list = doctorPatientService.selectByDoctorId(doctorId, name); + return Result.success(list); + } + + /** + * 根据患者ID获取其医生列表(管理员使用) + * @param patientId 患者ID + * @param name 医生姓名(可选) + * @return 医生列表 + */ + @GetMapping("/admin/patient/{patientId}/doctors") + public Result getDoctorsByPatientForAdmin(@PathVariable Integer patientId, @RequestParam(required = false) String name) { + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !"admin".equals(currentUser.getRole())) { + return Result.error("401", "无权限"); + } + + List list = doctorPatientService.selectByPatientId(patientId, name); + return Result.success(list); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/FileController.java b/src/main/java/com/example/controller/FileController.java new file mode 100644 index 0000000..89a1658 --- /dev/null +++ b/src/main/java/com/example/controller/FileController.java @@ -0,0 +1,102 @@ +package com.example.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import com.example.common.Result; +import org.springframework.util.ResourceUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.servlet.http.HttpServletResponse; +import java.io.FileNotFoundException; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 文件上传接口 + */ +@RestController +@RequestMapping("/files") +public class FileController { + + // 文件上传存储路径 + private static final String filePath = System.getProperty("user.dir") + "/file/"; + private static final String http = "http://localhost:9090/files/"; + + /** + * 文件上传 + */ + @PostMapping("/upload") + public Result upload(MultipartFile file) { + synchronized (FileController.class) { + String flag = System.currentTimeMillis() + ""; + String fileName = file.getOriginalFilename(); + try { + if (!FileUtil.isDirectory(filePath)) { + FileUtil.mkdir(filePath); + } + // 文件存储形式:时间戳-文件名 + FileUtil.writeBytes(file.getBytes(), filePath + flag + "-" + fileName); + System.out.println(fileName + "--上传成功"); + Thread.sleep(1L); + } catch (Exception e) { + System.err.println(fileName + "--文件上传失败"); + } + return Result.success(http + flag + "-" + fileName); + } + } + + + /** + * 获取文件 + * + * @param flag + * @param response + */ + @GetMapping("/{flag}") + public void avatarPath(@PathVariable String flag, HttpServletResponse response) { + if (!FileUtil.isDirectory(filePath)) { + FileUtil.mkdir(filePath); + } + OutputStream os; + List fileNames = FileUtil.listFileNames(filePath); + String avatar = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse(""); + try { + if (StrUtil.isNotEmpty(avatar)) { + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(avatar, "UTF-8")); + response.setContentType("application/octet-stream"); + byte[] bytes = FileUtil.readBytes(filePath + avatar); + os = response.getOutputStream(); + os.write(bytes); + os.flush(); + os.close(); + } + } catch (Exception e) { + System.out.println("文件下载失败"); + } + } + + /** + * 删除文件 + * + * @param flag + */ + @DeleteMapping("/{flag}") + public void delFile(@PathVariable String flag) { + if (!FileUtil.isDirectory(filePath)) { + FileUtil.mkdir(filePath); + } + List fileNames = FileUtil.listFileNames(filePath); + String filename = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse(""); + FileUtil.del(filePath + filename); + System.out.println("删除文件" + filename + "成功"); + } + + +} diff --git a/src/main/java/com/example/controller/PatientController.java b/src/main/java/com/example/controller/PatientController.java new file mode 100644 index 0000000..c50174c --- /dev/null +++ b/src/main/java/com/example/controller/PatientController.java @@ -0,0 +1,256 @@ +package com.example.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.entity.Doctor; +import com.example.entity.DoctorPatient; +import com.example.entity.Patient; +import com.example.service.DoctorPatientService; +import com.example.service.PatientService; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 患者控制器 + */ +@RestController +@RequestMapping("/patient") +public class PatientController { + + @Resource + private PatientService patientService; + + @Resource + private DoctorPatientService doctorPatientService; + + /** + * 新增 + */ + @PostMapping + public Result add(@RequestBody Patient patient) { + patientService.add(patient); + return Result.success(); + } + + /** + * 删除 + */ + @DeleteMapping("/{id}") + public Result delete(@PathVariable Integer id) { + patientService.deleteById(id); + return Result.success(); + } + + /** + * 更新 + */ + @PutMapping + public Result update(@RequestBody Patient patient) { + patientService.updateById(patient); + return Result.success(); + } + + /** + * 根据ID查询 + */ + @GetMapping("/{id}") + public Result getById(@PathVariable Integer id) { + Patient patient = patientService.selectById(id); + return Result.success(patient); + } + + /** + * 查询全部 + */ + @GetMapping + public Result findAll() { + Account currentUser = TokenUtils.getCurrentUser(); + List patients = new ArrayList<>(); + + // 管理员可以查看所有患者 + if (currentUser != null && "admin".equals(currentUser.getRole())) { + patients = patientService.selectAll(null); + } + // 医生只能查看自己的患者 + else if (currentUser != null && "doctor".equals(currentUser.getRole())) { + List doctorPatients = doctorPatientService.selectByDoctorId(currentUser.getId(), null); + if (!doctorPatients.isEmpty()) { + // 提取患者信息 + patients = doctorPatients.stream() + .map(DoctorPatient::getPatient) + .filter(p -> p != null) + .collect(Collectors.toList()); + } + } + // 患者只能查看自己的信息 + else if (currentUser != null && "patient".equals(currentUser.getRole())) { + Patient patient = patientService.selectById(currentUser.getId()); + if (patient != null) { + patients.add(patient); + } + } + + return Result.success(patients); + } + + /** + * 分页查询 + */ + @GetMapping("/page") + public Result findPage(@RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String name) { + Account currentUser = TokenUtils.getCurrentUser(); + + // 管理员可以查看所有患者 + if (currentUser != null && "admin".equals(currentUser.getRole())) { + return Result.success(patientService.selectPage(pageNum, pageSize, name)); + } + // 医生只能查看自己的患者 + else if (currentUser != null && "doctor".equals(currentUser.getRole())) { + List doctorPatients = doctorPatientService.selectByDoctorId(currentUser.getId(), name); + + if (!doctorPatients.isEmpty()) { + // 提取患者信息 + List patients = doctorPatients.stream() + .map(DoctorPatient::getPatient) + .filter(p -> p != null) + .collect(Collectors.toList()); + + // 手动进行分页 + int total = patients.size(); + int fromIndex = (pageNum - 1) * pageSize; + int toIndex = Math.min(fromIndex + pageSize, total); + + if (fromIndex >= total) { + patients = new ArrayList<>(); + } else { + patients = patients.subList(fromIndex, toIndex); + } + + return Result.success(patients); + } else { + return Result.success(new ArrayList<>()); + } + } + // 患者只能查看自己的信息 + else if (currentUser != null && "patient".equals(currentUser.getRole())) { + Patient patient = patientService.selectById(currentUser.getId()); + List list = new ArrayList<>(); + if (patient != null) { + list.add(patient); + } + return Result.success(list); + } + + return Result.error("401", "无权限"); + } + + /** + * 分页查询(与Admin页面保持一致的接口) + */ + @GetMapping("/selectPage") + public Result selectPage( + Patient patient, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + Account currentUser = TokenUtils.getCurrentUser(); + + // 管理员可以查看所有患者 + if (currentUser != null && "admin".equals(currentUser.getRole())) { + // 使用PageHelper手动创建PageInfo + PageHelper.startPage(pageNum, pageSize); + List patients = patientService.selectAll(patient); + PageInfo pageInfo = new PageInfo<>(patients); + return Result.success(pageInfo); + } + // 医生只能查看自己的患者 + else if (currentUser != null && "doctor".equals(currentUser.getRole())) { + List doctorPatients = doctorPatientService.selectByDoctorId(currentUser.getId(), patient.getName()); + + if (!doctorPatients.isEmpty()) { + // 提取患者信息 + List patients = doctorPatients.stream() + .map(DoctorPatient::getPatient) + .filter(p -> p != null) + .collect(Collectors.toList()); + + // 手动进行分页 + int total = patients.size(); + int fromIndex = (pageNum - 1) * pageSize; + int toIndex = Math.min(fromIndex + pageSize, total); + + if (fromIndex >= total) { + patients = new ArrayList<>(); + } else { + patients = patients.subList(fromIndex, toIndex); + } + + // 创建PageInfo对象 + PageInfo pageInfo = new PageInfo<>(); + pageInfo.setList(patients); + pageInfo.setTotal(total); + pageInfo.setPageNum(pageNum); + pageInfo.setPageSize(pageSize); + pageInfo.setPages((total + pageSize - 1) / pageSize); + + return Result.success(pageInfo); + } else { + PageInfo pageInfo = new PageInfo<>(); + pageInfo.setList(new ArrayList<>()); + pageInfo.setTotal(0); + return Result.success(pageInfo); + } + } + // 患者只能查看自己的信息 + else if (currentUser != null && "patient".equals(currentUser.getRole())) { + Patient currentPatient = patientService.selectById(currentUser.getId()); + List list = new ArrayList<>(); + if (currentPatient != null) { + list.add(currentPatient); + } + + // 创建PageInfo对象 + PageInfo pageInfo = new PageInfo<>(); + pageInfo.setList(list); + pageInfo.setTotal(list.size()); + pageInfo.setPageNum(1); + pageInfo.setPageSize(pageSize); + pageInfo.setPages(1); + + return Result.success(pageInfo); + } + + return Result.error("401", "无权限"); + } + + /** + * 更新患者个人资料 + */ + @PutMapping("/update") + public Result updateProfile(@RequestBody Patient patient) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !"patient".equals(currentUser.getRole())) { + return Result.error("401", "无权限"); + } + + // 确保只能更新自己的信息 + if (!currentUser.getId().equals(patient.getId())) { + return Result.error("403", "只能更新自己的信息"); + } + + // 更新个人资料 + patientService.updateById(patient); + + return Result.success(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/RecommendationController.java b/src/main/java/com/example/controller/RecommendationController.java new file mode 100644 index 0000000..9e9ae53 --- /dev/null +++ b/src/main/java/com/example/controller/RecommendationController.java @@ -0,0 +1,47 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.service.RecommendationService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 推荐系统控制器 + */ +@RestController +@RequestMapping("/recommendation") +public class RecommendationController { + + @Resource + private RecommendationService recommendationService; + + /** + * 获取个性化推荐资源 + * @param limit 推荐资源数量,默认为4 + * @return 推荐资源列表 + */ + @GetMapping("/resources") + public Result getRecommendedResources(@RequestParam(defaultValue = "4") Integer limit) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "用户未登录"); + } + + // 验证用户是否为患者 + if (!"patient".equals(currentUser.getRole())) { + return Result.error("403", "只有患者才能获取个性化推荐"); + } + + // 获取推荐资源 + List> recommendations = + recommendationService.getRecommendationsForUser(currentUser.getId(), limit); + + return Result.success(recommendations); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/ResourceCategoryController.java b/src/main/java/com/example/controller/ResourceCategoryController.java new file mode 100644 index 0000000..f19e49a --- /dev/null +++ b/src/main/java/com/example/controller/ResourceCategoryController.java @@ -0,0 +1,86 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.entity.ResourceCategory; +import com.example.service.ResourceCategoryService; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 资源分类控制器 + */ +@RestController +@RequestMapping("/resourceCategory") +public class ResourceCategoryController { + + @Resource + private ResourceCategoryService resourceCategoryService; + + /** + * 获取所有分类 + */ + @GetMapping + public Result findAll() { + List list = resourceCategoryService.findAll(); + return Result.success(list); + } + + /** + * 根据父分类ID获取子分类 + */ + @GetMapping("/parent/{parentId}") + public Result findByParentId(@PathVariable Integer parentId) { + List list = resourceCategoryService.findByParentId(parentId); + return Result.success(list); + } + + /** + * 根据ID获取分类 + */ + @GetMapping("/{id}") + public Result findById(@PathVariable Integer id) { + ResourceCategory category = resourceCategoryService.findById(id); + return Result.success(category); + } + + /** + * 新增分类 + */ + @PostMapping + public Result add(@RequestBody ResourceCategory category) { + resourceCategoryService.add(category); + return Result.success(); + } + + /** + * 更新分类 + */ + @PutMapping + public Result update(@RequestBody ResourceCategory category) { + resourceCategoryService.update(category); + return Result.success(); + } + + /** + * 删除分类 + */ + @DeleteMapping("/{id}") + public Result delete(@PathVariable Integer id) { + resourceCategoryService.delete(id); + return Result.success(); + } + + /** + * 分页查询 + */ + @GetMapping("/selectPage") + public Result selectPage(@RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + ResourceCategory category) { + PageInfo pageInfo = resourceCategoryService.selectPage(category, pageNum, pageSize); + return Result.success(pageInfo); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/ResourceRatingController.java b/src/main/java/com/example/controller/ResourceRatingController.java new file mode 100644 index 0000000..366b60a --- /dev/null +++ b/src/main/java/com/example/controller/ResourceRatingController.java @@ -0,0 +1,167 @@ +package com.example.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.entity.ResourceRating; +import com.example.service.ResourceRatingService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 资源评分控制器 + */ +@RestController +@RequestMapping("/resourceRating") +public class ResourceRatingController { + + @Resource + private ResourceRatingService resourceRatingService; + + /** + * 获取资源评分列表(分页) + */ + @GetMapping("/resource/{resourceId}") + public Result findResourceRatingsPage( + @PathVariable Integer resourceId, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + + // 查询评分列表 + List ratings = resourceRatingService.findByResourceId(resourceId); + + // 构建分页结果 + Map pageResult = new HashMap<>(); + int start = (pageNum - 1) * pageSize; + int end = Math.min(start + pageSize, ratings.size()); + + // 截取当前页数据 + List pageRatings = ratings.subList( + start < ratings.size() ? start : 0, + end < ratings.size() ? end : 0 + ); + + pageResult.put("list", pageRatings); + pageResult.put("total", ratings.size()); + pageResult.put("pageNum", pageNum); + pageResult.put("pageSize", pageSize); + + return Result.success(pageResult); + } + + /** + * 获取资源平均评分 + */ + @GetMapping("/average/{resourceId}") + public Result getAverageRating(@PathVariable Integer resourceId) { + Double averageRating = resourceRatingService.getAverageRating(resourceId); + return Result.success(averageRating); + } + + /** + * 获取资源评分数量 + */ + @GetMapping("/count/{resourceId}") + public Result getRatingCount(@PathVariable Integer resourceId) { + Integer count = resourceRatingService.getRatingCount(resourceId); + return Result.success(count); + } + + /** + * 添加或更新评分 + */ + @PostMapping + public Result add(@RequestBody ResourceRating resourceRating) { + resourceRatingService.rateResource(resourceRating); + return Result.success(); + } + + /** + * 获取当前用户的所有评分 + */ + @GetMapping("/user") + public Result getUserRatings() { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "用户未登录"); + } + + // 验证用户是否为患者 + if (!"patient".equals(currentUser.getRole())) { + return Result.error("403", "只有患者可以查看自己的评分"); + } + + // 获取用户评分列表 + List ratings = resourceRatingService.findByPatientId(currentUser.getId()); + return Result.success(ratings); + } + + /** + * 获取所有评分数据(用于推荐算法) + */ + @GetMapping("/all") + public Result getAllRatings() { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "用户未登录"); + } + + // 验证用户是否为患者 + if (!"patient".equals(currentUser.getRole())) { + return Result.error("403", "只有患者可以访问评分数据"); + } + + // 获取所有评分列表 + List ratings = resourceRatingService.findAll(); + return Result.success(ratings); + } + + /** + * 获取资源的平均评分和评分数量 + */ + @GetMapping("/resource/{resourceId}/stats") + public Result getResourceRatingStats(@PathVariable Integer resourceId) { + Double averageRating = resourceRatingService.getAverageRating(resourceId); + Integer count = resourceRatingService.getRatingCount(resourceId); + + Map stats = new HashMap<>(); + stats.put("averageRating", averageRating); + stats.put("ratingCount", count); + + return Result.success(stats); + } + + /** + * 删除评价 + */ + @DeleteMapping("/{id}") + public Result deleteRating(@PathVariable Integer id) { + resourceRatingService.deleteRating(id); + return Result.success(); + } + + /** + * 根据资源ID查询评分记录 + */ + @GetMapping("/{resourceId}") + public Result findByResourceId(@PathVariable Integer resourceId) { + List ratings = resourceRatingService.findByResourceId(resourceId); + double averageRating = resourceRatingService.getAverageRating(resourceId); + Integer count = resourceRatingService.getRatingCount(resourceId); + + Map result = new HashMap<>(); + result.put("ratings", ratings); + result.put("averageRating", averageRating); + result.put("count", count != null ? count : 0); + + return Result.success(result); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/SymptomRecordController.java b/src/main/java/com/example/controller/SymptomRecordController.java new file mode 100644 index 0000000..35fbe61 --- /dev/null +++ b/src/main/java/com/example/controller/SymptomRecordController.java @@ -0,0 +1,91 @@ +package com.example.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.common.Result; +import com.example.entity.SymptomRecord; +import com.example.service.SymptomRecordService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 症状记录控制器 + */ +@RestController +@RequestMapping("/symptomRecord") +public class SymptomRecordController { + + @Resource + private SymptomRecordService symptomRecordService; + + /** + * 添加症状记录 + */ + @PostMapping + public Result add(@RequestBody SymptomRecord symptomRecord) { + symptomRecordService.add(symptomRecord); + return Result.success(); + } + + /** + * 更新症状记录 + */ + @PutMapping + public Result update(@RequestBody SymptomRecord symptomRecord) { + symptomRecordService.update(symptomRecord); + return Result.success(); + } + + /** + * 删除症状记录 + */ + @DeleteMapping("/{id}") + public Result delete(@PathVariable Integer id) { + symptomRecordService.delete(id); + return Result.success(); + } + + /** + * 根据ID查询症状记录 + */ + @GetMapping("/{id}") + public Result getById(@PathVariable Integer id) { + SymptomRecord symptomRecord = symptomRecordService.getById(id); + return Result.success(symptomRecord); + } + + /** + * 根据患者ID查询症状记录 + */ + @GetMapping("/patient/{patientId}") + public Result getByPatientId(@PathVariable Integer patientId) { + List list = symptomRecordService.getByPatientId(patientId); + return Result.success(list); + } + + /** + * 查询当前登录患者的症状记录 + */ + @GetMapping("/my") + public Result getMyRecords() { + List list = symptomRecordService.getByPatientId(null); + return Result.success(list); + } + + /** + * 分页查询症状记录 + */ + @GetMapping("/selectPage") + public Result selectPage( + @RequestParam(required = false) String patientName, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + SymptomRecord symptomRecord = new SymptomRecord(); + symptomRecord.setPatientName(patientName); + com.github.pagehelper.PageInfo pageInfo = symptomRecordService.selectPage(symptomRecord, pageNum, pageSize, startDate, endDate); + return Result.success(pageInfo); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/SymptomStatisticsController.java b/src/main/java/com/example/controller/SymptomStatisticsController.java new file mode 100644 index 0000000..b6ea877 --- /dev/null +++ b/src/main/java/com/example/controller/SymptomStatisticsController.java @@ -0,0 +1,66 @@ +package com.example.controller; + +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.service.SymptomRecordService; +import jakarta.annotation.Resource; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.Map; + +/** + * 症状记录统计控制器 + */ +@RestController +@RequestMapping("/symptomStatistics") +public class SymptomStatisticsController { + + @Resource + private SymptomRecordService symptomRecordService; + + /** + * 获取症状记录统计数据 + */ + @GetMapping + public Result getStatistics( + @RequestParam(required = false) Integer patientId, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) { + + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "请先登录"); + } + + Map result; + + // 根据用户角色获取不同的统计数据 + switch (currentUser.getRole()) { + case "admin": + // 管理员可以查看所有数据或指定患者的数据 + result = symptomRecordService.getStatisticsData(patientId, startDate, endDate); + break; + case "doctor": + // 如果请求包含patientId且该患者属于当前医生,则返回该患者的数据 + // 否则返回该医生的所有患者数据 + if (patientId != null) { + result = symptomRecordService.getStatisticsData(patientId, startDate, endDate); + } else { + result = symptomRecordService.getStatisticsByDoctor(currentUser.getId(), startDate, endDate); + } + break; + case "patient": + // 患者只能查看自己的数据 + result = symptomRecordService.getStatisticsData(currentUser.getId(), startDate, endDate); + break; + default: + return Result.error("403", "无权限访问"); + } + + return Result.success(result); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/TreatmentResourceController.java b/src/main/java/com/example/controller/TreatmentResourceController.java new file mode 100644 index 0000000..af79d50 --- /dev/null +++ b/src/main/java/com/example/controller/TreatmentResourceController.java @@ -0,0 +1,220 @@ +package com.example.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.common.Result; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.entity.ResourceCategory; +import com.example.entity.TreatmentResource; +import com.example.service.ResourceCategoryService; +import com.example.service.TreatmentResourceService; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 心理治疗资源控制器 + */ +@RestController +@RequestMapping("/treatmentResource") +public class TreatmentResourceController { + + @Resource + private TreatmentResourceService treatmentResourceService; + + @Resource + private ResourceCategoryService resourceCategoryService; + + /** + * 分页搜索资源 + */ + @GetMapping("/search") + public Result search( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(defaultValue = "") String keyword) { + + // 获取当前用户角色 + Account currentUser = TokenUtils.getCurrentUser(); + String role = currentUser != null ? currentUser.getRole() : "patient"; + + Page page = new Page<>(pageNum, pageSize); + page = treatmentResourceService.search(page, keyword, role); + + return Result.success(page); + } + + /** + * 分页查询 + */ + @GetMapping("/selectPage") + public Result selectPage( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + TreatmentResource resource) { + + PageInfo pageInfo = treatmentResourceService.selectPage(resource, pageNum, pageSize); + return Result.success(pageInfo); + } + + /** + * 根据分类获取资源 + */ + @GetMapping("/category/{categoryId}") + public Result findByCategoryId(@PathVariable Integer categoryId) { + List list = treatmentResourceService.findByCategoryId(categoryId); + return Result.success(list); + } + + /** + * 根据ID获取资源 + */ + @GetMapping("/{id}") + public Result findById(@PathVariable Integer id) { + TreatmentResource resource = treatmentResourceService.findById(id); + treatmentResourceService.incrementVisitCount(id); + return Result.success(resource); + } + + /** + * 新增资源 + */ + @PostMapping + public Result add(@RequestBody TreatmentResource resource) { + treatmentResourceService.add(resource); + return Result.success(); + } + + /** + * 更新资源 + */ + @PutMapping + public Result update(@RequestBody TreatmentResource resource) { + treatmentResourceService.update(resource); + return Result.success(); + } + + /** + * 删除资源 + */ + @DeleteMapping("/{id}") + public Result delete(@PathVariable Integer id) { + treatmentResourceService.delete(id); + return Result.success(); + } + + /** + * 审核资源 + */ + @PutMapping("/audit/{id}") + public Result audit(@PathVariable Integer id, @RequestParam String status) { + treatmentResourceService.changeAuditStatus(id, status); + return Result.success(); + } + + /** + * 下载资源 + */ + @GetMapping("/download/{id}") + public Result download(@PathVariable Integer id) { + TreatmentResource resource = treatmentResourceService.findById(id); + if (resource != null) { + treatmentResourceService.incrementDownloadCount(id); + } + return Result.success(resource); + } + + /** + * 增加资源访问次数(新路径) + */ + @PutMapping("/incrementVisit/{id}") + public Result incrementVisit(@PathVariable Integer id) { + treatmentResourceService.incrementVisitCount(id); + return Result.success(); + } + + /** + * 增加资源下载次数(新路径) + */ + @PutMapping("/incrementDownload/{id}") + public Result incrementDownload(@PathVariable Integer id) { + treatmentResourceService.incrementDownloadCount(id); + return Result.success(); + } + + /** + * 查询医生自己发布的资源 + */ + @GetMapping("/myResources") + public Result myResources( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + TreatmentResource resource) { + + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "未登录"); + } + + // 只有医生可以查询自己发布的资源 + if (!"doctor".equals(currentUser.getRole())) { + return Result.error("403", "无权限查询"); + } + + // 强制设置发布者ID为当前登录用户 + resource.setPublisherId(currentUser.getId()); + + // 使用PageHelper进行分页 + PageInfo pageInfo = treatmentResourceService.findByPublisher(resource, pageNum, pageSize); + + return Result.success(pageInfo); + } + + /** + * 获取资源使用情况统计数据 + */ + @GetMapping("/statistics") + public Result getStatistics() { + return Result.success(treatmentResourceService.getResourceStatistics()); + } + + /** + * 获取所有已审核通过的心理资源 + */ + @GetMapping("/all") + public Result getAllApprovedResources(@RequestParam(required = false) String auditStatus) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + return Result.error("401", "用户未登录"); + } + + // 查询关键词为空,表示获取所有资源 + String keyword = ""; + + // 根据审核状态查询资源 + List resources; + + if (auditStatus != null && !auditStatus.isEmpty()) { + // 使用TreatmentResourceService的selectPage方法 + TreatmentResource queryParam = new TreatmentResource(); + queryParam.setAuditStatus(auditStatus); + // 这里不使用分页,所以传一个很大的pageSize + PageInfo pageInfo = treatmentResourceService.selectPage(queryParam, 1, 1000); + resources = pageInfo.getList(); + } else { + // 默认只返回已通过审核的资源 + TreatmentResource queryParam = new TreatmentResource(); + queryParam.setAuditStatus("已通过"); + // 这里不使用分页,所以传一个很大的pageSize + PageInfo pageInfo = treatmentResourceService.selectPage(queryParam, 1, 1000); + resources = pageInfo.getList(); + } + + return Result.success(resources); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/controller/WebController.java b/src/main/java/com/example/controller/WebController.java new file mode 100644 index 0000000..7a1dc5b --- /dev/null +++ b/src/main/java/com/example/controller/WebController.java @@ -0,0 +1,93 @@ +package com.example.controller; + +import com.example.common.config.TokenUtils; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.poi.excel.ExcelUtil; +import cn.hutool.poi.excel.ExcelWriter; +import com.example.common.Result; +import com.example.entity.*; +import com.example.service.*; +import com.example.exception.CustomException; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.*; + +/** + * 描述:系统用户操作相关接口 + */ +@RestController +public class WebController { + + @Resource + private AdminService adminService; + @Resource + private DoctorService doctorService; + @Resource + private PatientService patientService; + + /** + * 描述:用户登录接口 + */ + @PostMapping("/login") + public Result login(@RequestBody Account account) { + Account loginAccount = null; + if ("admin".equals(account.getRole())) { + loginAccount = adminService.login(account); + } + if ("doctor".equals(account.getRole())) { + loginAccount = doctorService.login(account); + } + if ("patient".equals(account.getRole())) { + loginAccount = patientService.login(account); + } + + return Result.success(loginAccount); + } + + /** + * 描述:用户注册接口 + */ + @PostMapping("/register") + public Result register(@RequestBody Account account) { + if ("admin".equals(account.getRole())) { + adminService.register(account); + } + if ("doctor".equals(account.getRole())) { + doctorService.register(account); + } + if ("patient".equals(account.getRole())) { + patientService.register(account); + } + + return Result.success(); + } + + /** + * 描述:更新密码接口 + */ + @PutMapping("/updatePassword") + public Result updatePassword(@RequestBody Account account) { + if ("admin".equals(account.getRole())) { + adminService.updatePassword(account); + } + if ("doctor".equals(account.getRole())) { + doctorService.updatePassword(account); + } + if ("patient".equals(account.getRole())) { + patientService.updatePassword(account); + } + + return Result.success(); + } +} diff --git a/src/main/java/com/example/entity/Account.java b/src/main/java/com/example/entity/Account.java new file mode 100644 index 0000000..8c70498 --- /dev/null +++ b/src/main/java/com/example/entity/Account.java @@ -0,0 +1,30 @@ +package com.example.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +@Data +public class Account { + private Integer id; + /** 账号 */ + private String username; + /** 密码 */ + private String password; + /** 姓名 */ + private String name; + /** 性别 */ + private String sex; + /** 手机号 */ + private String phone; + /** 邮箱 */ + private String email; + /** 角色 */ + private String role; + @TableField(exist = false) + private String newPassword; + @TableField(exist = false) + private String token; + /** 医生ID (用于患者注册时选择医生) */ + @TableField(exist = false) + private Integer doctorId; +} diff --git a/src/main/java/com/example/entity/Admin.java b/src/main/java/com/example/entity/Admin.java new file mode 100644 index 0000000..3774149 --- /dev/null +++ b/src/main/java/com/example/entity/Admin.java @@ -0,0 +1,33 @@ +package com.example.entity; + +import cn.hutool.core.annotation.Alias; +import java.util.List; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +/** + * 管理员 + */ +@Data +@TableName("admin") +public class Admin extends Account { + + /** id */ + @TableId(type = IdType.AUTO) + private Integer id; + /** 账号 */ + private String username; + /** 密码 */ + private String password; + /** 名称 */ + private String name; + /** 头像 */ + private String avatar; + /** 性别 */ + private String sex; + /** 手机号 */ + private String phone; +} diff --git a/src/main/java/com/example/entity/Announcement.java b/src/main/java/com/example/entity/Announcement.java new file mode 100644 index 0000000..7f44b02 --- /dev/null +++ b/src/main/java/com/example/entity/Announcement.java @@ -0,0 +1,59 @@ +package com.example.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * 公告实体类 + */ +@Data +@TableName("announcement") +public class Announcement { + + /** + * ID + */ + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createTime; + + /** + * 是否置顶 + */ + private Boolean isTop; + + /** + * 状态(1-发布,0-草稿) + */ + private Integer status; + + /** + * 发布人ID + */ + private Integer publisherId; + + /** + * 发布人姓名 + */ + private String publisherName; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/AssessmentReport.java b/src/main/java/com/example/entity/AssessmentReport.java new file mode 100644 index 0000000..5173834 --- /dev/null +++ b/src/main/java/com/example/entity/AssessmentReport.java @@ -0,0 +1,41 @@ +package com.example.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@TableName("assessment_report") +public class AssessmentReport { + + @TableId(type = IdType.AUTO) + private Integer id; + + private Integer patientId; + + private String reportPeriod; + + private LocalDate startDate; + + private LocalDate endDate; + + private String summary; + + private String trendAnalysis; + + private String recommendation; + + private String doctorComment; + + private String status; + + private LocalDateTime createTime; + + // 非数据库字段,用于前端显示 + private String patientName; + private String doctorName; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/ChatMessage.java b/src/main/java/com/example/entity/ChatMessage.java new file mode 100644 index 0000000..640bb2d --- /dev/null +++ b/src/main/java/com/example/entity/ChatMessage.java @@ -0,0 +1,92 @@ +package com.example.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.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.Date; + +/** + * 聊天消息实体类 + */ +@Data +@TableName("chat_message") +@JsonIgnoreProperties(ignoreUnknown = true) // 忽略未知属性 +public class ChatMessage { + + /** + * 消息ID + */ + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 发送者ID + */ + private Integer senderId; + + /** + * 接收者ID + */ + private Integer receiverId; + + /** + * 发送者类型(doctor/patient) + */ + private String senderType; + + /** + * 接收者类型(doctor/patient) + */ + private String receiverType; + + /** + * 消息内容 + */ + private String content; + + /** + * 发送时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date sendTime; + + /** + * 是否已读(0-未读,1-已读) + */ + private Boolean isRead; + + /** + * 阅读时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date readTime; + + /** + * 发送者名称(非数据库字段) + */ + @TableField(exist = false) + private String senderName; + + /** + * 接收者名称(非数据库字段) + */ + @TableField(exist = false) + private String receiverName; + + /** + * 发送者头像(非数据库字段) + */ + @TableField(exist = false) + private String senderAvatar; + + /** + * 临时消息ID(用于前端标识临时消息,非数据库字段) + */ + @TableField(exist = false) + private String tempId; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/Doctor.java b/src/main/java/com/example/entity/Doctor.java new file mode 100644 index 0000000..31a239c --- /dev/null +++ b/src/main/java/com/example/entity/Doctor.java @@ -0,0 +1,41 @@ +package com.example.entity; + +import cn.hutool.core.annotation.Alias; +import java.util.List; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +/** + * 医生 + */ +@Data +@TableName("doctor") +public class Doctor extends Account { + + /** id */ + @TableId(type = IdType.AUTO) + private Integer id; + /** 账号 */ + private String username; + /** 密码 */ + private String password; + /** 名称 */ + private String name; + /** 头像 */ + private String avatar; + /** 性别 */ + private String sex; + /** 手机号 */ + private String phone; + /** 邮箱 */ + private String email; + /** 执业证书编号 */ + private String certificateNo; + /** 所属医院 */ + private String hospital; + /** 科室 */ + private String department; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/DoctorPatient.java b/src/main/java/com/example/entity/DoctorPatient.java new file mode 100644 index 0000000..ad37046 --- /dev/null +++ b/src/main/java/com/example/entity/DoctorPatient.java @@ -0,0 +1,56 @@ +package com.example.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 lombok.Data; + +import java.util.Date; + +@TableName("doctor_patient") +@Data +public class DoctorPatient { + /** + * ID + */ + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 医生ID + */ + private Integer doctorId; + + /** + * 患者ID + */ + private Integer patientId; + + /** + * 关系建立时间 + */ + private Date relationshipStart; + + /** + * 状态(正常/已结束) + */ + private String status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 医生信息 + */ + @TableField(exist = false) + private Doctor doctor; + + /** + * 患者信息 + */ + @TableField(exist = false) + private Patient patient; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/Patient.java b/src/main/java/com/example/entity/Patient.java new file mode 100644 index 0000000..902198b --- /dev/null +++ b/src/main/java/com/example/entity/Patient.java @@ -0,0 +1,35 @@ +package com.example.entity; + +import cn.hutool.core.annotation.Alias; +import java.util.List; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +/** + * 患者 + */ +@Data +@TableName("patient") +public class Patient extends Account { + + /** id */ + @TableId(type = IdType.AUTO) + private Integer id; + /** 账号 */ + private String username; + /** 密码 */ + private String password; + /** 名称 */ + private String name; + /** 头像 */ + private String avatar; + /** 性别 */ + private String sex; + /** 手机号 */ + private String phone; + /** 邮箱 */ + private String email; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/ResourceCategory.java b/src/main/java/com/example/entity/ResourceCategory.java new file mode 100644 index 0000000..535165b --- /dev/null +++ b/src/main/java/com/example/entity/ResourceCategory.java @@ -0,0 +1,17 @@ +package com.example.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("resource_category") +public class ResourceCategory { + + @TableId(type = IdType.AUTO) + private Integer id; + private String name; + private String description; + private Integer parentId; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/ResourceRating.java b/src/main/java/com/example/entity/ResourceRating.java new file mode 100644 index 0000000..ec89025 --- /dev/null +++ b/src/main/java/com/example/entity/ResourceRating.java @@ -0,0 +1,57 @@ +package com.example.entity; + +import java.time.LocalDateTime; + +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 lombok.Data; + +/** + * 资源评分实体类 + */ +@Data +@TableName("resource_rating") +public class ResourceRating { + + /** + * ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + /** + * 资源ID + */ + private Integer resourceId; + + /** + * 患者ID + */ + private Integer patientId; + + /** + * 评分(1-5) + */ + private Integer rating; + + /** + * 评价内容 + */ + private String comment; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 患者姓名 (非数据库字段) + */ + @TableField(exist = false) + private String patientName; + + @TableField(exist = false) + private String resourceTitle; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/SymptomRecord.java b/src/main/java/com/example/entity/SymptomRecord.java new file mode 100644 index 0000000..7eb6176 --- /dev/null +++ b/src/main/java/com/example/entity/SymptomRecord.java @@ -0,0 +1,121 @@ +package com.example.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 lombok.Data; + +import java.util.Date; + +@TableName("symptom_record") +@Data +public class SymptomRecord { + /** + * ID + */ + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 患者ID + */ + private Integer patientId; + + /** + * 症状描述 + */ + private String symptomDesc; + + /** + * 严重程度等级(1-10) + */ + private Integer severityLevel; + + /** + * 情绪基线值(1-10) + */ + private Integer emotionScore; + + /** + * 活力持续时间(小时) + */ + private Integer energyDuration; + + /** + * 兴趣恢复度(百分比) + */ + private Integer interestRecovery; + + /** + * 入睡所需时间(分钟) + */ + private Integer sleepTime; + + /** + * 夜间觉醒次数 + */ + private Integer wakeCount; + + /** + * 晨醒时间 + */ + private String morningWake; + + /** + * 进食完成度(百分比) + */ + private Integer mealCompletion; + + /** + * 疼痛位置 + */ + private String painLocation; + + /** + * 疼痛强度(1-10) + */ + private Integer painIntensity; + + /** + * 短期记忆测试成功数(0-3) + */ + private Integer memoryTest; + + /** + * 决策耗时(分钟) + */ + private Integer decisionTime; + + /** + * 注意力跨度(分钟) + */ + private Integer attentionSpan; + + /** + * 自杀意念频率(次数) + */ + private Integer suicidalThought; + + /** + * 记录时间 + */ + private Date recordTime; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 患者信息(非数据库字段) + */ + @TableField(exist = false) + private Patient patient; + + /** + * 患者名称(非数据库字段,用于搜索) + */ + @TableField(exist = false) + private String patientName; +} \ No newline at end of file diff --git a/src/main/java/com/example/entity/TreatmentResource.java b/src/main/java/com/example/entity/TreatmentResource.java new file mode 100644 index 0000000..d4eccae --- /dev/null +++ b/src/main/java/com/example/entity/TreatmentResource.java @@ -0,0 +1,33 @@ +package com.example.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 lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("treatment_resource") +public class TreatmentResource { + + @TableId(type = IdType.AUTO) + private Integer id; + private String title; + private Integer categoryId; + private String content; + private String resourceUrl; + private String applicableSymptoms; + private Integer visitCount; + private Integer downloadCount; + private Integer publisherId; + private String auditStatus; + private LocalDateTime createTime; + + @TableField(exist = false) + private String categoryName; + + @TableField(exist = false) + private String publisherName; +} \ No newline at end of file diff --git a/src/main/java/com/example/exception/CustomException.java b/src/main/java/com/example/exception/CustomException.java new file mode 100644 index 0000000..a4116f3 --- /dev/null +++ b/src/main/java/com/example/exception/CustomException.java @@ -0,0 +1,35 @@ +package com.example.exception; + +import com.example.common.enums.ResultCodeEnum; + +public class CustomException extends RuntimeException { + private String code; + private String msg; + + public CustomException(ResultCodeEnum resultCodeEnum) { + this.code = resultCodeEnum.code; + this.msg = resultCodeEnum.msg; + } + + public CustomException(String code, String msg) { + this.code = code; + this.msg = msg; + } + + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/src/main/java/com/example/exception/GlobalExceptionHandler.java b/src/main/java/com/example/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..fd8eb35 --- /dev/null +++ b/src/main/java/com/example/exception/GlobalExceptionHandler.java @@ -0,0 +1,30 @@ +package com.example.exception; + +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; +import com.example.common.Result; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +@ControllerAdvice(basePackages="com.example.controller") +public class GlobalExceptionHandler { + + private static final Log log = LogFactory.get(); + + + //统一异常处理@ExceptionHandler,主要用于Exception + @ExceptionHandler(Exception.class) + @ResponseBody//返回json串 + public Result error(HttpServletRequest request, Exception e){ + log.error("异常信息:",e); + return Result.error(); + } + + @ExceptionHandler(CustomException.class) + @ResponseBody//返回json串 + public Result customError(HttpServletRequest request, CustomException e){ + return Result.error(e.getCode(), e.getMsg()); + } +} diff --git a/src/main/java/com/example/mapper/AdminMapper.java b/src/main/java/com/example/mapper/AdminMapper.java new file mode 100644 index 0000000..ef38319 --- /dev/null +++ b/src/main/java/com/example/mapper/AdminMapper.java @@ -0,0 +1,32 @@ +package com.example.mapper; + +import com.example.entity.*; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; + +public interface AdminMapper extends BaseMapper { + + /** + * 查询所有 + */ + List selectAll(Admin admin); + + /** + * 根据ID查询 + */ + Admin selectById(Integer id); + + /** + * 删除 + */ + int deleteById(Integer id); + + @Select("select * from admin where `username` = #{name}") + Admin selectByUsername(@Param("name") String userName); + + + +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/AnnouncementMapper.java b/src/main/java/com/example/mapper/AnnouncementMapper.java new file mode 100644 index 0000000..454d56f --- /dev/null +++ b/src/main/java/com/example/mapper/AnnouncementMapper.java @@ -0,0 +1,41 @@ +package com.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.entity.Announcement; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 公告Mapper + */ +public interface AnnouncementMapper extends BaseMapper { + + /** + * 查询所有已发布的公告 + * @return 公告列表 + */ + @Select("SELECT * FROM announcement WHERE status = 1 ORDER BY is_top DESC, create_time DESC") + List selectAllPublished(); + + /** + * 查询所有已发布的公告(分页) + * @param title 标题关键字 + * @return 公告列表 + */ + @Select("") + List selectAllPublishedWithKeyword(@Param("title") String title); + + /** + * 查询所有公告(包括草稿,仅管理员可用) + * @param title 标题关键字 + * @return 公告列表 + */ + @Select("") + List selectAllForAdmin(@Param("title") String title); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/AssessmentReportMapper.java b/src/main/java/com/example/mapper/AssessmentReportMapper.java new file mode 100644 index 0000000..b1bf782 --- /dev/null +++ b/src/main/java/com/example/mapper/AssessmentReportMapper.java @@ -0,0 +1,50 @@ +package com.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.entity.AssessmentReport; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +public interface AssessmentReportMapper extends BaseMapper { + + /** + * 查询患者的评估报告列表 + */ + @Select("SELECT ar.*, p.name as patientName " + + "FROM assessment_report ar " + + "LEFT JOIN patient p ON ar.patient_id = p.id " + + "WHERE ar.patient_id = #{patientId} " + + "ORDER BY ar.create_time DESC") + List selectByPatientId(@Param("patientId") Integer patientId); + + /** + * 查询医生负责的患者的评估报告列表 + */ + @Select("SELECT ar.*, p.name as patientName " + + "FROM assessment_report ar " + + "LEFT JOIN patient p ON ar.patient_id = p.id " + + "INNER JOIN doctor_patient dp ON ar.patient_id = dp.patient_id " + + "WHERE dp.doctor_id = #{doctorId} " + + "ORDER BY ar.create_time DESC") + List selectByDoctorId(@Param("doctorId") Integer doctorId); + + /** + * 查询所有评估报告(管理员使用) + */ + @Select("SELECT ar.*, p.name as patientName " + + "FROM assessment_report ar " + + "LEFT JOIN patient p ON ar.patient_id = p.id " + + "ORDER BY ar.create_time DESC") + List selectAllReports(); + + /** + * 根据ID查询评估报告 + */ + @Select("SELECT id, patient_id, report_period, start_date, end_date, summary, trend_analysis, " + + "recommendation, doctor_comment, status, create_time " + + "FROM assessment_report " + + "WHERE id = #{id}") + AssessmentReport selectById(@Param("id") Integer id); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/ChatMessageMapper.java b/src/main/java/com/example/mapper/ChatMessageMapper.java new file mode 100644 index 0000000..bd6696f --- /dev/null +++ b/src/main/java/com/example/mapper/ChatMessageMapper.java @@ -0,0 +1,51 @@ +package com.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.entity.ChatMessage; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +@Mapper +public interface ChatMessageMapper extends BaseMapper { + + /** + * 查询两个用户之间的聊天记录 + */ + @Select("SELECT * FROM chat_message WHERE " + + "((sender_id = #{userId1} AND sender_type = #{userType1} AND receiver_id = #{userId2} AND receiver_type = #{userType2}) OR " + + "(sender_id = #{userId2} AND sender_type = #{userType2} AND receiver_id = #{userId1} AND receiver_type = #{userType1})) " + + "ORDER BY send_time ASC") + List selectChatHistory(@Param("userId1") Integer userId1, @Param("userType1") String userType1, + @Param("userId2") Integer userId2, @Param("userType2") String userType2); + + /** + * 将消息标记为已读 + */ + @Update("UPDATE chat_message SET is_read = 1, read_time = NOW() " + + "WHERE receiver_id = #{receiverId} AND receiver_type = #{receiverType} " + + "AND sender_id = #{senderId} AND sender_type = #{senderType} AND is_read = 0") + int markAsRead(@Param("receiverId") Integer receiverId, @Param("receiverType") String receiverType, + @Param("senderId") Integer senderId, @Param("senderType") String senderType); + + /** + * 查询用户的聊天列表(最近联系人) + */ + @Select("SELECT DISTINCT " + + "CASE WHEN sender_id = #{userId} AND sender_type = #{userType} THEN receiver_id ELSE sender_id END AS contact_id, " + + "CASE WHEN sender_id = #{userId} AND sender_type = #{userType} THEN receiver_type ELSE sender_type END AS contact_type " + + "FROM chat_message " + + "WHERE (sender_id = #{userId} AND sender_type = #{userType}) OR (receiver_id = #{userId} AND receiver_type = #{userType}) " + + "ORDER BY MAX(send_time) DESC") + List selectChatContacts(@Param("userId") Integer userId, @Param("userType") String userType); + + /** + * 统计未读消息数 + */ + @Select("SELECT COUNT(*) FROM chat_message WHERE receiver_id = #{receiverId} AND receiver_type = #{receiverType} AND sender_id = #{senderId} AND sender_type = #{senderType} AND is_read = 0") + int countUnreadMessages(@Param("receiverId") Integer receiverId, @Param("receiverType") String receiverType, + @Param("senderId") Integer senderId, @Param("senderType") String senderType); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/DoctorMapper.java b/src/main/java/com/example/mapper/DoctorMapper.java new file mode 100644 index 0000000..bd514a9 --- /dev/null +++ b/src/main/java/com/example/mapper/DoctorMapper.java @@ -0,0 +1,29 @@ +package com.example.mapper; + +import com.example.entity.*; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; + +public interface DoctorMapper extends BaseMapper { + + /** + * 查询所有 + */ + List selectAll(Doctor doctor); + + /** + * 根据ID查询 + */ + Doctor selectById(Integer id); + + /** + * 删除 + */ + int deleteById(Integer id); + + @Select("select * from doctor where `username` = #{name}") + Doctor selectByUsername(@Param("name") String userName); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/DoctorPatientMapper.java b/src/main/java/com/example/mapper/DoctorPatientMapper.java new file mode 100644 index 0000000..2d32e30 --- /dev/null +++ b/src/main/java/com/example/mapper/DoctorPatientMapper.java @@ -0,0 +1,28 @@ +package com.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.entity.DoctorPatient; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface DoctorPatientMapper extends BaseMapper { + + /** + * 根据医生ID查询关联的患者 + * @param doctorId 医生ID + * @param name 患者姓名(可选) + * @return 患者列表 + */ + List selectByDoctorId(@Param("doctorId") Integer doctorId, @Param("name") String name); + + /** + * 根据患者ID查询关联的医生 + * @param patientId 患者ID + * @param name 医生姓名(可选) + * @return 医生列表 + */ + List selectByPatientId(@Param("patientId") Integer patientId, @Param("name") String name); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/PatientMapper.java b/src/main/java/com/example/mapper/PatientMapper.java new file mode 100644 index 0000000..176e225 --- /dev/null +++ b/src/main/java/com/example/mapper/PatientMapper.java @@ -0,0 +1,29 @@ +package com.example.mapper; + +import com.example.entity.*; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.springframework.stereotype.Repository; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; + +public interface PatientMapper extends BaseMapper { + + /** + * 查询所有 + */ + List selectAll(Patient patient); + + /** + * 根据ID查询 + */ + Patient selectById(Integer id); + + /** + * 删除 + */ + int deleteById(Integer id); + + @Select("select * from patient where `username` = #{name}") + Patient selectByUsername(@Param("name") String userName); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/ResourceCategoryMapper.java b/src/main/java/com/example/mapper/ResourceCategoryMapper.java new file mode 100644 index 0000000..8935053 --- /dev/null +++ b/src/main/java/com/example/mapper/ResourceCategoryMapper.java @@ -0,0 +1,20 @@ +package com.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.entity.ResourceCategory; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +public interface ResourceCategoryMapper extends BaseMapper { + + @Select("SELECT * FROM resource_category WHERE parent_id = #{parentId}") + List selectByParentId(Integer parentId); + + @Select("SELECT * FROM resource_category ORDER BY parent_id, id") + List selectAllCategories(); + + @Select("SELECT * FROM resource_category WHERE name LIKE CONCAT('%', #{name}, '%') ORDER BY parent_id, id") + List selectPage(@Param("name") String name); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/ResourceRatingMapper.java b/src/main/java/com/example/mapper/ResourceRatingMapper.java new file mode 100644 index 0000000..e7b369a --- /dev/null +++ b/src/main/java/com/example/mapper/ResourceRatingMapper.java @@ -0,0 +1,46 @@ +package com.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.entity.ResourceRating; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +public interface ResourceRatingMapper extends BaseMapper { + + /** + * 根据资源ID查询评分记录,包含用户名信息 + */ + @Select("SELECT r.*, p.name as patient_name " + + "FROM resource_rating r " + + "LEFT JOIN patient p ON r.patient_id = p.id " + + "WHERE r.resource_id = #{resourceId} " + + "ORDER BY r.create_time DESC") + List selectByResourceId(@Param("resourceId") Integer resourceId); + + /** + * 查询用户是否已对某资源评分 + */ + @Select("SELECT COUNT(*) FROM resource_rating WHERE resource_id = #{resourceId} AND patient_id = #{patientId}") + int countByResourceIdAndPatientId(@Param("resourceId") Integer resourceId, @Param("patientId") Integer patientId); + + /** + * 获取资源的平均评分 + */ + @Select("SELECT IFNULL(AVG(rating), 0) FROM resource_rating WHERE resource_id = #{resourceId}") + double getAverageRating(@Param("resourceId") Integer resourceId); + + /** + * 获取资源的评价数量 + */ + @Select("SELECT COUNT(*) FROM resource_rating WHERE resource_id = #{resourceId}") + Integer getRatingCount(@Param("resourceId") Integer resourceId); + + /** + * 根据资源ID和患者ID查询评分记录 + */ + @Select("SELECT * FROM resource_rating WHERE resource_id = #{resourceId} AND patient_id = #{patientId} LIMIT 1") + ResourceRating findByResourceAndPatient(@Param("resourceId") Integer resourceId, @Param("patientId") Integer patientId); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/SymptomRecordMapper.java b/src/main/java/com/example/mapper/SymptomRecordMapper.java new file mode 100644 index 0000000..a545d61 --- /dev/null +++ b/src/main/java/com/example/mapper/SymptomRecordMapper.java @@ -0,0 +1,36 @@ +package com.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.entity.SymptomRecord; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDate; +import java.util.List; + +/** + * 症状记录Mapper接口 + */ +@Mapper +public interface SymptomRecordMapper extends BaseMapper { + + /** + * 查询症状记录并关联患者信息 + * @param patientId 患者ID (可选) + * @return 症状记录列表 + */ + List selectWithPatient(@Param("patientId") Integer patientId); + + /** + * 根据患者ID和日期范围查询症状记录 + */ + @Select("SELECT * FROM symptom_record " + + "WHERE patient_id = #{patientId} " + + "AND record_time BETWEEN #{startDate} AND #{endDate} " + + "ORDER BY record_time ASC") + List selectByPatientIdAndDateRange( + @Param("patientId") Integer patientId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); +} \ No newline at end of file diff --git a/src/main/java/com/example/mapper/TreatmentResourceMapper.java b/src/main/java/com/example/mapper/TreatmentResourceMapper.java new file mode 100644 index 0000000..9f6793f --- /dev/null +++ b/src/main/java/com/example/mapper/TreatmentResourceMapper.java @@ -0,0 +1,67 @@ +package com.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.entity.TreatmentResource; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +public interface TreatmentResourceMapper extends BaseMapper { + + @Select("SELECT r.*, c.name as category_name " + + "FROM treatment_resource r " + + "LEFT JOIN resource_category c ON r.category_id = c.id " + + "WHERE r.audit_status = '已通过' " + + "AND (r.title LIKE CONCAT('%', #{keyword}, '%') " + + "OR r.applicable_symptoms LIKE CONCAT('%', #{keyword}, '%')) " + + "ORDER BY r.create_time DESC") + Page searchResourcesForPatient(Page page, @Param("keyword") String keyword); + + @Select("SELECT r.*, c.name as category_name " + + "FROM treatment_resource r " + + "LEFT JOIN resource_category c ON r.category_id = c.id " + + "WHERE (r.title LIKE CONCAT('%', #{keyword}, '%') " + + "OR r.applicable_symptoms LIKE CONCAT('%', #{keyword}, '%')) " + + "ORDER BY r.create_time DESC") + Page searchResourcesForAdmin(Page page, @Param("keyword") String keyword); + + @Select("SELECT r.*, c.name as category_name " + + "FROM treatment_resource r " + + "LEFT JOIN resource_category c ON r.category_id = c.id " + + "WHERE (r.title LIKE CONCAT('%', #{keyword}, '%') " + + "OR r.applicable_symptoms LIKE CONCAT('%', #{keyword}, '%')) " + + "ORDER BY r.create_time DESC") + List selectAll(@Param("keyword") String keyword); + + @Select("SELECT r.*, c.name as category_name " + + "FROM treatment_resource r " + + "LEFT JOIN resource_category c ON r.category_id = c.id " + + "WHERE r.audit_status = '已通过' " + + "AND (r.title LIKE CONCAT('%', #{keyword}, '%') " + + "OR r.applicable_symptoms LIKE CONCAT('%', #{keyword}, '%')) " + + "ORDER BY r.create_time DESC") + List selectAllAudited(@Param("keyword") String keyword); + + @Select("SELECT r.*, c.name as category_name " + + "FROM treatment_resource r " + + "LEFT JOIN resource_category c ON r.category_id = c.id " + + "WHERE r.category_id = #{categoryId} " + + "AND r.audit_status = '已通过' " + + "ORDER BY r.create_time DESC") + List selectByCategoryId(@Param("categoryId") Integer categoryId); + + @Update("UPDATE treatment_resource SET visit_count = visit_count + 1 WHERE id = #{id}") + int incrementVisitCount(@Param("id") Integer id); + + @Update("UPDATE treatment_resource SET download_count = download_count + 1 WHERE id = #{id}") + int incrementDownloadCount(@Param("id") Integer id); + + /** + * 根据发布者ID查询所有资源 + */ + @Select("SELECT * FROM treatment_resource WHERE publisher_id = #{publisherId} AND (title LIKE CONCAT('%', #{keyword}, '%') OR applicable_symptoms LIKE CONCAT('%', #{keyword}, '%'))") + List selectByPublisher(@Param("publisherId") Integer publisherId, @Param("keyword") String keyword); +} \ No newline at end of file diff --git a/src/main/java/com/example/service/AIChatService.java b/src/main/java/com/example/service/AIChatService.java new file mode 100644 index 0000000..f7af1a6 --- /dev/null +++ b/src/main/java/com/example/service/AIChatService.java @@ -0,0 +1,171 @@ +package com.example.service; + +import com.example.common.config.TokenUtils; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.Account; +import com.example.exception.CustomException; +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Service +public class AIChatService { + + private static final Logger log = LoggerFactory.getLogger(AIChatService.class); + + @Resource + private RestTemplate restTemplate; + + @Value("${dashscope.api-key}") + private String apiKey; + + @Value("${dashscope.model}") + private String model; + + private final String API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"; + + /** + * 发送消息到通义千问API并获取回复 + */ + public String sendMessage(String message) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + log.info("准备发送消息到通义千问API,用户ID: {}, 消息内容: {}", currentUser.getId(), message); + + // 构建请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + apiKey); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("model", model); + + // 添加参数 + Map parameters = new HashMap<>(); + parameters.put("temperature", 0.7); + parameters.put("top_p", 0.8); + parameters.put("result_format", "message"); + requestBody.put("parameters", parameters); + + // 添加用户消息 + Map input = new HashMap<>(); + List> messages = new ArrayList<>(); + + Map userMessage = new HashMap<>(); + userMessage.put("role", "user"); + userMessage.put("content", message); + messages.add(userMessage); + + input.put("messages", messages); + requestBody.put("input", input); + + try { + // 记录完整请求 + log.info("发送请求到API: {}", API_URL); + log.info("请求体: {}", requestBody); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange(API_URL, HttpMethod.POST, requestEntity, Map.class); + + // 记录响应 + log.info("API响应状态码: {}", response.getStatusCode()); + log.debug("API响应内容: {}", response.getBody()); + + if (response.getStatusCode() == HttpStatus.OK) { + Map responseBody = response.getBody(); + if (responseBody != null) { + // 检查错误字段 + if (responseBody.containsKey("code") && !responseBody.get("code").equals(200) && responseBody.containsKey("message")) { + String errorMessage = (String) responseBody.get("message"); + log.error("API返回错误: {}", errorMessage); + return "API错误: " + errorMessage; + } + + // 尝试多种可能的响应格式 + String content = extractContentFromResponse(responseBody); + if (content != null) { + return content; + } + } + + log.warn("无法从API响应中提取内容: {}", responseBody); + return "抱歉,我无法理解您的问题。"; + } else { + log.error("API响应错误状态码: {}", response.getStatusCode()); + throw new CustomException(ResultCodeEnum.SYSTEM_ERROR.code, "AI服务异常,请稍后再试"); + } + } catch (Exception e) { + log.error("调用通义千问API出错: ", e); + throw new CustomException(ResultCodeEnum.SYSTEM_ERROR.code, "AI服务异常: " + e.getMessage()); + } + } + + /** + * 尝试从不同格式的响应中提取内容 + */ + private String extractContentFromResponse(Map responseBody) { + // 格式1: output.choices[0].message.content + try { + if (responseBody.containsKey("output")) { + Map output = (Map) responseBody.get("output"); + if (output.containsKey("choices")) { + List> choices = (List>) output.get("choices"); + if (choices != null && !choices.isEmpty()) { + Map choice = choices.get(0); + if (choice.containsKey("message")) { + Map message = (Map) choice.get("message"); + if (message.containsKey("content")) { + return (String) message.get("content"); + } + } + } + } + } + } catch (Exception e) { + log.warn("尝试格式1提取内容失败: ", e); + } + + // 格式2: data.choices[0].content + try { + if (responseBody.containsKey("data")) { + Map data = (Map) responseBody.get("data"); + if (data.containsKey("choices")) { + List> choices = (List>) data.get("choices"); + if (choices != null && !choices.isEmpty()) { + Map choice = choices.get(0); + if (choice.containsKey("content")) { + return (String) choice.get("content"); + } + } + } + } + } catch (Exception e) { + log.warn("尝试格式2提取内容失败: ", e); + } + + // 格式3: result + try { + if (responseBody.containsKey("result")) { + return (String) responseBody.get("result"); + } + } catch (Exception e) { + log.warn("尝试格式3提取内容失败: ", e); + } + + // 记录完整响应,便于调试 + log.info("响应体解析失败,完整响应: {}", responseBody); + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/AdminService.java b/src/main/java/com/example/service/AdminService.java new file mode 100644 index 0000000..ea9163e --- /dev/null +++ b/src/main/java/com/example/service/AdminService.java @@ -0,0 +1,134 @@ +package com.example.service; + +import cn.hutool.core.util.ObjectUtil; +import com.example.common.Constants; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.*; +import com.example.exception.CustomException; +import com.example.mapper.*; +import com.example.common.config.TokenUtils; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Service +public class AdminService { + + @Resource + private AdminMapper adminMapper; + + /** + * 新增 + */ + public void add(Admin admin) { + // 唯一校验 + Admin dbAdmin = adminMapper.selectByUsername(admin.getUsername()); + if (ObjectUtil.isNotNull(dbAdmin)) { + throw new CustomException(ResultCodeEnum.USER_EXIST_ERROR); + } + if (ObjectUtil.isEmpty(admin.getPassword())) { + admin.setPassword(Constants.USER_DEFAULT_PASSWORD); + } + if (ObjectUtil.isEmpty(admin.getName())) { + admin.setName(admin.getUsername()); + } + admin.setRole("admin"); + adminMapper.insert(admin); + } + + /** + * 删除 + */ + public void deleteById(Integer id) { + adminMapper.deleteById(id); + } + + /** + * 批量删除 + */ + public void deleteBatch(List ids) { + for (Integer id : ids) { + adminMapper.deleteById(id); + } + } + + /** + * 修改 + */ + public void updateById(Admin admin) { + adminMapper.updateById(admin); + } + + /** + * 根据ID查询 + */ + public Admin selectById(Integer id) { + return adminMapper.selectById(id); + } + + /** + * 查询所有 + */ + public List selectAll(Admin admin) { + return adminMapper.selectAll(admin); + } + + /** + * 分页查询 + */ + public PageInfo selectPage(Admin admin, Integer pageNum, Integer pageSize) { + + PageHelper.startPage(pageNum, pageSize); + List list = adminMapper.selectAll(admin); + + return PageInfo.of(list); + } + + /** + * 注册 + */ + public void register(Account account) { + Admin admin = new Admin(); + admin.setUsername(account.getUsername()); + admin.setPassword(account.getPassword()); + this.add(admin); + } + + public Admin login(Account account) { + Admin dbAdmin = adminMapper.selectByUsername(account.getUsername()); + if (ObjectUtil.isNull(dbAdmin)) { + throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR); + } + if (!account.getPassword().equals(dbAdmin.getPassword())) { + throw new CustomException(ResultCodeEnum.USER_ACCOUNT_ERROR); + } + // 生成token + String tokenData = dbAdmin.getId() + "-admin"; + String token = TokenUtils.genToken(tokenData, dbAdmin.getPassword()); + dbAdmin.setToken(token); + + + return dbAdmin; + } + + /** + * 修改密码 + */ + public void updatePassword(Account account) { + Admin dbAdmin = adminMapper.selectByUsername(account.getUsername()); + if (ObjectUtil.isNull(dbAdmin)) { + throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR); + } + if (!account.getPassword().equals(dbAdmin.getPassword())) { + throw new CustomException(ResultCodeEnum.PARAM_PASSWORD_ERROR); + } + dbAdmin.setPassword(account.getNewPassword()); + adminMapper.updateById(dbAdmin); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/service/AnnouncementService.java b/src/main/java/com/example/service/AnnouncementService.java new file mode 100644 index 0000000..57bfc8d --- /dev/null +++ b/src/main/java/com/example/service/AnnouncementService.java @@ -0,0 +1,164 @@ +package com.example.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.example.common.config.TokenUtils; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.Account; +import com.example.entity.Announcement; +import com.example.exception.CustomException; +import com.example.mapper.AnnouncementMapper; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +/** + * 公告服务类 + */ +@Service +public class AnnouncementService { + + @Resource + private AnnouncementMapper announcementMapper; + + /** + * 添加公告 + * @param announcement 公告信息 + */ + public void add(Announcement announcement) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + if (!"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "只有管理员可以发布公告"); + } + + // 设置默认值 + if (announcement.getIsTop() == null) { + announcement.setIsTop(false); + } + + if (announcement.getStatus() == null) { + announcement.setStatus(0); // 默认为草稿状态 + } + + announcement.setCreateTime(new Date()); + announcement.setPublisherId(currentUser.getId()); + announcement.setPublisherName(currentUser.getName()); + + announcementMapper.insert(announcement); + } + + /** + * 更新公告 + * @param announcement 公告信息 + */ + public void update(Announcement announcement) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + if (!"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "只有管理员可以修改公告"); + } + + // 获取原公告 + Announcement dbAnnouncement = announcementMapper.selectById(announcement.getId()); + if (dbAnnouncement == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "公告不存在"); + } + + // 只更新部分字段,保留创建时间等信息 + dbAnnouncement.setTitle(announcement.getTitle()); + dbAnnouncement.setContent(announcement.getContent()); + dbAnnouncement.setIsTop(announcement.getIsTop()); + dbAnnouncement.setStatus(announcement.getStatus()); + + announcementMapper.updateById(dbAnnouncement); + } + + /** + * 删除公告 + * @param id 公告ID + */ + public void delete(Integer id) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + if (!"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "只有管理员可以删除公告"); + } + + announcementMapper.deleteById(id); + } + + /** + * 获取公告详情 + * @param id 公告ID + * @return 公告信息 + */ + public Announcement getById(Integer id) { + return announcementMapper.selectById(id); + } + + /** + * 获取所有已发布的公告(分页,所有用户可访问) + * @param pageNum 页码 + * @param pageSize 每页大小 + * @param title 标题关键字 + * @return 分页公告列表 + */ + public PageInfo getPublishedPage(Integer pageNum, Integer pageSize, String title) { + PageHelper.startPage(pageNum, pageSize); + List list = announcementMapper.selectAllPublishedWithKeyword(title); + return new PageInfo<>(list); + } + + /** + * 获取所有公告(分页,包括草稿,仅管理员可用) + * @param pageNum 页码 + * @param pageSize 每页大小 + * @param title 标题关键字 + * @return 分页公告列表 + */ + public PageInfo getAllForAdmin(Integer pageNum, Integer pageSize, String title) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + if (!"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "只有管理员可以查看所有公告"); + } + + PageHelper.startPage(pageNum, pageSize); + List list = announcementMapper.selectAllForAdmin(title); + return new PageInfo<>(list); + } + + /** + * 获取最新的几条已发布公告(用于首页展示) + * @param limit 限制条数 + * @return 公告列表 + */ + public List getLatestPublished(Integer limit) { + PageHelper.startPage(1, limit); + return announcementMapper.selectAllPublished(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/AssessmentReportService.java b/src/main/java/com/example/service/AssessmentReportService.java new file mode 100644 index 0000000..eb9555a --- /dev/null +++ b/src/main/java/com/example/service/AssessmentReportService.java @@ -0,0 +1,530 @@ +package com.example.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.example.common.enums.ResultCodeEnum; +import com.example.common.config.TokenUtils; +import com.example.entity.Account; +import com.example.entity.AssessmentReport; +import com.example.entity.Patient; +import com.example.entity.SymptomRecord; +import com.example.exception.CustomException; +import com.example.mapper.AssessmentReportMapper; +import com.example.mapper.SymptomRecordMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.scheduling.annotation.Scheduled; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Date; +import java.util.DoubleSummaryStatistics; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.HashMap; + +@Service +public class AssessmentReportService { + + @Resource + private AssessmentReportMapper assessmentReportMapper; + + @Resource + private SymptomRecordMapper symptomRecordMapper; + + @Resource + private PatientService patientService; + + /** + * 生成评估报告 + * @param patientId 患者ID + * @param reportPeriod 报告周期类型("week"/"month") + * @return 生成的评估报告 + */ + public AssessmentReport generateReport(Integer patientId, String reportPeriod) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + String role = currentUser.getRole(); + if (!"admin".equals(role) && !"doctor".equals(role) && + (!"patient".equals(role) || !currentUser.getId().equals(patientId))) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权生成该患者的评估报告"); + } + + // 设置报告时间范围 + LocalDate endDate = LocalDate.now(); + LocalDate startDate; + + if ("week".equals(reportPeriod)) { + startDate = endDate.minusWeeks(1); + } else if ("month".equals(reportPeriod)) { + startDate = endDate.minusMonths(1); + } else { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无效的报告周期类型"); + } + + // 获取时间范围内的症状记录 + List records = symptomRecordMapper.selectByPatientIdAndDateRange( + patientId, startDate, endDate); + + if (records.isEmpty()) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "所选时间范围内没有症状记录"); + } + + // 创建评估报告 + AssessmentReport report = new AssessmentReport(); + report.setPatientId(patientId); + report.setReportPeriod("week".equals(reportPeriod) ? "周报" : "月报"); + report.setStartDate(startDate); + report.setEndDate(endDate); + report.setStatus("未读"); + report.setCreateTime(LocalDateTime.now()); + + // 生成报告内容 + generateReportContent(report, records); + + // 保存报告 + assessmentReportMapper.insert(report); + + return report; + } + + /** + * 生成报告内容 + */ + private void generateReportContent(AssessmentReport report, List records) { + // 计算统计数据 + DoubleSummaryStatistics severityStats = records.stream() + .map(SymptomRecord::getSeverityLevel) + .filter(val -> val != null) + .mapToDouble(Integer::doubleValue) + .summaryStatistics(); + + DoubleSummaryStatistics emotionStats = records.stream() + .map(SymptomRecord::getEmotionScore) + .filter(val -> val != null) + .mapToDouble(Integer::doubleValue) + .summaryStatistics(); + + DoubleSummaryStatistics sleepStats = records.stream() + .map(SymptomRecord::getSleepTime) + .filter(val -> val != null) + .mapToDouble(Integer::doubleValue) + .summaryStatistics(); + + // 生成总体评估 + StringBuilder summary = new StringBuilder(); + summary.append("本") + .append(report.getReportPeriod()) + .append("期间(") + .append(report.getStartDate().format(DateTimeFormatter.ISO_LOCAL_DATE)) + .append(" 至 ") + .append(report.getEndDate().format(DateTimeFormatter.ISO_LOCAL_DATE)) + .append("),共记录了 ") + .append(records.size()) + .append(" 条症状数据。\n\n"); + + summary.append("严重程度评分:平均 ") + .append(String.format("%.1f", severityStats.getAverage())) + .append(",最高 ") + .append((int)severityStats.getMax()) + .append(",最低 ") + .append((int)severityStats.getMin()) + .append("。\n"); + + summary.append("情绪基线评分:平均 ") + .append(String.format("%.1f", emotionStats.getAverage())) + .append(",最高 ") + .append((int)emotionStats.getMax()) + .append(",最低 ") + .append((int)emotionStats.getMin()) + .append("。\n"); + + summary.append("入睡时间:平均 ") + .append(String.format("%.1f", sleepStats.getAverage())) + .append(" 分钟,最长 ") + .append((int)sleepStats.getMax()) + .append(" 分钟,最短 ") + .append((int)sleepStats.getMin()) + .append(" 分钟。\n"); + + // 设置总体评估 + report.setSummary(summary.toString()); + + // 生成趋势分析 + StringBuilder trend = new StringBuilder(); + + // 按记录时间转换为LocalDate进行分组 + Map> recordsByDate = new HashMap<>(); + for (SymptomRecord record : records) { + Date recordTime = record.getRecordTime(); + if (recordTime != null) { + LocalDate recordDate = new java.sql.Date(recordTime.getTime()).toLocalDate(); + if (!recordsByDate.containsKey(recordDate)) { + recordsByDate.put(recordDate, new ArrayList<>()); + } + recordsByDate.get(recordDate).add(record); + } + } + + // 获取排序后的日期列表 + List dates = new ArrayList<>(recordsByDate.keySet()); + dates.sort(LocalDate::compareTo); + + if (dates.size() > 1) { + // 计算首尾日期的平均严重程度 + double firstDaySeverity = recordsByDate.get(dates.get(0)).stream() + .map(SymptomRecord::getSeverityLevel) + .filter(val -> val != null) + .mapToDouble(Integer::doubleValue) + .average() + .orElse(0); + + double lastDaySeverity = recordsByDate.get(dates.get(dates.size() - 1)).stream() + .map(SymptomRecord::getSeverityLevel) + .filter(val -> val != null) + .mapToDouble(Integer::doubleValue) + .average() + .orElse(0); + + // 趋势分析 + trend.append("症状严重程度:"); + if (lastDaySeverity < firstDaySeverity) { + trend.append("整体呈下降趋势,相比初始记录降低了 ") + .append(String.format("%.1f", firstDaySeverity - lastDaySeverity)) + .append(" 点,病情有所好转。"); + } else if (lastDaySeverity > firstDaySeverity) { + trend.append("整体呈上升趋势,相比初始记录增加了 ") + .append(String.format("%.1f", lastDaySeverity - firstDaySeverity)) + .append(" 点,病情有所加重。"); + } else { + trend.append("整体保持稳定,症状严重程度变化不明显。"); + } + trend.append("\n\n"); + + // 情绪基线趋势分析 + double firstDayEmotion = recordsByDate.get(dates.get(0)).stream() + .map(SymptomRecord::getEmotionScore) + .filter(val -> val != null) + .mapToDouble(Integer::doubleValue) + .average() + .orElse(0); + + double lastDayEmotion = recordsByDate.get(dates.get(dates.size() - 1)).stream() + .map(SymptomRecord::getEmotionScore) + .filter(val -> val != null) + .mapToDouble(Integer::doubleValue) + .average() + .orElse(0); + + trend.append("情绪状态:"); + if (lastDayEmotion > firstDayEmotion) { + trend.append("呈积极改善趋势,情绪基线提高了 ") + .append(String.format("%.1f", lastDayEmotion - firstDayEmotion)) + .append(" 点。"); + } else if (lastDayEmotion < firstDayEmotion) { + trend.append("呈下降趋势,情绪基线降低了 ") + .append(String.format("%.1f", firstDayEmotion - lastDayEmotion)) + .append(" 点,需要关注情绪变化。"); + } else { + trend.append("情绪基线保持稳定。"); + } + } else { + trend.append("记录天数不足,无法进行趋势分析。建议持续记录症状,以便更准确地评估病情变化。"); + } + + // 设置趋势分析 + report.setTrendAnalysis(trend.toString()); + + // 生成建议 + StringBuilder recommendation = new StringBuilder(); + + // 基于严重程度的建议 + double avgSeverity = severityStats.getAverage(); + if (avgSeverity >= 7) { + recommendation.append("● 症状严重程度较高,建议及时就医或保持与医生的密切沟通。\n"); + recommendation.append("● 注意休息,避免过度劳累和压力。\n"); + } else if (avgSeverity >= 4) { + recommendation.append("● 症状处于中等程度,建议遵循医嘱进行调整。\n"); + recommendation.append("● 可以尝试适当的放松训练和心理调适方法。\n"); + } else { + recommendation.append("● 症状程度较轻,建议继续保持良好的生活习惯。\n"); + } + + // 基于情绪状态的建议 + double avgEmotion = emotionStats.getAverage(); + if (avgEmotion <= 4) { + recommendation.append("● 情绪基线较低,建议尝试以下方法改善情绪:\n"); + recommendation.append(" - 每天进行至少30分钟的适当运动\n"); + recommendation.append(" - 学习简单的冥想或深呼吸放松技巧\n"); + recommendation.append(" - 与亲友多交流,分享感受\n"); + } + + // 基于睡眠情况的建议 + double avgSleep = sleepStats.getAverage(); + if (avgSleep > 30) { + recommendation.append("● 入睡时间较长,建议改善睡眠习惯:\n"); + recommendation.append(" - 保持规律的睡眠时间表\n"); + recommendation.append(" - 睡前一小时避免使用电子设备\n"); + recommendation.append(" - 睡前可以喝杯温热的牛奶或进行轻度放松活动\n"); + } + + // 一般性建议 + recommendation.append("\n持续记录症状变化,这将帮助您和医生更好地了解病情发展,制定更适合的治疗方案。"); + + // 设置建议 + report.setRecommendation(recommendation.toString()); + } + + /** + * 每周一凌晨2点自动生成周报 + */ + @Scheduled(cron = "0 0 2 ? * MON") + public void generateWeeklyReports() { + List patients = getPatients(); + for (Account patient : patients) { + try { + generateReportForPatient(patient.getId(), "week"); + } catch (Exception e) { + // 记录错误但不中断流程 + System.err.println("为患者 " + patient.getId() + " 生成周报失败: " + e.getMessage()); + } + } + } + + /** + * 每月1日凌晨3点自动生成月报 + */ + @Scheduled(cron = "0 0 3 1 * ?") + public void generateMonthlyReports() { + List patients = getPatients(); + for (Account patient : patients) { + try { + generateReportForPatient(patient.getId(), "month"); + } catch (Exception e) { + // 记录错误但不中断流程 + System.err.println("为患者 " + patient.getId() + " 生成月报失败: " + e.getMessage()); + } + } + } + + /** + * 为患者生成报告(内部方法,用于定时任务) + */ + private AssessmentReport generateReportForPatient(Integer patientId, String reportPeriod) { + LocalDate endDate = LocalDate.now(); + LocalDate startDate; + + if ("week".equals(reportPeriod)) { + startDate = endDate.minusWeeks(1); + } else { + startDate = endDate.minusMonths(1); + } + + // 获取时间范围内的症状记录 + List records = symptomRecordMapper.selectByPatientIdAndDateRange( + patientId, startDate, endDate); + + // 如果没有记录,则不生成报告 + if (records.isEmpty()) { + return null; + } + + // 创建评估报告 + AssessmentReport report = new AssessmentReport(); + report.setPatientId(patientId); + report.setReportPeriod("week".equals(reportPeriod) ? "周报" : "月报"); + report.setStartDate(startDate); + report.setEndDate(endDate); + report.setStatus("未读"); + report.setCreateTime(LocalDateTime.now()); + + // 生成报告内容 + generateReportContent(report, records); + + // 保存报告 + assessmentReportMapper.insert(report); + + return report; + } + + /** + * 获取所有患者列表 + */ + private List getPatients() { + // 使用PatientService获取所有患者 + Patient queryPatient = new Patient(); + return new ArrayList<>(patientService.selectAll(queryPatient)); + } + + /** + * 获取患者的评估报告列表 + */ + public List getReportsByPatientId(Integer patientId) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + String role = currentUser.getRole(); + if (!"admin".equals(role) && !"doctor".equals(role) && + (!"patient".equals(role) || !currentUser.getId().equals(patientId))) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权查看该患者的评估报告"); + } + + return assessmentReportMapper.selectByPatientId(patientId); + } + + /** + * 获取医生负责的患者的评估报告列表 + */ + public List getReportsByDoctorId(Integer doctorId) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + String role = currentUser.getRole(); + if (!"admin".equals(role) && + (!"doctor".equals(role) || !currentUser.getId().equals(doctorId))) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权查看该医生负责的评估报告"); + } + + return assessmentReportMapper.selectByDoctorId(doctorId); + } + + /** + * 获取所有评估报告列表(管理员使用) + */ + public List getAllReports() { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + if (!"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权查看所有评估报告"); + } + + return assessmentReportMapper.selectAllReports(); + } + + /** + * 添加医生批注 + */ + public void addDoctorComment(Integer reportId, String comment) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 验证权限 + if (!"doctor".equals(currentUser.getRole()) && !"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权添加批注"); + } + + // 获取报告 + AssessmentReport report = assessmentReportMapper.selectById(reportId); + if (report == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "报告不存在"); + } + + // 更新批注 + report.setDoctorComment(comment); + assessmentReportMapper.updateById(report); + } + + /** + * 更新报告状态为已读 + */ + public void markAsRead(Integer reportId) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 获取报告 + AssessmentReport report = assessmentReportMapper.selectById(reportId); + if (report == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "报告不存在"); + } + + // 验证权限 + if (!"admin".equals(currentUser.getRole()) && + !"doctor".equals(currentUser.getRole()) && + (!"patient".equals(currentUser.getRole()) || !currentUser.getId().equals(report.getPatientId()))) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权操作该报告"); + } + + // 更新状态 + report.setStatus("已读"); + assessmentReportMapper.updateById(report); + } + + /** + * 通过ID获取单个报告 + * @param reportId 报告ID + * @return 评估报告对象 + */ + public AssessmentReport getReportById(Integer reportId) { + if (reportId == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "报告ID不能为空"); + } + + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 查询报告 + AssessmentReport report = assessmentReportMapper.selectById(reportId); + + if (report == null) { + return null; + } + + // 权限检查 + String role = currentUser.getRole(); + Integer userId = currentUser.getId(); + + // 管理员可以查看所有报告 + if ("admin".equals(role)) { + return report; + } + + // 医生可以查看自己负责的患者的报告 + if ("doctor".equals(role)) { + List doctorReports = getReportsByDoctorId(userId); + boolean hasAccess = doctorReports.stream() + .anyMatch(r -> r.getId().equals(reportId)); + + if (hasAccess) { + return report; + } + } + + // 患者只能查看自己的报告 + if ("patient".equals(role) && userId.equals(report.getPatientId())) { + return report; + } + + // 无权限访问 + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权访问该报告"); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/ChatMessageService.java b/src/main/java/com/example/service/ChatMessageService.java new file mode 100644 index 0000000..d1d980a --- /dev/null +++ b/src/main/java/com/example/service/ChatMessageService.java @@ -0,0 +1,221 @@ +package com.example.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.example.common.config.TokenUtils; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.Account; +import com.example.entity.ChatMessage; +import com.example.entity.Doctor; +import com.example.entity.DoctorPatient; +import com.example.entity.Patient; +import com.example.exception.CustomException; +import com.example.mapper.ChatMessageMapper; +import com.example.mapper.DoctorMapper; +import com.example.mapper.PatientMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class ChatMessageService { + + @Resource + private ChatMessageMapper chatMessageMapper; + + @Resource + private DoctorPatientService doctorPatientService; + + @Resource + private DoctorMapper doctorMapper; + + @Resource + private PatientMapper patientMapper; + + /** + * 发送消息 - 通过HTTP接口调用 + */ + public ChatMessage sendMessage(ChatMessage chatMessage) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 根据当前用户角色设置发送者信息 + chatMessage.setSenderId(currentUser.getId()); + chatMessage.setSenderType(currentUser.getRole()); + + // 检查发送者和接收者是否有关联关系 + checkRelationship(chatMessage.getSenderId(), chatMessage.getSenderType(), + chatMessage.getReceiverId(), chatMessage.getReceiverType()); + + // 设置消息状态 + chatMessage.setSendTime(new Date()); + chatMessage.setIsRead(false); + + // 保存消息 + chatMessageMapper.insert(chatMessage); + + return chatMessage; + } + + /** + * 直接保存消息 - 通过WebSocket调用,不依赖TokenUtils + */ + public ChatMessage directSaveMessage(ChatMessage chatMessage) { + // 检查发送者和接收者是否有关联关系 + checkRelationship(chatMessage.getSenderId(), chatMessage.getSenderType(), + chatMessage.getReceiverId(), chatMessage.getReceiverType()); + + // 保存消息 + chatMessageMapper.insert(chatMessage); + + return chatMessage; + } + + /** + * 获取聊天历史记录 + */ + public List getChatHistory(Integer contactId, String contactType) { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 检查关系 + checkRelationship(currentUser.getId(), currentUser.getRole(), contactId, contactType); + + // 查询聊天记录 + List messages = chatMessageMapper.selectChatHistory( + currentUser.getId(), currentUser.getRole(), contactId, contactType); + + // 将对方发送的未读消息标记为已读 + chatMessageMapper.markAsRead(currentUser.getId(), currentUser.getRole(), contactId, contactType); + + // 填充发送者和接收者的名称 + fillUserInfo(messages); + + return messages; + } + + /** + * 获取联系人列表 + */ + public List> getContactList() { + // 获取当前用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + List> result = new ArrayList<>(); + + // 根据用户角色获取联系人 + if ("doctor".equals(currentUser.getRole())) { + // 医生获取其患者列表 + List relations = doctorPatientService.selectByDoctorId(currentUser.getId(), null); + for (DoctorPatient relation : relations) { + if ("正常".equals(relation.getStatus()) && relation.getPatient() != null) { + Map contact = new HashMap<>(); + contact.put("contactId", relation.getPatientId()); + contact.put("contactType", "patient"); + contact.put("contactName", relation.getPatient().getName()); + contact.put("contactAvatar", relation.getPatient().getAvatar()); + // 查询未读消息数 + int unread = chatMessageMapper.countUnreadMessages( + currentUser.getId(), currentUser.getRole(), relation.getPatientId(), "patient"); + contact.put("unreadCount", unread); + + // 查询最新一条消息 + List lastMessages = chatMessageMapper.selectChatHistory( + currentUser.getId(), currentUser.getRole(), relation.getPatientId(), "patient"); + if (!lastMessages.isEmpty()) { + contact.put("lastMessage", lastMessages.get(lastMessages.size() - 1)); + } + + result.add(contact); + } + } + } else if ("patient".equals(currentUser.getRole())) { + // 患者获取其医生列表 + List relations = doctorPatientService.selectByPatientId(currentUser.getId(), null); + for (DoctorPatient relation : relations) { + if ("正常".equals(relation.getStatus()) && relation.getDoctor() != null) { + Map contact = new HashMap<>(); + contact.put("contactId", relation.getDoctorId()); + contact.put("contactType", "doctor"); + contact.put("contactName", relation.getDoctor().getName()); + contact.put("contactAvatar", relation.getDoctor().getAvatar()); + // 查询未读消息数 + int unread = chatMessageMapper.countUnreadMessages( + currentUser.getId(), currentUser.getRole(), relation.getDoctorId(), "doctor"); + contact.put("unreadCount", unread); + + // 查询最新一条消息 + List lastMessages = chatMessageMapper.selectChatHistory( + currentUser.getId(), currentUser.getRole(), relation.getDoctorId(), "doctor"); + if (!lastMessages.isEmpty()) { + contact.put("lastMessage", lastMessages.get(lastMessages.size() - 1)); + } + + result.add(contact); + } + } + } + + return result; + } + + /** + * 检查用户关系 + */ + private void checkRelationship(Integer senderId, String senderType, Integer receiverId, String receiverType) { + // 检查医生和患者是否存在关联关系 + if ("doctor".equals(senderType) && "patient".equals(receiverType)) { + List relations = doctorPatientService.selectByDoctorId(senderId, null); + boolean hasRelation = relations.stream() + .anyMatch(dp -> dp.getPatientId().equals(receiverId) && "正常".equals(dp.getStatus())); + + if (!hasRelation) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "您与该患者没有关联关系"); + } + } else if ("patient".equals(senderType) && "doctor".equals(receiverType)) { + List relations = doctorPatientService.selectByPatientId(senderId, null); + boolean hasRelation = relations.stream() + .anyMatch(dp -> dp.getDoctorId().equals(receiverId) && "正常".equals(dp.getStatus())); + + if (!hasRelation) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "您与该医生没有关联关系"); + } + } else { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无效的聊天关系"); + } + } + + /** + * 填充用户信息 + */ + private void fillUserInfo(List messages) { + for (ChatMessage message : messages) { + // 填充发送者信息 + if ("doctor".equals(message.getSenderType())) { + Doctor doctor = doctorMapper.selectById(message.getSenderId()); + if (doctor != null) { + message.setSenderName(doctor.getName()); + message.setSenderAvatar(doctor.getAvatar()); + } + } else if ("patient".equals(message.getSenderType())) { + Patient patient = patientMapper.selectById(message.getSenderId()); + if (patient != null) { + message.setSenderName(patient.getName()); + message.setSenderAvatar(patient.getAvatar()); + } + } + + // 可以根据需要填充接收者信息 + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/DoctorPatientService.java b/src/main/java/com/example/service/DoctorPatientService.java new file mode 100644 index 0000000..9b5c16c --- /dev/null +++ b/src/main/java/com/example/service/DoctorPatientService.java @@ -0,0 +1,121 @@ +package com.example.service; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.common.Result; +import com.example.entity.Doctor; +import com.example.entity.DoctorPatient; +import com.example.entity.Patient; +import com.example.mapper.DoctorMapper; +import com.example.mapper.DoctorPatientMapper; +import com.example.mapper.PatientMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +@Service +public class DoctorPatientService { + + @Resource + private DoctorPatientMapper doctorPatientMapper; + + @Resource + private DoctorMapper doctorMapper; + + @Resource + private PatientMapper patientMapper; + + /** + * 新增医生患者关系 + * @param doctorPatient 关系信息 + */ + public void add(DoctorPatient doctorPatient) { + // 检查医生ID是否存在 + Doctor doctor = doctorMapper.selectById(doctorPatient.getDoctorId()); + if (ObjectUtil.isEmpty(doctor)) { + throw new RuntimeException("医生不存在"); + } + + // 检查患者ID是否存在 + Patient patient = patientMapper.selectById(doctorPatient.getPatientId()); + if (ObjectUtil.isEmpty(patient)) { + throw new RuntimeException("患者不存在"); + } + + // 检查关系是否已存在 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(DoctorPatient::getDoctorId, doctorPatient.getDoctorId()) + .eq(DoctorPatient::getPatientId, doctorPatient.getPatientId()) + .eq(DoctorPatient::getStatus, "正常"); + + if (doctorPatientMapper.selectCount(queryWrapper) > 0) { + throw new RuntimeException("医生和患者关系已存在"); + } + + // 设置默认值 + doctorPatient.setRelationshipStart(new Date()); + doctorPatient.setStatus("正常"); + doctorPatient.setCreateTime(new Date()); + + doctorPatientMapper.insert(doctorPatient); + } + + /** + * 更新医生患者关系 + * @param doctorPatient 关系信息 + */ + public void update(DoctorPatient doctorPatient) { + DoctorPatient dbDoctorPatient = doctorPatientMapper.selectById(doctorPatient.getId()); + if (ObjectUtil.isEmpty(dbDoctorPatient)) { + throw new RuntimeException("关系不存在"); + } + + doctorPatientMapper.updateById(doctorPatient); + } + + /** + * 删除医生患者关系 + * @param id 关系ID + */ + public void delete(Integer id) { + doctorPatientMapper.deleteById(id); + } + + /** + * 结束医生患者关系 + * @param id 关系ID + */ + public void endRelationship(Integer id) { + DoctorPatient doctorPatient = doctorPatientMapper.selectById(id); + if (ObjectUtil.isEmpty(doctorPatient)) { + throw new RuntimeException("关系不存在"); + } + + doctorPatient.setStatus("已结束"); + doctorPatientMapper.updateById(doctorPatient); + } + + /** + * 根据医生ID查询患者 + * @param doctorId 医生ID + * @param name 患者姓名(可选) + * @return 患者列表 + */ + public List selectByDoctorId(Integer doctorId, String name) { + return doctorPatientMapper.selectByDoctorId(doctorId, name); + } + + /** + * 根据患者ID查询医生 + * @param patientId 患者ID + * @param name 医生姓名(可选) + * @return 医生列表 + */ + public List selectByPatientId(Integer patientId, String name) { + return doctorPatientMapper.selectByPatientId(patientId, name); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/DoctorService.java b/src/main/java/com/example/service/DoctorService.java new file mode 100644 index 0000000..e0eca51 --- /dev/null +++ b/src/main/java/com/example/service/DoctorService.java @@ -0,0 +1,132 @@ +package com.example.service; + +import cn.hutool.core.util.ObjectUtil; +import com.example.common.Constants; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.*; +import com.example.exception.CustomException; +import com.example.mapper.*; +import com.example.common.config.TokenUtils; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Service +public class DoctorService { + + @Resource + private DoctorMapper doctorMapper; + + /** + * 新增 + */ + public void add(Doctor doctor) { + // 唯一校验 + Doctor dbDoctor = doctorMapper.selectByUsername(doctor.getUsername()); + if (ObjectUtil.isNotNull(dbDoctor)) { + throw new CustomException(ResultCodeEnum.USER_EXIST_ERROR); + } + if (ObjectUtil.isEmpty(doctor.getPassword())) { + doctor.setPassword(Constants.USER_DEFAULT_PASSWORD); + } + if (ObjectUtil.isEmpty(doctor.getName())) { + doctor.setName(doctor.getUsername()); + } + doctor.setRole("doctor"); + doctorMapper.insert(doctor); + } + + /** + * 删除 + */ + public void deleteById(Integer id) { + doctorMapper.deleteById(id); + } + + /** + * 批量删除 + */ + public void deleteBatch(List ids) { + for (Integer id : ids) { + doctorMapper.deleteById(id); + } + } + + /** + * 修改 + */ + public void updateById(Doctor doctor) { + doctorMapper.updateById(doctor); + } + + /** + * 根据ID查询 + */ + public Doctor selectById(Integer id) { + return doctorMapper.selectById(id); + } + + /** + * 查询所有 + */ + public List selectAll(Doctor doctor) { + return doctorMapper.selectAll(doctor); + } + + /** + * 分页查询 + */ + public PageInfo selectPage(Doctor doctor, Integer pageNum, Integer pageSize) { + PageHelper.startPage(pageNum, pageSize); + List list = doctorMapper.selectAll(doctor); + return PageInfo.of(list); + } + + /** + * 注册 + */ + public void register(Account account) { + Doctor doctor = new Doctor(); + doctor.setUsername(account.getUsername()); + doctor.setPassword(account.getPassword()); + doctor.setSex(account.getSex()); + doctor.setName(account.getName()); + doctor.setPhone(account.getPhone()); + this.add(doctor); + } + + public Doctor login(Account account) { + Doctor dbDoctor = doctorMapper.selectByUsername(account.getUsername()); + if (ObjectUtil.isNull(dbDoctor)) { + throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR); + } + if (!account.getPassword().equals(dbDoctor.getPassword())) { + throw new CustomException(ResultCodeEnum.USER_ACCOUNT_ERROR); + } + // 生成token + String tokenData = dbDoctor.getId() + "-doctor"; + String token = TokenUtils.genToken(tokenData, dbDoctor.getPassword()); + dbDoctor.setToken(token); + return dbDoctor; + } + + /** + * 修改密码 + */ + public void updatePassword(Account account) { + Doctor dbDoctor = doctorMapper.selectByUsername(account.getUsername()); + if (ObjectUtil.isNull(dbDoctor)) { + throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR); + } + if (!account.getPassword().equals(dbDoctor.getPassword())) { + throw new CustomException(ResultCodeEnum.PARAM_PASSWORD_ERROR); + } + dbDoctor.setPassword(account.getNewPassword()); + doctorMapper.updateById(dbDoctor); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/PatientService.java b/src/main/java/com/example/service/PatientService.java new file mode 100644 index 0000000..5b3014f --- /dev/null +++ b/src/main/java/com/example/service/PatientService.java @@ -0,0 +1,200 @@ +package com.example.service; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.common.Constants; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.*; +import com.example.exception.CustomException; +import com.example.mapper.*; +import com.example.common.config.TokenUtils; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +@Service +public class PatientService { + + @Resource + private PatientMapper patientMapper; + + @Resource + private DoctorPatientMapper doctorPatientMapper; + + /** + * 新增 + */ + public void add(Patient patient) { + // 校验数据 + if (ObjectUtil.isEmpty(patient.getUsername()) || ObjectUtil.isEmpty(patient.getPassword())) { + throw new CustomException(ResultCodeEnum.PARAM_LOST_ERROR); + } + + // 检查用户名是否存在 + Patient dbPatient = selectByUsername(patient.getUsername()); + if (dbPatient != null) { + throw new CustomException(ResultCodeEnum.USER_EXIST_ERROR); + } + + // 设置默认值 + if (ObjectUtil.isEmpty(patient.getName())) { + patient.setName(patient.getUsername()); + } + patient.setRole("patient"); + + patientMapper.insert(patient); + } + + /** + * 通过ID删除 + */ + public void deleteById(Integer id) { + patientMapper.deleteById(id); + } + + /** + * 批量删除 + */ + public void deleteBatch(List ids) { + for (Integer id : ids) { + patientMapper.deleteById(id); + } + } + + /** + * 更新 + */ + public void updateById(Patient patient) { + patientMapper.updateById(patient); + } + + /** + * 通过ID查询 + */ + public Patient selectById(Integer id) { + return patientMapper.selectById(id); + } + + /** + * 通过用户名查询 + */ + public Patient selectByUsername(String username) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Patient::getUsername, username); + return patientMapper.selectOne(queryWrapper); + } + + /** + * 查询所有 + */ + public List selectAll(Patient patient) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + return patientMapper.selectList(queryWrapper); + } + + /** + * 分页查询 - 按患者信息查询 + */ + public Page selectPage(Patient patient, Integer pageNum, Integer pageSize) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotEmpty(patient.getName())) { + queryWrapper.like(Patient::getName, patient.getName()); + } + + return patientMapper.selectPage(new Page<>(pageNum, pageSize), queryWrapper); + } + + /** + * 分页查询 - 按患者姓名查询 + */ + public Page selectPage(Integer pageNum, Integer pageSize, String name) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotEmpty(name)) { + queryWrapper.like(Patient::getName, name); + } + + return patientMapper.selectPage(new Page<>(pageNum, pageSize), queryWrapper); + } + + /** + * 用户登录 + */ + public Account login(Account account) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Patient::getUsername, account.getUsername()); + queryWrapper.eq(Patient::getPassword, account.getPassword()); + Patient patient = patientMapper.selectOne(queryWrapper); + + if (ObjectUtil.isEmpty(patient)) { + throw new CustomException(ResultCodeEnum.USER_ACCOUNT_ERROR); + } + + // 生成token + String tokenData = patient.getId() + "-" + patient.getRole(); + String token = TokenUtils.genToken(tokenData, patient.getPassword()); + patient.setToken(token); + + return patient; + } + + /** + * 用户注册 + */ + public void register(Account account) { + // 检查用户名是否已存在 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Patient::getUsername, account.getUsername()); + if (patientMapper.selectCount(queryWrapper) > 0) { + throw new CustomException(ResultCodeEnum.USER_EXIST_ERROR); + } + + Patient patient = new Patient(); + patient.setUsername(account.getUsername()); + patient.setPassword(account.getPassword()); + patient.setName(account.getName()); + patient.setSex(account.getSex()); + patient.setPhone(account.getPhone()); + patient.setEmail(account.getEmail()); + patient.setRole("patient"); + add(patient); + + // 如果选择了医生,则建立医患关系 + if (account.getDoctorId() != null) { + DoctorPatient doctorPatient = new DoctorPatient(); + doctorPatient.setDoctorId(account.getDoctorId()); + doctorPatient.setPatientId(patient.getId()); + doctorPatient.setRelationshipStart(new Date()); + doctorPatient.setStatus("正常"); + doctorPatient.setCreateTime(new Date()); + + doctorPatientMapper.insert(doctorPatient); + } + } + + /** + * 修改密码 + */ + public void updatePassword(Account account) { + Patient dbPatient = selectById(account.getId()); + if (ObjectUtil.isEmpty(dbPatient)) { + throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR); + } + + if (!account.getPassword().equals(dbPatient.getPassword())) { + throw new CustomException(ResultCodeEnum.PARAM_PASSWORD_ERROR); + } + + Patient patient = new Patient(); + patient.setId(account.getId()); + patient.setPassword(account.getNewPassword()); + + updateById(patient); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/RecommendationService.java b/src/main/java/com/example/service/RecommendationService.java new file mode 100644 index 0000000..a0211ce --- /dev/null +++ b/src/main/java/com/example/service/RecommendationService.java @@ -0,0 +1,385 @@ +package com.example.service; + +import com.example.common.config.TokenUtils; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.Account; +import com.example.entity.ResourceRating; +import com.example.entity.TreatmentResource; +import com.example.exception.CustomException; +import com.example.mapper.ResourceCategoryMapper; +import com.example.mapper.ResourceRatingMapper; +import com.example.mapper.TreatmentResourceMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 基于协同过滤的推荐服务 + */ +@Service +public class RecommendationService { + + @Resource + private ResourceRatingMapper resourceRatingMapper; + + @Resource + private TreatmentResourceMapper treatmentResourceMapper; + + @Resource + private ResourceCategoryMapper resourceCategoryMapper; + + /** + * 获取针对特定用户的资源推荐 + * @param userId 用户ID + * @param limit 推荐数量限制 + * @return 推荐资源列表 + */ + public List> getRecommendationsForUser(Integer userId, Integer limit) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null || !userId.equals(currentUser.getId())) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 只有患者才能获取推荐 + if (!"patient".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "只有患者才能获取推荐"); + } + + // 获取所有评分数据 + List allRatings = resourceRatingMapper.selectList(null); + + // 获取目标用户的评分记录 + List userRatings = allRatings.stream() + .filter(r -> r.getPatientId().equals(userId)) + .collect(Collectors.toList()); + + // 如果用户没有评分记录,返回热门资源 + if (userRatings.isEmpty()) { + return getPopularResources(limit); + } + + // 计算用户相似度 + List> userSimilarities = calculateUserSimilarity(userId, allRatings); + + // 获取用户已评分的资源ID + Set userRatedResourceIds = userRatings.stream() + .map(ResourceRating::getResourceId) + .collect(Collectors.toSet()); + + // 获取所有已审核通过的资源 + List allResources = treatmentResourceMapper.selectAllAudited(""); + + // 获取用户未评分的资源 + List unratedResources = allResources.stream() + .filter(resource -> !userRatedResourceIds.contains(resource.getId())) + .collect(Collectors.toList()); + + // 对每个未评分资源预测评分并生成推荐 + List> recommendationsWithScores = new ArrayList<>(); + + for (TreatmentResource resource : unratedResources) { + double predictedRating = predictRating(resource.getId(), userSimilarities, allRatings); + + // 只推荐预测评分大于0的资源 + if (predictedRating > 0) { + Map recommendation = new HashMap<>(); + recommendation.put("resource", resource); + recommendation.put("predictedRating", predictedRating); + recommendation.put("similarityScore", Math.min(100, predictedRating * 20)); // 将5分制转换为100分制 + recommendation.put("reason", getRecommendationReason(resource, userSimilarities, allRatings, userRatings, allResources)); + + // 添加分类名称 + com.example.entity.ResourceCategory category = resourceCategoryMapper.selectById(resource.getCategoryId()); + if (category != null) { + recommendation.put("categoryName", category.getName()); + } + + recommendationsWithScores.add(recommendation); + } + } + + // 按预测评分降序排序,取前几个作为推荐 + recommendationsWithScores.sort((a, b) -> + Double.compare((Double) b.get("predictedRating"), (Double) a.get("predictedRating"))); + + // 限制推荐数量 + int resultLimit = Math.min(limit, recommendationsWithScores.size()); + return recommendationsWithScores.subList(0, resultLimit); + } + + /** + * 获取热门资源作为推荐(用于冷启动问题) + */ + private List> getPopularResources(Integer limit) { + // 获取所有已审核通过的资源 + List allResources = treatmentResourceMapper.selectAllAudited(""); + + // 按照访问次数和下载次数之和排序 + allResources.sort((a, b) -> { + int aPopularity = (a.getVisitCount() != null ? a.getVisitCount() : 0) + + (a.getDownloadCount() != null ? a.getDownloadCount() : 0); + int bPopularity = (b.getVisitCount() != null ? b.getVisitCount() : 0) + + (b.getDownloadCount() != null ? b.getDownloadCount() : 0); + return Integer.compare(bPopularity, aPopularity); + }); + + // 转换为推荐格式 + List> recommendations = new ArrayList<>(); + int resultLimit = Math.min(limit, allResources.size()); + + for (int i = 0; i < resultLimit; i++) { + TreatmentResource resource = allResources.get(i); + Map recommendation = new HashMap<>(); + recommendation.put("resource", resource); + recommendation.put("predictedRating", 4.0); // 默认预测评分 + recommendation.put("similarityScore", 80.0); // 默认相似度分数 + recommendation.put("reason", "系统热门资源推荐"); + + // 添加分类名称 + com.example.entity.ResourceCategory category = resourceCategoryMapper.selectById(resource.getCategoryId()); + if (category != null) { + recommendation.put("categoryName", category.getName()); + } + + recommendations.add(recommendation); + } + + return recommendations; + } + + /** + * 计算用户相似度 - 使用余弦相似度 + */ + private List> calculateUserSimilarity(Integer targetUserId, List allRatings) { + // 获取目标用户的评分记录 + List targetUserRatings = allRatings.stream() + .filter(r -> r.getPatientId().equals(targetUserId)) + .collect(Collectors.toList()); + + if (targetUserRatings.isEmpty()) { + return Collections.emptyList(); + } + + // 用户ID列表(去除目标用户) + List userIds = allRatings.stream() + .map(ResourceRating::getPatientId) + .distinct() + .filter(id -> !id.equals(targetUserId)) + .collect(Collectors.toList()); + + // 计算每个用户与目标用户的相似度 + List> similarities = new ArrayList<>(); + + for (Integer userId : userIds) { + // 获取当前用户的评分记录 + List userRatings = allRatings.stream() + .filter(r -> r.getPatientId().equals(userId)) + .collect(Collectors.toList()); + + if (userRatings.isEmpty()) { + continue; + } + + // 找出两个用户共同评分的资源 + Set targetResourceIds = targetUserRatings.stream() + .map(ResourceRating::getResourceId) + .collect(Collectors.toSet()); + + Set userResourceIds = userRatings.stream() + .map(ResourceRating::getResourceId) + .collect(Collectors.toSet()); + + Set commonResourceIds = new HashSet<>(targetResourceIds); + commonResourceIds.retainAll(userResourceIds); + + // 如果没有共同评分的资源,相似度为0 + if (commonResourceIds.isEmpty()) { + continue; + } + + // 提取两个用户对共同资源的评分向量 + double[] targetVector = new double[commonResourceIds.size()]; + double[] userVector = new double[commonResourceIds.size()]; + + int index = 0; + for (Integer resourceId : commonResourceIds) { + double targetRating = targetUserRatings.stream() + .filter(r -> r.getResourceId().equals(resourceId)) + .findFirst() + .map(r -> (double) r.getRating()) + .orElse(0.0); + + double userRating = userRatings.stream() + .filter(r -> r.getResourceId().equals(resourceId)) + .findFirst() + .map(r -> (double) r.getRating()) + .orElse(0.0); + + targetVector[index] = targetRating; + userVector[index] = userRating; + index++; + } + + // 计算余弦相似度 + // 余弦相似度 = (A·B) / (||A|| * ||B||) + double dotProduct = 0.0; + double targetMagnitude = 0.0; + double userMagnitude = 0.0; + + for (int i = 0; i < targetVector.length; i++) { + dotProduct += targetVector[i] * userVector[i]; + targetMagnitude += targetVector[i] * targetVector[i]; + userMagnitude += userVector[i] * userVector[i]; + } + + targetMagnitude = Math.sqrt(targetMagnitude); + userMagnitude = Math.sqrt(userMagnitude); + + // 避免除以零 + double similarity = (targetMagnitude > 0 && userMagnitude > 0) + ? dotProduct / (targetMagnitude * userMagnitude) + : 0; + + Map userSimilarity = new HashMap<>(); + userSimilarity.put("userId", userId); + userSimilarity.put("similarity", similarity); + similarities.add(userSimilarity); + } + + // 按相似度降序排序 + similarities.sort((a, b) -> Double.compare( + (Double) b.get("similarity"), + (Double) a.get("similarity") + )); + + return similarities; + } + + /** + * 预测用户对资源的评分 + */ + private double predictRating(Integer resourceId, List> userSimilarities, + List allRatings) { + // 获取已对该资源评分的相似用户 + List> similarUsersWithRating = userSimilarities.stream() + .filter(s -> allRatings.stream() + .anyMatch(r -> r.getPatientId().equals(s.get("userId")) && + r.getResourceId().equals(resourceId))) + .collect(Collectors.toList()); + + // 如果没有相似用户对该资源评分,返回默认预测值 + if (similarUsersWithRating.isEmpty()) { + return 0; + } + + // 计算加权评分和 + double weightedSum = 0.0; + double similaritySum = 0.0; + + for (Map user : similarUsersWithRating) { + Integer userId = (Integer) user.get("userId"); + double similarity = (Double) user.get("similarity"); + + // 找到该用户对该资源的评分 + double rating = allRatings.stream() + .filter(r -> r.getPatientId().equals(userId) && r.getResourceId().equals(resourceId)) + .findFirst() + .map(r -> (double) r.getRating()) + .orElse(0.0); + + // 加权和 + weightedSum += similarity * rating; + similaritySum += similarity; + } + + // 计算加权平均 + return similaritySum > 0 ? weightedSum / similaritySum : 0; + } + + /** + * 获取推荐理由 + */ + private String getRecommendationReason(TreatmentResource resource, + List> userSimilarities, + List allRatings, + List userRatings, + List allResources) { + // 找出评分最高的相似用户对该资源的评价 + List> topSimilarUsers = userSimilarities.stream() + .limit(3) + .collect(Collectors.toList()); + + // 找出这些用户中有对该资源评分的用户 + List> usersWithRating = topSimilarUsers.stream() + .filter(s -> allRatings.stream() + .anyMatch(r -> r.getPatientId().equals(s.get("userId")) && + r.getResourceId().equals(resource.getId()))) + .collect(Collectors.toList()); + + if (usersWithRating.isEmpty()) { + // 基于内容的推荐理由 + if (!userRatings.isEmpty()) { + // 查找用户评分最高的资源 + ResourceRating topRatedRating = userRatings.stream() + .max(Comparator.comparing(ResourceRating::getRating)) + .orElse(null); + + if (topRatedRating != null) { + // 获取用户最喜欢的资源详情 + Integer topRatedResourceId = topRatedRating.getResourceId(); + TreatmentResource topRatedResource = allResources.stream() + .filter(r -> r.getId().equals(topRatedResourceId)) + .findFirst() + .orElse(null); + + // 如果当前资源与用户最喜欢的资源属于同一分类 + if (topRatedResource != null && resource.getCategoryId().equals(topRatedResource.getCategoryId())) { + return "根据您对同类资源的偏好推荐"; + } + + // 如果当前资源适用的症状与用户评分高的资源相似 + List userPreferredSymptoms = userRatings.stream() + .filter(r -> r.getRating() >= 4) + .map(r -> { + // 获取资源详情 + Optional res = allResources.stream() + .filter(tr -> tr.getId().equals(r.getResourceId())) + .findFirst(); + return res.map(TreatmentResource::getApplicableSymptoms).orElse(""); + }) + .flatMap(s -> Arrays.stream(s.split("[,、]"))) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + + List resourceSymptoms = Arrays.stream( + resource.getApplicableSymptoms() != null + ? resource.getApplicableSymptoms().split("[,、]") + : new String[0]) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + + boolean hasCommonSymptoms = resourceSymptoms.stream() + .anyMatch(userPreferredSymptoms::contains); + + if (hasCommonSymptoms) { + return "包含您可能关注的症状"; + } + } + } + + return "基于系统热门资源推荐"; + } + + // 基于相似用户的推荐理由 + if (usersWithRating.size() == 1) { + return "与您兴趣相似的用户喜欢此资源"; + } + + return usersWithRating.size() + "位与您兴趣相似的用户喜欢此资源"; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/ResourceCategoryService.java b/src/main/java/com/example/service/ResourceCategoryService.java new file mode 100644 index 0000000..3653c53 --- /dev/null +++ b/src/main/java/com/example/service/ResourceCategoryService.java @@ -0,0 +1,88 @@ +package com.example.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.ResourceCategory; +import com.example.entity.TreatmentResource; +import com.example.exception.CustomException; +import com.example.mapper.ResourceCategoryMapper; +import com.example.mapper.TreatmentResourceMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; + +import java.util.List; + +@Service +public class ResourceCategoryService { + + @Resource + private ResourceCategoryMapper resourceCategoryMapper; + + @Resource + private TreatmentResourceMapper treatmentResourceMapper; + + public List findAll() { + return resourceCategoryMapper.selectAllCategories(); + } + + public List findByParentId(Integer parentId) { + return resourceCategoryMapper.selectByParentId(parentId); + } + + public ResourceCategory findById(Integer id) { + return resourceCategoryMapper.selectById(id); + } + + public void add(ResourceCategory category) { + // 检查是否存在同名分类 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ResourceCategory::getName, category.getName()) + .eq(ResourceCategory::getParentId, category.getParentId()); + + if (resourceCategoryMapper.selectCount(wrapper) > 0) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "已存在同名分类"); + } + + resourceCategoryMapper.insert(category); + } + + public void update(ResourceCategory category) { + // 检查是否存在同名分类(排除自身) + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ResourceCategory::getName, category.getName()) + .eq(ResourceCategory::getParentId, category.getParentId()) + .ne(ResourceCategory::getId, category.getId()); + + if (resourceCategoryMapper.selectCount(wrapper) > 0) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "已存在同名分类"); + } + + resourceCategoryMapper.updateById(category); + } + + public void delete(Integer id) { + // 检查是否有子分类 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ResourceCategory::getParentId, id); + + if (resourceCategoryMapper.selectCount(wrapper) > 0) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "该分类下存在子分类,无法删除"); + } + + // 检查是否有关联的资源 + List resources = treatmentResourceMapper.selectByCategoryId(id); + if (resources != null && !resources.isEmpty()) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "该分类下存在资源,无法删除"); + } + + resourceCategoryMapper.deleteById(id); + } + + public PageInfo selectPage(ResourceCategory category, Integer pageNum, Integer pageSize) { + PageHelper.startPage(pageNum, pageSize); + List list = resourceCategoryMapper.selectPage(category.getName() != null ? category.getName() : ""); + return new PageInfo<>(list); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/ResourceRatingService.java b/src/main/java/com/example/service/ResourceRatingService.java new file mode 100644 index 0000000..bf7cbd0 --- /dev/null +++ b/src/main/java/com/example/service/ResourceRatingService.java @@ -0,0 +1,161 @@ +package com.example.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.common.config.TokenUtils; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.Account; +import com.example.entity.ResourceRating; +import com.example.entity.TreatmentResource; +import com.example.exception.CustomException; +import com.example.mapper.ResourceRatingMapper; +import com.example.mapper.TreatmentResourceMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +public class ResourceRatingService { + + @Resource + private ResourceRatingMapper resourceRatingMapper; + + @Resource + private TreatmentResourceMapper treatmentResourceMapper; + + /** + * 添加评分 + */ + public void add(ResourceRating resourceRating) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 只有患者可以添加评分 + if (!"patient".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "只有患者可以评分"); + } + + // 设置患者ID + resourceRating.setPatientId(currentUser.getId()); + + // 检查评分是否有效 + if (resourceRating.getRating() == null || resourceRating.getRating() < 1 || resourceRating.getRating() > 5) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "评分必须在1-5之间"); + } + + // 检查是否已经评分过 + int count = resourceRatingMapper.countByResourceIdAndPatientId(resourceRating.getResourceId(), currentUser.getId()); + if (count > 0) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "您已经评价过该资源"); + } + + // 设置创建时间 + resourceRating.setCreateTime(LocalDateTime.now()); + + // 保存评分 + resourceRatingMapper.insert(resourceRating); + } + + /** + * 根据资源ID查询评分记录 + */ + public List findByResourceId(Integer resourceId) { + return resourceRatingMapper.selectByResourceId(resourceId); + } + + /** + * 获取资源的平均评分 + */ + public double getAverageRating(Integer resourceId) { + return resourceRatingMapper.getAverageRating(resourceId); + } + + public Integer getRatingCount(Integer resourceId) { + return resourceRatingMapper.getRatingCount(resourceId); + } + + public void rateResource(ResourceRating rating) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 只有患者才能评价资源 + if (!"patient".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "只有患者才能评价资源"); + } + + // 检查资源是否存在 + TreatmentResource resource = treatmentResourceMapper.selectById(rating.getResourceId()); + if (resource == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "资源不存在"); + } + + // 检查资源是否已通过审核 + if (!"已通过".equals(resource.getAuditStatus())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "资源未通过审核,无法评价"); + } + + // 检查患者是否已经评价过该资源 + ResourceRating existingRating = resourceRatingMapper.findByResourceAndPatient( + rating.getResourceId(), currentUser.getId()); + + if (existingRating != null) { + // 更新评价 + existingRating.setRating(rating.getRating()); + existingRating.setComment(rating.getComment()); + resourceRatingMapper.updateById(existingRating); + } else { + // 新增评价 + rating.setPatientId(currentUser.getId()); + rating.setCreateTime(LocalDateTime.now()); + resourceRatingMapper.insert(rating); + } + } + + public void deleteRating(Integer id) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 查询评价 + ResourceRating rating = resourceRatingMapper.selectById(id); + if (rating == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "评价不存在"); + } + + // 只有评价的患者本人或管理员才能删除评价 + if (!currentUser.getId().equals(rating.getPatientId()) && !"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权限删除该评价"); + } + + resourceRatingMapper.deleteById(id); + } + + /** + * 根据患者ID查询评分记录 + */ + public List findByPatientId(Integer patientId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ResourceRating::getPatientId, patientId); + queryWrapper.orderByDesc(ResourceRating::getCreateTime); + return resourceRatingMapper.selectList(queryWrapper); + } + + /** + * 查询所有评分记录 + */ + public List findAll() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.orderByDesc(ResourceRating::getCreateTime); + return resourceRatingMapper.selectList(queryWrapper); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/SymptomRecordService.java b/src/main/java/com/example/service/SymptomRecordService.java new file mode 100644 index 0000000..7fae10b --- /dev/null +++ b/src/main/java/com/example/service/SymptomRecordService.java @@ -0,0 +1,735 @@ +package com.example.service; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.common.config.TokenUtils; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.Account; +import com.example.entity.DoctorPatient; +import com.example.entity.SymptomRecord; +import com.example.exception.CustomException; +import com.example.mapper.SymptomRecordMapper; +import com.github.pagehelper.PageHelper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.DoubleSummaryStatistics; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +/** + * 症状记录服务类 + */ +@Service +public class SymptomRecordService { + + @Resource + private SymptomRecordMapper symptomRecordMapper; + + @Resource + private DoctorPatientService doctorPatientService; + + /** + * 添加症状记录 + */ + public void add(SymptomRecord symptomRecord) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR); + } + + // 只有患者可以添加症状记录 + if (!"patient".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.NO_AUTH_ERROR); + } + + // 设置患者ID + symptomRecord.setPatientId(currentUser.getId()); + + // 设置记录时间和创建时间 + if (symptomRecord.getRecordTime() == null) { + symptomRecord.setRecordTime(new Date()); + } + symptomRecord.setCreateTime(new Date()); + + symptomRecordMapper.insert(symptomRecord); + } + + /** + * 更新症状记录 + */ + public void update(SymptomRecord symptomRecord) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR); + } + + // 检查记录是否存在 + SymptomRecord existRecord = symptomRecordMapper.selectById(symptomRecord.getId()); + if (existRecord == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR); + } + + // 权限检查 + checkPermission(currentUser, existRecord.getPatientId()); + + // 更新记录 + symptomRecordMapper.updateById(symptomRecord); + } + + /** + * 删除症状记录 + */ + public void delete(Integer id) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR); + } + + // 检查记录是否存在 + SymptomRecord existRecord = symptomRecordMapper.selectById(id); + if (existRecord == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR); + } + + // 权限检查:患者只能删除自己的记录,医生不能删除记录,管理员可以删除所有记录 + if ("patient".equals(currentUser.getRole())) { + if (!currentUser.getId().equals(existRecord.getPatientId())) { + throw new CustomException(ResultCodeEnum.NO_AUTH_ERROR); + } + } else if ("doctor".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.NO_AUTH_ERROR); + } + + symptomRecordMapper.deleteById(id); + } + + /** + * 根据ID查询症状记录 + */ + public SymptomRecord getById(Integer id) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR); + } + + SymptomRecord record = symptomRecordMapper.selectById(id); + if (record == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR); + } + + // 权限检查 + checkPermission(currentUser, record.getPatientId()); + + return record; + } + + /** + * 根据患者ID查询所有症状记录 + */ + public List getByPatientId(Integer patientId) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR); + } + + // 根据角色处理 + if ("patient".equals(currentUser.getRole())) { + // 患者只能查看自己的记录 + patientId = currentUser.getId(); + } else if ("doctor".equals(currentUser.getRole())) { + // 医生只能查看自己负责的患者记录 + if (patientId != null) { + // 检查该患者是否是该医生负责的 + checkDoctorPatientRelationship(currentUser.getId(), patientId); + } else { + // 获取医生负责的所有患者 + List doctorPatients = doctorPatientService.selectByDoctorId(currentUser.getId(), null); + List patientIds = doctorPatients.stream() + .map(DoctorPatient::getPatientId) + .collect(Collectors.toList()); + + if (patientIds.isEmpty()) { + return new ArrayList<>(); + } + + // 查询这些患者的所有记录 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SymptomRecord::getPatientId, patientIds); + queryWrapper.orderByDesc(SymptomRecord::getRecordTime); + return symptomRecordMapper.selectList(queryWrapper); + } + } + + // 查询记录 + return symptomRecordMapper.selectWithPatient(patientId); + } + + /** + * 分页查询症状记录 + */ + public Page getPage(Integer pageNum, Integer pageSize, String startDate, String endDate) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR); + } + + // 创建查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 根据角色设置查询条件 + if ("patient".equals(currentUser.getRole())) { + // 患者只能查看自己的记录 + queryWrapper.eq(SymptomRecord::getPatientId, currentUser.getId()); + } else if ("doctor".equals(currentUser.getRole())) { + // 医生只能查看自己负责的患者记录 + List doctorPatients = doctorPatientService.selectByDoctorId(currentUser.getId(), null); + List patientIds = doctorPatients.stream() + .map(DoctorPatient::getPatientId) + .collect(Collectors.toList()); + + if (patientIds.isEmpty()) { + // 没有负责的患者,返回空结果 + return new Page<>(pageNum, pageSize); + } + + queryWrapper.in(SymptomRecord::getPatientId, patientIds); + } + + // 添加日期条件 + if (ObjectUtil.isNotEmpty(startDate) && ObjectUtil.isNotEmpty(endDate)) { + Date start = DateUtil.parseDate(startDate); + Date end = DateUtil.parseDate(endDate); + queryWrapper.between(SymptomRecord::getRecordTime, start, end); + } + + // 按记录时间降序排序 + queryWrapper.orderByDesc(SymptomRecord::getRecordTime); + + // 执行分页查询 + return symptomRecordMapper.selectPage(new Page<>(pageNum, pageSize), queryWrapper); + } + + /** + * 检查用户是否有权限操作指定患者的记录 + */ + private void checkPermission(Account currentUser, Integer patientId) { + if ("patient".equals(currentUser.getRole())) { + // 患者只能操作自己的记录 + if (!currentUser.getId().equals(patientId)) { + throw new CustomException(ResultCodeEnum.NO_AUTH_ERROR); + } + } else if ("doctor".equals(currentUser.getRole())) { + // 医生只能操作自己负责的患者的记录 + checkDoctorPatientRelationship(currentUser.getId(), patientId); + } + // 管理员可以操作所有记录 + } + + /** + * 检查医生与患者是否存在关联关系 + */ + private void checkDoctorPatientRelationship(Integer doctorId, Integer patientId) { + List doctorPatients = doctorPatientService.selectByDoctorId(doctorId, null); + boolean isResponsible = doctorPatients.stream() + .anyMatch(dp -> dp.getPatientId().equals(patientId)); + + if (!isResponsible) { + throw new CustomException(ResultCodeEnum.NO_AUTH_ERROR); + } + } + + /** + * 批量查询症状记录关联的患者信息 + */ + public List getRecordsWithPatient(List records) { + if (records == null || records.isEmpty()) { + return records; + } + + // 从记录中提取所有患者ID + List patientIds = records.stream() + .map(SymptomRecord::getPatientId) + .distinct() + .collect(Collectors.toList()); + + // 这里利用Mapper中已有的selectWithPatient方法,查询所有关联患者信息的记录 + List recordsWithPatient = new ArrayList<>(); + for (Integer patientId : patientIds) { + List patientRecords = symptomRecordMapper.selectWithPatient(patientId); + recordsWithPatient.addAll(patientRecords); + } + + // 根据原记录ID匹配,返回带患者信息的记录 + Map recordMap = recordsWithPatient.stream() + .collect(Collectors.toMap(SymptomRecord::getId, record -> record, (a, b) -> a)); + + return records.stream() + .map(record -> { + SymptomRecord recordWithPatient = recordMap.get(record.getId()); + return recordWithPatient != null ? recordWithPatient : record; + }) + .collect(Collectors.toList()); + } + + /** + * 分页查询症状记录(使用PageHelper) + */ + public com.github.pagehelper.PageInfo selectPage(SymptomRecord symptomRecord, Integer pageNum, Integer pageSize, String startDate, String endDate) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR); + } + + // 构建查询条件获取所有符合条件的记录 + List allRecords = new ArrayList<>(); + + // 获取患者名称搜索条件 + final String patientName; + if (symptomRecord != null && ObjectUtil.isNotEmpty(symptomRecord.getPatientName())) { + patientName = symptomRecord.getPatientName().trim().toLowerCase(); + } else { + patientName = ""; + } + + // 根据角色设置查询条件 + if ("patient".equals(currentUser.getRole())) { + // 患者只能查看自己的记录 + symptomRecord = symptomRecord == null ? new SymptomRecord() : symptomRecord; + symptomRecord.setPatientId(currentUser.getId()); + allRecords = symptomRecordMapper.selectWithPatient(currentUser.getId()); + } else if ("doctor".equals(currentUser.getRole())) { + // 医生只能查看自己负责的患者记录 + List doctorPatients = doctorPatientService.selectByDoctorId(currentUser.getId(), null); + List patientIds = doctorPatients.stream() + .map(DoctorPatient::getPatientId) + .collect(Collectors.toList()); + + if (patientIds.isEmpty()) { + // 没有负责的患者,返回空结果 + return new com.github.pagehelper.PageInfo<>(new ArrayList<>()); + } + + // 查询患者ID在列表中的记录,并带上患者信息 + for (Integer patientId : patientIds) { + List patientRecords = symptomRecordMapper.selectWithPatient(patientId); + allRecords.addAll(patientRecords); + } + } else { + // 管理员可以查看所有记录 + // 这里需要查询所有带有患者信息的记录,但由于没有查询全部的接口,我们先查询所有患者ID + List tempRecords = symptomRecordMapper.selectList(null); + List allPatientIds = tempRecords.stream() + .map(SymptomRecord::getPatientId) + .distinct() + .collect(Collectors.toList()); + + // 查询每个患者的记录并带上患者信息 + for (Integer patientId : allPatientIds) { + List patientRecords = symptomRecordMapper.selectWithPatient(patientId); + allRecords.addAll(patientRecords); + } + } + + // 根据患者名称过滤 + if (ObjectUtil.isNotEmpty(patientName)) { + allRecords = allRecords.stream() + .filter(record -> { + if (record.getPatient() != null && ObjectUtil.isNotEmpty(record.getPatient().getName())) { + return record.getPatient().getName().toLowerCase().contains(patientName); + } + return false; + }) + .collect(Collectors.toList()); + } + + // 根据日期过滤 + if (ObjectUtil.isNotEmpty(startDate) && ObjectUtil.isNotEmpty(endDate)) { + Date start = DateUtil.parseDate(startDate); + Date end = DateUtil.parseDate(endDate); + allRecords = allRecords.stream() + .filter(record -> { + Date recordTime = record.getRecordTime(); + return recordTime != null && !recordTime.before(start) && !recordTime.after(end); + }) + .collect(Collectors.toList()); + } + + // 按记录时间降序排序 + allRecords.sort((a, b) -> { + if (a.getRecordTime() == null || b.getRecordTime() == null) { + return 0; + } + return b.getRecordTime().compareTo(a.getRecordTime()); + }); + + // 在内存中执行分页 + int start = (pageNum - 1) * pageSize; + int end = Math.min(start + pageSize, allRecords.size()); + + List pagedRecords; + if (start < allRecords.size()) { + pagedRecords = allRecords.subList(start, end); + } else { + pagedRecords = new ArrayList<>(); + } + + // 创建PageInfo对象 + com.github.pagehelper.PageInfo pageInfo = new com.github.pagehelper.PageInfo<>(pagedRecords); + // 手动设置分页信息 + pageInfo.setPageNum(pageNum); + pageInfo.setPageSize(pageSize); + pageInfo.setTotal(allRecords.size()); + pageInfo.setPages((int) Math.ceil((double) allRecords.size() / pageSize)); + + return pageInfo; + } + + /** + * 获取症状记录统计数据 + * @param patientId 患者ID(可选,为null表示查询所有) + * @param startDate 开始日期(可选) + * @param endDate 结束日期(可选) + * @return 统计数据 + */ + public Map getStatisticsData(Integer patientId, Date startDate, Date endDate) { + Map result = new HashMap<>(); + + try { + // 构建查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (patientId != null) { + queryWrapper.eq(SymptomRecord::getPatientId, patientId); + } + + if (startDate != null) { + queryWrapper.ge(SymptomRecord::getRecordTime, startDate); + } + + if (endDate != null) { + queryWrapper.le(SymptomRecord::getRecordTime, endDate); + } + + // 获取所有符合条件的记录 + List records = symptomRecordMapper.selectList(queryWrapper); + + // 按日期分组的严重程度平均值 + Map severityByDate = new LinkedHashMap<>(); + Map emotionByDate = new LinkedHashMap<>(); + Map sleepByDate = new LinkedHashMap<>(); + + // 过去7天的日期 + List last7Days = new ArrayList<>(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + Calendar calendar = Calendar.getInstance(); + + for (int i = 6; i >= 0; i--) { + calendar.setTime(new Date()); + calendar.add(Calendar.DAY_OF_MONTH, -i); + String dateStr = dateFormat.format(calendar.getTime()); + last7Days.add(dateStr); + + // 初始化数据 + severityByDate.put(dateStr, 0.0); + emotionByDate.put(dateStr, 0.0); + sleepByDate.put(dateStr, 0.0); + } + + // 记录每个日期的记录数量 + Map countByDate = new HashMap<>(); + for (String date : last7Days) { + countByDate.put(date, 0); + } + + // 处理记录 + for (SymptomRecord record : records) { + if (record.getRecordTime() != null) { + String recordDate = dateFormat.format(record.getRecordTime()); + + // 只处理过去7天的数据 + if (last7Days.contains(recordDate)) { + // 更新计数 + countByDate.put(recordDate, countByDate.getOrDefault(recordDate, 0) + 1); + + // 更新严重程度总和 + if (record.getSeverityLevel() != null) { + severityByDate.put(recordDate, severityByDate.getOrDefault(recordDate, 0.0) + record.getSeverityLevel()); + } + + // 更新情绪评分总和 + if (record.getEmotionScore() != null) { + emotionByDate.put(recordDate, emotionByDate.getOrDefault(recordDate, 0.0) + record.getEmotionScore()); + } + + // 更新入睡时间总和 + if (record.getSleepTime() != null) { + sleepByDate.put(recordDate, sleepByDate.getOrDefault(recordDate, 0.0) + record.getSleepTime()); + } + } + } + } + + // 计算每日平均值 + for (String date : last7Days) { + int count = countByDate.get(date); + if (count > 0) { + severityByDate.put(date, severityByDate.get(date) / count); + emotionByDate.put(date, emotionByDate.get(date) / count); + sleepByDate.put(date, sleepByDate.get(date) / count); + } + } + + // 构建返回结果 + result.put("dates", last7Days); + result.put("severityData", last7Days.stream().map(severityByDate::get).collect(Collectors.toList())); + result.put("emotionData", last7Days.stream().map(emotionByDate::get).collect(Collectors.toList())); + result.put("sleepData", last7Days.stream().map(sleepByDate::get).collect(Collectors.toList())); + + // 计算总体统计数据 + DoubleSummaryStatistics severityStats = records.stream() + .filter(r -> r.getSeverityLevel() != null) + .mapToDouble(SymptomRecord::getSeverityLevel) + .summaryStatistics(); + + DoubleSummaryStatistics emotionStats = records.stream() + .filter(r -> r.getEmotionScore() != null) + .mapToDouble(SymptomRecord::getEmotionScore) + .summaryStatistics(); + + DoubleSummaryStatistics sleepStats = records.stream() + .filter(r -> r.getSleepTime() != null) + .mapToDouble(SymptomRecord::getSleepTime) + .summaryStatistics(); + + // 添加总体统计 + Map summaryStats = new HashMap<>(); + summaryStats.put("recordCount", records.size()); + summaryStats.put("avgSeverity", severityStats.getAverage()); + summaryStats.put("avgEmotion", emotionStats.getAverage()); + summaryStats.put("avgSleepTime", sleepStats.getAverage()); + + result.put("summary", summaryStats); + + // 计算各项分布 + Map severityDistribution = records.stream() + .filter(r -> r.getSeverityLevel() != null) + .collect(Collectors.groupingBy(SymptomRecord::getSeverityLevel, Collectors.counting())); + + Map emotionDistribution = records.stream() + .filter(r -> r.getEmotionScore() != null) + .collect(Collectors.groupingBy(SymptomRecord::getEmotionScore, Collectors.counting())); + + result.put("severityDistribution", severityDistribution); + result.put("emotionDistribution", emotionDistribution); + + } catch (Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 根据医生ID获取其患者的症状记录统计数据 + * @param doctorId 医生ID + * @param startDate 开始日期(可选) + * @param endDate 结束日期(可选) + * @return 统计数据 + */ + public Map getStatisticsByDoctor(Integer doctorId, Date startDate, Date endDate) { + Map result = new HashMap<>(); + + // 获取医生的所有患者 + List patientIds = new ArrayList<>(); + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(DoctorPatient::getDoctorId, doctorId); + queryWrapper.eq(DoctorPatient::getStatus, "正常"); + + List doctorPatients = doctorPatientService.selectByDoctorId(doctorId, null); + patientIds = doctorPatients.stream() + .map(DoctorPatient::getPatientId) + .collect(Collectors.toList()); + } catch (Exception e) { + e.printStackTrace(); + return result; + } + + if (patientIds.isEmpty()) { + return result; + } + + try { + // 构建查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SymptomRecord::getPatientId, patientIds); + + if (startDate != null) { + queryWrapper.ge(SymptomRecord::getRecordTime, startDate); + } + + if (endDate != null) { + queryWrapper.le(SymptomRecord::getRecordTime, endDate); + } + + // 获取所有符合条件的记录 + List records = symptomRecordMapper.selectList(queryWrapper); + + // 处理和上面的方法相同...(后续统计处理逻辑与上面的方法相同) + // 按日期分组的严重程度平均值 + Map severityByDate = new LinkedHashMap<>(); + Map emotionByDate = new LinkedHashMap<>(); + Map sleepByDate = new LinkedHashMap<>(); + + // 过去7天的日期 + List last7Days = new ArrayList<>(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + Calendar calendar = Calendar.getInstance(); + + for (int i = 6; i >= 0; i--) { + calendar.setTime(new Date()); + calendar.add(Calendar.DAY_OF_MONTH, -i); + String dateStr = dateFormat.format(calendar.getTime()); + last7Days.add(dateStr); + + // 初始化数据 + severityByDate.put(dateStr, 0.0); + emotionByDate.put(dateStr, 0.0); + sleepByDate.put(dateStr, 0.0); + } + + // 记录每个日期的记录数量 + Map countByDate = new HashMap<>(); + for (String date : last7Days) { + countByDate.put(date, 0); + } + + // 处理记录 + for (SymptomRecord record : records) { + if (record.getRecordTime() != null) { + String recordDate = dateFormat.format(record.getRecordTime()); + + // 只处理过去7天的数据 + if (last7Days.contains(recordDate)) { + // 更新计数 + countByDate.put(recordDate, countByDate.getOrDefault(recordDate, 0) + 1); + + // 更新严重程度总和 + if (record.getSeverityLevel() != null) { + severityByDate.put(recordDate, severityByDate.getOrDefault(recordDate, 0.0) + record.getSeverityLevel()); + } + + // 更新情绪评分总和 + if (record.getEmotionScore() != null) { + emotionByDate.put(recordDate, emotionByDate.getOrDefault(recordDate, 0.0) + record.getEmotionScore()); + } + + // 更新入睡时间总和 + if (record.getSleepTime() != null) { + sleepByDate.put(recordDate, sleepByDate.getOrDefault(recordDate, 0.0) + record.getSleepTime()); + } + } + } + } + + // 计算每日平均值 + for (String date : last7Days) { + int count = countByDate.get(date); + if (count > 0) { + severityByDate.put(date, severityByDate.get(date) / count); + emotionByDate.put(date, emotionByDate.get(date) / count); + sleepByDate.put(date, sleepByDate.get(date) / count); + } + } + + // 构建返回结果 + result.put("dates", last7Days); + result.put("severityData", last7Days.stream().map(severityByDate::get).collect(Collectors.toList())); + result.put("emotionData", last7Days.stream().map(emotionByDate::get).collect(Collectors.toList())); + result.put("sleepData", last7Days.stream().map(sleepByDate::get).collect(Collectors.toList())); + + // 计算总体统计数据 + DoubleSummaryStatistics severityStats = records.stream() + .filter(r -> r.getSeverityLevel() != null) + .mapToDouble(SymptomRecord::getSeverityLevel) + .summaryStatistics(); + + DoubleSummaryStatistics emotionStats = records.stream() + .filter(r -> r.getEmotionScore() != null) + .mapToDouble(SymptomRecord::getEmotionScore) + .summaryStatistics(); + + DoubleSummaryStatistics sleepStats = records.stream() + .filter(r -> r.getSleepTime() != null) + .mapToDouble(SymptomRecord::getSleepTime) + .summaryStatistics(); + + // 添加总体统计 + Map summaryStats = new HashMap<>(); + summaryStats.put("recordCount", records.size()); + summaryStats.put("avgSeverity", severityStats.getAverage()); + summaryStats.put("avgEmotion", emotionStats.getAverage()); + summaryStats.put("avgSleepTime", sleepStats.getAverage()); + summaryStats.put("patientCount", patientIds.size()); + + result.put("summary", summaryStats); + + // 计算各项分布 + Map severityDistribution = records.stream() + .filter(r -> r.getSeverityLevel() != null) + .collect(Collectors.groupingBy(SymptomRecord::getSeverityLevel, Collectors.counting())); + + Map emotionDistribution = records.stream() + .filter(r -> r.getEmotionScore() != null) + .collect(Collectors.groupingBy(SymptomRecord::getEmotionScore, Collectors.counting())); + + result.put("severityDistribution", severityDistribution); + result.put("emotionDistribution", emotionDistribution); + + // 按患者分组的数据 + Map> recordsByPatient = records.stream() + .collect(Collectors.groupingBy(SymptomRecord::getPatientId)); + + Map patientStats = new HashMap<>(); + for (Map.Entry> entry : recordsByPatient.entrySet()) { + Integer pid = entry.getKey(); + List patientRecords = entry.getValue(); + + DoubleSummaryStatistics patientSeverityStats = patientRecords.stream() + .filter(r -> r.getSeverityLevel() != null) + .mapToDouble(SymptomRecord::getSeverityLevel) + .summaryStatistics(); + + Map pStats = new HashMap<>(); + pStats.put("recordCount", patientRecords.size()); + pStats.put("avgSeverity", patientSeverityStats.getAverage()); + pStats.put("maxSeverity", patientSeverityStats.getMax()); + + patientStats.put(pid, pStats); + } + + result.put("patientStats", patientStats); + + } catch (Exception e) { + e.printStackTrace(); + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/service/TreatmentResourceService.java b/src/main/java/com/example/service/TreatmentResourceService.java new file mode 100644 index 0000000..779925f --- /dev/null +++ b/src/main/java/com/example/service/TreatmentResourceService.java @@ -0,0 +1,256 @@ +package com.example.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.example.common.config.TokenUtils; +import com.example.common.enums.ResultCodeEnum; +import com.example.entity.Account; +import com.example.entity.TreatmentResource; +import com.example.exception.CustomException; +import com.example.mapper.TreatmentResourceMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Comparator; +import java.util.stream.Collectors; + +@Service +public class TreatmentResourceService { + + @Resource + private TreatmentResourceMapper treatmentResourceMapper; + + public Page search(Page page, String keyword, String role) { + if ("admin".equals(role)) { + return treatmentResourceMapper.searchResourcesForAdmin(page, keyword); + } else { + return treatmentResourceMapper.searchResourcesForPatient(page, keyword); + } + } + + public List findByCategoryId(Integer categoryId) { + return treatmentResourceMapper.selectByCategoryId(categoryId); + } + + public TreatmentResource findById(Integer id) { + return treatmentResourceMapper.selectById(id); + } + + public void add(TreatmentResource resource) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 允许管理员和医生添加资源 + if (!"admin".equals(currentUser.getRole()) && !"doctor".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权限添加资源"); + } + + // 设置默认值 + resource.setPublisherId(currentUser.getId()); + resource.setVisitCount(0); + resource.setDownloadCount(0); + + // 如果是医生添加,则默认为待审核状态 + if ("doctor".equals(currentUser.getRole())) { + resource.setAuditStatus("待审核"); + } else { + // 管理员添加的资源默认已审核 + resource.setAuditStatus("已通过"); + } + + resource.setCreateTime(LocalDateTime.now()); + + treatmentResourceMapper.insert(resource); + } + + public void update(TreatmentResource resource) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 仅允许管理员修改资源 + if (!"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权限修改资源"); + } + + // 检查资源是否存在 + TreatmentResource dbResource = treatmentResourceMapper.selectById(resource.getId()); + if (dbResource == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "资源不存在"); + } + + treatmentResourceMapper.updateById(resource); + } + + public void delete(Integer id) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 仅允许管理员删除资源 + if (!"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权限删除资源"); + } + + // 检查资源是否存在 + TreatmentResource dbResource = treatmentResourceMapper.selectById(id); + if (dbResource == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "资源不存在"); + } + + treatmentResourceMapper.deleteById(id); + } + + public void changeAuditStatus(Integer id, String status) { + // 获取当前登录用户 + Account currentUser = TokenUtils.getCurrentUser(); + if (currentUser == null) { + throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR); + } + + // 仅允许管理员审核资源 + if (!"admin".equals(currentUser.getRole())) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "无权限审核资源"); + } + + // 检查资源是否存在 + TreatmentResource dbResource = treatmentResourceMapper.selectById(id); + if (dbResource == null) { + throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "资源不存在"); + } + + // 更新审核状态 + TreatmentResource resource = new TreatmentResource(); + resource.setId(id); + resource.setAuditStatus(status); + + treatmentResourceMapper.updateById(resource); + } + + public void incrementVisitCount(Integer id) { + treatmentResourceMapper.incrementVisitCount(id); + } + + public void incrementDownloadCount(Integer id) { + treatmentResourceMapper.incrementDownloadCount(id); + } + + public PageInfo selectPage(TreatmentResource resource, Integer pageNum, Integer pageSize) { + // 获取当前用户角色 + Account currentUser = TokenUtils.getCurrentUser(); + String role = currentUser != null ? currentUser.getRole() : "patient"; + + // 使用PageHelper进行分页,与Admin页面一致 + PageHelper.startPage(pageNum, pageSize); + + // 获取查询关键词 + String keyword = resource.getTitle() != null ? resource.getTitle() : ""; + + // 直接使用Mapper的查询方法 + List list; + if ("admin".equals(role)) { + list = treatmentResourceMapper.selectAll(keyword); + } else { + list = treatmentResourceMapper.selectAllAudited(keyword); + } + + // 创建PageInfo对象 + return new PageInfo<>(list); + } + + /** + * 查询特定发布者的资源 + */ + public PageInfo findByPublisher(TreatmentResource resource, Integer pageNum, Integer pageSize) { + // 使用PageHelper进行分页,与Admin页面一致 + PageHelper.startPage(pageNum, pageSize); + + // 获取查询关键词 + String keyword = resource.getTitle() != null ? resource.getTitle() : ""; + + // 调用Mapper查询方法,需要添加对应的Mapper方法 + List list = treatmentResourceMapper.selectByPublisher(resource.getPublisherId(), keyword); + + // 创建PageInfo对象 + return new PageInfo<>(list); + } + + /** + * 获取资源使用情况统计数据 + * @return 资源使用情况统计数据 + */ + public Map getResourceStatistics() { + Map statistics = new HashMap<>(); + + try { + // 查询所有资源 + List resources = treatmentResourceMapper.selectList(null); + + // 按分类统计资源数量 + Map countByCategory = resources.stream() + .collect(Collectors.groupingBy(TreatmentResource::getCategoryId, Collectors.counting())); + + // 统计总访问次数和总下载次数 + int totalVisits = resources.stream() + .mapToInt(r -> r.getVisitCount() != null ? r.getVisitCount() : 0) + .sum(); + + int totalDownloads = resources.stream() + .mapToInt(r -> r.getDownloadCount() != null ? r.getDownloadCount() : 0) + .sum(); + + // 访问次数最多的资源TOP5 + List topVisitResources = resources.stream() + .sorted(Comparator.comparing(r -> r.getVisitCount() != null ? -r.getVisitCount() : 0)) + .limit(5) + .collect(Collectors.toList()); + + // 下载次数最多的资源TOP5 + List topDownloadResources = resources.stream() + .sorted(Comparator.comparing(r -> r.getDownloadCount() != null ? -r.getDownloadCount() : 0)) + .limit(5) + .collect(Collectors.toList()); + + // 按资源分类统计访问次数和下载次数 + Map visitsByCategory = new HashMap<>(); + Map downloadsByCategory = new HashMap<>(); + + for (TreatmentResource resource : resources) { + Integer categoryId = resource.getCategoryId(); + Integer visitCount = resource.getVisitCount() != null ? resource.getVisitCount() : 0; + Integer downloadCount = resource.getDownloadCount() != null ? resource.getDownloadCount() : 0; + + visitsByCategory.put(categoryId, visitsByCategory.getOrDefault(categoryId, 0) + visitCount); + downloadsByCategory.put(categoryId, downloadsByCategory.getOrDefault(categoryId, 0) + downloadCount); + } + + // 构建返回结果 + statistics.put("totalResources", resources.size()); + statistics.put("totalVisits", totalVisits); + statistics.put("totalDownloads", totalDownloads); + statistics.put("countByCategory", countByCategory); + statistics.put("visitsByCategory", visitsByCategory); + statistics.put("downloadsByCategory", downloadsByCategory); + statistics.put("topVisitResources", topVisitResources); + statistics.put("topDownloadResources", topDownloadResources); + + } catch (Exception e) { + e.printStackTrace(); + } + + return statistics; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/websocket/ChatWebSocket.java b/src/main/java/com/example/websocket/ChatWebSocket.java new file mode 100644 index 0000000..8c88cda --- /dev/null +++ b/src/main/java/com/example/websocket/ChatWebSocket.java @@ -0,0 +1,195 @@ +package com.example.websocket; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.example.entity.ChatMessage; +import com.example.service.ChatMessageService; +import jakarta.annotation.Resource; +import jakarta.websocket.*; +import jakarta.websocket.server.PathParam; +import jakarta.websocket.server.ServerEndpoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@ServerEndpoint("/ws/chat/{userId}/{userType}") +@Component +public class ChatWebSocket { + + private static final Logger log = LoggerFactory.getLogger(ChatWebSocket.class); + + // 静态变量,用来记录当前在线连接数 + private static int onlineCount = 0; + + // 用户ID和WebSocket的映射关系 + private static Map clients = new ConcurrentHashMap<>(); + + // 与某个客户端的连接会话,用于发送数据 + private Session session; + + // 当前连接用户ID + private Integer userId; + + // 当前连接用户类型 + private String userType; + + // 注入Service,因为@ServerEndpoint不支持直接注入,需要通过静态变量 + private static ChatMessageService chatMessageService; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Resource + public void setChatMessageService(ChatMessageService chatMessageService) { + ChatWebSocket.chatMessageService = chatMessageService; + } + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("userId") Integer userId, @PathParam("userType") String userType) { + this.session = session; + this.userId = userId; + this.userType = userType; + + // 将当前WebSocket对象加入到Map中 + String key = userId + ":" + userType; + clients.put(key, this); + + addOnlineCount(); + log.info("有新连接加入,当前在线人数为:{}", getOnlineCount()); + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + // 从Map中移除 + String key = userId + ":" + userType; + clients.remove(key); + + subOnlineCount(); + log.info("有一连接关闭,当前在线人数为:{}", getOnlineCount()); + } + + /** + * 收到客户端消息后调用的方法 + */ + @OnMessage + public void onMessage(String message, Session session) { + log.info("收到来自用户{}:{}的消息:{}", userId, userType, message); + + try { + // 解析消息 + ChatMessage chatMessage = objectMapper.readValue(message, ChatMessage.class); + + // 直接设置发送者信息,不再通过TokenUtils获取 + chatMessage.setSenderId(userId); + chatMessage.setSenderType(userType); + chatMessage.setSendTime(new Date()); + chatMessage.setIsRead(false); + + // 保存临时ID,用于前端识别消息 + String tempId = null; + try { + // 从消息中提取tempId字段 + Map messageMap = objectMapper.readValue(message, Map.class); + if (messageMap.containsKey("tempId")) { + tempId = messageMap.get("tempId").toString(); + } + } catch (Exception e) { + log.warn("提取tempId失败", e); + } + + // 保存消息到数据库 + try { + // 检查发送者和接收者是否有关联关系并保存消息 + chatMessageService.directSaveMessage(chatMessage); + + // 将保存后的消息转为Map,以便添加tempId + Map responseMap = objectMapper.convertValue(chatMessage, Map.class); + if (tempId != null) { + responseMap.put("tempId", tempId); + } + String responseJson = objectMapper.writeValueAsString(responseMap); + + // 转发消息给接收者 + String receiverKey = chatMessage.getReceiverId() + ":" + chatMessage.getReceiverType(); + ChatWebSocket receiverSocket = clients.get(receiverKey); + if (receiverSocket != null) { + // 接收者在线,发送消息 + receiverSocket.sendMessage(responseJson); + } + + // 同时也返回给发送者 + sendMessage(responseJson); + } catch (Exception e) { + log.error("处理消息时发生错误", e); + // 发送错误消息给客户端 + ChatMessage errorMessage = new ChatMessage(); + errorMessage.setSenderId(0); + errorMessage.setSenderType("system"); + errorMessage.setReceiverId(userId); + errorMessage.setReceiverType(userType); + errorMessage.setContent("发送消息失败:" + e.getMessage()); + errorMessage.setSendTime(new Date()); + + // 将错误消息转为Map,以便添加tempId + Map errorMap = objectMapper.convertValue(errorMessage, Map.class); + if (tempId != null) { + errorMap.put("tempId", tempId); + } + sendMessage(objectMapper.writeValueAsString(errorMap)); + } + } catch (Exception e) { + log.error("处理消息时发生错误", e); + } + } + + /** + * 发生错误时调用 + */ + @OnError + public void onError(Session session, Throwable error) { + log.error("发生错误", error); + } + + /** + * 发送消息 + */ + public void sendMessage(String message) { + try { + this.session.getBasicRemote().sendText(message); + } catch (IOException e) { + log.error("发送消息失败", e); + } + } + + /** + * 服务端主动推送消息 + */ + public static void sendMessage(Integer userId, String userType, String message) { + String key = userId + ":" + userType; + ChatWebSocket socket = clients.get(key); + if (socket != null) { + socket.sendMessage(message); + } + } + + public static synchronized int getOnlineCount() { + return onlineCount; + } + + public static synchronized void addOnlineCount() { + ChatWebSocket.onlineCount++; + } + + public static synchronized void subOnlineCount() { + ChatWebSocket.onlineCount--; + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..68c667f --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,28 @@ +server: + port: 9090 + +# 数据库配置 +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: 123456 + url: jdbc:mysql://localhost:3306/psychologicaltreatment?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8&allowPublicKeyRetrieval=true + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + +# 配置mybatis实体和xml映射 +mybatis: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + map-underscore-to-camel-case: true + mapper-locations: classpath:mapper/*.xml + +fileBaseUrl: http://localhost:${server.port} + +# 通义千问API配置 +dashscope: + api-key: sk-0b1b6c3287364c2d8b8e1f656bfafece + model: qwen-turbo diff --git a/src/main/resources/mapper/AdminMapper.xml b/src/main/resources/mapper/AdminMapper.xml new file mode 100644 index 0000000..e7fc083 --- /dev/null +++ b/src/main/resources/mapper/AdminMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + delete from admin where id = #{id} + + + \ No newline at end of file diff --git a/src/main/resources/mapper/DoctorMapper.xml b/src/main/resources/mapper/DoctorMapper.xml new file mode 100644 index 0000000..ef37ac3 --- /dev/null +++ b/src/main/resources/mapper/DoctorMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + delete from doctor where id = #{id} + + + \ No newline at end of file diff --git a/src/main/resources/mapper/DoctorPatientMapper.xml b/src/main/resources/mapper/DoctorPatientMapper.xml new file mode 100644 index 0000000..f9752b1 --- /dev/null +++ b/src/main/resources/mapper/DoctorPatientMapper.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/PatientMapper.xml b/src/main/resources/mapper/PatientMapper.xml new file mode 100644 index 0000000..263e8ac --- /dev/null +++ b/src/main/resources/mapper/PatientMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + delete from patient where id = #{id} + + + \ No newline at end of file diff --git a/src/main/resources/mapper/SymptomRecordMapper.xml b/src/main/resources/mapper/SymptomRecordMapper.xml new file mode 100644 index 0000000..695e1f6 --- /dev/null +++ b/src/main/resources/mapper/SymptomRecordMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vue/.env.development b/vue/.env.development new file mode 100644 index 0000000..62bb4ac --- /dev/null +++ b/vue/.env.development @@ -0,0 +1 @@ +VITE_BASE_URL='http://localhost:9090' \ No newline at end of file diff --git a/vue/.env.production b/vue/.env.production new file mode 100644 index 0000000..2beb7d1 --- /dev/null +++ b/vue/.env.production @@ -0,0 +1 @@ +VITE_BASE_URL='http://:9090' \ No newline at end of file diff --git a/vue/.gitignore b/vue/.gitignore new file mode 100644 index 0000000..0d3640a --- /dev/null +++ b/vue/.gitignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +node_modules/ +public/ diff --git a/vue/index.html b/vue/index.html new file mode 100644 index 0000000..8f9ac0a --- /dev/null +++ b/vue/index.html @@ -0,0 +1,16 @@ + + + + + + + + 管理系统 + + + +
+ + + + diff --git a/vue/jsconfig.json b/vue/jsconfig.json new file mode 100644 index 0000000..5a1f2d2 --- /dev/null +++ b/vue/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/vue/package-lock.json b/vue/package-lock.json new file mode 100644 index 0000000..83a512b --- /dev/null +++ b/vue/package-lock.json @@ -0,0 +1,3399 @@ +{ + "name": "vue", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vue", + "version": "0.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", + "axios": "^1.6.8", + "china-area-data": "^5.0.1", + "echarts": "^5.5.0", + "element-china-area-data": "^6.1.0", + "element-plus": "^2.7.2", + "pinia": "^3.0.1", + "vue": "^3.4.21", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "sass": "^1.71.1", + "unplugin-auto-import": "^0.17.5", + "unplugin-element-plus": "^0.8.0", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.2.8" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@transloadit/prettier-bytes": { + "version": "0.0.7", + "resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", + "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" + }, + "node_modules/@uppy/companion-client": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz", + "integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==", + "dependencies": { + "@uppy/utils": "^4.1.2", + "namespace-emitter": "^2.0.1" + } + }, + "node_modules/@uppy/core": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz", + "integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==", + "dependencies": { + "@transloadit/prettier-bytes": "0.0.7", + "@uppy/store-default": "^2.1.1", + "@uppy/utils": "^4.1.3", + "lodash.throttle": "^4.1.1", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "nanoid": "^3.1.25", + "preact": "^10.5.13" + } + }, + "node_modules/@uppy/store-default": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz", + "integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==" + }, + "node_modules/@uppy/utils": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz", + "integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==", + "dependencies": { + "lodash.throttle": "^4.1.1" + } + }, + "node_modules/@uppy/xhr-upload": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz", + "integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==", + "dependencies": { + "@uppy/companion-client": "^2.2.2", + "@uppy/utils": "^4.1.2", + "nanoid": "^3.1.25" + }, + "peerDependencies": { + "@uppy/core": "^2.3.3" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.33.tgz", + "integrity": "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.33", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.33.tgz", + "integrity": "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==", + "dependencies": { + "@vue/compiler-core": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.33.tgz", + "integrity": "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.33", + "@vue/compiler-dom": "3.5.33", + "@vue/compiler-ssr": "3.5.33", + "@vue/shared": "3.5.33", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.10", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.33.tgz", + "integrity": "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==", + "dependencies": { + "@vue/compiler-dom": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.33.tgz", + "integrity": "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==", + "dependencies": { + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.33.tgz", + "integrity": "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==", + "dependencies": { + "@vue/reactivity": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.33.tgz", + "integrity": "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==", + "dependencies": { + "@vue/reactivity": "3.5.33", + "@vue/runtime-core": "3.5.33", + "@vue/shared": "3.5.33", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.33.tgz", + "integrity": "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==", + "dependencies": { + "@vue/compiler-ssr": "3.5.33", + "@vue/shared": "3.5.33" + }, + "peerDependencies": { + "vue": "3.5.33" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.33.tgz", + "integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==" + }, + "node_modules/@vueuse/core": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.0.0.tgz", + "integrity": "sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "12.0.0", + "@vueuse/shared": "12.0.0", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-12.0.0.tgz", + "integrity": "sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-12.0.0.tgz", + "integrity": "sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@wangeditor/basic-modules": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz", + "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==", + "dependencies": { + "is-url": "^1.2.4" + }, + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.throttle": "^4.1.1", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/code-highlight": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz", + "integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==", + "dependencies": { + "prismjs": "^1.23.0" + }, + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/core": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz", + "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==", + "dependencies": { + "@types/event-emitter": "^0.3.3", + "event-emitter": "^0.3.5", + "html-void-elements": "^2.0.0", + "i18next": "^20.4.0", + "scroll-into-view-if-needed": "^2.2.28", + "slate-history": "^0.66.0" + }, + "peerDependencies": { + "@uppy/core": "^2.1.1", + "@uppy/xhr-upload": "^2.0.3", + "dom7": "^3.0.0", + "is-hotkey": "^0.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.foreach": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "lodash.toarray": "^4.4.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/editor": { + "version": "5.1.23", + "resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz", + "integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==", + "dependencies": { + "@uppy/core": "^2.1.1", + "@uppy/xhr-upload": "^2.0.3", + "@wangeditor/basic-modules": "^1.1.7", + "@wangeditor/code-highlight": "^1.0.3", + "@wangeditor/core": "^1.1.19", + "@wangeditor/list-module": "^1.0.5", + "@wangeditor/table-module": "^1.1.4", + "@wangeditor/upload-image-module": "^1.0.2", + "@wangeditor/video-module": "^1.1.4", + "dom7": "^3.0.0", + "is-hotkey": "^0.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.foreach": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "lodash.toarray": "^4.4.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/editor-for-vue": { + "version": "5.1.12", + "resolved": "https://registry.npmmirror.com/@wangeditor/editor-for-vue/-/editor-for-vue-5.1.12.tgz", + "integrity": "sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==", + "peerDependencies": { + "@wangeditor/editor": ">=5.1.0", + "vue": "^3.0.5" + } + }, + "node_modules/@wangeditor/list-module": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz", + "integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==", + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/table-module": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz", + "integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==", + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/upload-image-module": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz", + "integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==", + "peerDependencies": { + "@uppy/core": "^2.0.3", + "@uppy/xhr-upload": "^2.0.3", + "@wangeditor/basic-modules": "1.x", + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.foreach": "^4.5.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/video-module": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz", + "integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==", + "peerDependencies": { + "@uppy/core": "^2.1.4", + "@uppy/xhr-upload": "^2.0.7", + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.16.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/china-area-data": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/china-area-data/-/china-area-data-5.0.1.tgz", + "integrity": "sha512-BQDPpiv5Nn+018ekcJK2oSD9PAD+E1bvXB0wgabc//dFVS/KvRqCgg0QOEUt3vBkx9XzB5a9BmkJCEZDBxVjVw==" + }, + "node_modules/china-division": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/china-division/-/china-division-2.7.0.tgz", + "integrity": "sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA==" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom7": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz", + "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", + "dependencies": { + "ssr-window": "^3.0.0-alpha.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/element-china-area-data": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/element-china-area-data/-/element-china-area-data-6.1.0.tgz", + "integrity": "sha512-IkpcjwQv2A/2AxFiSoaISZ+oMw1rZCPUSOg5sOCwT5jKc96TaawmKZeY81xfxXsO0QbKxU5LLc6AirhG52hUmg==", + "dependencies": { + "china-division": "^2.7.0" + } + }, + "node_modules/element-plus": { + "version": "2.13.7", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.7.tgz", + "integrity": "sha512-XdHATFZOyzVFL1DaHQ90IOJQSg9UnSAV+bhDW+YB5UoZ0Hxs50mwqjqfwXkuwpSag+VXXizVcErBR6Movo5daw==", + "dependencies": { + "@ctrl/tinycolor": "^4.2.0", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "12.0.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0", + "vue-component-type-helpers": "^3.2.4" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/i18next": { + "version": "20.6.1", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz", + "integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==", + "dependencies": { + "@babel/runtime": "^7.12.0" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, + "node_modules/lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", + "dependencies": { + "wildcard": "^1.1.0" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.13", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.13.tgz", + "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.29.1", + "resolved": "https://registry.npmmirror.com/preact/-/preact-10.29.1.tgz", + "integrity": "sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.99.0", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.99.0.tgz", + "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", + "dev": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true + }, + "node_modules/slate": { + "version": "0.72.8", + "resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz", + "integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==", + "dependencies": { + "immer": "^9.0.6", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + } + }, + "node_modules/slate-history": { + "version": "0.66.0", + "resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz", + "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/snabbdom": { + "version": "3.6.3", + "resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.3.tgz", + "integrity": "sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==", + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssr-window": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz", + "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==" + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==" + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true + }, + "node_modules/unimport": { + "version": "3.14.6", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", + "scule": "^1.3.0", + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.17.8", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.17.8.tgz", + "integrity": "sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.0", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.10", + "minimatch": "^9.0.4", + "unimport": "^3.7.2", + "unplugin": "^1.11.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-element-plus": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/unplugin-element-plus/-/unplugin-element-plus-0.8.0.tgz", + "integrity": "sha512-jByUGY3FG2B8RJKFryqxx4eNtSTj+Hjlo8edcOdJymewndDQjThZ1pRUQHRjQsbKhTV2jEctJV7t7RJ405UL4g==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.2", + "es-module-lexer": "^1.3.0", + "magic-string": "^0.30.1", + "unplugin": "^1.3.2" + }, + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.26.0", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz", + "integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.6", + "@rollup/pluginutils": "^5.0.4", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.3", + "minimatch": "^9.0.3", + "resolve": "^1.22.4", + "unplugin": "^1.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/unplugin-vue-components/node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unplugin-vue-components/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin-vue-components/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.33", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.33.tgz", + "integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==", + "dependencies": { + "@vue/compiler-dom": "3.5.33", + "@vue/compiler-sfc": "3.5.33", + "@vue/runtime-dom": "3.5.33", + "@vue/server-renderer": "3.5.33", + "@vue/shared": "3.5.33" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.2.8", + "resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-3.2.8.tgz", + "integrity": "sha512-9689efAXhN/EV86plgkL/XFiJSXhGtWPG6JDboZ+QnjlUWUUQrQ0ILKQtw4iQsuwIwu5k6Aw+JnehDe7161e7A==" + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true + }, + "node_modules/wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==" + }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/vue/package.json b/vue/package.json new file mode 100644 index 0000000..da667a3 --- /dev/null +++ b/vue/package.json @@ -0,0 +1,32 @@ +{ + "name": "vue", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", + "axios": "^1.6.8", + "china-area-data": "^5.0.1", + "echarts": "^5.5.0", + "element-china-area-data": "^6.1.0", + "element-plus": "^2.7.2", + "pinia": "^3.0.1", + "vue": "^3.4.21", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "sass": "^1.71.1", + "unplugin-auto-import": "^0.17.5", + "unplugin-element-plus": "^0.8.0", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.2.8" + } +} diff --git a/vue/src/App.vue b/vue/src/App.vue new file mode 100644 index 0000000..7c2aa3f --- /dev/null +++ b/vue/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/vue/src/assets/css/global.css b/vue/src/assets/css/global.css new file mode 100644 index 0000000..ee8e02b --- /dev/null +++ b/vue/src/assets/css/global.css @@ -0,0 +1,21 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + color: #333; + font-size: 14px; +} + +a { + text-decoration: none; +} + +.card { + background-color: #fff; + border-radius: 5px; + padding: 10px; + box-shadow: 0 0 10px rgba(0,0,0,.1); +} \ No newline at end of file diff --git a/vue/src/assets/css/index.scss b/vue/src/assets/css/index.scss new file mode 100644 index 0000000..2c76031 --- /dev/null +++ b/vue/src/assets/css/index.scss @@ -0,0 +1,7 @@ +@forward "element-plus/theme-chalk/src/common/var.scss" with ($colors: ( + "primary": ("base": #009788), + "success": ("base": #3880ee), + "warning": ("base": #7d83d5), + "danger": ("base": #e35e5e), + "info": ("base": #F9B44C), +)); \ No newline at end of file diff --git a/vue/src/assets/css/manager.css b/vue/src/assets/css/manager.css new file mode 100644 index 0000000..c71e307 --- /dev/null +++ b/vue/src/assets/css/manager.css @@ -0,0 +1,108 @@ +.manager-container { + background-color: #f8f8ff; + min-height: 100vh; +} + +.manager-header { + height: 60px; + background-color: #2E8B57; /* 海洋绿 */ + display: flex; + align-items: center; + box-shadow: 0 1px 4px rgba(46, 139, 87, 0.3); +} + +.manager-header-left { + background-color: #2E8B57; /* 海洋绿 */ + width: 230px; + height: 100%; + padding-left: 10px; + display: flex; + align-items: center +} + +.manager-header-left img { + width: 30px; + height: 30px; +} + +.manager-header-left .title { + font-weight: bold; + font-size: 20px; + margin-left: 10px; + color: #fff; +} + +.manager-header-center { + flex: 1; + padding-left: 20px; +} + +.manager-main-left { + width: 200px; + background-color: #ffffff; /* 白色 */ + min-height: calc(100vh - 60px); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.1); +} + +.manager-main-right { + flex: 1; + width: 0; + background-color: #f8f8ff; + padding: 10px; +} + + +/* ElementUI */ +.el-menu { + border: none; + background-color: #ffffff; /* 白色 */ +} + +.el-tooltip__trigger { + outline: none; +} + +:deep(.el-sub-menu__title) { + color: #333 !important; + height: 50px; + line-height: 50px; + background-color: #ffffff !important; /* 白色 */ +} + +.el-sub-menu__title:hover { + background-color: #2E8B57 !important; /* 海洋绿,悬停效果 */ + color: #fff !important; +} + +.el-menu-item { + color: #333; + height: 50px; + line-height: 50px; + background-color: #ffffff !important; /* 白色 */ +} + +.el-menu--inline .el-menu-item { + background-color: #f5f5f5 !important; /* 浅灰色,区分子菜单 */ +} + +.el-menu-item.is-active { + background-color: #2E8B57 !important; /* 海洋绿,活动状态 */ + color: #fff; +} + +.el-menu-item:not(.is-active):hover { + color: #fff; + background-color: #2E8B57 !important; /* 海洋绿,悬停效果 */ +} + +:deep(.el-breadcrumb__inner) { + color: #fff !important; +} + +:deep(.manager-header-right span) { + color: #ffffff; +} + +.notification-icon { + color: #ffffff !important; +} diff --git a/vue/src/assets/fonts/FontAwesome.otf b/vue/src/assets/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/vue/src/assets/fonts/FontAwesome.otf differ diff --git a/vue/src/assets/fonts/font-awesome.css b/vue/src/assets/fonts/font-awesome.css new file mode 100644 index 0000000..eab1cbb --- /dev/null +++ b/vue/src/assets/fonts/font-awesome.css @@ -0,0 +1,2337 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +/* FONT PATH + * -------------------------- */ +@font-face { + font-family: 'FontAwesome'; + src: url('fontawesome-webfont.eot?v=4.7.0'); + src: url('fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('fontawesome-webfont.woff?v=4.7.0') format('woff'), url('fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +/* makes the font 33% larger relative to the icon container */ +.fa-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.fa-2x { + font-size: 2em; +} +.fa-3x { + font-size: 3em; +} +.fa-4x { + font-size: 4em; +} +.fa-5x { + font-size: 5em; +} +.fa-fw { + width: 1.28571429em; + text-align: center; +} +.fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.fa-ul > li { + position: relative; +} +.fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.fa-li.fa-lg { + left: -1.85714286em; +} +.fa-border { + padding: .2em .25em .15em; + border: solid 0.08em #eeeeee; + border-radius: .1em; +} +.fa-pull-left { + float: left; +} +.fa-pull-right { + float: right; +} +.fa.fa-pull-left { + margin-right: .3em; +} +.fa.fa-pull-right { + margin-left: .3em; +} +/* Deprecated as of 4.4.0 */ +.pull-right { + float: right; +} +.pull-left { + float: left; +} +.fa.pull-left { + margin-right: .3em; +} +.fa.pull-right { + margin-left: .3em; +} +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + filter: none; +} +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.fa-stack-1x { + line-height: inherit; +} +.fa-stack-2x { + font-size: 2em; +} +.fa-inverse { + color: #ffffff; +} +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.fa-glass:before { + content: "\f000"; +} +.fa-music:before { + content: "\f001"; +} +.fa-search:before { + content: "\f002"; +} +.fa-envelope-o:before { + content: "\f003"; +} +.fa-heart:before { + content: "\f004"; +} +.fa-star:before { + content: "\f005"; +} +.fa-star-o:before { + content: "\f006"; +} +.fa-user:before { + content: "\f007"; +} +.fa-film:before { + content: "\f008"; +} +.fa-th-large:before { + content: "\f009"; +} +.fa-th:before { + content: "\f00a"; +} +.fa-th-list:before { + content: "\f00b"; +} +.fa-check:before { + content: "\f00c"; +} +.fa-remove:before, +.fa-close:before, +.fa-times:before { + content: "\f00d"; +} +.fa-search-plus:before { + content: "\f00e"; +} +.fa-search-minus:before { + content: "\f010"; +} +.fa-power-off:before { + content: "\f011"; +} +.fa-signal:before { + content: "\f012"; +} +.fa-gear:before, +.fa-cog:before { + content: "\f013"; +} +.fa-trash-o:before { + content: "\f014"; +} +.fa-home:before { + content: "\f015"; +} +.fa-file-o:before { + content: "\f016"; +} +.fa-clock-o:before { + content: "\f017"; +} +.fa-road:before { + content: "\f018"; +} +.fa-download:before { + content: "\f019"; +} +.fa-arrow-circle-o-down:before { + content: "\f01a"; +} +.fa-arrow-circle-o-up:before { + content: "\f01b"; +} +.fa-inbox:before { + content: "\f01c"; +} +.fa-play-circle-o:before { + content: "\f01d"; +} +.fa-rotate-right:before, +.fa-repeat:before { + content: "\f01e"; +} +.fa-refresh:before { + content: "\f021"; +} +.fa-list-alt:before { + content: "\f022"; +} +.fa-lock:before { + content: "\f023"; +} +.fa-flag:before { + content: "\f024"; +} +.fa-headphones:before { + content: "\f025"; +} +.fa-volume-off:before { + content: "\f026"; +} +.fa-volume-down:before { + content: "\f027"; +} +.fa-volume-up:before { + content: "\f028"; +} +.fa-qrcode:before { + content: "\f029"; +} +.fa-barcode:before { + content: "\f02a"; +} +.fa-tag:before { + content: "\f02b"; +} +.fa-tags:before { + content: "\f02c"; +} +.fa-book:before { + content: "\f02d"; +} +.fa-bookmark:before { + content: "\f02e"; +} +.fa-print:before { + content: "\f02f"; +} +.fa-camera:before { + content: "\f030"; +} +.fa-font:before { + content: "\f031"; +} +.fa-bold:before { + content: "\f032"; +} +.fa-italic:before { + content: "\f033"; +} +.fa-text-height:before { + content: "\f034"; +} +.fa-text-width:before { + content: "\f035"; +} +.fa-align-left:before { + content: "\f036"; +} +.fa-align-center:before { + content: "\f037"; +} +.fa-align-right:before { + content: "\f038"; +} +.fa-align-justify:before { + content: "\f039"; +} +.fa-list:before { + content: "\f03a"; +} +.fa-dedent:before, +.fa-outdent:before { + content: "\f03b"; +} +.fa-indent:before { + content: "\f03c"; +} +.fa-video-camera:before { + content: "\f03d"; +} +.fa-photo:before, +.fa-image:before, +.fa-picture-o:before { + content: "\f03e"; +} +.fa-pencil:before { + content: "\f040"; +} +.fa-map-marker:before { + content: "\f041"; +} +.fa-adjust:before { + content: "\f042"; +} +.fa-tint:before { + content: "\f043"; +} +.fa-edit:before, +.fa-pencil-square-o:before { + content: "\f044"; +} +.fa-share-square-o:before { + content: "\f045"; +} +.fa-check-square-o:before { + content: "\f046"; +} +.fa-arrows:before { + content: "\f047"; +} +.fa-step-backward:before { + content: "\f048"; +} +.fa-fast-backward:before { + content: "\f049"; +} +.fa-backward:before { + content: "\f04a"; +} +.fa-play:before { + content: "\f04b"; +} +.fa-pause:before { + content: "\f04c"; +} +.fa-stop:before { + content: "\f04d"; +} +.fa-forward:before { + content: "\f04e"; +} +.fa-fast-forward:before { + content: "\f050"; +} +.fa-step-forward:before { + content: "\f051"; +} +.fa-eject:before { + content: "\f052"; +} +.fa-chevron-left:before { + content: "\f053"; +} +.fa-chevron-right:before { + content: "\f054"; +} +.fa-plus-circle:before { + content: "\f055"; +} +.fa-minus-circle:before { + content: "\f056"; +} +.fa-times-circle:before { + content: "\f057"; +} +.fa-check-circle:before { + content: "\f058"; +} +.fa-question-circle:before { + content: "\f059"; +} +.fa-info-circle:before { + content: "\f05a"; +} +.fa-crosshairs:before { + content: "\f05b"; +} +.fa-times-circle-o:before { + content: "\f05c"; +} +.fa-check-circle-o:before { + content: "\f05d"; +} +.fa-ban:before { + content: "\f05e"; +} +.fa-arrow-left:before { + content: "\f060"; +} +.fa-arrow-right:before { + content: "\f061"; +} +.fa-arrow-up:before { + content: "\f062"; +} +.fa-arrow-down:before { + content: "\f063"; +} +.fa-mail-forward:before, +.fa-share:before { + content: "\f064"; +} +.fa-expand:before { + content: "\f065"; +} +.fa-compress:before { + content: "\f066"; +} +.fa-plus:before { + content: "\f067"; +} +.fa-minus:before { + content: "\f068"; +} +.fa-asterisk:before { + content: "\f069"; +} +.fa-exclamation-circle:before { + content: "\f06a"; +} +.fa-gift:before { + content: "\f06b"; +} +.fa-leaf:before { + content: "\f06c"; +} +.fa-fire:before { + content: "\f06d"; +} +.fa-eye:before { + content: "\f06e"; +} +.fa-eye-slash:before { + content: "\f070"; +} +.fa-warning:before, +.fa-exclamation-triangle:before { + content: "\f071"; +} +.fa-plane:before { + content: "\f072"; +} +.fa-calendar:before { + content: "\f073"; +} +.fa-random:before { + content: "\f074"; +} +.fa-comment:before { + content: "\f075"; +} +.fa-magnet:before { + content: "\f076"; +} +.fa-chevron-up:before { + content: "\f077"; +} +.fa-chevron-down:before { + content: "\f078"; +} +.fa-retweet:before { + content: "\f079"; +} +.fa-shopping-cart:before { + content: "\f07a"; +} +.fa-folder:before { + content: "\f07b"; +} +.fa-folder-open:before { + content: "\f07c"; +} +.fa-arrows-v:before { + content: "\f07d"; +} +.fa-arrows-h:before { + content: "\f07e"; +} +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: "\f080"; +} +.fa-twitter-square:before { + content: "\f081"; +} +.fa-facebook-square:before { + content: "\f082"; +} +.fa-camera-retro:before { + content: "\f083"; +} +.fa-key:before { + content: "\f084"; +} +.fa-gears:before, +.fa-cogs:before { + content: "\f085"; +} +.fa-comments:before { + content: "\f086"; +} +.fa-thumbs-o-up:before { + content: "\f087"; +} +.fa-thumbs-o-down:before { + content: "\f088"; +} +.fa-star-half:before { + content: "\f089"; +} +.fa-heart-o:before { + content: "\f08a"; +} +.fa-sign-out:before { + content: "\f08b"; +} +.fa-linkedin-square:before { + content: "\f08c"; +} +.fa-thumb-tack:before { + content: "\f08d"; +} +.fa-external-link:before { + content: "\f08e"; +} +.fa-sign-in:before { + content: "\f090"; +} +.fa-trophy:before { + content: "\f091"; +} +.fa-github-square:before { + content: "\f092"; +} +.fa-upload:before { + content: "\f093"; +} +.fa-lemon-o:before { + content: "\f094"; +} +.fa-phone:before { + content: "\f095"; +} +.fa-square-o:before { + content: "\f096"; +} +.fa-bookmark-o:before { + content: "\f097"; +} +.fa-phone-square:before { + content: "\f098"; +} +.fa-twitter:before { + content: "\f099"; +} +.fa-facebook-f:before, +.fa-facebook:before { + content: "\f09a"; +} +.fa-github:before { + content: "\f09b"; +} +.fa-unlock:before { + content: "\f09c"; +} +.fa-credit-card:before { + content: "\f09d"; +} +.fa-feed:before, +.fa-rss:before { + content: "\f09e"; +} +.fa-hdd-o:before { + content: "\f0a0"; +} +.fa-bullhorn:before { + content: "\f0a1"; +} +.fa-bell:before { + content: "\f0f3"; +} +.fa-certificate:before { + content: "\f0a3"; +} +.fa-hand-o-right:before { + content: "\f0a4"; +} +.fa-hand-o-left:before { + content: "\f0a5"; +} +.fa-hand-o-up:before { + content: "\f0a6"; +} +.fa-hand-o-down:before { + content: "\f0a7"; +} +.fa-arrow-circle-left:before { + content: "\f0a8"; +} +.fa-arrow-circle-right:before { + content: "\f0a9"; +} +.fa-arrow-circle-up:before { + content: "\f0aa"; +} +.fa-arrow-circle-down:before { + content: "\f0ab"; +} +.fa-globe:before { + content: "\f0ac"; +} +.fa-wrench:before { + content: "\f0ad"; +} +.fa-tasks:before { + content: "\f0ae"; +} +.fa-filter:before { + content: "\f0b0"; +} +.fa-briefcase:before { + content: "\f0b1"; +} +.fa-arrows-alt:before { + content: "\f0b2"; +} +.fa-group:before, +.fa-users:before { + content: "\f0c0"; +} +.fa-chain:before, +.fa-link:before { + content: "\f0c1"; +} +.fa-cloud:before { + content: "\f0c2"; +} +.fa-flask:before { + content: "\f0c3"; +} +.fa-cut:before, +.fa-scissors:before { + content: "\f0c4"; +} +.fa-copy:before, +.fa-files-o:before { + content: "\f0c5"; +} +.fa-paperclip:before { + content: "\f0c6"; +} +.fa-save:before, +.fa-floppy-o:before { + content: "\f0c7"; +} +.fa-square:before { + content: "\f0c8"; +} +.fa-navicon:before, +.fa-reorder:before, +.fa-bars:before { + content: "\f0c9"; +} +.fa-list-ul:before { + content: "\f0ca"; +} +.fa-list-ol:before { + content: "\f0cb"; +} +.fa-strikethrough:before { + content: "\f0cc"; +} +.fa-underline:before { + content: "\f0cd"; +} +.fa-table:before { + content: "\f0ce"; +} +.fa-magic:before { + content: "\f0d0"; +} +.fa-truck:before { + content: "\f0d1"; +} +.fa-pinterest:before { + content: "\f0d2"; +} +.fa-pinterest-square:before { + content: "\f0d3"; +} +.fa-google-plus-square:before { + content: "\f0d4"; +} +.fa-google-plus:before { + content: "\f0d5"; +} +.fa-money:before { + content: "\f0d6"; +} +.fa-caret-down:before { + content: "\f0d7"; +} +.fa-caret-up:before { + content: "\f0d8"; +} +.fa-caret-left:before { + content: "\f0d9"; +} +.fa-caret-right:before { + content: "\f0da"; +} +.fa-columns:before { + content: "\f0db"; +} +.fa-unsorted:before, +.fa-sort:before { + content: "\f0dc"; +} +.fa-sort-down:before, +.fa-sort-desc:before { + content: "\f0dd"; +} +.fa-sort-up:before, +.fa-sort-asc:before { + content: "\f0de"; +} +.fa-envelope:before { + content: "\f0e0"; +} +.fa-linkedin:before { + content: "\f0e1"; +} +.fa-rotate-left:before, +.fa-undo:before { + content: "\f0e2"; +} +.fa-legal:before, +.fa-gavel:before { + content: "\f0e3"; +} +.fa-dashboard:before, +.fa-tachometer:before { + content: "\f0e4"; +} +.fa-comment-o:before { + content: "\f0e5"; +} +.fa-comments-o:before { + content: "\f0e6"; +} +.fa-flash:before, +.fa-bolt:before { + content: "\f0e7"; +} +.fa-sitemap:before { + content: "\f0e8"; +} +.fa-umbrella:before { + content: "\f0e9"; +} +.fa-paste:before, +.fa-clipboard:before { + content: "\f0ea"; +} +.fa-lightbulb-o:before { + content: "\f0eb"; +} +.fa-exchange:before { + content: "\f0ec"; +} +.fa-cloud-download:before { + content: "\f0ed"; +} +.fa-cloud-upload:before { + content: "\f0ee"; +} +.fa-user-md:before { + content: "\f0f0"; +} +.fa-stethoscope:before { + content: "\f0f1"; +} +.fa-suitcase:before { + content: "\f0f2"; +} +.fa-bell-o:before { + content: "\f0a2"; +} +.fa-coffee:before { + content: "\f0f4"; +} +.fa-cutlery:before { + content: "\f0f5"; +} +.fa-file-text-o:before { + content: "\f0f6"; +} +.fa-building-o:before { + content: "\f0f7"; +} +.fa-hospital-o:before { + content: "\f0f8"; +} +.fa-ambulance:before { + content: "\f0f9"; +} +.fa-medkit:before { + content: "\f0fa"; +} +.fa-fighter-jet:before { + content: "\f0fb"; +} +.fa-beer:before { + content: "\f0fc"; +} +.fa-h-square:before { + content: "\f0fd"; +} +.fa-plus-square:before { + content: "\f0fe"; +} +.fa-angle-double-left:before { + content: "\f100"; +} +.fa-angle-double-right:before { + content: "\f101"; +} +.fa-angle-double-up:before { + content: "\f102"; +} +.fa-angle-double-down:before { + content: "\f103"; +} +.fa-angle-left:before { + content: "\f104"; +} +.fa-angle-right:before { + content: "\f105"; +} +.fa-angle-up:before { + content: "\f106"; +} +.fa-angle-down:before { + content: "\f107"; +} +.fa-desktop:before { + content: "\f108"; +} +.fa-laptop:before { + content: "\f109"; +} +.fa-tablet:before { + content: "\f10a"; +} +.fa-mobile-phone:before, +.fa-mobile:before { + content: "\f10b"; +} +.fa-circle-o:before { + content: "\f10c"; +} +.fa-quote-left:before { + content: "\f10d"; +} +.fa-quote-right:before { + content: "\f10e"; +} +.fa-spinner:before { + content: "\f110"; +} +.fa-circle:before { + content: "\f111"; +} +.fa-mail-reply:before, +.fa-reply:before { + content: "\f112"; +} +.fa-github-alt:before { + content: "\f113"; +} +.fa-folder-o:before { + content: "\f114"; +} +.fa-folder-open-o:before { + content: "\f115"; +} +.fa-smile-o:before { + content: "\f118"; +} +.fa-frown-o:before { + content: "\f119"; +} +.fa-meh-o:before { + content: "\f11a"; +} +.fa-gamepad:before { + content: "\f11b"; +} +.fa-keyboard-o:before { + content: "\f11c"; +} +.fa-flag-o:before { + content: "\f11d"; +} +.fa-flag-checkered:before { + content: "\f11e"; +} +.fa-terminal:before { + content: "\f120"; +} +.fa-code:before { + content: "\f121"; +} +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: "\f122"; +} +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: "\f123"; +} +.fa-location-arrow:before { + content: "\f124"; +} +.fa-crop:before { + content: "\f125"; +} +.fa-code-fork:before { + content: "\f126"; +} +.fa-unlink:before, +.fa-chain-broken:before { + content: "\f127"; +} +.fa-question:before { + content: "\f128"; +} +.fa-info:before { + content: "\f129"; +} +.fa-exclamation:before { + content: "\f12a"; +} +.fa-superscript:before { + content: "\f12b"; +} +.fa-subscript:before { + content: "\f12c"; +} +.fa-eraser:before { + content: "\f12d"; +} +.fa-puzzle-piece:before { + content: "\f12e"; +} +.fa-microphone:before { + content: "\f130"; +} +.fa-microphone-slash:before { + content: "\f131"; +} +.fa-shield:before { + content: "\f132"; +} +.fa-calendar-o:before { + content: "\f133"; +} +.fa-fire-extinguisher:before { + content: "\f134"; +} +.fa-rocket:before { + content: "\f135"; +} +.fa-maxcdn:before { + content: "\f136"; +} +.fa-chevron-circle-left:before { + content: "\f137"; +} +.fa-chevron-circle-right:before { + content: "\f138"; +} +.fa-chevron-circle-up:before { + content: "\f139"; +} +.fa-chevron-circle-down:before { + content: "\f13a"; +} +.fa-html5:before { + content: "\f13b"; +} +.fa-css3:before { + content: "\f13c"; +} +.fa-anchor:before { + content: "\f13d"; +} +.fa-unlock-alt:before { + content: "\f13e"; +} +.fa-bullseye:before { + content: "\f140"; +} +.fa-ellipsis-h:before { + content: "\f141"; +} +.fa-ellipsis-v:before { + content: "\f142"; +} +.fa-rss-square:before { + content: "\f143"; +} +.fa-play-circle:before { + content: "\f144"; +} +.fa-ticket:before { + content: "\f145"; +} +.fa-minus-square:before { + content: "\f146"; +} +.fa-minus-square-o:before { + content: "\f147"; +} +.fa-level-up:before { + content: "\f148"; +} +.fa-level-down:before { + content: "\f149"; +} +.fa-check-square:before { + content: "\f14a"; +} +.fa-pencil-square:before { + content: "\f14b"; +} +.fa-external-link-square:before { + content: "\f14c"; +} +.fa-share-square:before { + content: "\f14d"; +} +.fa-compass:before { + content: "\f14e"; +} +.fa-toggle-down:before, +.fa-caret-square-o-down:before { + content: "\f150"; +} +.fa-toggle-up:before, +.fa-caret-square-o-up:before { + content: "\f151"; +} +.fa-toggle-right:before, +.fa-caret-square-o-right:before { + content: "\f152"; +} +.fa-euro:before, +.fa-eur:before { + content: "\f153"; +} +.fa-gbp:before { + content: "\f154"; +} +.fa-dollar:before, +.fa-usd:before { + content: "\f155"; +} +.fa-rupee:before, +.fa-inr:before { + content: "\f156"; +} +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { + content: "\f157"; +} +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { + content: "\f158"; +} +.fa-won:before, +.fa-krw:before { + content: "\f159"; +} +.fa-bitcoin:before, +.fa-btc:before { + content: "\f15a"; +} +.fa-file:before { + content: "\f15b"; +} +.fa-file-text:before { + content: "\f15c"; +} +.fa-sort-alpha-asc:before { + content: "\f15d"; +} +.fa-sort-alpha-desc:before { + content: "\f15e"; +} +.fa-sort-amount-asc:before { + content: "\f160"; +} +.fa-sort-amount-desc:before { + content: "\f161"; +} +.fa-sort-numeric-asc:before { + content: "\f162"; +} +.fa-sort-numeric-desc:before { + content: "\f163"; +} +.fa-thumbs-up:before { + content: "\f164"; +} +.fa-thumbs-down:before { + content: "\f165"; +} +.fa-youtube-square:before { + content: "\f166"; +} +.fa-youtube:before { + content: "\f167"; +} +.fa-xing:before { + content: "\f168"; +} +.fa-xing-square:before { + content: "\f169"; +} +.fa-youtube-play:before { + content: "\f16a"; +} +.fa-dropbox:before { + content: "\f16b"; +} +.fa-stack-overflow:before { + content: "\f16c"; +} +.fa-instagram:before { + content: "\f16d"; +} +.fa-flickr:before { + content: "\f16e"; +} +.fa-adn:before { + content: "\f170"; +} +.fa-bitbucket:before { + content: "\f171"; +} +.fa-bitbucket-square:before { + content: "\f172"; +} +.fa-tumblr:before { + content: "\f173"; +} +.fa-tumblr-square:before { + content: "\f174"; +} +.fa-long-arrow-down:before { + content: "\f175"; +} +.fa-long-arrow-up:before { + content: "\f176"; +} +.fa-long-arrow-left:before { + content: "\f177"; +} +.fa-long-arrow-right:before { + content: "\f178"; +} +.fa-apple:before { + content: "\f179"; +} +.fa-windows:before { + content: "\f17a"; +} +.fa-android:before { + content: "\f17b"; +} +.fa-linux:before { + content: "\f17c"; +} +.fa-dribbble:before { + content: "\f17d"; +} +.fa-skype:before { + content: "\f17e"; +} +.fa-foursquare:before { + content: "\f180"; +} +.fa-trello:before { + content: "\f181"; +} +.fa-female:before { + content: "\f182"; +} +.fa-male:before { + content: "\f183"; +} +.fa-gittip:before, +.fa-gratipay:before { + content: "\f184"; +} +.fa-sun-o:before { + content: "\f185"; +} +.fa-moon-o:before { + content: "\f186"; +} +.fa-archive:before { + content: "\f187"; +} +.fa-bug:before { + content: "\f188"; +} +.fa-vk:before { + content: "\f189"; +} +.fa-weibo:before { + content: "\f18a"; +} +.fa-renren:before { + content: "\f18b"; +} +.fa-pagelines:before { + content: "\f18c"; +} +.fa-stack-exchange:before { + content: "\f18d"; +} +.fa-arrow-circle-o-right:before { + content: "\f18e"; +} +.fa-arrow-circle-o-left:before { + content: "\f190"; +} +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: "\f191"; +} +.fa-dot-circle-o:before { + content: "\f192"; +} +.fa-wheelchair:before { + content: "\f193"; +} +.fa-vimeo-square:before { + content: "\f194"; +} +.fa-turkish-lira:before, +.fa-try:before { + content: "\f195"; +} +.fa-plus-square-o:before { + content: "\f196"; +} +.fa-space-shuttle:before { + content: "\f197"; +} +.fa-slack:before { + content: "\f198"; +} +.fa-envelope-square:before { + content: "\f199"; +} +.fa-wordpress:before { + content: "\f19a"; +} +.fa-openid:before { + content: "\f19b"; +} +.fa-institution:before, +.fa-bank:before, +.fa-university:before { + content: "\f19c"; +} +.fa-mortar-board:before, +.fa-graduation-cap:before { + content: "\f19d"; +} +.fa-yahoo:before { + content: "\f19e"; +} +.fa-google:before { + content: "\f1a0"; +} +.fa-reddit:before { + content: "\f1a1"; +} +.fa-reddit-square:before { + content: "\f1a2"; +} +.fa-stumbleupon-circle:before { + content: "\f1a3"; +} +.fa-stumbleupon:before { + content: "\f1a4"; +} +.fa-delicious:before { + content: "\f1a5"; +} +.fa-digg:before { + content: "\f1a6"; +} +.fa-pied-piper-pp:before { + content: "\f1a7"; +} +.fa-pied-piper-alt:before { + content: "\f1a8"; +} +.fa-drupal:before { + content: "\f1a9"; +} +.fa-joomla:before { + content: "\f1aa"; +} +.fa-language:before { + content: "\f1ab"; +} +.fa-fax:before { + content: "\f1ac"; +} +.fa-building:before { + content: "\f1ad"; +} +.fa-child:before { + content: "\f1ae"; +} +.fa-paw:before { + content: "\f1b0"; +} +.fa-spoon:before { + content: "\f1b1"; +} +.fa-cube:before { + content: "\f1b2"; +} +.fa-cubes:before { + content: "\f1b3"; +} +.fa-behance:before { + content: "\f1b4"; +} +.fa-behance-square:before { + content: "\f1b5"; +} +.fa-steam:before { + content: "\f1b6"; +} +.fa-steam-square:before { + content: "\f1b7"; +} +.fa-recycle:before { + content: "\f1b8"; +} +.fa-automobile:before, +.fa-car:before { + content: "\f1b9"; +} +.fa-cab:before, +.fa-taxi:before { + content: "\f1ba"; +} +.fa-tree:before { + content: "\f1bb"; +} +.fa-spotify:before { + content: "\f1bc"; +} +.fa-deviantart:before { + content: "\f1bd"; +} +.fa-soundcloud:before { + content: "\f1be"; +} +.fa-database:before { + content: "\f1c0"; +} +.fa-file-pdf-o:before { + content: "\f1c1"; +} +.fa-file-word-o:before { + content: "\f1c2"; +} +.fa-file-excel-o:before { + content: "\f1c3"; +} +.fa-file-powerpoint-o:before { + content: "\f1c4"; +} +.fa-file-photo-o:before, +.fa-file-picture-o:before, +.fa-file-image-o:before { + content: "\f1c5"; +} +.fa-file-zip-o:before, +.fa-file-archive-o:before { + content: "\f1c6"; +} +.fa-file-sound-o:before, +.fa-file-audio-o:before { + content: "\f1c7"; +} +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: "\f1c8"; +} +.fa-file-code-o:before { + content: "\f1c9"; +} +.fa-vine:before { + content: "\f1ca"; +} +.fa-codepen:before { + content: "\f1cb"; +} +.fa-jsfiddle:before { + content: "\f1cc"; +} +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-saver:before, +.fa-support:before, +.fa-life-ring:before { + content: "\f1cd"; +} +.fa-circle-o-notch:before { + content: "\f1ce"; +} +.fa-ra:before, +.fa-resistance:before, +.fa-rebel:before { + content: "\f1d0"; +} +.fa-ge:before, +.fa-empire:before { + content: "\f1d1"; +} +.fa-git-square:before { + content: "\f1d2"; +} +.fa-git:before { + content: "\f1d3"; +} +.fa-y-combinator-square:before, +.fa-yc-square:before, +.fa-hacker-news:before { + content: "\f1d4"; +} +.fa-tencent-weibo:before { + content: "\f1d5"; +} +.fa-qq:before { + content: "\f1d6"; +} +.fa-wechat:before, +.fa-weixin:before { + content: "\f1d7"; +} +.fa-send:before, +.fa-paper-plane:before { + content: "\f1d8"; +} +.fa-send-o:before, +.fa-paper-plane-o:before { + content: "\f1d9"; +} +.fa-history:before { + content: "\f1da"; +} +.fa-circle-thin:before { + content: "\f1db"; +} +.fa-header:before { + content: "\f1dc"; +} +.fa-paragraph:before { + content: "\f1dd"; +} +.fa-sliders:before { + content: "\f1de"; +} +.fa-share-alt:before { + content: "\f1e0"; +} +.fa-share-alt-square:before { + content: "\f1e1"; +} +.fa-bomb:before { + content: "\f1e2"; +} +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: "\f1e3"; +} +.fa-tty:before { + content: "\f1e4"; +} +.fa-binoculars:before { + content: "\f1e5"; +} +.fa-plug:before { + content: "\f1e6"; +} +.fa-slideshare:before { + content: "\f1e7"; +} +.fa-twitch:before { + content: "\f1e8"; +} +.fa-yelp:before { + content: "\f1e9"; +} +.fa-newspaper-o:before { + content: "\f1ea"; +} +.fa-wifi:before { + content: "\f1eb"; +} +.fa-calculator:before { + content: "\f1ec"; +} +.fa-paypal:before { + content: "\f1ed"; +} +.fa-google-wallet:before { + content: "\f1ee"; +} +.fa-cc-visa:before { + content: "\f1f0"; +} +.fa-cc-mastercard:before { + content: "\f1f1"; +} +.fa-cc-discover:before { + content: "\f1f2"; +} +.fa-cc-amex:before { + content: "\f1f3"; +} +.fa-cc-paypal:before { + content: "\f1f4"; +} +.fa-cc-stripe:before { + content: "\f1f5"; +} +.fa-bell-slash:before { + content: "\f1f6"; +} +.fa-bell-slash-o:before { + content: "\f1f7"; +} +.fa-trash:before { + content: "\f1f8"; +} +.fa-copyright:before { + content: "\f1f9"; +} +.fa-at:before { + content: "\f1fa"; +} +.fa-eyedropper:before { + content: "\f1fb"; +} +.fa-paint-brush:before { + content: "\f1fc"; +} +.fa-birthday-cake:before { + content: "\f1fd"; +} +.fa-area-chart:before { + content: "\f1fe"; +} +.fa-pie-chart:before { + content: "\f200"; +} +.fa-line-chart:before { + content: "\f201"; +} +.fa-lastfm:before { + content: "\f202"; +} +.fa-lastfm-square:before { + content: "\f203"; +} +.fa-toggle-off:before { + content: "\f204"; +} +.fa-toggle-on:before { + content: "\f205"; +} +.fa-bicycle:before { + content: "\f206"; +} +.fa-bus:before { + content: "\f207"; +} +.fa-ioxhost:before { + content: "\f208"; +} +.fa-angellist:before { + content: "\f209"; +} +.fa-cc:before { + content: "\f20a"; +} +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: "\f20b"; +} +.fa-meanpath:before { + content: "\f20c"; +} +.fa-buysellads:before { + content: "\f20d"; +} +.fa-connectdevelop:before { + content: "\f20e"; +} +.fa-dashcube:before { + content: "\f210"; +} +.fa-forumbee:before { + content: "\f211"; +} +.fa-leanpub:before { + content: "\f212"; +} +.fa-sellsy:before { + content: "\f213"; +} +.fa-shirtsinbulk:before { + content: "\f214"; +} +.fa-simplybuilt:before { + content: "\f215"; +} +.fa-skyatlas:before { + content: "\f216"; +} +.fa-cart-plus:before { + content: "\f217"; +} +.fa-cart-arrow-down:before { + content: "\f218"; +} +.fa-diamond:before { + content: "\f219"; +} +.fa-ship:before { + content: "\f21a"; +} +.fa-user-secret:before { + content: "\f21b"; +} +.fa-motorcycle:before { + content: "\f21c"; +} +.fa-street-view:before { + content: "\f21d"; +} +.fa-heartbeat:before { + content: "\f21e"; +} +.fa-venus:before { + content: "\f221"; +} +.fa-mars:before { + content: "\f222"; +} +.fa-mercury:before { + content: "\f223"; +} +.fa-intersex:before, +.fa-transgender:before { + content: "\f224"; +} +.fa-transgender-alt:before { + content: "\f225"; +} +.fa-venus-double:before { + content: "\f226"; +} +.fa-mars-double:before { + content: "\f227"; +} +.fa-venus-mars:before { + content: "\f228"; +} +.fa-mars-stroke:before { + content: "\f229"; +} +.fa-mars-stroke-v:before { + content: "\f22a"; +} +.fa-mars-stroke-h:before { + content: "\f22b"; +} +.fa-neuter:before { + content: "\f22c"; +} +.fa-genderless:before { + content: "\f22d"; +} +.fa-facebook-official:before { + content: "\f230"; +} +.fa-pinterest-p:before { + content: "\f231"; +} +.fa-whatsapp:before { + content: "\f232"; +} +.fa-server:before { + content: "\f233"; +} +.fa-user-plus:before { + content: "\f234"; +} +.fa-user-times:before { + content: "\f235"; +} +.fa-hotel:before, +.fa-bed:before { + content: "\f236"; +} +.fa-viacoin:before { + content: "\f237"; +} +.fa-train:before { + content: "\f238"; +} +.fa-subway:before { + content: "\f239"; +} +.fa-medium:before { + content: "\f23a"; +} +.fa-yc:before, +.fa-y-combinator:before { + content: "\f23b"; +} +.fa-optin-monster:before { + content: "\f23c"; +} +.fa-opencart:before { + content: "\f23d"; +} +.fa-expeditedssl:before { + content: "\f23e"; +} +.fa-battery-4:before, +.fa-battery:before, +.fa-battery-full:before { + content: "\f240"; +} +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "\f241"; +} +.fa-battery-2:before, +.fa-battery-half:before { + content: "\f242"; +} +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "\f243"; +} +.fa-battery-0:before, +.fa-battery-empty:before { + content: "\f244"; +} +.fa-mouse-pointer:before { + content: "\f245"; +} +.fa-i-cursor:before { + content: "\f246"; +} +.fa-object-group:before { + content: "\f247"; +} +.fa-object-ungroup:before { + content: "\f248"; +} +.fa-sticky-note:before { + content: "\f249"; +} +.fa-sticky-note-o:before { + content: "\f24a"; +} +.fa-cc-jcb:before { + content: "\f24b"; +} +.fa-cc-diners-club:before { + content: "\f24c"; +} +.fa-clone:before { + content: "\f24d"; +} +.fa-balance-scale:before { + content: "\f24e"; +} +.fa-hourglass-o:before { + content: "\f250"; +} +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "\f251"; +} +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "\f252"; +} +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "\f253"; +} +.fa-hourglass:before { + content: "\f254"; +} +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "\f255"; +} +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: "\f256"; +} +.fa-hand-scissors-o:before { + content: "\f257"; +} +.fa-hand-lizard-o:before { + content: "\f258"; +} +.fa-hand-spock-o:before { + content: "\f259"; +} +.fa-hand-pointer-o:before { + content: "\f25a"; +} +.fa-hand-peace-o:before { + content: "\f25b"; +} +.fa-trademark:before { + content: "\f25c"; +} +.fa-registered:before { + content: "\f25d"; +} +.fa-creative-commons:before { + content: "\f25e"; +} +.fa-gg:before { + content: "\f260"; +} +.fa-gg-circle:before { + content: "\f261"; +} +.fa-tripadvisor:before { + content: "\f262"; +} +.fa-odnoklassniki:before { + content: "\f263"; +} +.fa-odnoklassniki-square:before { + content: "\f264"; +} +.fa-get-pocket:before { + content: "\f265"; +} +.fa-wikipedia-w:before { + content: "\f266"; +} +.fa-safari:before { + content: "\f267"; +} +.fa-chrome:before { + content: "\f268"; +} +.fa-firefox:before { + content: "\f269"; +} +.fa-opera:before { + content: "\f26a"; +} +.fa-internet-explorer:before { + content: "\f26b"; +} +.fa-tv:before, +.fa-television:before { + content: "\f26c"; +} +.fa-contao:before { + content: "\f26d"; +} +.fa-500px:before { + content: "\f26e"; +} +.fa-amazon:before { + content: "\f270"; +} +.fa-calendar-plus-o:before { + content: "\f271"; +} +.fa-calendar-minus-o:before { + content: "\f272"; +} +.fa-calendar-times-o:before { + content: "\f273"; +} +.fa-calendar-check-o:before { + content: "\f274"; +} +.fa-industry:before { + content: "\f275"; +} +.fa-map-pin:before { + content: "\f276"; +} +.fa-map-signs:before { + content: "\f277"; +} +.fa-map-o:before { + content: "\f278"; +} +.fa-map:before { + content: "\f279"; +} +.fa-commenting:before { + content: "\f27a"; +} +.fa-commenting-o:before { + content: "\f27b"; +} +.fa-houzz:before { + content: "\f27c"; +} +.fa-vimeo:before { + content: "\f27d"; +} +.fa-black-tie:before { + content: "\f27e"; +} +.fa-fonticons:before { + content: "\f280"; +} +.fa-reddit-alien:before { + content: "\f281"; +} +.fa-edge:before { + content: "\f282"; +} +.fa-credit-card-alt:before { + content: "\f283"; +} +.fa-codiepie:before { + content: "\f284"; +} +.fa-modx:before { + content: "\f285"; +} +.fa-fort-awesome:before { + content: "\f286"; +} +.fa-usb:before { + content: "\f287"; +} +.fa-product-hunt:before { + content: "\f288"; +} +.fa-mixcloud:before { + content: "\f289"; +} +.fa-scribd:before { + content: "\f28a"; +} +.fa-pause-circle:before { + content: "\f28b"; +} +.fa-pause-circle-o:before { + content: "\f28c"; +} +.fa-stop-circle:before { + content: "\f28d"; +} +.fa-stop-circle-o:before { + content: "\f28e"; +} +.fa-shopping-bag:before { + content: "\f290"; +} +.fa-shopping-basket:before { + content: "\f291"; +} +.fa-hashtag:before { + content: "\f292"; +} +.fa-bluetooth:before { + content: "\f293"; +} +.fa-bluetooth-b:before { + content: "\f294"; +} +.fa-percent:before { + content: "\f295"; +} +.fa-gitlab:before { + content: "\f296"; +} +.fa-wpbeginner:before { + content: "\f297"; +} +.fa-wpforms:before { + content: "\f298"; +} +.fa-envira:before { + content: "\f299"; +} +.fa-universal-access:before { + content: "\f29a"; +} +.fa-wheelchair-alt:before { + content: "\f29b"; +} +.fa-question-circle-o:before { + content: "\f29c"; +} +.fa-blind:before { + content: "\f29d"; +} +.fa-audio-description:before { + content: "\f29e"; +} +.fa-volume-control-phone:before { + content: "\f2a0"; +} +.fa-braille:before { + content: "\f2a1"; +} +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4"; +} +.fa-glide:before { + content: "\f2a5"; +} +.fa-glide-g:before { + content: "\f2a6"; +} +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7"; +} +.fa-low-vision:before { + content: "\f2a8"; +} +.fa-viadeo:before { + content: "\f2a9"; +} +.fa-viadeo-square:before { + content: "\f2aa"; +} +.fa-snapchat:before { + content: "\f2ab"; +} +.fa-snapchat-ghost:before { + content: "\f2ac"; +} +.fa-snapchat-square:before { + content: "\f2ad"; +} +.fa-pied-piper:before { + content: "\f2ae"; +} +.fa-first-order:before { + content: "\f2b0"; +} +.fa-yoast:before { + content: "\f2b1"; +} +.fa-themeisle:before { + content: "\f2b2"; +} +.fa-google-plus-circle:before, +.fa-google-plus-official:before { + content: "\f2b3"; +} +.fa-fa:before, +.fa-font-awesome:before { + content: "\f2b4"; +} +.fa-handshake-o:before { + content: "\f2b5"; +} +.fa-envelope-open:before { + content: "\f2b6"; +} +.fa-envelope-open-o:before { + content: "\f2b7"; +} +.fa-linode:before { + content: "\f2b8"; +} +.fa-address-book:before { + content: "\f2b9"; +} +.fa-address-book-o:before { + content: "\f2ba"; +} +.fa-vcard:before, +.fa-address-card:before { + content: "\f2bb"; +} +.fa-vcard-o:before, +.fa-address-card-o:before { + content: "\f2bc"; +} +.fa-user-circle:before { + content: "\f2bd"; +} +.fa-user-circle-o:before { + content: "\f2be"; +} +.fa-user-o:before { + content: "\f2c0"; +} +.fa-id-badge:before { + content: "\f2c1"; +} +.fa-drivers-license:before, +.fa-id-card:before { + content: "\f2c2"; +} +.fa-drivers-license-o:before, +.fa-id-card-o:before { + content: "\f2c3"; +} +.fa-quora:before { + content: "\f2c4"; +} +.fa-free-code-camp:before { + content: "\f2c5"; +} +.fa-telegram:before { + content: "\f2c6"; +} +.fa-thermometer-4:before, +.fa-thermometer:before, +.fa-thermometer-full:before { + content: "\f2c7"; +} +.fa-thermometer-3:before, +.fa-thermometer-three-quarters:before { + content: "\f2c8"; +} +.fa-thermometer-2:before, +.fa-thermometer-half:before { + content: "\f2c9"; +} +.fa-thermometer-1:before, +.fa-thermometer-quarter:before { + content: "\f2ca"; +} +.fa-thermometer-0:before, +.fa-thermometer-empty:before { + content: "\f2cb"; +} +.fa-shower:before { + content: "\f2cc"; +} +.fa-bathtub:before, +.fa-s15:before, +.fa-bath:before { + content: "\f2cd"; +} +.fa-podcast:before { + content: "\f2ce"; +} +.fa-window-maximize:before { + content: "\f2d0"; +} +.fa-window-minimize:before { + content: "\f2d1"; +} +.fa-window-restore:before { + content: "\f2d2"; +} +.fa-times-rectangle:before, +.fa-window-close:before { + content: "\f2d3"; +} +.fa-times-rectangle-o:before, +.fa-window-close-o:before { + content: "\f2d4"; +} +.fa-bandcamp:before { + content: "\f2d5"; +} +.fa-grav:before { + content: "\f2d6"; +} +.fa-etsy:before { + content: "\f2d7"; +} +.fa-imdb:before { + content: "\f2d8"; +} +.fa-ravelry:before { + content: "\f2d9"; +} +.fa-eercast:before { + content: "\f2da"; +} +.fa-microchip:before { + content: "\f2db"; +} +.fa-snowflake-o:before { + content: "\f2dc"; +} +.fa-superpowers:before { + content: "\f2dd"; +} +.fa-wpexplorer:before { + content: "\f2de"; +} +.fa-meetup:before { + content: "\f2e0"; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/vue/src/assets/fonts/fontawesome-webfont.eot b/vue/src/assets/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/vue/src/assets/fonts/fontawesome-webfont.eot differ diff --git a/vue/src/assets/fonts/fontawesome-webfont.svg b/vue/src/assets/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/vue/src/assets/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vue/src/assets/fonts/fontawesome-webfont.ttf b/vue/src/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/vue/src/assets/fonts/fontawesome-webfont.ttf differ diff --git a/vue/src/assets/fonts/fontawesome-webfont.woff b/vue/src/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/vue/src/assets/fonts/fontawesome-webfont.woff differ diff --git a/vue/src/assets/fonts/fontawesome-webfont.woff2 b/vue/src/assets/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/vue/src/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/vue/src/assets/imgs/404.jpg b/vue/src/assets/imgs/404.jpg new file mode 100644 index 0000000..7ce045c Binary files /dev/null and b/vue/src/assets/imgs/404.jpg differ diff --git a/vue/src/assets/imgs/VCG41N1408837996.jpg b/vue/src/assets/imgs/VCG41N1408837996.jpg new file mode 100644 index 0000000..12949fa Binary files /dev/null and b/vue/src/assets/imgs/VCG41N1408837996.jpg differ diff --git a/vue/src/assets/imgs/avatar.png b/vue/src/assets/imgs/avatar.png new file mode 100644 index 0000000..e16488e Binary files /dev/null and b/vue/src/assets/imgs/avatar.png differ diff --git a/vue/src/assets/imgs/login.svg b/vue/src/assets/imgs/login.svg new file mode 100644 index 0000000..3c15f1b --- /dev/null +++ b/vue/src/assets/imgs/login.svg @@ -0,0 +1 @@ +voice interface \ No newline at end of file diff --git a/vue/src/assets/imgs/logo.png b/vue/src/assets/imgs/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/vue/src/assets/imgs/logo.png differ diff --git a/vue/src/assets/imgs/register.svg b/vue/src/assets/imgs/register.svg new file mode 100644 index 0000000..99e6523 --- /dev/null +++ b/vue/src/assets/imgs/register.svg @@ -0,0 +1 @@ +absorbed in \ No newline at end of file diff --git a/vue/src/assets/logo.png b/vue/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/vue/src/assets/logo.png differ diff --git a/vue/src/main.js b/vue/src/main.js new file mode 100644 index 0000000..7cd4f23 --- /dev/null +++ b/vue/src/main.js @@ -0,0 +1,21 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import zhCn from 'element-plus/dist/locale/zh-cn.mjs' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +import '@/assets/css/global.css' + +const app = createApp(App) + +app.use(router) +app.use(ElementPlus, { + locale: zhCn +}) +app.mount('#app') + +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} \ No newline at end of file diff --git a/vue/src/router/index.js b/vue/src/router/index.js new file mode 100644 index 0000000..a073378 --- /dev/null +++ b/vue/src/router/index.js @@ -0,0 +1,46 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Chat from "@/views/manager/Chat.vue"; + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { path: '/', redirect: '/login' }, + { + path: '/manager', + name: 'Manager', + component: () => import('@/views/Manager.vue'), + redirect: '/manager/login', + children: [ + { path: 'password', meta: { name: '修改密码' }, component: () => import('@/views/manager/Password.vue') }, + { path: 'home', meta: { name: '系统首页' }, component: () => import('@/views/manager/Home.vue') }, + { path: 'admin', meta: { name: '管理员' }, component: () => import("@/views/manager/Admin.vue") }, + { path: 'patient', meta: { name: '患者' }, component: () => import("@/views/manager/Patient.vue") }, + { path: 'doctor', meta: { name: '医生' }, component: () => import("@/views/manager/Doctor.vue") }, + { path: 'pAdmin', meta: { name: '个人信息', roles: ['admin'] }, component: () => import("@/views/manager/pAdmin.vue") }, + { path: 'pPatient', meta: { name: '个人信息', roles: ['patient'] }, component: () => import("@/views/manager/pPatient.vue") }, + { path: 'pDoctor', meta: { name: '个人信息', roles: ['doctor'] }, component: () => import("@/views/manager/pDoctor.vue") }, + { path: 'symptomRecord', meta: { name: '症状记录' }, component: () => import("@/views/manager/SymptomRecord.vue") }, + { path: 'resourceCategory', meta: { name: '资源分类管理' }, component: () => import("@/views/manager/ResourceCategory.vue") }, + { path: 'treatmentResource', meta: { name: '心理资源管理' }, component: () => import("@/views/manager/TreatmentResource.vue") }, + { path: 'resourceRecommendation', meta: { name: '心理资源推荐', roles: ['patient'] }, component: () => import("@/views/manager/ResourceRecommendation.vue") }, + { path: 'myResources', meta: { name: '我的资源' }, component: () => import("@/views/manager/MyResources.vue") }, + { path: 'aiChat', meta: { name: '智能问答' }, component: () => import("@/views/manager/AIChat.vue") }, + { path: 'symptomStatistics', meta: { name: '症状统计', roles: ['admin', 'doctor', 'patient'] }, component: () => import('@/views/manager/SymptomStatistics.vue') }, + { path: 'assessmentReports', meta: { name: '评估报告管理', roles: ['admin'] }, component: () => import('@/views/manager/AssessmentReports.vue') }, + { path: 'patientReports', meta: { name: '患者评估报告', roles: ['doctor'] }, component: () => import('@/views/manager/PatientReports.vue') }, + { path: 'myReports', meta: { name: '我的评估报告', roles: ['patient'] }, component: () => import('@/views/manager/MyReports.vue') }, + { path: 'generateReport', meta: { name: '生成评估报告', roles: ['admin', 'doctor'] }, component: () => import('@/views/manager/GenerateReport.vue') }, + { path: 'reportDetail/:id', meta: { name: '报告详情', roles: ['admin', 'doctor', 'patient'] }, component: () => import('@/views/manager/ReportDetail.vue') }, + { path: 'announcement', meta: { name: '公告管理' }, component: () => import('@/views/manager/Announcement.vue') }, + { path: 'announcementList', meta: { name: '公告列表' }, component: () => import('@/views/manager/AnnouncementList.vue') }, + ] + }, + { path: '/login', component: () => import('@/views/Login.vue') }, + { path: '/register', component: () => import("@/views/Register.vue") }, + { path: '/404', component: () => import('@/views/404.vue') }, + { path: '/manager/chat', name: 'Chat', component: Chat }, + { path: '/:pathMatch(.*)', redirect: '/404', hidden: true } + ] +}) + +export default router diff --git a/vue/src/utils/request.js b/vue/src/utils/request.js new file mode 100644 index 0000000..87a6fd9 --- /dev/null +++ b/vue/src/utils/request.js @@ -0,0 +1,54 @@ +import router from '../router' +import axios from "axios"; +import {ElMessage} from "element-plus"; + +const request = axios.create({ + baseURL: import.meta.env.VITE_BASE_URL, + timeout: 30000 // 后台接口超时时间设置 +}) + +// request 拦截器 +// 可以自请求发送前对请求做一些处理 +request.interceptors.request.use(config => { + config.headers['Content-Type'] = 'application/json;charset=utf-8'; + const user = JSON.parse(localStorage.getItem('xm-user') || '{}') + // console.log(user) + config.headers['token'] = user.token || '' + return config +}, error => { + return Promise.reject(error) +}); + +// response 拦截器 +// 可以在接口响应后统一处理结果 +request.interceptors.response.use( + response => { + let res = response.data; + // 如果是返回的文件 + if (response.config.responseType === 'blob') { + return res + } + // 兼容服务端返回的字符串数据 + if (typeof res === 'string') { + res = res ? JSON.parse(res) : res + } + // 当权限验证不通过的时候给出提示 + if (res.code === '401') { + router.push("/login") + } + return res; + }, + error => { + if (error.response.status === 404) { + ElMessage.error('未找到请求接口') + } else if (error.response.status === 500) { + ElMessage.error('系统异常,请查看后端控制台报错') + } else { + console.error(error.message) + } + return Promise.reject(error) + } +) + + +export default request diff --git a/vue/src/views/404.vue b/vue/src/views/404.vue new file mode 100644 index 0000000..f920543 --- /dev/null +++ b/vue/src/views/404.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/Login.vue b/vue/src/views/Login.vue new file mode 100644 index 0000000..a8005a9 --- /dev/null +++ b/vue/src/views/Login.vue @@ -0,0 +1,339 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/Manager.vue b/vue/src/views/Manager.vue new file mode 100644 index 0000000..60ac8a5 --- /dev/null +++ b/vue/src/views/Manager.vue @@ -0,0 +1,408 @@ + + + + + diff --git a/vue/src/views/Register.vue b/vue/src/views/Register.vue new file mode 100644 index 0000000..225270a --- /dev/null +++ b/vue/src/views/Register.vue @@ -0,0 +1,394 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/AIChat.vue b/vue/src/views/manager/AIChat.vue new file mode 100644 index 0000000..9fa5c97 --- /dev/null +++ b/vue/src/views/manager/AIChat.vue @@ -0,0 +1,430 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/Admin.vue b/vue/src/views/manager/Admin.vue new file mode 100644 index 0000000..a611861 --- /dev/null +++ b/vue/src/views/manager/Admin.vue @@ -0,0 +1,159 @@ + + + diff --git a/vue/src/views/manager/Announcement.vue b/vue/src/views/manager/Announcement.vue new file mode 100644 index 0000000..7261d23 --- /dev/null +++ b/vue/src/views/manager/Announcement.vue @@ -0,0 +1,248 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/AnnouncementList.vue b/vue/src/views/manager/AnnouncementList.vue new file mode 100644 index 0000000..138f1e7 --- /dev/null +++ b/vue/src/views/manager/AnnouncementList.vue @@ -0,0 +1,252 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/AssessmentReports.vue b/vue/src/views/manager/AssessmentReports.vue new file mode 100644 index 0000000..13c2b69 --- /dev/null +++ b/vue/src/views/manager/AssessmentReports.vue @@ -0,0 +1,446 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/Chat.vue b/vue/src/views/manager/Chat.vue new file mode 100644 index 0000000..144b5ed --- /dev/null +++ b/vue/src/views/manager/Chat.vue @@ -0,0 +1,733 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/Doctor.vue b/vue/src/views/manager/Doctor.vue new file mode 100644 index 0000000..d08d322 --- /dev/null +++ b/vue/src/views/manager/Doctor.vue @@ -0,0 +1,195 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/GenerateReport.vue b/vue/src/views/manager/GenerateReport.vue new file mode 100644 index 0000000..e1778d4 --- /dev/null +++ b/vue/src/views/manager/GenerateReport.vue @@ -0,0 +1,232 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/Home.vue b/vue/src/views/manager/Home.vue new file mode 100644 index 0000000..34f1b36 --- /dev/null +++ b/vue/src/views/manager/Home.vue @@ -0,0 +1,692 @@ + + + + + + diff --git a/vue/src/views/manager/MyReports.vue b/vue/src/views/manager/MyReports.vue new file mode 100644 index 0000000..6bccaa0 --- /dev/null +++ b/vue/src/views/manager/MyReports.vue @@ -0,0 +1,239 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/MyResources.vue b/vue/src/views/manager/MyResources.vue new file mode 100644 index 0000000..27b474a --- /dev/null +++ b/vue/src/views/manager/MyResources.vue @@ -0,0 +1,438 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/Password.vue b/vue/src/views/manager/Password.vue new file mode 100644 index 0000000..98d1f49 --- /dev/null +++ b/vue/src/views/manager/Password.vue @@ -0,0 +1,74 @@ + + + diff --git a/vue/src/views/manager/Patient.vue b/vue/src/views/manager/Patient.vue new file mode 100644 index 0000000..45dd667 --- /dev/null +++ b/vue/src/views/manager/Patient.vue @@ -0,0 +1,230 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/PatientReports.vue b/vue/src/views/manager/PatientReports.vue new file mode 100644 index 0000000..e042b42 --- /dev/null +++ b/vue/src/views/manager/PatientReports.vue @@ -0,0 +1,253 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/ReportDetail.vue b/vue/src/views/manager/ReportDetail.vue new file mode 100644 index 0000000..67f9165 --- /dev/null +++ b/vue/src/views/manager/ReportDetail.vue @@ -0,0 +1,366 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/ResourceCategory.vue b/vue/src/views/manager/ResourceCategory.vue new file mode 100644 index 0000000..43fe2b4 --- /dev/null +++ b/vue/src/views/manager/ResourceCategory.vue @@ -0,0 +1,245 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/ResourceRecommendation.vue b/vue/src/views/manager/ResourceRecommendation.vue new file mode 100644 index 0000000..62d9fd7 --- /dev/null +++ b/vue/src/views/manager/ResourceRecommendation.vue @@ -0,0 +1,496 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/SymptomRecord.vue b/vue/src/views/manager/SymptomRecord.vue new file mode 100644 index 0000000..3bfd2c4 --- /dev/null +++ b/vue/src/views/manager/SymptomRecord.vue @@ -0,0 +1,490 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/SymptomStatistics.vue b/vue/src/views/manager/SymptomStatistics.vue new file mode 100644 index 0000000..db9c77e --- /dev/null +++ b/vue/src/views/manager/SymptomStatistics.vue @@ -0,0 +1,641 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/TreatmentResource.vue b/vue/src/views/manager/TreatmentResource.vue new file mode 100644 index 0000000..670b140 --- /dev/null +++ b/vue/src/views/manager/TreatmentResource.vue @@ -0,0 +1,791 @@ + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/User.vue b/vue/src/views/manager/User.vue new file mode 100644 index 0000000..a2c9fe7 --- /dev/null +++ b/vue/src/views/manager/User.vue @@ -0,0 +1,167 @@ + + + diff --git a/vue/src/views/manager/pAdmin.vue b/vue/src/views/manager/pAdmin.vue new file mode 100644 index 0000000..d7fd47a --- /dev/null +++ b/vue/src/views/manager/pAdmin.vue @@ -0,0 +1,110 @@ + + + + + + + diff --git a/vue/src/views/manager/pDoctor.vue b/vue/src/views/manager/pDoctor.vue new file mode 100644 index 0000000..c343ac3 --- /dev/null +++ b/vue/src/views/manager/pDoctor.vue @@ -0,0 +1,171 @@ + + + + + + + \ No newline at end of file diff --git a/vue/src/views/manager/pPatient.vue b/vue/src/views/manager/pPatient.vue new file mode 100644 index 0000000..b555bc7 --- /dev/null +++ b/vue/src/views/manager/pPatient.vue @@ -0,0 +1,156 @@ + + + + + + + \ No newline at end of file diff --git a/vue/vite.config.js b/vue/vite.config.js new file mode 100644 index 0000000..86023f8 --- /dev/null +++ b/vue/vite.config.js @@ -0,0 +1,103 @@ +import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import ElementPlus from 'unplugin-element-plus/vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + // 按需定制主题配置 + ElementPlus({ + useSource: true, + }), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + ], + // 预加载项目必需的组件 + optimizeDeps: { + include: [ + "vue", + "vue-router", + "axios", + "path-browserify", + "element-plus/es/components/base/style/css", + "element-plus/es/components/message/style/css", + "element-plus/es/components/message-box/style/css", + "element-plus/es/components/form/style/css", + "element-plus/es/components/form-item/style/css", + "element-plus/es/components/button/style/css", + "element-plus/es/components/input/style/css", + "element-plus/es/components/input-number/style/css", + "element-plus/es/components/switch/style/css", + "element-plus/es/components/upload/style/css", + "element-plus/es/components/menu/style/css", + "element-plus/es/components/col/style/css", + "element-plus/es/components/icon/style/css", + "element-plus/es/components/row/style/css", + "element-plus/es/components/tag/style/css", + "element-plus/es/components/dialog/style/css", + "element-plus/es/components/loading/style/css", + "element-plus/es/components/radio/style/css", + "element-plus/es/components/radio-group/style/css", + "element-plus/es/components/popover/style/css", + "element-plus/es/components/scrollbar/style/css", + "element-plus/es/components/tooltip/style/css", + "element-plus/es/components/dropdown/style/css", + "element-plus/es/components/dropdown-menu/style/css", + "element-plus/es/components/dropdown-item/style/css", + "element-plus/es/components/sub-menu/style/css", + "element-plus/es/components/menu-item/style/css", + "element-plus/es/components/divider/style/css", + "element-plus/es/components/card/style/css", + "element-plus/es/components/link/style/css", + "element-plus/es/components/breadcrumb/style/css", + "element-plus/es/components/breadcrumb-item/style/css", + "element-plus/es/components/table/style/css", + "element-plus/es/components/tree-select/style/css", + "element-plus/es/components/table-column/style/css", + "element-plus/es/components/select/style/css", + "element-plus/es/components/option/style/css", + "element-plus/es/components/pagination/style/css", + "element-plus/es/components/tree/style/css", + "element-plus/es/components/alert/style/css", + "element-plus/es/components/radio-button/style/css", + "element-plus/es/components/checkbox-group/style/css", + "element-plus/es/components/checkbox/style/css", + "element-plus/es/components/tabs/style/css", + "element-plus/es/components/tab-pane/style/css", + "element-plus/es/components/rate/style/css", + "element-plus/es/components/date-picker/style/css", + "element-plus/es/components/notification/style/css", + "element-plus/es/components/image/style/css", + "element-plus/es/components/statistic/style/css", + "element-plus/es/components/watermark/style/css", + "element-plus/es/components/config-provider/style/css", + "element-plus/es/components/text/style/css", + "element-plus/es/components/drawer/style/css", + "element-plus/es/components/color-picker/style/css", + ], + }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + css: { + preprocessorOptions: { + scss: { + // 自动导入定制化样式文件进行样式覆盖 + additionalData: ` + @use "@/assets/css/index.scss" as *; + `, + } + } + }, +})