diff --git a/gym-manage-api/gym-coach/pom.xml b/gym-manage-api/gym-coach/pom.xml new file mode 100644 index 0000000..011f5f9 --- /dev/null +++ b/gym-manage-api/gym-coach/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + + cn.novalon.gym.manage + gym-manage-api + 1.0.0 + + + gym-coach + jar + + Gym Coach + Coach Management Module - Course Start/End, Violation Tracking + + + + cn.novalon.gym.manage + manage-common + ${project.version} + + + cn.novalon.gym.manage + manage-db + ${project.version} + + + cn.novalon.gym.manage + manage-sys + ${project.version} + + + cn.novalon.gym.manage + gym-groupCourse + ${project.version} + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.data + spring-data-commons + + + org.springdoc + springdoc-openapi-starter-webflux-ui + + + org.projectlombok + lombok + provided + + + diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/dao/CoachViolationDao.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/dao/CoachViolationDao.java new file mode 100644 index 0000000..3c39a2e --- /dev/null +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/dao/CoachViolationDao.java @@ -0,0 +1,15 @@ +package cn.novalon.gym.manage.coach.dao; + +import cn.novalon.gym.manage.coach.entity.CoachViolationEntity; +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import org.springframework.stereotype.Repository; + +/** + * 教练违规记录 DAO + * + * @author 张翔 + * @date 2026-07-20 + */ +@Repository +public interface CoachViolationDao extends R2dbcRepository { +} diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/entity/CoachViolationEntity.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/entity/CoachViolationEntity.java new file mode 100644 index 0000000..7d043a5 --- /dev/null +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/entity/CoachViolationEntity.java @@ -0,0 +1,61 @@ +package cn.novalon.gym.manage.coach.entity; + +import cn.novalon.gym.manage.db.entity.BaseEntity; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +import java.time.LocalDateTime; + +/** + * 教练违规记录实体类 - 对应 coach_violation 表 + * + * @author 张翔 + * @date 2026-07-20 + */ +@Table("coach_violation") +public class CoachViolationEntity extends BaseEntity { + + @Column("coach_id") + private Long coachId; + + @Column("course_id") + private Long courseId; + + @Column("violation_time") + private LocalDateTime violationTime; + + @Column("violation_reason") + private String violationReason; + + public Long getCoachId() { + return coachId; + } + + public void setCoachId(Long coachId) { + this.coachId = coachId; + } + + public Long getCourseId() { + return courseId; + } + + public void setCourseId(Long courseId) { + this.courseId = courseId; + } + + public LocalDateTime getViolationTime() { + return violationTime; + } + + public void setViolationTime(LocalDateTime violationTime) { + this.violationTime = violationTime; + } + + public String getViolationReason() { + return violationReason; + } + + public void setViolationReason(String violationReason) { + this.violationReason = violationReason; + } +} diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/enums/BookingStatus.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/enums/BookingStatus.java new file mode 100644 index 0000000..9d6bbcb --- /dev/null +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/enums/BookingStatus.java @@ -0,0 +1,42 @@ +package cn.novalon.gym.manage.coach.enums; + +/** + * 团课预约状态枚举 + * + * @author 张翔 + * @date 2026-07-20 + */ +public enum BookingStatus { + + BOOKED("0", "已预约"), + CANCELLED("1", "已取消"), + ATTENDED("2", "已出席"), + ABSENT("3", "缺席"), + COACH_ABSENT("4", "教练缺席"), + LATE("5", "迟到"); + + private final String value; + private final String desc; + + BookingStatus(String value, String desc) { + this.value = value; + this.desc = desc; + } + + public String getValue() { + return value; + } + + public String getDesc() { + return desc; + } + + public static BookingStatus fromValue(String value) { + for (BookingStatus status : values()) { + if (status.value.equals(value)) { + return status; + } + } + return BOOKED; + } +} diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/enums/ViolationReason.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/enums/ViolationReason.java new file mode 100644 index 0000000..c845c98 --- /dev/null +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/enums/ViolationReason.java @@ -0,0 +1,30 @@ +package cn.novalon.gym.manage.coach.enums; + +/** + * 教练违规原因枚举 + * + * @author 张翔 + * @date 2026-07-20 + */ +public enum ViolationReason { + + COACH_LATE("COACH_LATE", "教练迟到"), + COACH_ABSENT("COACH_ABSENT", "教练缺席"), + NOT_MANUAL_END("NOT_MANUAL_END", "教练未手动结课"); + + private final String value; + private final String desc; + + ViolationReason(String value, String desc) { + this.value = value; + this.desc = desc; + } + + public String getValue() { + return value; + } + + public String getDesc() { + return desc; + } +} diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/handler/CoachCourseHandler.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/handler/CoachCourseHandler.java new file mode 100644 index 0000000..09d8784 --- /dev/null +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/handler/CoachCourseHandler.java @@ -0,0 +1,68 @@ +package cn.novalon.gym.manage.coach.handler; + +import cn.novalon.gym.manage.coach.service.CoachCourseService; +import cn.novalon.gym.manage.sys.util.AuthUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.util.Map; + +/** + * 教练开课/结课处理器 + * + * @author 张翔 + * @date 2026-07-20 + */ +@Component +@Tag(name = "教练开课结课", description = "教练开课与结课操作") +public class CoachCourseHandler { + + private static final Logger logger = LoggerFactory.getLogger(CoachCourseHandler.class); + private final CoachCourseService coachCourseService; + private final AuthUtil authUtil; + + public CoachCourseHandler(CoachCourseService coachCourseService, AuthUtil authUtil) { + this.coachCourseService = coachCourseService; + this.authUtil = authUtil; + } + + @Operation(summary = "教练开课", description = "教练手动开课,记录实际开课时间,若超时则判定迟到") + public Mono startCourse(ServerRequest request) { + Long courseId = Long.valueOf(request.pathVariable("courseId")); + Long coachId = authUtil.getMemberIdOrThrow(request); + + return coachCourseService.startCourse(courseId, coachId) + .flatMap(result -> ServerResponse.ok().bodyValue(Map.of( + "message", "开课成功", + "status", result.getStatus(), + "actualStartTime", result.getActualStartTime() != null ? result.getActualStartTime().toString() : "" + ))) + .onErrorResume(e -> { + logger.error("开课失败: {}", e.getMessage()); + return ServerResponse.badRequest().bodyValue(Map.of("error", e.getMessage())); + }); + } + + @Operation(summary = "教练结课", description = "教练手动结课,记录实际结课时间") + public Mono endCourse(ServerRequest request) { + Long courseId = Long.valueOf(request.pathVariable("courseId")); + Long coachId = authUtil.getMemberIdOrThrow(request); + + return coachCourseService.endCourse(courseId, coachId) + .flatMap(result -> ServerResponse.ok().bodyValue(Map.of( + "message", "结课成功", + "status", result.getStatus(), + "actualEndTime", result.getActualEndTime() != null ? result.getActualEndTime().toString() : "" + ))) + .onErrorResume(e -> { + logger.error("结课失败: {}", e.getMessage()); + return ServerResponse.badRequest().bodyValue(Map.of("error", e.getMessage())); + }); + } +} diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/handler/CoachHandler.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/handler/CoachHandler.java new file mode 100644 index 0000000..766ea2c --- /dev/null +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/handler/CoachHandler.java @@ -0,0 +1,118 @@ +package cn.novalon.gym.manage.coach.handler; + +import cn.novalon.gym.manage.coach.service.CoachCourseService; +import cn.novalon.gym.manage.sys.core.domain.SysUser; +import cn.novalon.gym.manage.sys.dto.request.CoachCreateRequest; +import cn.novalon.gym.manage.sys.dto.request.CoachUpdateRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Validator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 教练管理处理器(从 manage-app 迁移至 gym-coach 模块) + * + * @author 张翔 + * @date 2026-07-19 + */ +@Component +@Tag(name = "教练管理", description = "教练相关操作") +public class CoachHandler { + + private static final Logger logger = LoggerFactory.getLogger(CoachHandler.class); + private final CoachCourseService coachCourseService; + private final Validator validator; + + public CoachHandler(CoachCourseService coachCourseService, Validator validator) { + this.coachCourseService = coachCourseService; + this.validator = validator; + } + + @Operation(summary = "获取所有教练", description = "获取系统中所有教练列表") + public Mono getAllCoaches(ServerRequest request) { + return ServerResponse.ok() + .body(coachCourseService.getAllCoaches(), SysUser.class); + } + + @Operation(summary = "创建教练", description = "创建新教练(创建用户并分配教练角色)") + public Mono createCoach(ServerRequest request) { + return request.bodyToMono(CoachCreateRequest.class) + .flatMap(req -> { + var violations = validator.validate(req); + if (!violations.isEmpty()) { + Map errors = new HashMap<>(); + violations.forEach(v -> errors.put(v.getPropertyPath().toString(), v.getMessage())); + return ServerResponse.badRequest().bodyValue(errors); + } + return coachCourseService.createCoach( + req.getUsername(), req.getPassword(), + req.getNickname(), req.getEmail(), req.getPhone()) + .flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user)) + .onErrorResume(e -> { + logger.error("创建教练失败", e); + return ServerResponse.badRequest() + .bodyValue(Map.of("error", e.getMessage())); + }); + }); + } + + @Operation(summary = "更新教练信息", description = "更新教练基本信息(昵称、邮箱、手机号)") + public Mono updateCoach(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return request.bodyToMono(CoachUpdateRequest.class) + .flatMap(req -> coachCourseService.updateCoach(id, req.getNickname(), req.getEmail(), req.getPhone()) + .flatMap(user -> ServerResponse.ok().bodyValue(user)) + .switchIfEmpty(ServerResponse.notFound().build()) + .onErrorResume(e -> { + logger.error("更新教练失败", e); + return ServerResponse.badRequest() + .bodyValue(Map.of("error", e.getMessage())); + })); + } + + @Operation(summary = "禁用教练", description = "禁用教练账号,自动取消其所有非进行中的团课") + public Mono disableCoach(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return coachCourseService.disableCoach(id) + .then(ServerResponse.ok().bodyValue(Map.of("message", "教练已禁用"))) + .onErrorResume(e -> { + logger.error("禁用教练失败: {}", e.getMessage()); + return ServerResponse.badRequest().bodyValue(Map.of("error", e.getMessage())); + }); + } + + @Operation(summary = "获取教练的团课", description = "获取指定教练教授的所有团课") + public Mono getCoachCourses(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return coachCourseService.getCoachCourses(id) + .collectList() + .flatMap(courses -> ServerResponse.ok().bodyValue(courses)) + .switchIfEmpty(ServerResponse.ok().bodyValue(List.of())); + } + + @Operation(summary = "获取教练违规次数统计", description = "获取所有教练的违规次数") + public Mono getViolationCounts(ServerRequest request) { + return coachCourseService.getViolationCounts() + .collectList() + .flatMap(counts -> ServerResponse.ok().bodyValue(counts)); + } + + @Operation(summary = "获取教练违规记录", description = "获取指定教练的违规记录") + public Mono getCoachViolations(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return coachCourseService.getCoachViolations(id) + .collectList() + .flatMap(violations -> ServerResponse.ok().bodyValue(violations)) + .switchIfEmpty(ServerResponse.ok().bodyValue(List.of())); + } +} diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseScheduler.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseScheduler.java new file mode 100644 index 0000000..f301e68 --- /dev/null +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseScheduler.java @@ -0,0 +1,178 @@ +package cn.novalon.gym.manage.coach.scheduler; + +import cn.novalon.gym.manage.coach.enums.ViolationReason; +import cn.novalon.gym.manage.common.util.RedisUtil; +import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao; +import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao; +import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity; +import cn.novalon.gym.manage.groupcourse.enums.CourseStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 教练课程定时调度器 + * + * 功能: + * 1. 检查未手动开课的课程,超时标记为教练缺席(5)并记录违规 + * 2. 检查未手动结课的课程,超时标记为自动结束(6)并记录违规 + * + * @author 张翔 + * @date 2026-07-20 + */ +@Component +public class CoachCourseScheduler { + + private static final Logger logger = LoggerFactory.getLogger(CoachCourseScheduler.class); + private static final long ONE_HOUR_MINUTES = 60; + private static final long END_GRACE_MINUTES = 10; + + private final GroupCourseDao groupCourseDao; + private final GroupCourseBookingDao groupCourseBookingDao; + private final DatabaseClient databaseClient; + private final RedisUtil redisUtil; + + public CoachCourseScheduler(GroupCourseDao groupCourseDao, + GroupCourseBookingDao groupCourseBookingDao, + DatabaseClient databaseClient, + RedisUtil redisUtil) { + this.groupCourseDao = groupCourseDao; + this.groupCourseBookingDao = groupCourseBookingDao; + this.databaseClient = databaseClient; + this.redisUtil = redisUtil; + } + + /** + * 每分钟检查一次: + * - status=0(NORMAL) 的课程是否已过开课缺席阈值 + * - status=3(IN_PROGRESS) 或 status=7(COACH_LATE) 的课程是否已过结课宽限期 + */ + @Scheduled(fixedRate = 60000) + public void checkAndProcessCourses() { + logger.debug("教练课程调度器开始检查"); + + LocalDateTime now = LocalDateTime.now(); + + // 1. 检查未开课的课程 + processAbsentCourses(now) + .subscribe( + count -> { + if (count > 0) { + logger.info("教练课程调度器:处理了 {} 门教练缺席课程", count); + invalidateCache(); + } + }, + error -> logger.error("教练课程调度器(缺席检查)执行失败:{}", error.getMessage(), error) + ); + + // 2. 检查未结课的课程 + processAutoEndCourses(now) + .subscribe( + count -> { + if (count > 0) { + logger.info("教练课程调度器:处理了 {} 门自动结束课程", count); + invalidateCache(); + } + }, + error -> logger.error("教练课程调度器(自动结课)执行失败:{}", error.getMessage(), error) + ); + } + + /** + * 处理缺席课程:status=0 且已过开课缺席阈值 + */ + private Mono processAbsentCourses(LocalDateTime now) { + return groupCourseDao.findByStatusAndStartTimeBefore(databaseClient, "0", now) + .filter(course -> isAbsentThresholdExceeded(course, now)) + .flatMap(course -> markAsCoachAbsent(course, now)) + .count(); + } + + /** + * 处理自动结课:status IN ('3','7') 且 end_time + 10分钟 已过 + */ + private Mono processAutoEndCourses(LocalDateTime now) { + LocalDateTime endThreshold = now.minusMinutes(END_GRACE_MINUTES); + return groupCourseDao.findByStatusInAndEndTimeBefore(databaseClient, + new String[]{String.valueOf(CourseStatus.IN_PROGRESS.getValue()), + String.valueOf(CourseStatus.COACH_LATE.getValue())}, + endThreshold) + .flatMap(course -> markAsAutoEnded(course, now)) + .count(); + } + + /** + * 判断课程是否已过缺席阈值 + */ + private boolean isAbsentThresholdExceeded(GroupCourseEntity course, LocalDateTime now) { + long courseDurationMinutes = Duration.between(course.getStartTime(), course.getEndTime()).toMinutes(); + long minutesSinceStart = Duration.between(course.getStartTime(), now).toMinutes(); + + if (courseDurationMinutes >= ONE_HOUR_MINUTES) { + return minutesSinceStart > 30; + } else { + long thresholdB = Math.max(1, (long) (courseDurationMinutes * 0.25)); + return minutesSinceStart > thresholdB; + } + } + + /** + * 标记课程为教练缺席(5),更新预约记录为教练缺席(4),记录违规 + */ + private Mono markAsCoachAbsent(GroupCourseEntity course, LocalDateTime now) { + logger.info("课程 {} 教练缺席,标记为 COACH_ABSENT", course.getId()); + + return insertViolation(course.getCoachId(), course.getId(), now, ViolationReason.COACH_ABSENT) + .then(groupCourseDao.updateToCoachAbsent(course.getId(), now, now)) + .then(groupCourseBookingDao.updateStatusByCourseId(course.getId(), "0", "4")) + .thenReturn(course); + } + + /** + * 标记课程为自动结束(6),记录违规 + */ + private Mono markAsAutoEnded(GroupCourseEntity course, LocalDateTime now) { + logger.info("课程 {} 未手动结课,标记为 AUTO_ENDED", course.getId()); + + return insertViolation(course.getCoachId(), course.getId(), now, ViolationReason.NOT_MANUAL_END) + .then(groupCourseDao.updateToAutoEnded(course.getId(), now, now)) + .thenReturn(course); + } + + /** + * 插入违规记录(使用 DatabaseClient 直连) + */ + private Mono insertViolation(Long coachId, Long courseId, LocalDateTime violationTime, + ViolationReason reason) { + return databaseClient.sql(""" + INSERT INTO coach_violation (coach_id, course_id, violation_time, violation_reason, created_at, updated_at) + VALUES (:coachId, :courseId, :violationTime, :reason, :now, :now) + """) + .bind("coachId", coachId) + .bind("courseId", courseId) + .bind("violationTime", violationTime) + .bind("reason", reason.getValue()) + .bind("now", LocalDateTime.now()) + .then(); + } + + /** + * 清除统计缓存和团课缓存 —— 调度器触发时,如有课程状态变更则必须及时失效 + */ + private void invalidateCache() { + redisUtil.deleteByPattern("datacount:statistics:*").subscribe( + deleted -> logger.debug("调度器清除统计缓存,已删除 {} 条", deleted), + error -> logger.warn("调度器清除统计缓存失败: {}", error.getMessage()) + ); + redisUtil.deleteByPattern("group_course:*").subscribe( + deleted -> logger.debug("调度器清除团课缓存,已删除 {} 条", deleted), + error -> logger.warn("调度器清除团课缓存失败: {}", error.getMessage()) + ); + } +} diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/service/CoachCourseService.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/service/CoachCourseService.java new file mode 100644 index 0000000..18fb3b6 --- /dev/null +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/service/CoachCourseService.java @@ -0,0 +1,357 @@ +package cn.novalon.gym.manage.coach.service; + +import cn.novalon.gym.manage.coach.dao.CoachViolationDao; +import cn.novalon.gym.manage.coach.entity.CoachViolationEntity; +import cn.novalon.gym.manage.coach.enums.ViolationReason; +import cn.novalon.gym.manage.common.util.RedisUtil; +import cn.novalon.gym.manage.common.util.StatusConstants; +import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao; +import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao; +import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; +import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity; +import cn.novalon.gym.manage.groupcourse.enums.CourseStatus; +import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository; +import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository; +import cn.novalon.gym.manage.sys.core.domain.SysRole; +import cn.novalon.gym.manage.sys.core.domain.SysUser; +import cn.novalon.gym.manage.sys.core.domain.UserRole; +import cn.novalon.gym.manage.sys.core.repository.ISysRoleRepository; +import cn.novalon.gym.manage.sys.core.repository.ISysUserRepository; +import cn.novalon.gym.manage.sys.core.repository.IUserRoleRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Sort; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 教练课程服务(含教练管理 + 开课/结课逻辑 + 违规记录) + * + * @author 张翔 + * @date 2026-07-20 + */ +@Service +public class CoachCourseService { + + private static final Logger logger = LoggerFactory.getLogger(CoachCourseService.class); + private static final String COACH_ROLE_NAME = "教练"; + private static final long ONE_HOUR_MINUTES = 60; + + private final ISysUserRepository userRepository; + private final ISysRoleRepository roleRepository; + private final IUserRoleRepository userRoleRepository; + private final IGroupCourseRepository groupCourseRepository; + private final IGroupCourseBookingRepository bookingRepository; + private final GroupCourseDao groupCourseDao; + private final GroupCourseBookingDao groupCourseBookingDao; + private final CoachViolationDao violationDao; + private final DatabaseClient databaseClient; + private final PasswordEncoder passwordEncoder; + private final RedisUtil redisUtil; + + public CoachCourseService(ISysUserRepository userRepository, + ISysRoleRepository roleRepository, + IUserRoleRepository userRoleRepository, + IGroupCourseRepository groupCourseRepository, + IGroupCourseBookingRepository bookingRepository, + GroupCourseDao groupCourseDao, + GroupCourseBookingDao groupCourseBookingDao, + CoachViolationDao violationDao, + DatabaseClient databaseClient, + PasswordEncoder passwordEncoder, + RedisUtil redisUtil) { + this.userRepository = userRepository; + this.roleRepository = roleRepository; + this.userRoleRepository = userRoleRepository; + this.groupCourseRepository = groupCourseRepository; + this.bookingRepository = bookingRepository; + this.groupCourseDao = groupCourseDao; + this.groupCourseBookingDao = groupCourseBookingDao; + this.violationDao = violationDao; + this.databaseClient = databaseClient; + this.passwordEncoder = passwordEncoder; + this.redisUtil = redisUtil; + } + + // ==================== 教练管理(从原 CoachService 迁移) ==================== + + public Flux getAllCoaches() { + return getCoachRoleId() + .flatMapMany(roleId -> userRoleRepository.findByRoleId(roleId) + .map(UserRole::getUserId) + .collectList() + .flatMapMany(userIds -> { + if (userIds.isEmpty()) { + return Flux.empty(); + } + return Flux.fromIterable(userIds) + .flatMap(userRepository::findById) + .filter(user -> user.getDeletedAt() == null); + })); + } + + public Mono createCoach(String username, String password, String nickname, String email, String phone) { + return getCoachRoleId().flatMap(coachRoleId -> { + SysUser user = new SysUser(); + user.generateId(); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(password)); + user.setNickname(nickname); + user.setEmail(email); + user.setPhone(phone); + user.setStatus(StatusConstants.ENABLED); + + return userRepository.save(user) + .flatMap(saved -> { + UserRole userRole = new UserRole(); + userRole.setUserId(saved.getId()); + userRole.setRoleId(coachRoleId); + return userRoleRepository.save(userRole).thenReturn(saved); + }); + }); + } + + public Mono updateCoach(Long id, String nickname, String email, String phone) { + return userRepository.findById(id) + .switchIfEmpty(Mono.error(new RuntimeException("教练不存在"))) + .flatMap(user -> { + if (nickname != null) user.setNickname(nickname); + if (email != null) user.setEmail(email); + if (phone != null) user.setPhone(phone); + user.setUpdatedAt(LocalDateTime.now()); + return userRepository.update(user); + }); + } + + @Transactional(transactionManager = "connectionFactoryTransactionManager") + public Mono disableCoach(Long id) { + return userRepository.findById(id) + .switchIfEmpty(Mono.error(new RuntimeException("教练不存在"))) + .flatMap(user -> + groupCourseRepository.countByCoachIdAndStatus(id, 3L) + .flatMap(inProgressCount -> { + if (inProgressCount > 0) { + return Mono.error(new RuntimeException( + "该教练有 " + inProgressCount + " 门正在进行中的团课,无法禁用")); + } + return groupCourseRepository.cancelCoursesByCoachIdExceptStatus(id, 3L) + .then(); + }) + .then(Mono.defer(() -> { + user.setStatus(StatusConstants.DISABLED); + user.setUpdatedAt(LocalDateTime.now()); + return userRepository.update(user).then(); + })) + ) + .then(invalidateStatisticsCache()); + } + + public Flux getCoachCourses(Long coachId) { + return groupCourseRepository.findByCoachId(coachId, Sort.by(Sort.Direction.DESC, "startTime")) + .flatMap(course -> + bookingRepository.countValidBookings(course.getId()) + .map(count -> { + course.setCurrentMembers(count.intValue()); + return course; + }) + .defaultIfEmpty(course) + ); + } + + public Mono getCoachRoleId() { + return roleRepository.findByRoleName(COACH_ROLE_NAME) + .map(SysRole::getId) + .switchIfEmpty(Mono.error(new RuntimeException("教练角色未找到,请先执行数据库迁移"))); + } + + // ==================== 开课逻辑 ==================== + + /** + * 教练手动开课 + * 判定逻辑: + * - 长课时(>=1h): 10分钟内正常,10~30分钟迟到,>30分钟拒绝 + * - 短课时(<1h): 10%时长内正常,10%~25%迟到,>25%拒绝 + */ + public Mono startCourse(Long courseId, Long coachId) { + return groupCourseDao.findByIdIsAndDeletedAtIsNull(courseId) + .switchIfEmpty(Mono.error(new RuntimeException("团课不存在"))) + .flatMap(course -> { + // 验证教练身份 + if (!course.getCoachId().equals(coachId)) { + return Mono.error(new RuntimeException("您不是该课程的教练,无权开课")); + } + // 验证课程状态:只有 NORMAL(0) 可以开课 + if (!CourseStatus.NORMAL.getValue().equals(course.getStatus())) { + return Mono.error(new RuntimeException("当前课程状态不允许开课,当前状态: " + course.getStatus())); + } + + LocalDateTime now = LocalDateTime.now(); + long courseDurationMinutes = Duration.between(course.getStartTime(), course.getEndTime()).toMinutes(); + + if (courseDurationMinutes >= ONE_HOUR_MINUTES) { + return handleLongCourseStart(course, now); + } else { + return handleShortCourseStart(course, now, courseDurationMinutes); + } + }); + } + + private Mono handleLongCourseStart(GroupCourseEntity course, LocalDateTime now) { + long minutesSinceStart = Duration.between(course.getStartTime(), now).toMinutes(); + + if (minutesSinceStart < 0) { + return Mono.error(new RuntimeException("课程尚未到开课时间")); + } + if (minutesSinceStart <= 10) { + // 正常开课 + return doStartCourse(course, now, CourseStatus.IN_PROGRESS, null); + } + if (minutesSinceStart <= 30) { + // 教练迟到 + return recordViolation(course.getCoachId(), course.getId(), now, ViolationReason.COACH_LATE) + .then(doStartCourse(course, now, CourseStatus.COACH_LATE, ViolationReason.COACH_LATE)); + } + // >30分钟,拒绝(调度器应已标记为缺席) + return Mono.error(new RuntimeException("已超过开课时间30分钟,无法开课")); + } + + private Mono handleShortCourseStart(GroupCourseEntity course, LocalDateTime now, + long courseDurationMinutes) { + long minutesSinceStart = Duration.between(course.getStartTime(), now).toMinutes(); + long thresholdA = Math.max(1, (long) (courseDurationMinutes * 0.10)); + long thresholdB = Math.max(1, (long) (courseDurationMinutes * 0.25)); + + if (minutesSinceStart < 0) { + return Mono.error(new RuntimeException("课程尚未到开课时间")); + } + if (minutesSinceStart <= thresholdA) { + // 正常开课 + return doStartCourse(course, now, CourseStatus.IN_PROGRESS, null); + } + if (minutesSinceStart <= thresholdB) { + // 教练迟到 + return recordViolation(course.getCoachId(), course.getId(), now, ViolationReason.COACH_LATE) + .then(doStartCourse(course, now, CourseStatus.COACH_LATE, ViolationReason.COACH_LATE)); + } + // >thresholdB,拒绝 + return Mono.error(new RuntimeException("已超过开课时间,无法开课")); + } + + private Mono doStartCourse(GroupCourseEntity course, LocalDateTime now, + CourseStatus newStatus, ViolationReason violationReason) { + course.setStatus(newStatus.getValue()); + course.setActualStartTime(now); + course.setUpdatedAt(now); + return groupCourseDao.updateStartInfo(course.getId(), String.valueOf(newStatus.getValue()), now, now) + .then(groupCourseDao.findByIdIsAndDeletedAtIsNull(course.getId())) + .flatMap(entity -> invalidateStatisticsCache().thenReturn(entity)); + } + + // ==================== 结课逻辑 ==================== + + /** + * 教练手动结课 + * 可在 IN_PROGRESS(3) 或 COACH_LATE(7) 状态下结课 + * 必须在标注结课时间 + 10分钟内 + */ + public Mono endCourse(Long courseId, Long coachId) { + return groupCourseDao.findByIdIsAndDeletedAtIsNull(courseId) + .switchIfEmpty(Mono.error(new RuntimeException("团课不存在"))) + .flatMap(course -> { + // 验证教练身份 + if (!course.getCoachId().equals(coachId)) { + return Mono.error(new RuntimeException("您不是该课程的教练,无权结课")); + } + // 验证状态:IN_PROGRESS(3) 或 COACH_LATE(7) + Long status = course.getStatus(); + if (!CourseStatus.IN_PROGRESS.getValue().equals(status) + && !CourseStatus.COACH_LATE.getValue().equals(status)) { + return Mono.error(new RuntimeException("当前课程状态不允许结课,当前状态: " + status)); + } + + LocalDateTime now = LocalDateTime.now(); + long minutesAfterEnd = Duration.between(course.getEndTime(), now).toMinutes(); + + if (minutesAfterEnd > 10) { + return Mono.error(new RuntimeException("已超过结课时间10分钟,请等待系统自动结课")); + } + + course.setStatus(CourseStatus.ENDED.getValue()); + course.setActualEndTime(now); + course.setUpdatedAt(now); + return groupCourseDao.updateEndInfo(course.getId(), String.valueOf(CourseStatus.ENDED.getValue()), now, now) + .then(groupCourseDao.findByIdIsAndDeletedAtIsNull(course.getId())) + .flatMap(entity -> invalidateStatisticsCache().thenReturn(entity)); + }); + } + + // ==================== 违规记录 ==================== + + /** + * 记录教练违规行为(使用 DatabaseClient 直连,避免 R2DBC Entity 映射问题) + */ + public Mono recordViolation(Long coachId, Long courseId, LocalDateTime violationTime, + ViolationReason reason) { + LocalDateTime now = LocalDateTime.now(); + return databaseClient.sql(""" + INSERT INTO coach_violation (coach_id, course_id, violation_time, violation_reason, created_at, updated_at) + VALUES (:coachId, :courseId, :violationTime, :reason, :now, :now) + """) + .bind("coachId", coachId) + .bind("courseId", courseId) + .bind("violationTime", violationTime) + .bind("reason", reason.getValue()) + .bind("now", now) + .then(); + } + + // ==================== 违规查询 ==================== + + /** + * 获取所有教练的违规次数统计 + */ + public Flux> getViolationCounts() { + return databaseClient.sql(""" + SELECT coach_id, COUNT(*) AS count + FROM coach_violation + WHERE deleted_at IS NULL + GROUP BY coach_id + """) + .fetch() + .all(); + } + + /** + * 获取指定教练的违规记录 + */ + public Flux> getCoachViolations(Long coachId) { + return databaseClient.sql(""" + SELECT v.*, gc.course_name + FROM coach_violation v + LEFT JOIN group_course gc ON v.course_id = gc.id AND gc.deleted_at IS NULL + WHERE v.coach_id = :coachId AND v.deleted_at IS NULL + ORDER BY v.violation_time DESC + """) + .bind("coachId", coachId) + .fetch() + .all(); + } + + /** + * 清除统计缓存和团课缓存 —— 课程状态变更后调用,返回 Mono 确保链式执行 + */ + private Mono invalidateStatisticsCache() { + return redisUtil.deleteByPattern("datacount:statistics:*") + .then(redisUtil.deleteByPattern("group_course:*")) + .doOnNext(count -> {}) + .then(); + } +} diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/dao/DataStatisticsDao.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/dao/DataStatisticsDao.java index c08df57..d26c5f8 100644 --- a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/dao/DataStatisticsDao.java +++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/dao/DataStatisticsDao.java @@ -6,6 +6,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.LocalDateTime; +import java.util.Map; /** * 数据统计 DAO - 使用 DatabaseClient 执行跨表聚合查询 @@ -180,4 +181,68 @@ public class DataStatisticsDao { this.count = count; } } + + // ========== 教练相关统计 ========== + + /** 统计教练总数(角色为"教练"的用户) */ + public Mono countTotalCoaches() { + return databaseClient.sql(""" + SELECT COUNT(*) FROM sys_user u + INNER JOIN user_role ur ON u.id = ur.user_id + INNER JOIN sys_role sr ON ur.role_id = sr.id + WHERE sr.role_name = '教练' AND u.deleted_at IS NULL + """) + .map(row -> row.get(0, Long.class)) + .one(); + } + + /** 统计违规总数(时间范围) */ + public Mono countTotalViolations(LocalDateTime startTime, LocalDateTime endTime) { + return databaseClient.sql(""" + SELECT COUNT(*) FROM coach_violation + WHERE violation_time >= :startTime AND violation_time < :endTime AND deleted_at IS NULL + """) + .bind("startTime", startTime) + .bind("endTime", endTime) + .map(row -> row.get(0, Long.class)) + .one(); + } + + /** 按违规类型统计次数 */ + public Flux> countViolationsByReason(LocalDateTime startTime, LocalDateTime endTime) { + return databaseClient.sql(""" + SELECT violation_reason, COUNT(*) AS count + FROM coach_violation + WHERE violation_time >= :startTime AND violation_time < :endTime AND deleted_at IS NULL + GROUP BY violation_reason + """) + .bind("startTime", startTime) + .bind("endTime", endTime) + .fetch() + .all(); + } + + /** 统计有违规记录的教练数 */ + public Mono countViolatedCoaches(LocalDateTime startTime, LocalDateTime endTime) { + return databaseClient.sql(""" + SELECT COUNT(DISTINCT coach_id) FROM coach_violation + WHERE violation_time >= :startTime AND violation_time < :endTime AND deleted_at IS NULL + """) + .bind("startTime", startTime) + .bind("endTime", endTime) + .map(row -> row.get(0, Long.class)) + .one(); + } + + /** 统计区间内开课的团课数 */ + public Mono countCourses(LocalDateTime startTime, LocalDateTime endTime) { + return databaseClient.sql(""" + SELECT COUNT(*) FROM group_course + WHERE start_time >= :startTime AND start_time < :endTime AND deleted_at IS NULL + """) + .bind("startTime", startTime) + .bind("endTime", endTime) + .map(row -> row.get(0, Long.class)) + .one(); + } } \ No newline at end of file diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/domain/CoachStatistics.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/domain/CoachStatistics.java new file mode 100644 index 0000000..3c8fbf8 --- /dev/null +++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/domain/CoachStatistics.java @@ -0,0 +1,40 @@ +package cn.novalon.gym.manage.datacount.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 教练统计数据 + * + * @author 张翔 + * @date 2026-07-20 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CoachStatistics { + + /** 教练总数 */ + private Long totalCoaches; + + /** 违规总数 */ + private Long totalViolations; + + /** 迟到次数 */ + private Long lateCount; + + /** 缺席次数 */ + private Long absentCount; + + /** 未手动结课次数 */ + private Long notManualEndCount; + + /** 有违规记录的教练数 */ + private Long violatedCoaches; + + /** 统计区间内开课的团课数 */ + private Long totalCourses; +} diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/domain/StatisticsSummary.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/domain/StatisticsSummary.java index 2e7dd12..7d86d1b 100644 --- a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/domain/StatisticsSummary.java +++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/domain/StatisticsSummary.java @@ -37,6 +37,11 @@ public class StatisticsSummary { */ private SignInStatistics signInStatistics; + /** + * 教练统计数据 + */ + private CoachStatistics coachStatistics; + /** * 统计数据生成时间 */ diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/service/impl/DataStatisticsServiceImpl.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/service/impl/DataStatisticsServiceImpl.java index 074d29f..352d295 100644 --- a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/service/impl/DataStatisticsServiceImpl.java +++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/service/impl/DataStatisticsServiceImpl.java @@ -171,18 +171,47 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService { return count != null ? count : 0L; } + private Mono getCoachStatistics(StatisticsQuery query) { + LocalDateTime startTime = getStartTime(query); + LocalDateTime endTime = getEndTime(query); + + Mono totalCoachesMono = dataStatisticsDao.countTotalCoaches(); + Mono totalViolationsMono = dataStatisticsDao.countTotalViolations(startTime, endTime); + Mono violatedCoachesMono = dataStatisticsDao.countViolatedCoaches(startTime, endTime); + Mono totalCoursesMono = dataStatisticsDao.countCourses(startTime, endTime); + Mono> violationByReasonMono = dataStatisticsDao.countViolationsByReason(startTime, endTime) + .collectMap(row -> (String) row.get("violation_reason"), + row -> ((Number) row.get("count")).longValue()); + + return Mono.zip(totalCoachesMono, totalViolationsMono, violatedCoachesMono, totalCoursesMono, violationByReasonMono) + .map(tuple -> { + Map reasonMap = tuple.getT5(); + return CoachStatistics.builder() + .totalCoaches(tuple.getT1()) + .totalViolations(tuple.getT2()) + .lateCount(reasonMap.getOrDefault("COACH_LATE", 0L)) + .absentCount(reasonMap.getOrDefault("COACH_ABSENT", 0L)) + .notManualEndCount(reasonMap.getOrDefault("NOT_MANUAL_END", 0L)) + .violatedCoaches(tuple.getT3()) + .totalCourses(tuple.getT4()) + .build(); + }); + } + @Override public Mono getStatisticsSummary(StatisticsQuery query) { Mono memberStatsMono = getMemberStatistics(query); Mono bookingStatsMono = getBookingStatistics(query); Mono signInStatsMono = getSignInStatistics(query); + Mono coachStatsMono = getCoachStatistics(query); - return Mono.zip(memberStatsMono, bookingStatsMono, signInStatsMono) + return Mono.zip(memberStatsMono, bookingStatsMono, signInStatsMono, coachStatsMono) .map(tuple -> StatisticsSummary.builder() .statDate(LocalDateTime.now().toLocalDate().toString()) .memberStatistics(tuple.getT1()) .bookingStatistics(tuple.getT2()) .signInStatistics(tuple.getT3()) + .coachStatistics(tuple.getT4()) .generatedAt(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) .build()); } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseBookingDao.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseBookingDao.java index ec0bc5c..9b11e9a 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseBookingDao.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseBookingDao.java @@ -114,4 +114,11 @@ public interface GroupCourseBookingDao extends R2dbcRepository updateToAbsent(Long id, java.time.LocalDateTime updatedAt); + + /** + * 批量更新某课程的预约状态(教练缺席场景) + */ + @org.springframework.data.r2dbc.repository.Modifying + @org.springframework.data.r2dbc.repository.Query("UPDATE group_course_booking SET status = :newStatus, updated_at = NOW() WHERE course_id = :courseId AND status = :oldStatus AND deleted_at IS NULL") + Mono updateStatusByCourseId(Long courseId, String oldStatus, String newStatus); } \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java index aab2e18..2d5c32d 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java @@ -33,60 +33,110 @@ public interface GroupCourseDao extends R2dbcRepository @Query("UPDATE group_course SET status = '1', updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL") Mono cancelCourse(Long id, LocalDateTime updatedAt); - @Modifying - @Query("UPDATE group_course SET current_members = current_members + :delta, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL") - Mono updateCurrentMembers(Long id, Integer delta, LocalDateTime updatedAt); - @Modifying @Query("UPDATE group_course SET deleted_at = :deletedAt WHERE id = :id") Mono softDelete(Long id, LocalDateTime deletedAt); + // ---------- 教练相关 SQL 方法 ---------- + @Modifying - @Query("UPDATE group_course SET status = '2', updated_at = :updatedAt WHERE status = '0' AND end_time <= NOW() AND deleted_at IS NULL") - Mono completeExpiredCourses(LocalDateTime updatedAt); + @Query("UPDATE group_course SET status = :status, actual_start_time = :actualStartTime, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL") + Mono updateStartInfo(Long id, String status, LocalDateTime actualStartTime, LocalDateTime updatedAt); + + @Modifying + @Query("UPDATE group_course SET status = :status, actual_end_time = :actualEndTime, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL") + Mono updateEndInfo(Long id, String status, LocalDateTime actualEndTime, LocalDateTime updatedAt); + + @Modifying + @Query("UPDATE group_course SET status = '5', actual_end_time = :actualEndTime, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL") + Mono updateToCoachAbsent(Long id, LocalDateTime actualEndTime, LocalDateTime updatedAt); + + @Modifying + @Query("UPDATE group_course SET status = '6', actual_end_time = :actualEndTime, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL") + Mono updateToAutoEnded(Long id, LocalDateTime actualEndTime, LocalDateTime updatedAt); + + /** + * 查询指定状态且开始时间早于指定时间的课程(用于缺席检查) + */ + default Flux findByStatusAndStartTimeBefore(DatabaseClient databaseClient, String status, LocalDateTime time) { + return databaseClient.sql("SELECT * FROM group_course WHERE status = :status AND start_time < :time AND deleted_at IS NULL") + .bind("status", status) + .bind("time", time) + .map((row, meta) -> mapRowToEntity(row)) + .all(); + } + + /** + * 查询指定状态集合且结束时间早于指定时间的课程(用于自动结课检查) + */ + default Flux findByStatusInAndEndTimeBefore(DatabaseClient databaseClient, String[] statuses, LocalDateTime time) { + return databaseClient.sql("SELECT * FROM group_course WHERE status = ANY(:statuses::varchar[]) AND end_time < :time AND deleted_at IS NULL") + .bind("statuses", statuses) + .bind("time", time) + .map((row, meta) -> mapRowToEntity(row)) + .all(); + } Flux findByCourseTypeAndDeletedAtIsNull(Long courseType); + // ==================== 教练相关查询 ==================== + + Flux findByCoachIdAndDeletedAtIsNull(Long coachId); + + Flux findByCoachIdAndDeletedAtIsNull(Long coachId, Sort sort); + + @Query("SELECT COUNT(*) FROM group_course WHERE coach_id = :coachId AND status = :status AND deleted_at IS NULL") + Mono countByCoachIdAndStatus(Long coachId, String status); + + @Modifying + @Query("UPDATE group_course SET status = '1', updated_at = :updatedAt WHERE coach_id = :coachId AND status != :excludeStatus AND deleted_at IS NULL") + Mono cancelCoursesByCoachIdExceptStatus(Long coachId, String excludeStatus, LocalDateTime updatedAt); + /** * 多条件查询团课(使用 DatabaseClient 构建动态 SQL) */ default Flux searchGroupCourses(DatabaseClient databaseClient, GroupCourseQueryDto query) { - StringBuilder sql = new StringBuilder("SELECT * FROM group_course WHERE deleted_at IS NULL"); + StringBuilder sql = new StringBuilder( + "SELECT gc.*, COALESCE(b.cnt, 0) AS current_members " + + "FROM group_course gc " + + "LEFT JOIN (SELECT course_id, COUNT(*) AS cnt FROM group_course_booking WHERE status = '0' GROUP BY course_id) b " + + "ON gc.id = b.course_id " + + "WHERE gc.deleted_at IS NULL"); List conditions = new ArrayList<>(); // 默认不查询可预约团课(status = '0' 且未过期) - conditions.add("status = '0'"); - conditions.add("end_time > NOW()"); + conditions.add("gc.status = '0'"); + conditions.add("gc.end_time > NOW()"); // 1. 团课名称模糊查询 if (query.getCourseName() != null && !query.getCourseName().isEmpty()) { - conditions.add("course_name ILIKE :courseName"); + conditions.add("gc.course_name ILIKE :courseName"); } // 2. 基于团课类型查询 if (query.getCourseType() != null) { - conditions.add("course_type = :courseType"); + conditions.add("gc.course_type = :courseType"); } // 3. 基于日期时间段查询 if (query.getStartDate() != null) { - conditions.add("start_time >= :startDate"); + conditions.add("gc.start_time >= :startDate"); } if (query.getEndDate() != null) { - conditions.add("start_time <= :endDate"); + conditions.add("gc.start_time <= :endDate"); } // 4. 基于早晨/下午/夜晚时间段查询 if (query.getTimePeriod() != null && !query.getTimePeriod().isEmpty()) { switch (query.getTimePeriod().toLowerCase()) { case "morning": - conditions.add("EXTRACT(HOUR FROM start_time) >= 6 AND EXTRACT(HOUR FROM start_time) < 12"); + conditions.add("EXTRACT(HOUR FROM gc.start_time) >= 6 AND EXTRACT(HOUR FROM gc.start_time) < 12"); break; case "afternoon": - conditions.add("EXTRACT(HOUR FROM start_time) >= 12 AND EXTRACT(HOUR FROM start_time) < 18"); + conditions.add("EXTRACT(HOUR FROM gc.start_time) >= 12 AND EXTRACT(HOUR FROM gc.start_time) < 18"); break; case "evening": - conditions.add("EXTRACT(HOUR FROM start_time) >= 18 AND EXTRACT(HOUR FROM start_time) < 24"); + conditions.add("EXTRACT(HOUR FROM gc.start_time) >= 18 AND EXTRACT(HOUR FROM gc.start_time) < 24"); break; default: break; @@ -104,20 +154,20 @@ public interface GroupCourseDao extends R2dbcRepository List orderClauses = new ArrayList<>(); if (hasRemainingMost) { - orderClauses.add(" (max_members - current_members) DESC"); + orderClauses.add(" (gc.max_members - COALESCE(b.cnt, 0)) DESC"); } if (hasPriceSort) { if ("asc".equalsIgnoreCase(query.getPriceSort())) { - orderClauses.add(" stored_value_amount ASC"); + orderClauses.add(" gc.stored_value_amount ASC"); } else if ("desc".equalsIgnoreCase(query.getPriceSort())) { - orderClauses.add(" stored_value_amount DESC"); + orderClauses.add(" gc.stored_value_amount DESC"); } } sql.append(String.join(",", orderClauses)); } else { - sql.append(" ORDER BY start_time ASC"); + sql.append(" ORDER BY gc.start_time ASC"); } // 分页 @@ -145,30 +195,7 @@ public interface GroupCourseDao extends R2dbcRepository spec = spec.bind("limit", size); spec = spec.bind("offset", offset); - return spec.map((row, meta) -> { - GroupCourseEntity entity = new GroupCourseEntity(); - entity.setId(row.get("id", Long.class)); - entity.setCourseName(row.get("course_name", String.class)); - entity.setCoachId(row.get("coach_id", Long.class)); - entity.setCourseType(row.get("course_type", Long.class)); - entity.setStartTime(row.get("start_time", LocalDateTime.class)); - entity.setEndTime(row.get("end_time", LocalDateTime.class)); - entity.setMaxMembers(row.get("max_members", Integer.class)); - entity.setCurrentMembers(row.get("current_members", Integer.class)); - String statusStr = row.get("status", String.class); - entity.setStatus(statusStr != null ? Long.parseLong(statusStr) : null); - entity.setLocation(row.get("location", String.class)); - entity.setCoverImage(row.get("cover_image", String.class)); - entity.setDescription(row.get("description", String.class)); - entity.setStoredValueAmount(row.get("stored_value_amount", java.math.BigDecimal.class)); - entity.setQrCodePath(row.get("qr_code_path", String.class)); - entity.setCreateBy(row.get("create_by", String.class)); - entity.setUpdateBy(row.get("update_by", String.class)); - entity.setCreatedAt(row.get("created_at", LocalDateTime.class)); - entity.setUpdatedAt(row.get("updated_at", LocalDateTime.class)); - entity.setDeletedAt(row.get("deleted_at", LocalDateTime.class)); - return entity; - }).all(); + return spec.map((row, meta) -> mapRowToEntity(row)).all(); } /** @@ -233,14 +260,19 @@ public interface GroupCourseDao extends R2dbcRepository * 分页查询团课(支持 keyword 和 status 过滤) */ default Flux findByPageFiltered(DatabaseClient databaseClient, PageRequest pageRequest) { - StringBuilder sql = new StringBuilder("SELECT * FROM group_course WHERE deleted_at IS NULL"); + StringBuilder sql = new StringBuilder( + "SELECT gc.*, COALESCE(b.cnt, 0) AS current_members " + + "FROM group_course gc " + + "LEFT JOIN (SELECT course_id, COUNT(*) AS cnt FROM group_course_booking WHERE status = '0' GROUP BY course_id) b " + + "ON gc.id = b.course_id " + + "WHERE gc.deleted_at IS NULL"); List conditions = new ArrayList<>(); if (pageRequest.getKeyword() != null && !pageRequest.getKeyword().isEmpty()) { - conditions.add("course_name ILIKE :keyword"); + conditions.add("gc.course_name ILIKE :keyword"); } if (pageRequest.getStatus() != null && !pageRequest.getStatus().isEmpty()) { - conditions.add("status = :status"); + conditions.add("gc.status = :status"); } if (!conditions.isEmpty()) { @@ -249,7 +281,7 @@ public interface GroupCourseDao extends R2dbcRepository String sort = pageRequest.getSort() != null ? pageRequest.getSort() : "id"; String order = "desc".equalsIgnoreCase(pageRequest.getOrder()) ? "DESC" : "ASC"; - sql.append(" ORDER BY ").append(sort).append(" ").append(order); + sql.append(" ORDER BY gc.").append(sort).append(" ").append(order); int size = pageRequest.getSize(); if (size < 1) size = 10; @@ -268,30 +300,7 @@ public interface GroupCourseDao extends R2dbcRepository spec = spec.bind("limit", size); spec = spec.bind("offset", offset); - return spec.map((row, meta) -> { - GroupCourseEntity entity = new GroupCourseEntity(); - entity.setId(row.get("id", Long.class)); - entity.setCourseName(row.get("course_name", String.class)); - entity.setCoachId(row.get("coach_id", Long.class)); - entity.setCourseType(row.get("course_type", Long.class)); - entity.setStartTime(row.get("start_time", LocalDateTime.class)); - entity.setEndTime(row.get("end_time", LocalDateTime.class)); - entity.setMaxMembers(row.get("max_members", Integer.class)); - entity.setCurrentMembers(row.get("current_members", Integer.class)); - String statusStr = row.get("status", String.class); - entity.setStatus(statusStr != null ? Long.parseLong(statusStr) : null); - entity.setLocation(row.get("location", String.class)); - entity.setCoverImage(row.get("cover_image", String.class)); - entity.setDescription(row.get("description", String.class)); - entity.setStoredValueAmount(row.get("stored_value_amount", java.math.BigDecimal.class)); - entity.setQrCodePath(row.get("qr_code_path", String.class)); - entity.setCreateBy(row.get("create_by", String.class)); - entity.setUpdateBy(row.get("update_by", String.class)); - entity.setCreatedAt(row.get("created_at", LocalDateTime.class)); - entity.setUpdatedAt(row.get("updated_at", LocalDateTime.class)); - entity.setDeletedAt(row.get("deleted_at", LocalDateTime.class)); - return entity; - }).all(); + return spec.map((row, meta) -> mapRowToEntity(row)).all(); } /** @@ -323,4 +332,46 @@ public interface GroupCourseDao extends R2dbcRepository return spec.map((row, meta) -> row.get(0, Long.class)).one(); } + + /** + * 统计单门课程的有效预约人数(status='0') + */ + default Mono countValidBookings(DatabaseClient databaseClient, Long courseId) { + return databaseClient.sql("SELECT COUNT(*) FROM group_course_booking WHERE course_id = :id AND status = '0'") + .bind("id", courseId) + .map((row, meta) -> row.get(0, Long.class)) + .one() + .defaultIfEmpty(0L); + } + + /** + * 将数据库行映射为 GroupCourseEntity + */ + private GroupCourseEntity mapRowToEntity(io.r2dbc.spi.Row row) { + GroupCourseEntity entity = new GroupCourseEntity(); + entity.setId(row.get("id", Long.class)); + entity.setCourseName(row.get("course_name", String.class)); + entity.setCoachId(row.get("coach_id", Long.class)); + entity.setCourseType(row.get("course_type", Long.class)); + entity.setStartTime(row.get("start_time", LocalDateTime.class)); + entity.setEndTime(row.get("end_time", LocalDateTime.class)); + entity.setMaxMembers(row.get("max_members", Integer.class)); + Integer cm = row.get("current_members", Integer.class); + entity.setCurrentMembers(cm != null ? cm : 0); + String statusStr = row.get("status", String.class); + entity.setStatus(statusStr != null ? Long.parseLong(statusStr) : null); + entity.setLocation(row.get("location", String.class)); + entity.setCoverImage(row.get("cover_image", String.class)); + entity.setDescription(row.get("description", String.class)); + entity.setStoredValueAmount(row.get("stored_value_amount", java.math.BigDecimal.class)); + entity.setActualStartTime(row.get("actual_start_time", LocalDateTime.class)); + entity.setActualEndTime(row.get("actual_end_time", LocalDateTime.class)); + entity.setQrCodePath(row.get("qr_code_path", String.class)); + entity.setCreateBy(row.get("create_by", String.class)); + entity.setUpdateBy(row.get("update_by", String.class)); + entity.setCreatedAt(row.get("created_at", LocalDateTime.class)); + entity.setUpdatedAt(row.get("updated_at", LocalDateTime.class)); + entity.setDeletedAt(row.get("deleted_at", LocalDateTime.class)); + return entity; + } } \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourse.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourse.java index 2369e2d..66ca574 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourse.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourse.java @@ -40,6 +40,14 @@ public class GroupCourse extends BaseDomain{ @Schema(description = "课程状态", example = "0") private Long status; + //实际开课时间 + @Schema(description = "实际开课时间") + private LocalDateTime actualStartTime; + + //实际结课时间 + @Schema(description = "实际结课时间") + private LocalDateTime actualEndTime; + //上课地点 @Schema(description = "上课地点", example = "龙泉驿区幸福路") private String location; @@ -124,6 +132,22 @@ public class GroupCourse extends BaseDomain{ this.status = status; } + public LocalDateTime getActualStartTime() { + return actualStartTime; + } + + public void setActualStartTime(LocalDateTime actualStartTime) { + this.actualStartTime = actualStartTime; + } + + public LocalDateTime getActualEndTime() { + return actualEndTime; + } + + public void setActualEndTime(LocalDateTime actualEndTime) { + this.actualEndTime = actualEndTime; + } + public String getLocation() { return location; } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java index bd693c9..784547c 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java @@ -21,6 +21,9 @@ public class GroupCourseDetail extends BaseDomain { @Schema(description = "教练ID", example = "1") private Long coachId; + @Schema(description = "教练名称", example = "张教练") + private String coachName; + @Schema(description = "课程类型ID", example = "1") private Long courseType; @@ -99,6 +102,14 @@ public class GroupCourseDetail extends BaseDomain { this.coachId = coachId; } + public String getCoachName() { + return coachName; + } + + public void setCoachName(String coachName) { + this.coachName = coachName; + } + public Long getCourseType() { return courseType; } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseEntity.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseEntity.java index 5ef3fa7..4d65dc4 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseEntity.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseEntity.java @@ -38,7 +38,15 @@ public class GroupCourseEntity extends BaseEntity { @Column("current_members") private Integer currentMembers; - //课程状态:0-正常,1-已取消,2-已结束 + //实际开课时间 + @Column("actual_start_time") + private LocalDateTime actualStartTime; + + //实际结课时间 + @Column("actual_end_time") + private LocalDateTime actualEndTime; + + //课程状态:0-正常,1-已取消,2-已结束,3-进行中,5-教练缺席,6-自动结束,7-教练迟到 @Column("status") private Long status; @@ -158,6 +166,22 @@ public class GroupCourseEntity extends BaseEntity { this.storedValueAmount = storedValueAmount; } + public LocalDateTime getActualStartTime() { + return actualStartTime; + } + + public void setActualStartTime(LocalDateTime actualStartTime) { + this.actualStartTime = actualStartTime; + } + + public LocalDateTime getActualEndTime() { + return actualEndTime; + } + + public void setActualEndTime(LocalDateTime actualEndTime) { + this.actualEndTime = actualEndTime; + } + public String getQrCodePath() { return qrCodePath; } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/enums/CourseStatus.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/enums/CourseStatus.java index ed198e6..5c0b7c0 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/enums/CourseStatus.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/enums/CourseStatus.java @@ -11,7 +11,10 @@ public enum CourseStatus { NORMAL(0L, "正常"), CANCELLED(1L, "已取消"), ENDED(2L, "已结束"), - IN_PROGRESS(3L, "进行中"); + IN_PROGRESS(3L, "进行中"), + COACH_ABSENT(5L, "教练缺席"), + AUTO_ENDED(6L, "自动结束"), + COACH_LATE(7L, "教练迟到"); private final Long value; private final String desc; diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/BookingSagaHandler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/BookingSagaHandler.java index 582b497..7ad7e27 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/BookingSagaHandler.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/BookingSagaHandler.java @@ -2,8 +2,6 @@ package cn.novalon.gym.manage.groupcourse.handler; import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking; import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository; -import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository; -import cn.novalon.gym.manage.groupcourse.service.impl.GroupCourseRedisService; import cn.novalon.gym.manage.member.entity.MemberCard; import cn.novalon.gym.manage.member.entity.MemberCardRecord; import cn.novalon.gym.manage.member.enums.MemberCardType; @@ -34,11 +32,9 @@ import java.util.List; public class BookingSagaHandler { private final IGroupCourseBookingRepository bookingRepository; - private final IGroupCourseRepository courseRepository; private final IMemberCardRecordService memberCardRecordService; private final IMemberStoredCardService memberStoredCardService; private final MemberCardRepository memberCardRepository; - private final GroupCourseRedisService redisService; private static final double DEFAULT_GROUP_COURSE_PRICE = 50.0; @@ -72,24 +68,6 @@ public class BookingSagaHandler { steps.add(step2); rollbackSteps.add(0, step2); - // 步骤3:更新课程当前人数 - SagaStep step3 = new SagaStep( - "更新课程当前人数", - incrementCourseCurrentMembers(booking.getCourseId()), - Mono.defer(() -> decrementCourseCurrentMembers(booking.getCourseId())) - ); - steps.add(step3); - rollbackSteps.add(0, step3); - - // 步骤4:更新Redis预约计数 - SagaStep step4 = new SagaStep( - "更新Redis预约计数", - incrementRedisBookingCount(booking.getCourseId()), - Mono.defer(() -> decrementRedisBookingCount(booking.getCourseId())) - ); - steps.add(step4); - rollbackSteps.add(0, step4); - return executeSaga(steps, rollbackSteps) .then(Mono.just(booking)); } @@ -225,24 +203,6 @@ public class BookingSagaHandler { steps.add(step2); rollbackSteps.add(0, step2); - // 步骤3:减少课程当前人数 - SagaStep step3 = new SagaStep( - "减少课程当前人数", - decrementCourseCurrentMembers(courseId), - Mono.defer(() -> incrementCourseCurrentMembers(courseId)) - ); - steps.add(step3); - rollbackSteps.add(0, step3); - - // 步骤4:更新Redis预约计数 - SagaStep step4 = new SagaStep( - "更新Redis预约计数", - decrementRedisBookingCount(courseId), - Mono.defer(() -> incrementRedisBookingCount(courseId)) - ); - steps.add(step4); - rollbackSteps.add(0, step4); - return executeSaga(steps, rollbackSteps) .then(bookingRepository.findById(bookingId)); } @@ -301,22 +261,6 @@ public class BookingSagaHandler { }); } - private Mono incrementCourseCurrentMembers(Long courseId) { - return courseRepository.updateCurrentMembers(courseId, 1).then(); - } - - private Mono decrementCourseCurrentMembers(Long courseId) { - return courseRepository.updateCurrentMembers(courseId, -1).then(); - } - - private Mono incrementRedisBookingCount(Long courseId) { - return redisService.incrementBookingCount(courseId).then(); - } - - private Mono decrementRedisBookingCount(Long courseId) { - return redisService.decrementBookingCount(courseId).then(); - } - private Mono executeSaga(List steps, List rollbackSteps) { List completedSteps = new ArrayList<>(); return executeStep(steps, 0, completedSteps); diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java index 9b92588..6bbd6a9 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java @@ -7,21 +7,27 @@ import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail; import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto; import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService; +import cn.novalon.gym.manage.groupcourse.vo.GroupCourseVO; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Validator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.time.LocalDateTime; @Component @Tag(name="团课管理",description = "团课相关操作") public class GroupCourseHandler { + private static final Logger logger = LoggerFactory.getLogger(GroupCourseHandler.class); private final IGroupCourseService groupCourseService; private final Validator validator; private final RedisUtil redisUtil; @@ -37,11 +43,12 @@ public class GroupCourseHandler { this.objectMapper = objectMapper; } - @Operation(summary = "获取所有团课", description = "获取系统中所有团课列表") + @Operation(summary = "获取所有团课", description = "获取系统中所有团课列表(含教练昵称)") public Mono getAllGroupCourse(ServerRequest request){ boolean includeDeleted = Boolean.valueOf(request.queryParam("includeDeleted").orElse("false")); - return ServerResponse.ok() - .body(groupCourseService.findAll(includeDeleted), GroupCourse.class); + return groupCourseService.findAllAsVO(includeDeleted) + .collectList() + .flatMap(list -> ServerResponse.ok().bodyValue(list)); } @Operation(summary = "分页获取团课", description = "根据分页参数获取团课列表") @@ -65,7 +72,7 @@ public class GroupCourseHandler { pageRequest.setOrder("asc"); } - return groupCourseService.findByPage(pageRequest, includeDeleted) + return groupCourseService.findByPageAsVO(pageRequest, includeDeleted) .flatMap(response -> ServerResponse.ok().bodyValue(response)); }); } @@ -289,4 +296,50 @@ public class GroupCourseHandler { }); }); } + + @Operation(summary = "检查教练时间冲突", description = "检查教练在指定时间段内是否有时间冲突的团课") + public Mono checkCoachConflict(ServerRequest request) { + return request.bodyToMono(Map.class) + .flatMap(body -> { + if (body == null) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "请求体不能为空"); + return ServerResponse.badRequest().bodyValue(error); + } + + Object coachIdObj = body.get("coachId"); + Object startTimeStr = body.get("startTime"); + Object endTimeStr = body.get("endTime"); + Object excludeCourseIdObj = body.get("excludeCourseId"); + + if (coachIdObj == null || startTimeStr == null || endTimeStr == null) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "coachId、startTime、endTime 不能为空"); + return ServerResponse.badRequest().bodyValue(error); + } + + Long coachId = Long.valueOf(String.valueOf(coachIdObj)); + LocalDateTime startTime = LocalDateTime.parse(String.valueOf(startTimeStr)); + LocalDateTime endTime = LocalDateTime.parse(String.valueOf(endTimeStr)); + Long excludeCourseId = excludeCourseIdObj != null ? Long.valueOf(String.valueOf(excludeCourseIdObj)) : null; + + return groupCourseService.checkCoachConflict(coachId, startTime, endTime, excludeCourseId) + .flatMap(conflicts -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("hasConflict", !conflicts.isEmpty()); + response.put("conflicts", conflicts); + return ServerResponse.ok().bodyValue(response); + }); + }) + .onErrorResume(error -> { + logger.warn("检查教练时间冲突失败: {}", error.getMessage()); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "检查失败: " + error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java index c7a3053..cac770f 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java @@ -27,9 +27,14 @@ public interface IGroupCourseRepository { Mono deleteById(Long id); - Mono updateCurrentMembers(Long id, Integer delta); - Flux findByCourseType(Long courseType); Mono> searchGroupCourses(GroupCourseQueryDto query); + + // ==================== 教练相关 ==================== + + Flux findByCoachId(Long coachId); + Flux findByCoachId(Long coachId, Sort sort); + Mono countByCoachIdAndStatus(Long coachId, Long status); + Mono cancelCoursesByCoachIdExceptStatus(Long coachId, Long excludeStatus); } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java index 67729aa..f81c9cc 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java @@ -119,7 +119,6 @@ public class GroupCourseRepository implements IGroupCourseRepository { entity.setCreatedAt(LocalDateTime.now()); entity.setUpdatedAt(LocalDateTime.now()); entity.setStatus(0L); - entity.setCurrentMembers(0); return groupCourseDao.save(entity) .map(groupCourseConverter::toDomain); @@ -151,17 +150,6 @@ public class GroupCourseRepository implements IGroupCourseRepository { .then(); } - @Override - public Mono updateCurrentMembers(Long id, Integer delta) { - return groupCourseDao.updateCurrentMembers(id, delta, LocalDateTime.now()) - .flatMap(updated -> { - if (updated > 0) { - return findByIdAndDeletedAtIsNull(id); - } - return Mono.empty(); - }); - } - @Override public Flux findByCourseType(Long courseType) { return groupCourseDao.findByCourseTypeAndDeletedAtIsNull(courseType) @@ -190,4 +178,28 @@ public class GroupCourseRepository implements IGroupCourseRepository { }); }); } + + // ==================== 教练相关 ==================== + + @Override + public Flux findByCoachId(Long coachId) { + return groupCourseDao.findByCoachIdAndDeletedAtIsNull(coachId) + .map(groupCourseConverter::toDomain); + } + + @Override + public Flux findByCoachId(Long coachId, Sort sort) { + return groupCourseDao.findByCoachIdAndDeletedAtIsNull(coachId, sort) + .map(groupCourseConverter::toDomain); + } + + @Override + public Mono countByCoachIdAndStatus(Long coachId, Long status) { + return groupCourseDao.countByCoachIdAndStatus(coachId, String.valueOf(status)); + } + + @Override + public Mono cancelCoursesByCoachIdExceptStatus(Long coachId, Long excludeStatus) { + return groupCourseDao.cancelCoursesByCoachIdExceptStatus(coachId, String.valueOf(excludeStatus), LocalDateTime.now()); + } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/scheduler/GroupCourseExpireScheduler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/scheduler/GroupCourseExpireScheduler.java deleted file mode 100644 index da4a5f1..0000000 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/scheduler/GroupCourseExpireScheduler.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.novalon.gym.manage.groupcourse.scheduler; - -import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; - -/** - * 团课过期状态自动更新定时任务 - * - * 功能:定期检查已过期的团课(end_time <= NOW()),自动将 status 从 0 更新为 2(已结束) - * - * @date 2026-06-24 - */ -@Component -public class GroupCourseExpireScheduler { - - private static final Logger logger = LoggerFactory.getLogger(GroupCourseExpireScheduler.class); - - private final GroupCourseDao groupCourseDao; - - public GroupCourseExpireScheduler(GroupCourseDao groupCourseDao) { - this.groupCourseDao = groupCourseDao; - } - - /** - * 每分钟检查一次,将已过期但状态仍为 0 的团课标记为已结束(status = 2) - */ - @Scheduled(fixedRate = 60000) - public void completeExpiredCourses() { - logger.debug("定时任务开始检查已过期团课,更新状态为已结束"); - - groupCourseDao.completeExpiredCourses(LocalDateTime.now()) - .subscribe( - count -> { - if (count > 0) { - logger.info("定时任务完成,更新了 {} 条过期团课状态为已结束", count); - } - }, - error -> logger.error("过期团课状态更新定时任务执行失败:{}", error.getMessage(), error) - ); - } -} diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java index d50826b..d2c802a 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java @@ -6,14 +6,20 @@ import cn.novalon.gym.manage.common.dto.PageResponse; import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail; import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto; +import cn.novalon.gym.manage.groupcourse.vo.GroupCourseVO; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.time.LocalDateTime; +import java.util.List; + public interface IGroupCourseService { Mono findById(Long id); Mono findDetailById(Long id); Flux findAll(); Flux findAll(boolean includeDeleted); + Flux findAllAsVO(boolean includeDeleted); + Mono> findByPageAsVO(PageRequest pageRequest, boolean includeDeleted); Mono> findByPage(PageRequest pageRequest, boolean includeDeleted); @@ -28,4 +34,14 @@ public interface IGroupCourseService { Mono delete(Long id); Mono> searchGroupCourses(GroupCourseQueryDto query); + + /** + * 检查教练在指定时间段内是否有时间冲突的团课 + * @param coachId 教练ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param excludeCourseId 排除的课程ID(编辑时排除自身) + * @return 冲突的课程列表 + */ + Mono> checkCoachConflict(Long coachId, LocalDateTime startTime, LocalDateTime endTime, Long excludeCourseId); } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseBookingService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseBookingService.java index 6eb5bbe..c7d9c9f 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseBookingService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseBookingService.java @@ -72,8 +72,16 @@ public class GroupCourseBookingService implements IGroupCourseBookingService { return Mono.error(new RuntimeException("系统繁忙,请稍后重试")); } - // 2. 尝试从缓存获取课程信息,如果缓存不存在则从数据库获取 + // 2. 从缓存或数据库获取课程信息,并用实时预约数覆盖currentMembers return getCourseWithCache(courseId) + .flatMap(course -> + bookingRepository.countValidBookings(courseId) + .map(count -> { + course.setCurrentMembers(count.intValue()); + return course; + }) + .defaultIfEmpty(course) + ) .flatMap(course -> { // 3. 验证课程状态 Long courseStatus = course.getStatus(); @@ -150,35 +158,20 @@ public class GroupCourseBookingService implements IGroupCourseBookingService { if (saved.getId() == null) { return Mono.error(new RuntimeException("保存预约记录失败")); } - // 10. 更新课程当前人数 - return courseRepository.updateCurrentMembers(courseId, 1) - .flatMap(updatedCourse -> { - // 11. 更新Redis预约计数 - return redisService.incrementBookingCount(courseId) - .flatMap(count -> { - // 12. 释放锁 - return redisService.releaseLock(courseId, requestId) - .then(Mono.just(saved)); - }); - }) - .doOnSuccess(savedBooking -> { - logger.info("预约成功:bookingId={}, courseId={}, memberId={}", - saved.getId(), courseId, memberId); - // 发布预约成功事件 - bookingReminderEventPublisher.publishBookingSuccessEvent( - saved.getId(), - saved.getMemberId(), - saved.getCourseName(), - saved.getCourseStartTime().toString() - ); - }) - .doOnError(error -> { - // 失败时回滚Redis计数和课程人数 - redisService.decrementBookingCount(courseId).subscribe(); - courseRepository.updateCurrentMembers(courseId, -1).subscribe(); - logger.error("预约失败:courseId={}, memberId={}, error={}", - courseId, memberId, error.getMessage()); - }); + // 10. 释放锁 + return redisService.releaseLock(courseId, requestId) + .then(Mono.just(saved)); + }) + .doOnSuccess(savedBooking -> { + logger.info("预约成功:bookingId={}, courseId={}, memberId={}", + savedBooking.getId(), courseId, memberId); + // 发布预约成功事件 + bookingReminderEventPublisher.publishBookingSuccessEvent( + savedBooking.getId(), + savedBooking.getMemberId(), + savedBooking.getCourseName(), + savedBooking.getCourseStartTime().toString() + ); }); }) ); @@ -273,30 +266,22 @@ public class GroupCourseBookingService implements IGroupCourseBookingService { if (rows == 0) { return Mono.error(new RuntimeException("更新预约状态失败")); } - // 6. 减少课程当前人数 - return courseRepository.updateCurrentMembers(booking.getCourseId(), -1) - .flatMap(updatedCourse -> { - // 7. 更新Redis预约计数 - return redisService.decrementBookingCount(booking.getCourseId()) - .flatMap(count -> { - // 8. 释放锁 - return redisService.releaseLock(bookingId, requestId) - .then(bookingRepository.findById(bookingId)); - }); - }) - .doOnSuccess(updatedBooking -> { - logger.info("取消预约成功:bookingId={}, memberId={}", bookingId, memberId); - // 发布预约取消事件 - bookingReminderEventPublisher.publishBookingCancelEvent( - updatedBooking.getId(), - updatedBooking.getMemberId(), - updatedBooking.getCourseName() - ); - }) - .doOnError(error -> { - logger.error("取消预约失败:bookingId={}, memberId={}, error={}", - bookingId, memberId, error.getMessage()); - }); + // 6. 释放锁 + return redisService.releaseLock(bookingId, requestId) + .then(bookingRepository.findById(bookingId)); + }) + .doOnSuccess(updatedBooking -> { + logger.info("取消预约成功:bookingId={}, memberId={}", bookingId, memberId); + // 发布预约取消事件 + bookingReminderEventPublisher.publishBookingCancelEvent( + updatedBooking.getId(), + updatedBooking.getMemberId(), + updatedBooking.getCourseName() + ); + }) + .doOnError(error -> { + logger.error("取消预约失败:bookingId={}, memberId={}, error={}", + bookingId, memberId, error.getMessage()); }); }) .onErrorResume(error -> { diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisService.java index f8110e4..1e7a6df 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisService.java @@ -149,46 +149,4 @@ public class GroupCourseRedisService { }) .defaultIfEmpty(false); } - - /** - * 获取课程预约人数(缓存) - */ - public Mono getBookingCount(Long courseId) { - String key = "booking_count:" + courseId; - return reactiveRedisTemplate.opsForValue() - .get(key) - .map(obj -> (Integer) obj) - .defaultIfEmpty(0); - } - - /** - * 增加课程预约人数 - */ - public Mono incrementBookingCount(Long courseId) { - String key = "booking_count:" + courseId; - return reactiveRedisTemplate.opsForValue() - .increment(key) - .doOnSuccess(count -> logger.debug("预约人数增加:courseId={}, count={}", courseId, count)); - } - - /** - * 减少课程预约人数 - */ - public Mono decrementBookingCount(Long courseId) { - String key = "booking_count:" + courseId; - return reactiveRedisTemplate.opsForValue() - .decrement(key) - .doOnSuccess(count -> logger.debug("预约人数减少:courseId={}, count={}", courseId, count)); - } - - /** - * 设置课程预约人数(用于从数据库同步) - */ - public Mono setBookingCount(Long courseId, Integer count) { - String key = "booking_count:" + courseId; - return reactiveRedisTemplate.opsForValue() - .set(key, count) - .doOnSuccess(result -> logger.debug("预约人数已设置:courseId={}, count={}", courseId, count)) - .then(); - } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java index a2a44ef..ad4850e 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java @@ -19,12 +19,15 @@ import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository; import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseTypeRepository; import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService; import cn.novalon.gym.manage.groupcourse.util.QRCodeUtil; +import cn.novalon.gym.manage.groupcourse.vo.GroupCourseVO; import cn.novalon.gym.manage.file.core.service.ISysFileService; import cn.novalon.gym.manage.member.entity.MemberCard; import cn.novalon.gym.manage.member.entity.MemberCardRecord; import cn.novalon.gym.manage.member.enums.MemberCardType; import cn.novalon.gym.manage.member.repository.MemberCardRepository; import cn.novalon.gym.manage.member.service.IMemberCardRecordService; +import cn.novalon.gym.manage.sys.core.domain.SysUser; +import cn.novalon.gym.manage.sys.core.repository.ISysUserRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; @@ -35,7 +38,9 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.LocalDateTime; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; @Service @@ -53,6 +58,7 @@ public class GroupCourseService implements IGroupCourseService { private final GroupCourseStateMachine stateMachine; private final DatabaseClient databaseClient; private final ISysFileService fileService; + private final ISysUserRepository sysUserRepository; private static final String CACHE_KEY_PREFIX = "group_course:page:"; private static final String CACHE_KEY_ID_PREFIX = "group_course:id:"; @@ -71,7 +77,8 @@ public class GroupCourseService implements IGroupCourseService { ObjectMapper objectMapper, GroupCourseStateMachine stateMachine, DatabaseClient databaseClient, - ISysFileService fileService){ + ISysFileService fileService, + ISysUserRepository sysUserRepository){ this.groupCourseRepository = groupCourseRepository; this.bookingRepository = bookingRepository; this.groupCourseTypeRepository = groupCourseTypeRepository; @@ -83,19 +90,21 @@ public class GroupCourseService implements IGroupCourseService { this.stateMachine = stateMachine; this.databaseClient = databaseClient; this.fileService = fileService; + this.sysUserRepository = sysUserRepository; } @Override public Mono findDetailById(Long id) { String cacheKey = CACHE_KEY_DETAIL_PREFIX + id; - return redisUtil.get(cacheKey, String.class) + Mono cachedMono = redisUtil.get(cacheKey, String.class); + return cachedMono .flatMap(cachedJson -> { - if (cachedJson != null) { + if (cachedJson != null && !cachedJson.isEmpty()) { try { GroupCourseDetail detail = objectMapper.readValue(cachedJson, GroupCourseDetail.class); logger.info("缓存命中 - findDetailById: id={}", id); - return Mono.just(detail); + return Mono.just(detail); } catch (JsonProcessingException e) { logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage()); return redisUtil.delete(cacheKey).then(Mono.empty()); @@ -127,6 +136,8 @@ public class GroupCourseService implements IGroupCourseService { }) .switchIfEmpty(Mono.just(buildDetail(course, null))); }) + .flatMap(this::enrichCoachName) + .flatMap(this::enrichCurrentMembers) .flatMap(detail -> { try { String jsonData = objectMapper.writeValueAsString(detail); @@ -172,17 +183,64 @@ public class GroupCourseService implements IGroupCourseService { return detail; } + /** + * 为团课详情对象填充教练名称 + */ + private Mono enrichCoachName(GroupCourseDetail detail) { + Long coachId = detail.getCoachId(); + if (coachId == null) { + return Mono.just(detail); + } + return sysUserRepository.findByIdIncludingDeleted(coachId) + .map(user -> { + String name = user.getNickname() != null ? user.getNickname() : user.getUsername(); + detail.setCoachName(name); + logger.debug("enrichCoachName: courseId={}, coachId={}, coachName={}", detail.getId(), coachId, name); + return detail; + }) + .doOnNext(d -> {}) // 避免空doOnNext告警 + .defaultIfEmpty(detail) + .doOnDiscard(GroupCourseDetail.class, d -> + logger.warn("enrichCoachName: coach not found, coachId={}, courseId={}", coachId, detail.getId()) + ); + } + + /** + * 为团课详情对象填充实时预约人数 + */ + private Mono enrichCurrentMembers(GroupCourseDetail detail) { + return bookingRepository.countValidBookings(detail.getId()) + .map(count -> { + detail.setCurrentMembers(count.intValue()); + return detail; + }) + .defaultIfEmpty(detail); + } + + /** + * 为团课对象填充实时预约人数 + */ + private Mono enrichCurrentMembers(GroupCourse course) { + return bookingRepository.countValidBookings(course.getId()) + .map(count -> { + course.setCurrentMembers(count.intValue()); + return course; + }) + .defaultIfEmpty(course); + } + @Override public Mono findById(Long id) { String cacheKey = CACHE_KEY_ID_PREFIX + id; - return redisUtil.get(cacheKey, String.class) + Mono cachedMono = redisUtil.get(cacheKey, String.class); + return cachedMono .flatMap(cachedJson -> { - if (cachedJson != null) { + if (cachedJson != null && !cachedJson.isEmpty()) { try { GroupCourse groupCourse = objectMapper.readValue(cachedJson, GroupCourse.class); logger.info("缓存命中 - findById: id={}", id); - return Mono.just(groupCourse); + return Mono.just(groupCourse); } catch (JsonProcessingException e) { logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage()); return redisUtil.delete(cacheKey).then(Mono.empty()); @@ -204,7 +262,8 @@ public class GroupCourseService implements IGroupCourseService { } }) .doOnSubscribe(sub -> logger.debug("缓存未命中,查询数据库 - findById: id={}", id)) - ); + ) + .flatMap(this::enrichCurrentMembers); } @Override @@ -221,6 +280,47 @@ public class GroupCourseService implements IGroupCourseService { } } + @Override + public Flux findAllAsVO(boolean includeDeleted) { + return findAll(includeDeleted) + .flatMap(this::enrichCurrentMembers) + .collectList() + .flatMapMany(courses -> { + if (courses.isEmpty()) { + logger.info("findAllAsVO: no courses found"); + return Flux.empty(); + } + var coachIds = courses.stream() + .map(GroupCourse::getCoachId) + .filter(id -> id != null) + .distinct() + .toList(); + + logger.info("findAllAsVO: {} courses, {} unique coach IDs: {}", courses.size(), coachIds.size(), coachIds); + + if (coachIds.isEmpty()) { + logger.info("findAllAsVO: no coach IDs, returning VOs with null coachName"); + return Flux.fromIterable(courses.stream() + .map(c -> GroupCourseVO.from(c, null)) + .toList()); + } + + return Flux.fromIterable(coachIds) + .flatMap(sysUserRepository::findByIdIncludingDeleted) + .doOnNext(u -> logger.info("findAllAsVO: found coach id={}, nickname={}, username={}", u.getId(), u.getNickname(), u.getUsername())) + .collectMap(SysUser::getId, u -> u.getNickname() != null ? u.getNickname() : u.getUsername()) + .doOnNext(m -> logger.info("findAllAsVO: coachNameMap size={}, keys={}", m.size(), m.keySet())) + .flatMapMany(coachNameMap -> + Flux.fromIterable(courses.stream() + .map(c -> { + String name = coachNameMap.get(c.getCoachId()); + logger.debug("findAllAsVO: courseId={}, coachId={}, coachName={}", c.getId(), c.getCoachId(), name); + return GroupCourseVO.from(c, name); + }) + .toList())); + }); + } + @Override public Mono> findByPage(PageRequest pageRequest, boolean includeDeleted) { int page = pageRequest.getPage(); @@ -232,14 +332,15 @@ public class GroupCourseService implements IGroupCourseService { String cacheKey = CACHE_KEY_PREFIX + page + ":" + size + ":" + includeDeleted + ":" + sort + ":" + order + ":" + keyword + ":" + status; - return redisUtil.get(cacheKey, String.class) + Mono cachedMono = redisUtil.get(cacheKey, String.class); + return cachedMono .flatMap(cachedJson -> { - if (cachedJson != null) { + if (cachedJson != null && !cachedJson.isEmpty()) { try { PageResponse pageResponse = objectMapper.readValue(cachedJson, objectMapper.getTypeFactory().constructParametricType(PageResponse.class, GroupCourse.class)); logger.info("缓存命中 - findByPage: key={}", cacheKey); - return Mono.just(pageResponse); + return Mono.>just(pageResponse); } catch (JsonProcessingException e) { logger.warn("缓存解析失败,删除缓存 - key: {}, error: {}", cacheKey, e.getMessage()); return redisUtil.delete(cacheKey).then(Mono.empty()); @@ -272,6 +373,73 @@ public class GroupCourseService implements IGroupCourseService { ); } + @Override + public Mono> findByPageAsVO(PageRequest pageRequest, boolean includeDeleted) { + return findByPage(pageRequest, includeDeleted) + .flatMap(pageResponse -> { + List courses = pageResponse.getContent(); + if (courses.isEmpty()) { + PageResponse voResponse = new PageResponse<>( + Collections.emptyList(), + pageResponse.getTotalPages(), + pageResponse.getTotalElements(), + pageResponse.getCurrentPage(), + pageResponse.getPageSize() + ); + return Mono.just(voResponse); + } + + // 用实时预约人数覆盖缓存/DB中的currentMembers + return Flux.fromIterable(courses) + .flatMap(this::enrichCurrentMembers) + .collectList() + .flatMap(enrichedCourses -> { + var coachIds = enrichedCourses.stream() + .map(GroupCourse::getCoachId) + .filter(id -> id != null) + .distinct() + .toList(); + + logger.info("findByPageAsVO: {} courses, {} unique coach IDs: {}", enrichedCourses.size(), coachIds.size(), coachIds); + + if (coachIds.isEmpty()) { + List voList = enrichedCourses.stream() + .map(c -> GroupCourseVO.from(c, null)) + .toList(); + PageResponse voResponse = new PageResponse<>( + voList, + pageResponse.getTotalPages(), + pageResponse.getTotalElements(), + pageResponse.getCurrentPage(), + pageResponse.getPageSize() + ); + return Mono.just(voResponse); + } + + return Flux.fromIterable(coachIds) + .flatMap(sysUserRepository::findByIdIncludingDeleted) + .doOnNext(u -> logger.info("findByPageAsVO: found coach id={}, nickname={}", u.getId(), u.getNickname())) + .collectMap(SysUser::getId, u -> u.getNickname() != null ? u.getNickname() : u.getUsername()) + .map(coachNameMap -> { + List voList = enrichedCourses.stream() + .map(c -> { + String name = coachNameMap.get(c.getCoachId()); + logger.debug("findByPageAsVO: courseId={}, coachId={}, coachName={}", c.getId(), c.getCoachId(), name); + return GroupCourseVO.from(c, name); + }) + .toList(); + return new PageResponse<>( + voList, + pageResponse.getTotalPages(), + pageResponse.getTotalElements(), + pageResponse.getCurrentPage(), + pageResponse.getPageSize() + ); + }); + }); + }); + } + @Override public Mono create(GroupCourse groupCourse) { return groupCourseRepository.save(groupCourse) @@ -488,6 +656,15 @@ public class GroupCourseService implements IGroupCourseService { public Mono signIn(Long courseId, Long memberId) { return groupCourseRepository.findByIdAndDeletedAtIsNull(courseId) .switchIfEmpty(Mono.error(new RuntimeException("团课不存在或已删除"))) + .flatMap(course -> + // 用实时预约人数覆盖currentMembers + bookingRepository.countValidBookings(courseId) + .map(count -> { + course.setCurrentMembers(count.intValue()); + return course; + }) + .defaultIfEmpty(course) + ) .flatMap(course -> { // 校验1:团课已取消 if (course.getStatus().equals(CourseStatus.CANCELLED.getValue())) { @@ -518,11 +695,9 @@ public class GroupCourseService implements IGroupCourseService { return bookingRepository.findValidBooking(courseId, memberId) .switchIfEmpty(Mono.error(new RuntimeException("您未预约此团课"))) .flatMap(booking -> { - return groupCourseRepository.updateCurrentMembers(courseId, 1) - .flatMap(updatedCourse -> { - return bookingRepository.updateStatus(booking.getId(), "2") - .thenReturn(updatedCourse); - }); + // 更新预约状态为已出席(2),不再手动计数 + return bookingRepository.updateStatus(booking.getId(), "2") + .thenReturn(course); }); }) .doOnSuccess(course -> logger.info("团课签到成功 - courseId={}, memberId={}", courseId, memberId)) @@ -566,6 +741,37 @@ public class GroupCourseService implements IGroupCourseService { private Mono clearCache() { return redisUtil.deleteByPattern(CACHE_KEY_PREFIX + "*") .then(redisUtil.deleteByPattern(CACHE_KEY_ID_PREFIX + "*")) - .then(redisUtil.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*")).then(); + .then(redisUtil.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*")) + .then(redisUtil.deleteByPattern("datacount:statistics:*")) + .then(); + } + + @Override + public Mono> checkCoachConflict(Long coachId, LocalDateTime startTime, LocalDateTime endTime, Long excludeCourseId) { + if (coachId == null || startTime == null || endTime == null) { + return Mono.just(Collections.emptyList()); + } + return groupCourseRepository.findByCoachId(coachId) + .filter(course -> { + // 排除已取消的课程 + if (course.getStatus() != null && course.getStatus().equals(CourseStatus.CANCELLED.getValue())) { + return false; + } + // 排除自身(编辑时) + if (excludeCourseId != null && course.getId() != null && course.getId().equals(excludeCourseId)) { + return false; + } + // 检查时间是否重叠:startTime < course.endTime AND course.startTime < endTime + if (course.getStartTime() == null || course.getEndTime() == null) { + return false; + } + return startTime.isBefore(course.getEndTime()) && course.getStartTime().isBefore(endTime); + }) + .collectList() + .doOnNext(conflicts -> { + if (!conflicts.isEmpty()) { + logger.info("checkCoachConflict: coachId={}, conflicts={} courses", coachId, conflicts.size()); + } + }); } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/vo/GroupCourseVO.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/vo/GroupCourseVO.java new file mode 100644 index 0000000..70f713a --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/vo/GroupCourseVO.java @@ -0,0 +1,134 @@ +package cn.novalon.gym.manage.groupcourse.vo; + +import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 团课视图对象(含教练昵称) + * + * @author 张翔 + * @date 2026-07-19 + */ +public class GroupCourseVO { + + private Long id; + private String courseName; + private Long coachId; + private String coachName; + private Long courseType; + private LocalDateTime startTime; + private LocalDateTime endTime; + private LocalDateTime actualStartTime; + private LocalDateTime actualEndTime; + private Integer maxMembers; + private Integer currentMembers; + private Long status; + private String location; + private String coverImage; + private String description; + private BigDecimal storedValueAmount; + private String qrCodePath; + private String createBy; + private String updateBy; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public GroupCourseVO() {} + + /** + * 从领域对象和教练名称构建 VO + */ + public static GroupCourseVO from(GroupCourse course, String coachName) { + GroupCourseVO vo = new GroupCourseVO(); + vo.setId(course.getId()); + vo.setCourseName(course.getCourseName()); + vo.setCoachId(course.getCoachId()); + vo.setCoachName(coachName); + vo.setCourseType(course.getCourseType()); + vo.setStartTime(course.getStartTime()); + vo.setEndTime(course.getEndTime()); + vo.setActualStartTime(course.getActualStartTime()); + vo.setActualEndTime(course.getActualEndTime()); + vo.setMaxMembers(course.getMaxMembers()); + vo.setCurrentMembers(course.getCurrentMembers()); + vo.setStatus(course.getStatus()); + vo.setLocation(course.getLocation()); + vo.setCoverImage(course.getCoverImage()); + vo.setDescription(course.getDescription()); + vo.setStoredValueAmount(course.getStoredValueAmount()); + vo.setQrCodePath(course.getQrCodePath()); + vo.setCreateBy(course.getCreateBy()); + vo.setUpdateBy(course.getUpdateBy()); + vo.setCreatedAt(course.getCreatedAt()); + vo.setUpdatedAt(course.getUpdatedAt()); + return vo; + } + + // -- getters / setters -- + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getCourseName() { return courseName; } + public void setCourseName(String courseName) { this.courseName = courseName; } + + public Long getCoachId() { return coachId; } + public void setCoachId(Long coachId) { this.coachId = coachId; } + + public String getCoachName() { return coachName; } + public void setCoachName(String coachName) { this.coachName = coachName; } + + public Long getCourseType() { return courseType; } + public void setCourseType(Long courseType) { this.courseType = courseType; } + + public LocalDateTime getStartTime() { return startTime; } + public void setStartTime(LocalDateTime startTime) { this.startTime = startTime; } + + public LocalDateTime getEndTime() { return endTime; } + public void setEndTime(LocalDateTime endTime) { this.endTime = endTime; } + + public LocalDateTime getActualStartTime() { return actualStartTime; } + public void setActualStartTime(LocalDateTime actualStartTime) { this.actualStartTime = actualStartTime; } + + public LocalDateTime getActualEndTime() { return actualEndTime; } + public void setActualEndTime(LocalDateTime actualEndTime) { this.actualEndTime = actualEndTime; } + + public Integer getMaxMembers() { return maxMembers; } + public void setMaxMembers(Integer maxMembers) { this.maxMembers = maxMembers; } + + public Integer getCurrentMembers() { return currentMembers; } + public void setCurrentMembers(Integer currentMembers) { this.currentMembers = currentMembers; } + + public Long getStatus() { return status; } + public void setStatus(Long status) { this.status = status; } + + public String getLocation() { return location; } + public void setLocation(String location) { this.location = location; } + + public String getCoverImage() { return coverImage; } + public void setCoverImage(String coverImage) { this.coverImage = coverImage; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + public BigDecimal getStoredValueAmount() { return storedValueAmount; } + public void setStoredValueAmount(BigDecimal storedValueAmount) { this.storedValueAmount = storedValueAmount; } + + public String getQrCodePath() { return qrCodePath; } + public void setQrCodePath(String qrCodePath) { this.qrCodePath = qrCodePath; } + + public String getCreateBy() { return createBy; } + public void setCreateBy(String createBy) { this.createBy = createBy; } + + public String getUpdateBy() { return updateBy; } + public void setUpdateBy(String updateBy) { this.updateBy = updateBy; } + + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + + public LocalDateTime getUpdatedAt() { return updatedAt; } + public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } +} diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java index 8567be2..f2003a2 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java @@ -158,6 +158,7 @@ public class MemberServiceImpl implements MemberService { return MemberInfoVO.builder() .id(member.getId()) + .memberNo(member.getMemberNo()) .nickname(member.getNickname()) .phone(maskedPhone) .gender(genderEnum) @@ -166,6 +167,7 @@ public class MemberServiceImpl implements MemberService { .avatar(member.getAvatar()) .hasPhone(phone != null) .isSubscribed(member.getSubscribed() != null && member.getSubscribed()) + .lastLoginAt(member.getLastLoginAt()) .build(); } diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/vo/MemberInfoVO.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/vo/MemberInfoVO.java index f3e763d..7732861 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/vo/MemberInfoVO.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/vo/MemberInfoVO.java @@ -7,6 +7,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Date; /** @@ -25,6 +26,9 @@ public class MemberInfoVO { // 会员 ID private Long id; + // 会员编号 + private String memberNo; + // 昵称 private String nickname; @@ -48,4 +52,7 @@ public class MemberInfoVO { // 是否已关注公众号 private Boolean isSubscribed; + + // 最后登录时间 + private LocalDateTime lastLoginAt; } \ No newline at end of file diff --git a/gym-manage-api/manage-app/pom.xml b/gym-manage-api/manage-app/pom.xml index beeccbe..5afc9c2 100644 --- a/gym-manage-api/manage-app/pom.xml +++ b/gym-manage-api/manage-app/pom.xml @@ -169,6 +169,12 @@ 1.0.0 compile + + cn.novalon.gym.manage + gym-coach + 1.0.0 + compile + diff --git a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/ManageApplication.java b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/ManageApplication.java index 6ad96f5..620f137 100644 --- a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/ManageApplication.java +++ b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/ManageApplication.java @@ -25,7 +25,8 @@ import java.util.List; "cn.novalon.gym.manage.member.repository", "cn.novalon.gym.manage.groupcourse.dao", "cn.novalon.gym.manage.checkIn.repository", - "cn.novalon.gym.manage.payment.repository" + "cn.novalon.gym.manage.payment.repository", + "cn.novalon.gym.manage.coach.dao" }) @EnableReactiveElasticsearchRepositories(basePackages = "cn.novalon.gym.manage.member.es.repository") public class ManageApplication { diff --git a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java index ef16328..5f5c957 100644 --- a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java +++ b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java @@ -16,9 +16,12 @@ import cn.novalon.gym.manage.member.handler.MemberCardTransactionHandler; import cn.novalon.gym.manage.member.handler.MemberHandler; import cn.novalon.gym.manage.member.handler.MemberStoredCardHandler; import cn.novalon.gym.manage.member.handler.WechatAuthHandler; +import cn.novalon.gym.manage.notify.handler.BannerHandler; import cn.novalon.gym.manage.notify.handler.SysNoticeHandler; import cn.novalon.gym.manage.notify.handler.SysUserMessageHandler; import cn.novalon.gym.manage.payment.handler.PaymentHandler; +import cn.novalon.gym.manage.coach.handler.CoachCourseHandler; +import cn.novalon.gym.manage.coach.handler.CoachHandler; import cn.novalon.gym.manage.sys.handler.auth.PasswordDiagnosticHandler; import cn.novalon.gym.manage.sys.handler.auth.SysAuthHandler; import cn.novalon.gym.manage.sys.handler.config.SysConfigHandler; @@ -63,6 +66,7 @@ public class SystemRouter { SysAuthHandler authHandler, StatsHandler statsHandler, SysDictHandler dictHandler, + BannerHandler bannerHandler, SysNoticeHandler noticeHandler, SysUserMessageHandler messageHandler, SysFileHandler fileHandler, @@ -82,7 +86,9 @@ public class SystemRouter { CheckInHandler checkInHandler, DataStatisticsHandler dataStatisticsHandler, PhoneAuthHandler phoneAuthHandler, - PaymentHandler paymentHandler) { + PaymentHandler paymentHandler, + CoachHandler coachHandler, + CoachCourseHandler coachCourseHandler) { return route() // ========== 诊断路由 ========== @@ -116,6 +122,17 @@ public class SystemRouter { .GET("/api/users/{id}/roles", userHandler::getUserRoles) .POST("/api/users/{id}/roles", userHandler::assignRoles) + // ========== 教练路由 ========== + .GET("/api/coach/list", coachHandler::getAllCoaches) + .POST("/api/coach", coachHandler::createCoach) + .PUT("/api/coach/{id}", coachHandler::updateCoach) + .POST("/api/coach/{id}/disable", coachHandler::disableCoach) + .GET("/api/coach/{id}/courses", coachHandler::getCoachCourses) + .GET("/api/coach/violation-counts", coachHandler::getViolationCounts) + .GET("/api/coach/{id}/violations", coachHandler::getCoachViolations) + .POST("/api/coach/courses/{courseId}/start", coachCourseHandler::startCourse) + .POST("/api/coach/courses/{courseId}/end", coachCourseHandler::endCourse) + // ========== 菜单路由 ========== .GET("/api/menus", menuHandler::getAllMenus) .GET("/api/menus/tree", menuHandler::getMenuTree) @@ -188,6 +205,14 @@ public class SystemRouter { .PUT("/api/dict/data/{id}", dictHandler::updateDictData) .DELETE("/api/dict/data/{id}", dictHandler::deleteDictData) + // ========== 轮播图路由 ========== + .GET("/api/banners", bannerHandler::getAllBanners) + .GET("/api/banners/active", bannerHandler::getActiveBanners) + .GET("/api/banners/{id}", bannerHandler::getBannerById) + .POST("/api/banners", bannerHandler::createBanner) + .PUT("/api/banners/{id}", bannerHandler::updateBanner) + .DELETE("/api/banners/{id}", bannerHandler::deleteBanner) + // ========== 公告路由 ========== .GET("/api/notices", noticeHandler::getAllNotices) .GET("/api/notices/{id}", noticeHandler::getNoticeById) @@ -352,6 +377,7 @@ public class SystemRouter { .POST("/api/groupCourse/{id}/cancel", groupCourseHandler::cancelGroupCourse) .POST("/api/groupCourse/signin/{memberId}", groupCourseHandler::signIn) .POST("/api/groupCourse/search", groupCourseHandler::searchGroupCourses) + .POST("/api/groupCourse/check-coach-conflict", groupCourseHandler::checkCoachConflict) // ========= 签到模块路由 ========== // ===== 签到核心功能 ===== diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/RedisUtil.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/RedisUtil.java index 32245a5..c411b03 100644 --- a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/RedisUtil.java +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/RedisUtil.java @@ -43,7 +43,7 @@ public class RedisUtil { public Mono get(String key, Class clazz) { return reactiveRedisTemplate.opsForValue().get(key) .filter(clazz::isInstance) - .cast(clazz) + .map(clazz::cast) .onErrorResume(e -> { // 旧缓存数据格式不兼容时静默跳过,后续逻辑会 fallback 到 DB 查询 log.warn("读取 Redis 缓存失败(可能为旧格式): key={}, error={}", key, e.getMessage()); diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/converter/BannerConverter.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/converter/BannerConverter.java new file mode 100644 index 0000000..ef2c4f2 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/converter/BannerConverter.java @@ -0,0 +1,73 @@ +package cn.novalon.gym.manage.db.converter; + +import cn.novalon.gym.manage.notify.core.domain.Banner; +import cn.novalon.gym.manage.db.entity.BannerEntity; + +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 轮播图实体转换器 + * + * @author 张翔 + * @date 2026-07-18 + */ +@Component +public class BannerConverter { + + public Banner toDomain(BannerEntity entity) { + if (entity == null) { + return null; + } + Banner domain = new Banner(); + domain.setId(entity.getId()); + domain.setImageUrl(entity.getImageUrl()); + domain.setTitle(entity.getTitle()); + domain.setSubtitle(entity.getSubtitle()); + domain.setSortOrder(entity.getSortOrder()); + domain.setIsActive(entity.getIsActive()); + domain.setLinkUrl(entity.getLinkUrl()); + domain.setCreatedAt(entity.getCreatedAt()); + domain.setUpdatedAt(entity.getUpdatedAt()); + domain.setDeletedAt(entity.getDeletedAt()); + return domain; + } + + public BannerEntity toEntity(Banner domain) { + if (domain == null) { + return null; + } + BannerEntity entity = new BannerEntity(); + entity.setId(domain.getId()); + entity.setImageUrl(domain.getImageUrl()); + entity.setTitle(domain.getTitle()); + entity.setSubtitle(domain.getSubtitle()); + entity.setSortOrder(domain.getSortOrder()); + entity.setIsActive(domain.getIsActive()); + entity.setLinkUrl(domain.getLinkUrl()); + entity.setCreatedAt(domain.getCreatedAt()); + entity.setUpdatedAt(domain.getUpdatedAt()); + entity.setDeletedAt(domain.getDeletedAt()); + return entity; + } + + public List toDomainList(List entities) { + if (entities == null) { + return null; + } + return entities.stream() + .map(this::toDomain) + .collect(Collectors.toList()); + } + + public List toEntityList(List domains) { + if (domains == null) { + return null; + } + return domains.stream() + .map(this::toEntity) + .collect(Collectors.toList()); + } +} diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/dao/BannerDao.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/dao/BannerDao.java new file mode 100644 index 0000000..eff1ad6 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/dao/BannerDao.java @@ -0,0 +1,22 @@ +package cn.novalon.gym.manage.db.dao; + +import cn.novalon.gym.manage.db.entity.BannerEntity; +import org.springframework.data.domain.Sort; +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Repository +public interface BannerDao extends R2dbcRepository { + + Flux findByDeletedAtIsNull(); + + Flux findByDeletedAtIsNull(Sort sort); + + @Query("SELECT * FROM banner WHERE deleted_at IS NULL ORDER BY sort_order ASC") + Flux findActiveBanners(); + + Mono deleteByIdAndDeletedAtIsNull(Long id); +} diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/BannerEntity.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/BannerEntity.java new file mode 100644 index 0000000..7717ab1 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/BannerEntity.java @@ -0,0 +1,127 @@ +package cn.novalon.gym.manage.db.entity; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +import java.time.LocalDateTime; + +/** + * 轮播图数据库实体类 + * + * @author 张翔 + * @date 2026-07-18 + */ +@Table("banner") +public class BannerEntity { + + @Id + private Long id; + + @Column("image_url") + private String imageUrl; + + @Column("title") + private String title; + + @Column("subtitle") + private String subtitle; + + @Column("sort_order") + private Integer sortOrder; + + @Column("is_active") + private String isActive; + + @Column("link_url") + private String linkUrl; + + @Column("created_at") + private LocalDateTime createdAt; + + @Column("updated_at") + private LocalDateTime updatedAt; + + @Column("deleted_at") + private LocalDateTime deletedAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSubtitle() { + return subtitle; + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + public Integer getSortOrder() { + return sortOrder; + } + + public void setSortOrder(Integer sortOrder) { + this.sortOrder = sortOrder; + } + + public String getIsActive() { + return isActive; + } + + public void setIsActive(String isActive) { + this.isActive = isActive; + } + + public String getLinkUrl() { + return linkUrl; + } + + public void setLinkUrl(String linkUrl) { + this.linkUrl = linkUrl; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + public LocalDateTime getDeletedAt() { + return deletedAt; + } + + public void setDeletedAt(LocalDateTime deletedAt) { + this.deletedAt = deletedAt; + } +} diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/BannerRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/BannerRepository.java new file mode 100644 index 0000000..c9f458b --- /dev/null +++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/BannerRepository.java @@ -0,0 +1,59 @@ +package cn.novalon.gym.manage.db.repository; + +import cn.novalon.gym.manage.notify.core.domain.Banner; +import cn.novalon.gym.manage.notify.core.repository.IBannerRepository; +import cn.novalon.gym.manage.db.converter.BannerConverter; +import cn.novalon.gym.manage.db.dao.BannerDao; +import cn.novalon.gym.manage.db.entity.BannerEntity; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * 轮播图仓储实现类 + * + * @author 张翔 + * @date 2026-07-18 + */ +@Repository +public class BannerRepository implements IBannerRepository { + + private final BannerDao bannerDao; + private final BannerConverter bannerConverter; + + public BannerRepository(BannerDao bannerDao, BannerConverter bannerConverter) { + this.bannerDao = bannerDao; + this.bannerConverter = bannerConverter; + } + + @Override + public Flux findByDeletedAtIsNull() { + return bannerDao.findByDeletedAtIsNull(Sort.by(Sort.Direction.ASC, "sortOrder")) + .map(bannerConverter::toDomain); + } + + @Override + public Flux findActiveBanners() { + return bannerDao.findActiveBanners() + .map(bannerConverter::toDomain); + } + + @Override + public Mono findById(Long id) { + return bannerDao.findById(id) + .map(bannerConverter::toDomain); + } + + @Override + public Mono save(Banner banner) { + BannerEntity entity = bannerConverter.toEntity(banner); + return bannerDao.save(entity) + .map(bannerConverter::toDomain); + } + + @Override + public Mono deleteByIdAndDeletedAtIsNull(Long id) { + return bannerDao.deleteByIdAndDeletedAtIsNull(id); + } +} diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V10__Create_group_course_recommend.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V10__Create_group_course_recommend.sql new file mode 100644 index 0000000..61405d2 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V10__Create_group_course_recommend.sql @@ -0,0 +1,38 @@ +-- ============================================ +-- 团课推荐表 +-- 版本: V10 +-- 描述: 创建团课推荐表,用于首页推荐团课展示 +-- ============================================ + +CREATE TABLE IF NOT EXISTS group_course_recommend ( + id BIGSERIAL PRIMARY KEY, + course_id BIGINT NOT NULL, + recommend_title VARCHAR(200), + recommend_content TEXT, + recommend_reason VARCHAR(500), + priority INTEGER DEFAULT 0, + is_active BOOLEAN DEFAULT TRUE, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_group_course_recommend_course_id ON group_course_recommend(course_id); +CREATE INDEX IF NOT EXISTS idx_group_course_recommend_priority ON group_course_recommend(priority); +CREATE INDEX IF NOT EXISTS idx_group_course_recommend_is_active ON group_course_recommend(is_active); + +COMMENT ON TABLE group_course_recommend IS '团课推荐表'; +COMMENT ON COLUMN group_course_recommend.id IS '主键ID'; +COMMENT ON COLUMN group_course_recommend.course_id IS '团课ID(关联group_course.id)'; +COMMENT ON COLUMN group_course_recommend.recommend_title IS '推荐标题'; +COMMENT ON COLUMN group_course_recommend.recommend_content IS '推荐内容'; +COMMENT ON COLUMN group_course_recommend.recommend_reason IS '推荐理由'; +COMMENT ON COLUMN group_course_recommend.priority IS '优先级(数字越大优先级越高)'; +COMMENT ON COLUMN group_course_recommend.is_active IS '是否启用'; +COMMENT ON COLUMN group_course_recommend.create_by IS '创建人'; +COMMENT ON COLUMN group_course_recommend.update_by IS '更新人'; +COMMENT ON COLUMN group_course_recommend.created_at IS '创建时间'; +COMMENT ON COLUMN group_course_recommend.updated_at IS '更新时间'; +COMMENT ON COLUMN group_course_recommend.deleted_at IS '删除时间(软删除)'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V11__Add_group_course_qr_code_path.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V11__Add_group_course_qr_code_path.sql new file mode 100644 index 0000000..98923df --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V11__Add_group_course_qr_code_path.sql @@ -0,0 +1,9 @@ +-- ============================================ +-- 为团课表添加二维码路径字段 +-- 版本: V11 +-- 描述: 添加团课二维码路径,用于扫码签到 +-- ============================================ + +ALTER TABLE group_course ADD COLUMN IF NOT EXISTS qr_code_path VARCHAR(500); + +COMMENT ON COLUMN group_course.qr_code_path IS '二维码图片路径'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V12__Add_deleted_at_to_member_user.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V12__Add_deleted_at_to_member_user.sql new file mode 100644 index 0000000..ef760b1 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V12__Add_deleted_at_to_member_user.sql @@ -0,0 +1,10 @@ +-- ============================================ +-- 为 member_user 表添加 deleted_at 字段 +-- 版本: V12 +-- 描述: 补充软删除时间字段,与BaseEntity映射对齐 +-- ============================================ + +ALTER TABLE member_user + ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP; + +COMMENT ON COLUMN member_user.deleted_at IS '软删除时间'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Add_updated_at_to_member_card_transactions.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Add_updated_at_to_member_card_transactions.sql new file mode 100644 index 0000000..3f58785 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Add_updated_at_to_member_card_transactions.sql @@ -0,0 +1,10 @@ +-- ============================================ +-- 为 member_card_transactions 表添加 updated_at 字段 +-- 版本: V13 +-- 描述: 补充更新时间字段,与BaseEntity映射对齐 +-- ============================================ + +ALTER TABLE member_card_transactions + ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; + +COMMENT ON COLUMN member_card_transactions.updated_at IS '更新时间'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V14__Create_payment_order.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V14__Create_payment_order.sql new file mode 100644 index 0000000..61edb3b --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V14__Create_payment_order.sql @@ -0,0 +1,63 @@ +-- ============================================ +-- 支付订单表 +-- 版本: V14 +-- 描述: 创建支付订单表,用于记录支付交易信息 +-- ============================================ + +CREATE TABLE IF NOT EXISTS payment_order ( + id BIGSERIAL PRIMARY KEY, + order_no VARCHAR(64) NOT NULL UNIQUE, + member_id BIGINT, + order_type VARCHAR(32), + goods_desc VARCHAR(256), + trans_amt DECIMAL(12, 2), + trade_type VARCHAR(32), + pay_status VARCHAR(16) DEFAULT 'PENDING', + huifu_id VARCHAR(64), + req_seq_id VARCHAR(64), + req_date VARCHAR(16), + hf_seq_id VARCHAR(64), + pay_info TEXT, + qr_code TEXT, + pay_url TEXT, + remark VARCHAR(512), + notify_url VARCHAR(512), + pay_time TIMESTAMP, + expire_time TIMESTAMP, + error_code VARCHAR(64), + error_msg VARCHAR(512), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_payment_order_order_no ON payment_order(order_no); +CREATE INDEX IF NOT EXISTS idx_payment_order_member_id ON payment_order(member_id); +CREATE INDEX IF NOT EXISTS idx_payment_order_req_seq_id ON payment_order(req_seq_id); +CREATE INDEX IF NOT EXISTS idx_payment_order_status ON payment_order(pay_status); +CREATE INDEX IF NOT EXISTS idx_payment_order_expire_time ON payment_order(expire_time); + +COMMENT ON TABLE payment_order IS '支付订单表'; +COMMENT ON COLUMN payment_order.order_no IS '订单号'; +COMMENT ON COLUMN payment_order.member_id IS '会员ID'; +COMMENT ON COLUMN payment_order.order_type IS '订单类型'; +COMMENT ON COLUMN payment_order.goods_desc IS '商品描述'; +COMMENT ON COLUMN payment_order.trans_amt IS '交易金额'; +COMMENT ON COLUMN payment_order.trade_type IS '交易类型'; +COMMENT ON COLUMN payment_order.pay_status IS '支付状态:PENDING-待支付,SUCCESS-成功,FAILED-失败'; +COMMENT ON COLUMN payment_order.huifu_id IS '汇付商户ID'; +COMMENT ON COLUMN payment_order.req_seq_id IS '请求流水号'; +COMMENT ON COLUMN payment_order.req_date IS '请求日期'; +COMMENT ON COLUMN payment_order.hf_seq_id IS '汇付流水号'; +COMMENT ON COLUMN payment_order.pay_info IS '支付信息'; +COMMENT ON COLUMN payment_order.qr_code IS '二维码'; +COMMENT ON COLUMN payment_order.pay_url IS '支付链接'; +COMMENT ON COLUMN payment_order.remark IS '备注'; +COMMENT ON COLUMN payment_order.notify_url IS '通知地址'; +COMMENT ON COLUMN payment_order.pay_time IS '支付时间'; +COMMENT ON COLUMN payment_order.expire_time IS '过期时间'; +COMMENT ON COLUMN payment_order.error_code IS '错误码'; +COMMENT ON COLUMN payment_order.error_msg IS '错误信息'; +COMMENT ON COLUMN payment_order.created_at IS '创建时间'; +COMMENT ON COLUMN payment_order.updated_at IS '更新时间'; +COMMENT ON COLUMN payment_order.deleted_at IS '删除时间'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V15__Add_party_order_id.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V15__Add_party_order_id.sql new file mode 100644 index 0000000..72cda57 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V15__Add_party_order_id.sql @@ -0,0 +1,9 @@ +-- ============================================ +-- 支付订单表添加 party_order_id 字段 +-- 版本: V15 +-- 描述: 添加汇付支付返回的商户订单号 +-- ============================================ + +ALTER TABLE payment_order ADD COLUMN IF NOT EXISTS party_order_id VARCHAR(64); + +COMMENT ON COLUMN payment_order.party_order_id IS '汇付商户订单号'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_stored_card.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_stored_card.sql new file mode 100644 index 0000000..8d64f83 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_stored_card.sql @@ -0,0 +1,58 @@ +-- ============================================ +-- 储值卡相关表 +-- 版本: V16 +-- 描述: 创建储值卡表和充值记录表,每个会员只有一张储值卡 +-- ============================================ + +-- 储值卡表(包含支付密码) +CREATE TABLE IF NOT EXISTS member_stored_card ( + id BIGSERIAL PRIMARY KEY, + member_id BIGINT NOT NULL, + balance DECIMAL(10, 2) DEFAULT 0.00, + total_recharge DECIMAL(10, 2) DEFAULT 0.00, + total_consume DECIMAL(10, 2) DEFAULT 0.00, + pay_password VARCHAR(255), + is_pay_password_set BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_member_stored_card_member_id ON member_stored_card(member_id) WHERE deleted_at IS NULL; + +COMMENT ON TABLE member_stored_card IS '储值卡表,每个会员只有一张储值卡,包含支付密码'; +COMMENT ON COLUMN member_stored_card.member_id IS '会员用户ID'; +COMMENT ON COLUMN member_stored_card.balance IS '储值卡余额'; +COMMENT ON COLUMN member_stored_card.total_recharge IS '累计充值金额'; +COMMENT ON COLUMN member_stored_card.total_consume IS '累计消费金额'; +COMMENT ON COLUMN member_stored_card.pay_password IS '支付密码(BCrypt加密)'; +COMMENT ON COLUMN member_stored_card.is_pay_password_set IS '是否已设置支付密码'; + +-- 储值卡充值记录表 +CREATE TABLE IF NOT EXISTS member_stored_card_recharge ( + id BIGSERIAL PRIMARY KEY, + member_id BIGINT NOT NULL, + order_no VARCHAR(64) NOT NULL, + recharge_amount DECIMAL(10, 2) DEFAULT 0.00, + bonus_amount DECIMAL(10, 2) DEFAULT 0.00, + total_amount DECIMAL(10, 2) DEFAULT 0.00, + pay_amount DECIMAL(10, 2) DEFAULT 0.00, + status VARCHAR(32) DEFAULT 'PENDING', + pay_time TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_stored_card_recharge_member_id ON member_stored_card_recharge(member_id) WHERE deleted_at IS NULL; +CREATE UNIQUE INDEX IF NOT EXISTS idx_stored_card_recharge_order_no ON member_stored_card_recharge(order_no) WHERE deleted_at IS NULL; + +COMMENT ON TABLE member_stored_card_recharge IS '储值卡充值记录表'; +COMMENT ON COLUMN member_stored_card_recharge.member_id IS '会员用户ID'; +COMMENT ON COLUMN member_stored_card_recharge.order_no IS '订单号'; +COMMENT ON COLUMN member_stored_card_recharge.recharge_amount IS '充值金额'; +COMMENT ON COLUMN member_stored_card_recharge.bonus_amount IS '赠送金额'; +COMMENT ON COLUMN member_stored_card_recharge.total_amount IS '到账总金额(充值+赠送)'; +COMMENT ON COLUMN member_stored_card_recharge.pay_amount IS '实际支付金额'; +COMMENT ON COLUMN member_stored_card_recharge.status IS '充值状态:PENDING-待支付,SUCCESS-成功,FAILED-失败'; +COMMENT ON COLUMN member_stored_card_recharge.pay_time IS '支付时间'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V17__Make_member_card_id_nullable.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V17__Make_member_card_id_nullable.sql new file mode 100644 index 0000000..6bf2e9e --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V17__Make_member_card_id_nullable.sql @@ -0,0 +1,8 @@ +-- ============================================ +-- 将 group_course_booking 表的 member_card_id 改为可空 +-- 版本: V17 +-- 描述: 预约团课不再需要会员卡,member_card_id 允许为空 +-- ============================================ + +ALTER TABLE group_course_booking + ALTER COLUMN member_card_id DROP NOT NULL; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V18__Insert_group_course_data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V18__Insert_group_course_data.sql new file mode 100644 index 0000000..0075075 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V18__Insert_group_course_data.sql @@ -0,0 +1,209 @@ +-- ============================================ +-- 团课测试数据 +-- 版本: V18 +-- 描述: 插入团课标签、类型、关联关系及课程数据 +-- 5个团课类型,14个标签,每类型3个标签,每类型5个团课 +-- ============================================ + +-- ============================================ +-- 1. 团课标签数据(14个) +-- ============================================ + +INSERT INTO course_label (label_name, color, description) VALUES +('适合新手', '#52c41a', '适合健身初学者,动作简单易学'), +('中级过渡', '#faad14', '适合有一定基础的学员,逐步提升难度'), +('高级进阶', '#f5222d', '适合高级学员,强度和技巧要求较高'), +('减脂塑形', '#722ed1', '高效燃烧卡路里,助力减脂和体型塑造'), +('增肌强化', '#13c2c2', '注重肌肉力量和围度增长'), +('柔韧性训练', '#eb2f96', '提升身体柔韧性和关节活动度'), +('核心训练', '#1890ff', '强化核心肌群,改善身体稳定性'), +('心肺训练', '#fa8c16', '提升心肺功能和耐力水平'), +('低冲击', '#52c41a', '低冲击运动,对关节友好'), +('高强度', '#f5222d', '高强度训练,短时间内最大化训练效果'), +('团体互动', '#722ed1', '注重团队协作和互动氛围'), +('私教推荐', '#13c2c2', '私教推荐的高效训练课程'), +('热门课程', '#ff1493', '人气较高,广受学员欢迎'), +('新课上线', '#00ced1', '新上线课程,欢迎尝鲜体验') +ON CONFLICT DO NOTHING; + + +-- ============================================ +-- 2. 团课类型数据(5个) +-- ============================================ + +INSERT INTO group_course_type (type_name, base_difficulty, description, category) VALUES +('瑜伽', 3, '通过呼吸调控与体式练习,提升身体柔韧性、力量与心灵平静,适合各年龄段人群', '柔韧与平衡类'), +('动感单车', 5, '伴随动感音乐节奏进行室内骑行,高效燃烧卡路里,释放压力,体验极速快感', '基础有氧与热身'), +('HIIT训练', 8, '高强度间歇训练,通过短时爆发动作与间歇休息交替,短时间内达到最大燃脂效果', '高强度与爆发力'), +('普拉提', 4, '注重核心肌群控制、脊柱对齐和呼吸配合,有效改善体态,预防运动损伤', '柔韧与平衡类'), +('搏击操', 6, '融合拳击、踢腿、格斗动作的有氧运动,宣泄压力同时高效塑形,增强协调性', '高强度与爆发力') +ON CONFLICT DO NOTHING; + + +-- ============================================ +-- 3. 类型-标签关联数据(每类型3个标签,共15条) +-- ============================================ + +INSERT INTO course_type_label (type_id, label_id) +SELECT gt.id, cl.id FROM group_course_type gt, course_label cl +WHERE gt.type_name = '瑜伽' AND cl.label_name = '柔韧性训练' + OR gt.type_name = '瑜伽' AND cl.label_name = '低冲击' + OR gt.type_name = '瑜伽' AND cl.label_name = '适合新手' + OR gt.type_name = '动感单车' AND cl.label_name = '心肺训练' + OR gt.type_name = '动感单车' AND cl.label_name = '热门课程' + OR gt.type_name = '动感单车' AND cl.label_name = '中级过渡' + OR gt.type_name = 'HIIT训练' AND cl.label_name = '高强度' + OR gt.type_name = 'HIIT训练' AND cl.label_name = '减脂塑形' + OR gt.type_name = 'HIIT训练' AND cl.label_name = '高级进阶' + OR gt.type_name = '普拉提' AND cl.label_name = '核心训练' + OR gt.type_name = '普拉提' AND cl.label_name = '柔韧性训练' + OR gt.type_name = '普拉提' AND cl.label_name = '适合新手' + OR gt.type_name = '搏击操' AND cl.label_name = '高强度' + OR gt.type_name = '搏击操' AND cl.label_name = '减脂塑形' + OR gt.type_name = '搏击操' AND cl.label_name = '心肺训练' +ON CONFLICT DO NOTHING; + + +-- ============================================ +-- 4. 团课课程数据(5个类型 × 5个课程 = 25个) +-- start_time / end_time 分布在 2026-07-22 至 2026-07-28 +-- ============================================ + +-- ---------- 瑜伽课程(5个)---------- + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '哈他瑜伽入门', gt.id, '2026-07-22 08:00:00'::TIMESTAMP, '2026-07-22 09:00:00'::TIMESTAMP, 25, 12, '0', '瑜伽室A', '/static/course/yoga_hatha.jpg', '经典哈他瑜伽,从基础体式入手,配合呼吸引导,帮助初学者建立正确的瑜伽练习基础,感受身心的和谐统一。', 88.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '瑜伽'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '流瑜伽', gt.id, '2026-07-24 10:00:00'::TIMESTAMP, '2026-07-24 11:00:00'::TIMESTAMP, 20, 18, '0', '瑜伽室A', '/static/course/yoga_flow.jpg', '体式之间流畅串联,如行云流水般一气呵成。以拜日式为根基,结合站姿、平衡和扭转,提升力量与柔韧的整合能力。', 98.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '瑜伽'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '阴瑜伽', gt.id, '2026-07-26 18:00:00'::TIMESTAMP, '2026-07-26 19:15:00'::TIMESTAMP, 20, 8, '0', '瑜伽室B', '/static/course/yoga_yin.jpg', '阴瑜伽以长时间保持体式为特点,深度伸展筋膜和结缔组织,帮助你释放身体深层紧张,缓解一周的工作疲劳。适合所有级别。', 88.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '瑜伽'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '阿斯汤加瑜伽', gt.id, '2026-07-23 07:00:00'::TIMESTAMP, '2026-07-23 08:30:00'::TIMESTAMP, 15, 10, '0', '瑜伽室A', '/static/course/yoga_ashtanga.jpg', '阿斯汤加瑜伽以固定的体式序列和独特的呼吸法为核心,节奏紧凑,强度较高,能有效提升力量、柔韧和专注力。建议有一定瑜伽基础者参加。', 128.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '瑜伽'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '空中瑜伽', gt.id, '2026-07-25 19:00:00'::TIMESTAMP, '2026-07-25 20:00:00'::TIMESTAMP, 12, 12, '0', '瑜伽室B', '/static/course/yoga_aerial.jpg', '利用悬挂的瑜伽吊床完成各种体式,借助重力让脊柱自然延展,体验"飞翔"般的自由感。深受女性学员喜爱,名额有限请提前预约。', 128.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '瑜伽'; + + +-- ---------- 动感单车课程(5个)---------- + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '初级燃脂骑行', gt.id, '2026-07-22 19:00:00'::TIMESTAMP, '2026-07-22 19:45:00'::TIMESTAMP, 30, 22, '0', '单车房', '/static/course/spin_beginner.jpg', '适合新手的入门级骑行课程,教练将带领你掌握正确的骑行姿势和阻力调节技巧。在动感音乐中轻松燃烧300-400卡路里。', 58.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '动感单车'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '节奏爬坡', gt.id, '2026-07-24 18:30:00'::TIMESTAMP, '2026-07-24 19:15:00'::TIMESTAMP, 30, 15, '0', '单车房', '/static/course/spin_climb.jpg', '模拟山路爬坡骑行,通过逐渐增加阻力来挑战你的耐力和意志力。每一段爬坡后都有短暂的平路冲刺,节奏感十足,大汗淋漓的畅快体验。', 68.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '动感单车'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '极速冲刺', gt.id, '2026-07-26 20:00:00'::TIMESTAMP, '2026-07-26 20:45:00'::TIMESTAMP, 25, 25, '0', '单车房', '/static/course/spin_sprint.jpg', '高强度冲刺为主题的骑行课,多组30秒极限冲刺搭配短暂恢复,彻底引爆你的心肺极限。周五夜间的爆款课程,约满速度极快!', 68.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '动感单车'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '耐力骑行', gt.id, '2026-07-27 10:00:00'::TIMESTAMP, '2026-07-27 11:00:00'::TIMESTAMP, 30, 18, '0', '单车房', '/static/course/spin_endurance.jpg', '60分钟中低强度耐力骑行,适合周末早晨唤醒身体。在稳定的节奏中持续燃脂,配以舒缓的收尾拉伸,开启元气满满的周末。', 58.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '动感单车'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '音乐主题骑行', gt.id, '2026-07-23 19:30:00'::TIMESTAMP, '2026-07-23 20:15:00'::TIMESTAMP, 30, 20, '0', '单车房', '/static/course/spin_music.jpg', '以热门流行音乐为主题的骑行派对!每首歌曲对应一套骑行组合,跟着节拍加速、爬坡、冲刺,在音乐中忘记疲惫。', 68.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '动感单车'; + + +-- ---------- HIIT训练课程(5个)---------- + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT 'Tabata燃脂', gt.id, '2026-07-22 07:00:00'::TIMESTAMP, '2026-07-22 07:30:00'::TIMESTAMP, 15, 8, '0', '多功能训练区', '/static/course/hiit_tabata.jpg', '经典的Tabata模式:20秒全力运动 + 10秒休息,循环8轮共4分钟,搭配热身和拉伸,30分钟全身燃爆。早晨空腹训练燃脂效果加倍!', 98.00, 'system' +FROM group_course_type gt WHERE gt.type_name = 'HIIT训练'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '全身循环训练', gt.id, '2026-07-24 12:00:00'::TIMESTAMP, '2026-07-24 12:45:00'::TIMESTAMP, 15, 12, '0', '多功能训练区', '/static/course/hiit_circuit.jpg', '午间燃脂利器!8个动作站点轮流进行,涵盖深蹲跳、波比跳、壶铃摇摆、登山跑等,每个动作45秒,休息15秒,3轮循环让你全身湿透。', 98.00, 'system' +FROM group_course_type gt WHERE gt.type_name = 'HIIT训练'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '核心爆发力', gt.id, '2026-07-26 17:30:00'::TIMESTAMP, '2026-07-26 18:15:00'::TIMESTAMP, 12, 5, '0', '多功能训练区', '/static/course/hiit_core.jpg', '专注于核心肌群的HIIT训练,结合药球砸地、悬垂举腿、俄罗斯转体和平板支撑变式,打造钢铁般的核心力量,提升所有运动表现。', 108.00, 'system' +FROM group_course_type gt WHERE gt.type_name = 'HIIT训练'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '战绳挑战', gt.id, '2026-07-27 08:00:00'::TIMESTAMP, '2026-07-27 08:45:00'::TIMESTAMP, 10, 10, '0', '户外训练区', '/static/course/hiit_battle.jpg', '风靡全球的战绳训练!通过波浪、猛击、旋转等动作模式激活全身肌群,30秒一组高强度间歇,燃脂同时雕刻上肢线条。名额有限,已约满!', 128.00, 'system' +FROM group_course_type gt WHERE gt.type_name = 'HIIT训练'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '药球HIIT', gt.id, '2026-07-28 10:00:00'::TIMESTAMP, '2026-07-28 10:45:00'::TIMESTAMP, 12, 6, '0', '多功能训练区', '/static/course/hiit_medball.jpg', '以药球为主要工具的HIIT训练。药球砸地、旋转投掷、过头抛接等动作兼具力量和爆发力训练,动作多样不枯燥,周日上午的爆汗之选。', 108.00, 'system' +FROM group_course_type gt WHERE gt.type_name = 'HIIT训练'; + + +-- ---------- 普拉提课程(5个)---------- + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '垫上普拉提', gt.id, '2026-07-23 10:00:00'::TIMESTAMP, '2026-07-23 11:00:00'::TIMESTAMP, 20, 14, '0', '普拉提室', '/static/course/pilates_mat.jpg', '经典垫上普拉提,通过精准的动作控制激活深层核心肌群。从呼吸到骨盆稳定,从脊柱逐节卷动到四肢协调,一节课改善你的身体感知。', 108.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '普拉提'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '器械普拉提', gt.id, '2026-07-25 09:00:00'::TIMESTAMP, '2026-07-25 10:00:00'::TIMESTAMP, 10, 8, '0', '普拉提室', '/static/course/pilates_reformer.jpg', '使用专业普拉提核心床(Reformer)进行训练,弹簧阻力提供精准的负荷控制,适合需要针对性改善体态、康复训练或追求高效塑形的学员。', 158.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '普拉提'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '核心塑形', gt.id, '2026-07-27 14:00:00'::TIMESTAMP, '2026-07-27 15:00:00'::TIMESTAMP, 15, 9, '0', '普拉提室', '/static/course/pilates_sculpt.jpg', '专注于腹部、腰部和臀部的普拉提塑形课程。百次拍击、剪刀腿、肩桥等经典动作持续刺激目标肌群,打造紧致核心线条。', 128.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '普拉提'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '脊柱健康', gt.id, '2026-07-22 16:00:00'::TIMESTAMP, '2026-07-22 17:00:00'::TIMESTAMP, 15, 6, '0', '普拉提室', '/static/course/pilates_spine.jpg', '专为久坐办公人群设计,通过普拉提脊柱逐节运动改善驼背、圆肩等不良体态。融合猫牛式、脊柱旋转和游泳式,给你的脊柱一次深度保养。', 108.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '普拉提'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '产后恢复普拉提', gt.id, '2026-07-24 11:00:00'::TIMESTAMP, '2026-07-24 12:00:00'::TIMESTAMP, 10, 4, '0', '普拉提室', '/static/course/pilates_postnatal.jpg', '专为产后妈妈设计的温和修复课程,重点激活盆底肌和腹横肌,循序渐进恢复核心力量。小班教学,教练一对一关注每位学员的姿势质量。', 158.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '普拉提'; + + +-- ---------- 搏击操课程(5个)---------- + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '有氧搏击基础', gt.id, '2026-07-23 18:00:00'::TIMESTAMP, '2026-07-23 19:00:00'::TIMESTAMP, 25, 20, '0', '操厅A', '/static/course/boxing_basic.jpg', '零基础友好的搏击操入门课。从直拳、摆拳、勾拳到前踢、侧踹,教练分步讲解每个动作的要领,配合动感音乐串联成完整的搏击组合。', 68.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '搏击操'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT 'Kickboxing进阶', gt.id, '2026-07-25 19:00:00'::TIMESTAMP, '2026-07-25 20:00:00'::TIMESTAMP, 20, 15, '0', '操厅A', '/static/course/boxing_kickboxing.jpg', '融合踢拳技术的进阶搏击课程,增加连击组合、闪躲步法和膝肘技术。强度较基础课明显提升,汗如雨下的同时释放工作压力,宣泄效果一流。', 88.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '搏击操'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '综合格斗体能', gt.id, '2026-07-27 16:00:00'::TIMESTAMP, '2026-07-27 17:00:00'::TIMESTAMP, 20, 12, '0', '操厅A', '/static/course/boxing_mma.jpg', '融合拳击、泰拳、摔跤元素的综合体能训练。沙袋击打、地面对抗和爆发力训练交替进行,全面提升力量、速度和耐力。周六下午的硬核之选!', 98.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '搏击操'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '燃脂拳击', gt.id, '2026-07-22 12:00:00'::TIMESTAMP, '2026-07-22 13:00:00'::TIMESTAMP, 25, 16, '0', '操厅A', '/static/course/boxing_fatburn.jpg', '午间燃脂拳击课。空击组合与沙袋击打间歇进行,心率维持在高燃脂区间。一节课可消耗500-700卡路里,中午来打拳比喝咖啡更提神!', 68.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '搏击操'; + +INSERT INTO group_course (course_name, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by) +SELECT '泰拳基础', gt.id, '2026-07-28 15:00:00'::TIMESTAMP, '2026-07-28 16:00:00'::TIMESTAMP, 15, 7, '0', '操厅A', '/static/course/boxing_muaythai.jpg', '学习泰拳的八肢艺术(双拳、双腿、双膝、双肘),从基本站架到肘膝组合,感受泰拳的刚猛魅力。小班教学保证动作纠正质量。', 98.00, 'system' +FROM group_course_type gt WHERE gt.type_name = '搏击操'; + + +-- ============================================ +-- 5. 团课推荐数据(每类型推荐1个热门课程,共5条) +-- ============================================ + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active) +SELECT gc.id, '清晨之选 - 哈他瑜伽入门', '周一早晨8点,用温和的瑜伽唤醒身体,开启高效一周', '最受欢迎的入门瑜伽课,教练耐心细致', 5, TRUE +FROM group_course gc WHERE gc.course_name = '哈他瑜伽入门'; + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active) +SELECT gc.id, '周五狂欢 - 极速冲刺', '周五晚8点,用极速骑行释放一周的疲惫与压力', '单车房王牌课程,周周爆满,燃烧500+卡路里', 4, TRUE +FROM group_course gc WHERE gc.course_name = '极速冲刺'; + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active) +SELECT gc.id, '午间爆汗 - 全身循环训练', '午休45分钟,8个站点循环,高效利用碎片时间燃脂', '午间最受欢迎的HIIT课,适合忙碌的上班族', 3, TRUE +FROM group_course gc WHERE gc.course_name = '全身循环训练'; + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active) +SELECT gc.id, '久坐救星 - 脊柱健康普拉提', '专为久坐族设计的脊柱保养课,改善驼背圆肩', '全网好评,学员反馈"一节课腰就不疼了"', 2, TRUE +FROM group_course gc WHERE gc.course_name = '脊柱健康'; + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active) +SELECT gc.id, '释压首选 - 有氧搏击基础', '零基础也能打!在拳拳到肉的快感中忘记烦恼', '新手友好,发泄压力效果拔群,超过200名学员推荐', 1, TRUE +FROM group_course gc WHERE gc.course_name = '有氧搏击基础'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V19__Insert_system_data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V19__Insert_system_data.sql new file mode 100644 index 0000000..9f6d5da --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V19__Insert_system_data.sql @@ -0,0 +1,315 @@ +-- ============================================ +-- V19: 系统初始数据(角色/用户/权限/菜单/字典/配置) +-- 来源:合并原 V2 + V22 + V28 + V29 + V30 + V31 +-- ============================================ + +-- ============================================ +-- 1. 角色数据 +-- ============================================ +INSERT INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by, created_at, updated_at) +VALUES +(1, '超级管理员', 'admin', 1, 1, 'system', 'system', NOW(), NOW()), +(2, '测试管理员', 'test_admin', 2, 1, 'system', 'system', NOW(), NOW()), +(3, '普通用户', 'normal_user', 3, 1, 'system', 'system', NOW(), NOW()), +(4, '访客', 'guest', 4, 1, 'system', 'system', NOW(), NOW()); + +SELECT setval('sys_role_id_seq', 4); + +-- ============================================ +-- 2. 用户数据(密码均为 Test@123) +-- ============================================ +INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by, created_at, updated_at) +VALUES +(1, 'admin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'admin@novalon.com', '13800138000', '超级管理员', 1, 'system', 'system', NOW(), NOW()), +(2, 'user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'user@novalon.com', '13800138001', '普通用户', 1, 'system', 'system', NOW(), NOW()), +(10,'e2e_test_user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'e2e@test.com', '13900139000', 'E2E测试用户', 1, 'system', 'system', NOW(), NOW()) +ON CONFLICT (username) DO UPDATE SET + password = EXCLUDED.password, + status = EXCLUDED.status; + +SELECT setval('sys_user_id_seq', 10); + +-- ============================================ +-- 3. 用户角色关联 +-- ============================================ +INSERT INTO user_role (user_id, role_id, created_by, created_at) +VALUES +(1, 1, 'system', NOW()), +(2, 3, 'system', NOW()), +(10, 1, 'system', NOW()) +ON CONFLICT (user_id, role_id) DO NOTHING; + +-- ============================================ +-- 4. 权限数据 +-- ============================================ + +-- 4.1 系统管理权限 +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('用户查看', 'system:user:view', '/api/users', 'GET', '查看用户列表', 1, 'system', 'system', NOW(), NOW()), +('用户创建', 'system:user:create', '/api/users', 'POST', '创建用户', 1, 'system', 'system', NOW(), NOW()), +('用户编辑', 'system:user:edit', '/api/users', 'PUT', '编辑用户', 1, 'system', 'system', NOW(), NOW()), +('用户删除', 'system:user:delete', '/api/users', 'DELETE', '删除用户', 1, 'system', 'system', NOW(), NOW()), +('角色查看', 'system:role:view', '/api/roles', 'GET', '查看角色列表', 1, 'system', 'system', NOW(), NOW()), +('角色创建', 'system:role:create', '/api/roles', 'POST', '创建角色', 1, 'system', 'system', NOW(), NOW()), +('角色编辑', 'system:role:edit', '/api/roles', 'PUT', '编辑角色', 1, 'system', 'system', NOW(), NOW()), +('角色删除', 'system:role:delete', '/api/roles', 'DELETE', '删除角色', 1, 'system', 'system', NOW(), NOW()), +('角色分配权限', 'system:role:assign', '/api/roles/*/permissions', 'POST', '为角色分配权限', 1, 'system', 'system', NOW(), NOW()), +('权限查看', 'system:permission:view', '/api/permissions', 'GET', '查看权限列表', 1, 'system', 'system', NOW(), NOW()), +('权限创建', 'system:permission:create', '/api/permissions', 'POST', '创建权限', 1, 'system', 'system', NOW(), NOW()), +('权限编辑', 'system:permission:edit', '/api/permissions', 'PUT', '编辑权限', 1, 'system', 'system', NOW(), NOW()), +('权限删除', 'system:permission:delete', '/api/permissions', 'DELETE', '删除权限', 1, 'system', 'system', NOW(), NOW()), +('菜单查看', 'system:menu:view', '/api/menus', 'GET', '查看菜单列表', 1, 'system', 'system', NOW(), NOW()), +('菜单创建', 'system:menu:create', '/api/menus', 'POST', '创建菜单', 1, 'system', 'system', NOW(), NOW()), +('菜单编辑', 'system:menu:edit', '/api/menus', 'PUT', '编辑菜单', 1, 'system', 'system', NOW(), NOW()), +('菜单删除', 'system:menu:delete', '/api/menus', 'DELETE', '删除菜单', 1, 'system', 'system', NOW(), NOW()), +('字典查看', 'system:dict:view', '/api/dict', 'GET', '查看字典列表', 1, 'system', 'system', NOW(), NOW()), +('字典创建', 'system:dict:create', '/api/dict', 'POST', '创建字典', 1, 'system', 'system', NOW(), NOW()), +('字典编辑', 'system:dict:edit', '/api/dict', 'PUT', '编辑字典', 1, 'system', 'system', NOW(), NOW()), +('字典删除', 'system:dict:delete', '/api/dict', 'DELETE', '删除字典', 1, 'system', 'system', NOW(), NOW()), +('配置查看', 'system:config:view', '/api/config', 'GET', '查看系统配置', 1, 'system', 'system', NOW(), NOW()), +('配置创建', 'system:config:create', '/api/config', 'POST', '创建系统配置', 1, 'system', 'system', NOW(), NOW()), +('配置编辑', 'system:config:edit', '/api/config', 'PUT', '编辑系统配置', 1, 'system', 'system', NOW(), NOW()), +('配置删除', 'system:config:delete', '/api/config', 'DELETE', '删除系统配置', 1, 'system', 'system', NOW(), NOW()), +('日志查看', 'system:log:view', '/api/logs', 'GET', '查看日志', 1, 'system', 'system', NOW(), NOW()), +('文件上传', 'system:file:upload', '/api/files/upload', 'POST', '上传文件', 1, 'system', 'system', NOW(), NOW()), +('文件下载', 'system:file:download', '/api/files/download', 'GET', '下载文件', 1, 'system', 'system', NOW(), NOW()), +('文件删除', 'system:file:delete', '/api/files', 'DELETE', '删除文件', 1, 'system', 'system', NOW(), NOW()), +('公告查看', 'system:notice:view', '/api/notices', 'GET', '查看公告', 1, 'system', 'system', NOW(), NOW()), +('公告创建', 'system:notice:create', '/api/notices', 'POST', '创建公告', 1, 'system', 'system', NOW(), NOW()), +('公告编辑', 'system:notice:edit', '/api/notices', 'PUT', '编辑公告', 1, 'system', 'system', NOW(), NOW()), +('公告删除', 'system:notice:delete', '/api/notices', 'DELETE', '删除公告', 1, 'system', 'system', NOW(), NOW()); + +-- 4.2 业务模块权限 +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +-- 会员管理 +('会员查看', 'business:member:view', '/api/admin/members', 'GET', '查看会员列表及详情', 1, 'system', 'system', NOW(), NOW()), +('会员编辑', 'business:member:edit', '/api/admin/member', 'PUT', '编辑会员信息', 1, 'system', 'system', NOW(), NOW()), +('会员手机修改', 'business:member:phone', '/api/admin/member/phone', 'POST', '管理员修改会员手机号', 1, 'system', 'system', NOW(), NOW()), +-- 会员卡管理 +('会员卡查看', 'business:memberCard:view', '/api/member-cards', 'GET', '查看会员卡列表及详情', 1, 'system', 'system', NOW(), NOW()), +('会员卡创建', 'business:memberCard:create', '/api/member-cards', 'POST', '创建会员卡类型', 1, 'system', 'system', NOW(), NOW()), +('会员卡编辑', 'business:memberCard:edit', '/api/member-cards', 'PUT', '编辑会员卡类型', 1, 'system', 'system', NOW(), NOW()), +('会员卡删除', 'business:memberCard:delete', '/api/member-cards', 'DELETE', '删除会员卡类型', 1, 'system', 'system', NOW(), NOW()), +('会员卡购买', 'business:memberCard:purchase', '/api/member-card-records/purchase', 'POST', '为会员购买/绑定会员卡', 1, 'system', 'system', NOW(), NOW()), +('会员卡退卡', 'business:memberCard:refund', '/api/member-card-records/refund', 'POST', '会员卡退卡/解绑', 1, 'system', 'system', NOW(), NOW()), +-- 团课管理 +('团课查看', 'business:groupCourse:view', '/api/groupCourse', 'GET', '查看团课列表及详情', 1, 'system', 'system', NOW(), NOW()), +('团课创建', 'business:groupCourse:create', '/api/groupCourse', 'POST', '创建团课', 1, 'system', 'system', NOW(), NOW()), +('团课编辑', 'business:groupCourse:edit', '/api/groupCourse', 'PUT', '编辑团课信息', 1, 'system', 'system', NOW(), NOW()), +('团课删除', 'business:groupCourse:delete', '/api/groupCourse', 'DELETE', '删除团课', 1, 'system', 'system', NOW(), NOW()), +('团课取消', 'business:groupCourse:cancel', '/api/groupCourse/cancel', 'POST', '取消团课', 1, 'system', 'system', NOW(), NOW()), +-- 团课类型管理 +('团课类型查看', 'business:groupCourseType:view', '/api/groupCourse/types', 'GET', '查看团课类型列表', 1, 'system', 'system', NOW(), NOW()), +('团课类型创建', 'business:groupCourseType:create', '/api/groupCourse/types', 'POST', '创建团课类型', 1, 'system', 'system', NOW(), NOW()), +('团课类型编辑', 'business:groupCourseType:edit', '/api/groupCourse/types', 'PUT', '编辑团课类型', 1, 'system', 'system', NOW(), NOW()), +('团课类型删除', 'business:groupCourseType:delete', '/api/groupCourse/types', 'DELETE', '删除团课类型', 1, 'system', 'system', NOW(), NOW()), +-- 团课推荐管理 +('团课推荐查看', 'business:groupCourseRecommend:view', '/api/groupCourse/recommend', 'GET', '查看推荐团课列表', 1, 'system', 'system', NOW(), NOW()), +('团课推荐创建', 'business:groupCourseRecommend:create','/api/groupCourse/recommend', 'POST', '创建推荐团课', 1, 'system', 'system', NOW(), NOW()), +('团课推荐编辑', 'business:groupCourseRecommend:edit', '/api/groupCourse/recommend', 'PUT', '编辑推荐团课', 1, 'system', 'system', NOW(), NOW()), +('团课推荐删除', 'business:groupCourseRecommend:delete','/api/groupCourse/recommend', 'DELETE', '删除推荐团课', 1, 'system', 'system', NOW(), NOW()), +-- 团课预约管理 +('团课预约查看', 'business:groupCourseBooking:view', '/api/groupCourse/bookings', 'GET', '查看团课预约记录', 1, 'system', 'system', NOW(), NOW()), +('团课预约创建', 'business:groupCourseBooking:create', '/api/groupCourse/book', 'POST', '预约团课', 1, 'system', 'system', NOW(), NOW()), +('团课预约取消', 'business:groupCourseBooking:cancel', '/api/groupCourse/booking/cancel', 'POST', '取消团课预约', 1, 'system', 'system', NOW(), NOW()), +-- 签到管理 +('签到查看', 'business:checkIn:view', '/api/checkIn/records', 'GET', '查看签到记录', 1, 'system', 'system', NOW(), NOW()), +('签到操作', 'business:checkIn:create', '/api/checkIn', 'POST', '执行签到操作', 1, 'system', 'system', NOW(), NOW()), +('签到导出', 'business:checkIn:export', '/api/checkIn/records/export', 'GET', '导出签到记录', 1, 'system', 'system', NOW(), NOW()), +-- 数据统计 +('数据统计查看', 'business:dataCount:view', '/api/datacount', 'GET', '查看数据统计报表', 1, 'system', 'system', NOW(), NOW()), +('数据统计导出', 'business:dataCount:export', '/api/datacount/export', 'GET', '导出统计报表', 1, 'system', 'system', NOW(), NOW()), +-- 员工管理 +('员工查看', 'business:employee:view', '/api/employees/page', 'GET', '查看员工列表', 1, 'system', 'system', NOW(), NOW()), +('员工创建', 'business:employee:create', '/api/users', 'POST', '创建员工账号', 1, 'system', 'system', NOW(), NOW()), +('员工编辑', 'business:employee:edit', '/api/users', 'PUT', '编辑员工信息', 1, 'system', 'system', NOW(), NOW()), +('员工删除', 'business:employee:delete', '/api/users', 'DELETE', '删除/禁用员工账号', 1, 'system', 'system', NOW(), NOW()), +('员工分配角色', 'business:employee:assignRole', '/api/users/roles', 'POST', '为员工分配角色', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 5. 为超级管理员角色分配所有权限 +-- ============================================ +INSERT INTO sys_role_permission (role_id, permission_id, create_by, update_by, created_at, updated_at) +SELECT 1, id, 'system', 'system', NOW(), NOW() FROM sys_permission WHERE status = 1 + AND id NOT IN (SELECT permission_id FROM sys_role_permission WHERE role_id = 1); + +-- ============================================ +-- 6. 菜单数据 +-- ============================================ + +-- 6.1 一级菜单 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(1, '系统管理', 0, 1, 'M', NULL, NULL, 1, NOW(), NOW()), +(2, '审计日志', 0, 2, 'M', NULL, NULL, 1, NOW(), NOW()), +(3, '系统监控', 0, 3, 'M', NULL, NULL, 1, NOW(), NOW()), +(400, '团课管理', 0, 4, 'M', NULL, NULL, 1, NOW(), NOW()), +(500, '会员管理', 0, 5, 'M', NULL, NULL, 1, NOW(), NOW()), +(502, '会员卡管理', 0, 6, 'M', NULL, NULL, 1, NOW(), NOW()), +(504, '数据统计', 0, 7, 'M', NULL, NULL, 1, NOW(), NOW()); + +-- 6.2 系统管理 - 子菜单 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(11, '用户管理', 1, 1, 'C', 'system:user:list', 'system/user/index', 1, NOW(), NOW()), +(12, '角色管理', 1, 2, 'C', 'system:role:list', 'system/role/index', 1, NOW(), NOW()), +(13, '菜单管理', 1, 3, 'C', 'system:menu:list', 'system/menu/index', 1, NOW(), NOW()), +(14, '部门管理', 1, 4, 'C', 'system:dept:list', 'system/dept/index', 1, NOW(), NOW()), +(15, '字典管理', 1, 5, 'C', 'system:dict:list', 'system/dict/index', 1, NOW(), NOW()), +(16, '参数管理', 1, 6, 'C', 'system:config:list', 'system/config/index', 1, NOW(), NOW()), +(17, '通知公告', 1, 7, 'C', 'system:notice:list', 'system/notice/index', 1, NOW(), NOW()), +(18, '文件管理', 1, 8, 'C', 'system:file:list', 'system/file/index', 1, NOW(), NOW()), +(19, '轮播图管理', 0, 8, 'C', 'system:banner:list', 'banner/index', 1, NOW(), NOW()); + +-- 6.3 系统管理 - 按钮权限 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +-- 用户管理按钮 +(111, '用户查询', 11, 1, 'F', 'system:user:query', NULL, 1, NOW(), NOW()), +(112, '用户新增', 11, 2, 'F', 'system:user:add', NULL, 1, NOW(), NOW()), +(113, '用户修改', 11, 3, 'F', 'system:user:edit', NULL, 1, NOW(), NOW()), +(114, '用户删除', 11, 4, 'F', 'system:user:remove', NULL, 1, NOW(), NOW()), +(115, '用户导出', 11, 5, 'F', 'system:user:export', NULL, 1, NOW(), NOW()), +(116, '用户导入', 11, 6, 'F', 'system:user:import', NULL, 1, NOW(), NOW()), +(117, '重置密码', 11, 7, 'F', 'system:user:resetPwd', NULL, 1, NOW(), NOW()), +-- 角色管理按钮 +(121, '角色查询', 12, 1, 'F', 'system:role:query', NULL, 1, NOW(), NOW()), +(122, '角色新增', 12, 2, 'F', 'system:role:add', NULL, 1, NOW(), NOW()), +(123, '角色修改', 12, 3, 'F', 'system:role:edit', NULL, 1, NOW(), NOW()), +(124, '角色删除', 12, 4, 'F', 'system:role:remove', NULL, 1, NOW(), NOW()), +(125, '角色导出', 12, 5, 'F', 'system:role:export', NULL, 1, NOW(), NOW()), +-- 菜单管理按钮 +(131, '菜单查询', 13, 1, 'F', 'system:menu:query', NULL, 1, NOW(), NOW()), +(132, '菜单新增', 13, 2, 'F', 'system:menu:add', NULL, 1, NOW(), NOW()), +(133, '菜单修改', 13, 3, 'F', 'system:menu:edit', NULL, 1, NOW(), NOW()), +(134, '菜单删除', 13, 4, 'F', 'system:menu:remove', NULL, 1, NOW(), NOW()); + +-- 6.4 审计日志 - 子菜单 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(21, '操作日志', 2, 1, 'C', 'audit:operation:list', 'audit/operation/index', 1, NOW(), NOW()), +(22, '登录日志', 2, 2, 'C', 'audit:login:list', 'audit/login/index', 1, NOW(), NOW()), +(23, '异常日志', 2, 3, 'C', 'audit:exception:list', 'audit/exception/index', 1, NOW(), NOW()); + +-- 6.5 审计日志 - 按钮权限 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(211, '操作查询', 21, 1, 'F', 'audit:operation:query', NULL, 1, NOW(), NOW()), +(212, '操作删除', 21, 2, 'F', 'audit:operation:remove', NULL, 1, NOW(), NOW()), +(213, '操作导出', 21, 3, 'F', 'audit:operation:export', NULL, 1, NOW(), NOW()), +(221, '登录查询', 22, 1, 'F', 'audit:login:query', NULL, 1, NOW(), NOW()), +(222, '登录删除', 22, 2, 'F', 'audit:login:remove', NULL, 1, NOW(), NOW()), +(223, '登录导出', 22, 3, 'F', 'audit:login:export', NULL, 1, NOW(), NOW()), +(231, '异常查询', 23, 1, 'F', 'audit:exception:query', NULL, 1, NOW(), NOW()), +(232, '异常删除', 23, 2, 'F', 'audit:exception:remove', NULL, 1, NOW(), NOW()), +(233, '异常导出', 23, 3, 'F', 'audit:exception:export', NULL, 1, NOW(), NOW()); + +-- 6.6 系统监控 - 子菜单 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(31, '在线用户', 3, 1, 'C', 'monitor:online:list', 'monitor/online/index', 1, NOW(), NOW()), +(32, '定时任务', 3, 2, 'C', 'monitor:job:list', 'monitor/job/index', 1, NOW(), NOW()), +(33, '数据监控', 3, 3, 'C', 'monitor:data:list', 'monitor/data/index', 1, NOW(), NOW()), +(34, '服务监控', 3, 4, 'C', 'monitor:server:list', 'monitor/server/index', 1, NOW(), NOW()), +(35, '缓存监控', 3, 5, 'C', 'monitor:cache:list', 'monitor/cache/index', 1, NOW(), NOW()); + +-- 6.7 系统监控 - 按钮权限 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(311, '在线查询', 31, 1, 'F', 'monitor:online:query', NULL, 1, NOW(), NOW()), +(312, '在线强退', 31, 2, 'F', 'monitor:online:forceLogout', NULL, 1, NOW(), NOW()), +(321, '任务查询', 32, 1, 'F', 'monitor:job:query', NULL, 1, NOW(), NOW()), +(322, '任务新增', 32, 2, 'F', 'monitor:job:add', NULL, 1, NOW(), NOW()), +(323, '任务修改', 32, 3, 'F', 'monitor:job:edit', NULL, 1, NOW(), NOW()), +(324, '任务删除', 32, 4, 'F', 'monitor:job:remove', NULL, 1, NOW(), NOW()), +(325, '任务执行', 32, 5, 'F', 'monitor:job:execute', NULL, 1, NOW(), NOW()); + +-- 6.8 团课管理 - 子菜单 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(401, '团课管理', 400, 1, 'C', 'business:groupCourse:list', 'gymCourse/course/index', 1, NOW(), NOW()), +(402, '团课类型', 400, 2, 'C', 'business:groupCourseType:list', 'gymCourse/type/index', 1, NOW(), NOW()), +(403, '类型标签', 400, 3, 'C', 'business:groupCourseLabel:list', 'gymCourse/label/index', 1, NOW(), NOW()), +(404, '团课推荐', 400, 4, 'C', 'business:groupCourseRecommend:list', 'gymCourse/recommend/index', 1, NOW(), NOW()); + +-- 6.9 团课管理 - 按钮权限 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(4011, '团课查询', 401, 1, 'F', 'business:groupCourse:query', NULL, 1, NOW(), NOW()), +(4012, '团课新增', 401, 2, 'F', 'business:groupCourse:add', NULL, 1, NOW(), NOW()), +(4013, '团课修改', 401, 3, 'F', 'business:groupCourse:edit', NULL, 1, NOW(), NOW()), +(4014, '团课删除', 401, 4, 'F', 'business:groupCourse:remove', NULL, 1, NOW(), NOW()), +(4015, '团课取消', 401, 5, 'F', 'business:groupCourse:cancel', NULL, 1, NOW(), NOW()), +(4021, '类型查询', 402, 1, 'F', 'business:groupCourseType:query', NULL, 1, NOW(), NOW()), +(4022, '类型新增', 402, 2, 'F', 'business:groupCourseType:add', NULL, 1, NOW(), NOW()), +(4023, '类型修改', 402, 3, 'F', 'business:groupCourseType:edit', NULL, 1, NOW(), NOW()), +(4024, '类型删除', 402, 4, 'F', 'business:groupCourseType:remove', NULL, 1, NOW(), NOW()), +(4031, '标签查询', 403, 1, 'F', 'business:groupCourseLabel:query', NULL, 1, NOW(), NOW()), +(4032, '标签新增', 403, 2, 'F', 'business:groupCourseLabel:add', NULL, 1, NOW(), NOW()), +(4033, '标签修改', 403, 3, 'F', 'business:groupCourseLabel:edit', NULL, 1, NOW(), NOW()), +(4034, '标签删除', 403, 4, 'F', 'business:groupCourseLabel:remove', NULL, 1, NOW(), NOW()), +(4041, '推荐查询', 404, 1, 'F', 'business:groupCourseRecommend:query', NULL, 1, NOW(), NOW()), +(4042, '推荐新增', 404, 2, 'F', 'business:groupCourseRecommend:add', NULL, 1, NOW(), NOW()), +(4043, '推荐修改', 404, 3, 'F', 'business:groupCourseRecommend:edit', NULL, 1, NOW(), NOW()), +(4044, '推荐删除', 404, 4, 'F', 'business:groupCourseRecommend:remove', NULL, 1, NOW(), NOW()); + +-- 6.10 会员管理 - 子菜单 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(501, '会员管理', 500, 1, 'C', 'business:member:list', 'member/member/index', 1, NOW(), NOW()), +(503, '会员卡管理', 502, 1, 'C', 'business:memberCard:list', 'member/card/index', 1, NOW(), NOW()), +(505, '数据统计看板', 504, 1, 'C', 'business:statistics:view', 'statistics/dashboard/index', 1, NOW(), NOW()); + +-- 6.11 会员管理 - 按钮权限 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(5011, '会员查询', 501, 1, 'F', 'business:member:query', NULL, 1, NOW(), NOW()), +(5012, '会员新增', 501, 2, 'F', 'business:member:add', NULL, 1, NOW(), NOW()), +(5013, '会员修改', 501, 3, 'F', 'business:member:edit', NULL, 1, NOW(), NOW()), +(5014, '会员删除', 501, 4, 'F', 'business:member:remove', NULL, 1, NOW(), NOW()), +(5015, '会员详情', 501, 5, 'F', 'business:member:detail', NULL, 1, NOW(), NOW()), +(5031, '会员卡查询', 503, 1, 'F', 'business:memberCard:query', NULL, 1, NOW(), NOW()), +(5032, '会员卡新增', 503, 2, 'F', 'business:memberCard:add', NULL, 1, NOW(), NOW()), +(5033, '会员卡修改', 503, 3, 'F', 'business:memberCard:edit', NULL, 1, NOW(), NOW()), +(5034, '会员卡删除', 503, 4, 'F', 'business:memberCard:remove', NULL, 1, NOW(), NOW()), +(5051, '数据统计导出', 505, 1, 'F', 'business:statistics:export', NULL, 1, NOW(), NOW()); + +-- ============================================ +-- 7. 字典数据 +-- ============================================ + +-- 7.1 字典类型 +INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, update_by, created_at, updated_at) +VALUES +('用户状态', 'user_status', '0', '用户状态列表', 'system', 'system', NOW(), NOW()), +('菜单状态', 'menu_status', '0', '菜单状态列表', 'system', 'system', NOW(), NOW()), +('角色状态', 'role_status', '0', '角色状态列表', 'system', 'system', NOW(), NOW()), +('系统开关', 'sys_normal_disable', '0', '系统开关列表', 'system', 'system', NOW(), NOW()) +ON CONFLICT (dict_type) DO NOTHING; + +-- 7.2 字典数据 +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, update_by, created_at, updated_at) +VALUES +(1, '正常', '1', 'user_status', '', 'primary', 'Y', '0', 'system', 'system', NOW(), NOW()), +(2, '停用', '0', 'user_status', '', 'danger', 'N', '0', 'system', 'system', NOW(), NOW()), +(1, '正常', '0', 'menu_status', '', 'primary', 'Y', '0', 'system', 'system', NOW(), NOW()), +(2, '停用', '1', 'menu_status', '', 'danger', 'N', '0', 'system', 'system', NOW(), NOW()), +(1, '正常', '0', 'role_status', '', 'primary', 'Y', '0', 'system', 'system', NOW(), NOW()), +(2, '停用', '1', 'role_status', '', 'danger', 'N', '0', 'system', 'system', NOW(), NOW()), +(1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'system', 'system', NOW(), NOW()), +(2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 8. 系统配置 +-- ============================================ +INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, update_by, created_at, updated_at) +VALUES +('用户管理-用户初始密码', 'sys.user.initPassword', '123456', 'Y', 'system', 'system', NOW(), NOW()), +('主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue','Y', 'system', 'system', NOW(), NOW()), +('用户自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'system', 'system', NOW(), NOW()), +('用户自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'system', 'system', NOW(), NOW()), +('账号自助-密码验证码', 'sys.account.pwdCaptchaEnabled','true', 'Y', 'system', 'system', NOW(), NOW()), +('团课-取消预约免手续费次数', 'group_course.cancel_free_limit','3', 'Y', 'system', 'system', NOW(), NOW()) +ON CONFLICT (config_key) DO NOTHING; + +-- ============================================ +-- 9. 重置所有序列值 +-- ============================================ +SELECT setval('sys_user_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_user)); +SELECT setval('sys_role_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role)); +SELECT setval('user_role_id_seq', (SELECT COALESCE(MAX(id), 1) FROM user_role)); +SELECT setval('sys_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_permission)); +SELECT setval('sys_role_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role_permission)); +SELECT setval('sys_menu_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_menu)); +SELECT setval('sys_dict_type_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_dict_type)); +SELECT setval('sys_dict_data_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_dict_data)); +SELECT setval('sys_config_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_config)); diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V1__Create_all_tables.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V1__Create_all_tables.sql index ab538c2..9c1b50d 100644 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V1__Create_all_tables.sql +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V1__Create_all_tables.sql @@ -1,6 +1,8 @@ +-- ============================================ -- Novalon管理系统数据库初始化脚本 -- 版本: V1 --- 描述: 创建所有核心表结构(合并版) +-- 描述: 创建所有核心系统表结构 +-- ============================================ -- ============================================ -- 用户与角色相关表 @@ -140,7 +142,7 @@ CREATE TABLE IF NOT EXISTS sys_dict_data ( deleted_at TIMESTAMP ); --- 字典表(通用字典) +-- 通用字典表 CREATE TABLE IF NOT EXISTS sys_dictionary ( id BIGSERIAL PRIMARY KEY, type VARCHAR(100) NOT NULL, @@ -165,7 +167,7 @@ CREATE TABLE IF NOT EXISTS sys_config ( config_name VARCHAR(100) NOT NULL, config_key VARCHAR(100) NOT NULL UNIQUE, config_value VARCHAR(500) NOT NULL, - config_type VARCHAR(1) DEFAULT 'N', + config_type VARCHAR(1) DEFAULT 'Y', create_by VARCHAR(50), update_by VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, @@ -355,15 +357,6 @@ COMMENT ON TABLE sys_dict_data IS '字典数据表'; COMMENT ON TABLE sys_dictionary IS '通用字典表'; COMMENT ON TABLE sys_config IS '系统配置表'; COMMENT ON TABLE sys_login_log IS '登录日志表'; -COMMENT ON TABLE sys_exception_log IS '异常日志表'; -COMMENT ON TABLE operation_log IS '操作日志表'; -COMMENT ON TABLE audit_log IS '审计日志表'; -COMMENT ON TABLE audit_log_archive IS '审计日志归档表'; -COMMENT ON TABLE sys_notice IS '系统公告表'; -COMMENT ON TABLE sys_user_message IS '用户消息表'; -COMMENT ON TABLE sys_file IS '文件管理表'; -COMMENT ON TABLE oauth2_client IS 'OAuth2客户端表'; - COMMENT ON TABLE sys_exception_log IS '异常日志表'; COMMENT ON COLUMN sys_exception_log.id IS '主键ID'; COMMENT ON COLUMN sys_exception_log.username IS '操作用户'; @@ -375,7 +368,22 @@ COMMENT ON COLUMN sys_exception_log.exception_msg IS '异常消息'; COMMENT ON COLUMN sys_exception_log.exception_stack IS '异常堆栈'; COMMENT ON COLUMN sys_exception_log.ip IS 'IP地址'; COMMENT ON COLUMN sys_exception_log.create_time IS '创建时间'; - +COMMENT ON TABLE operation_log IS '操作日志表'; +COMMENT ON COLUMN operation_log.id IS '主键ID'; +COMMENT ON COLUMN operation_log.username IS '操作用户'; +COMMENT ON COLUMN operation_log.operation IS '操作描述'; +COMMENT ON COLUMN operation_log.method IS '请求方法'; +COMMENT ON COLUMN operation_log.params IS '请求参数'; +COMMENT ON COLUMN operation_log.result IS '操作结果'; +COMMENT ON COLUMN operation_log.ip IS 'IP地址'; +COMMENT ON COLUMN operation_log.duration IS '执行时长(毫秒)'; +COMMENT ON COLUMN operation_log.status IS '操作状态(0成功 1失败)'; +COMMENT ON COLUMN operation_log.error_msg IS '错误消息'; +COMMENT ON COLUMN operation_log.create_by IS '创建人'; +COMMENT ON COLUMN operation_log.update_by IS '更新人'; +COMMENT ON COLUMN operation_log.created_at IS '创建时间'; +COMMENT ON COLUMN operation_log.updated_at IS '更新时间'; +COMMENT ON COLUMN operation_log.deleted_at IS '删除时间'; COMMENT ON TABLE audit_log IS '审计日志表'; COMMENT ON COLUMN audit_log.id IS '主键ID'; COMMENT ON COLUMN audit_log.entity_type IS '实体类型(如User, Role等)'; @@ -389,7 +397,6 @@ COMMENT ON COLUMN audit_log.changed_fields IS '变更字段列表'; COMMENT ON COLUMN audit_log.ip_address IS 'IP地址'; COMMENT ON COLUMN audit_log.description IS '操作描述'; COMMENT ON COLUMN audit_log.created_at IS '记录创建时间'; - COMMENT ON TABLE audit_log_archive IS '审计日志归档表'; COMMENT ON COLUMN audit_log_archive.id IS '主键ID'; COMMENT ON COLUMN audit_log_archive.entity_type IS '实体类型(如User, Role等)'; @@ -405,3 +412,7 @@ COMMENT ON COLUMN audit_log_archive.user_agent IS '用户代理'; COMMENT ON COLUMN audit_log_archive.description IS '操作描述'; COMMENT ON COLUMN audit_log_archive.created_at IS '记录创建时间'; COMMENT ON COLUMN audit_log_archive.archived_at IS '归档时间'; +COMMENT ON TABLE sys_notice IS '系统公告表'; +COMMENT ON TABLE sys_user_message IS '用户消息表'; +COMMENT ON TABLE sys_file IS '文件管理表'; +COMMENT ON TABLE oauth2_client IS 'OAuth2客户端表'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_banner_menu.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_banner_menu.sql new file mode 100644 index 0000000..3e40add --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_banner_menu.sql @@ -0,0 +1,15 @@ +-- ============================================ +-- V20: 新增轮播图管理菜单按钮权限 +-- 注意: sys_menu(id=19, 轮播图管理) 已在 V19 中创建 +-- ============================================ + +-- 轮播图管理按钮权限 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(191, '轮播图查询', 19, 1, 'F', 'system:banner:query', NULL, 1, NOW(), NOW()), +(192, '轮播图新增', 19, 2, 'F', 'system:banner:add', NULL, 1, NOW(), NOW()), +(193, '轮播图修改', 19, 3, 'F', 'system:banner:edit', NULL, 1, NOW(), NOW()), +(194, '轮播图删除', 19, 4, 'F', 'system:banner:remove', NULL, 1, NOW(), NOW()) +ON CONFLICT (id) DO NOTHING; + +-- 重置菜单序列 +SELECT setval('sys_menu_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_menu)); diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V21__Create_banner_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V21__Create_banner_table.sql new file mode 100644 index 0000000..75a040c --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V21__Create_banner_table.sql @@ -0,0 +1,20 @@ +-- ============================================ +-- V21: 创建轮播图表并更新菜单为顶级菜单 +-- ============================================ + +-- 创建 banner 表 +CREATE TABLE IF NOT EXISTS banner ( + id BIGSERIAL PRIMARY KEY, + image_url VARCHAR(512) NOT NULL, + title VARCHAR(100) NOT NULL, + subtitle TEXT, + sort_order INT DEFAULT 0, + is_active VARCHAR(1) DEFAULT '1', + link_url VARCHAR(512), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 将轮播图管理菜单从系统管理子菜单调整为顶级菜单(parent_id=0, sort=8) +UPDATE sys_menu SET parent_id = 0, order_num = 8 WHERE id = 19; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_coach_role_menu.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_coach_role_menu.sql new file mode 100644 index 0000000..e9bca68 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_coach_role_menu.sql @@ -0,0 +1,49 @@ +-- ============================================ +-- V22: 教练管理 - 角色/权限/菜单 +-- ============================================ + +-- ============================================ +-- 1. 教练角色 +-- ============================================ +INSERT INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by, created_at, updated_at) +VALUES (5, '教练', 'coach', 5, 1, 'system', 'system', NOW(), NOW()) +ON CONFLICT (id) DO NOTHING; + +SELECT setval('sys_role_id_seq', COALESCE((SELECT MAX(id) FROM sys_role), 1)); + +-- ============================================ +-- 2. 教练管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('教练查看', 'system:coach:view', '/api/coach', 'GET', '查看教练列表', 1, 'system', 'system', NOW(), NOW()), +('教练创建', 'system:coach:create', '/api/coach', 'POST', '创建教练', 1, 'system', 'system', NOW(), NOW()), +('教练编辑', 'system:coach:edit', '/api/coach', 'PUT', '编辑教练信息', 1, 'system', 'system', NOW(), NOW()), +('教练删除', 'system:coach:delete', '/api/coach', 'DELETE', '删除(禁用)教练', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 3. 为超级管理员角色分配教练权限 +-- ============================================ +INSERT INTO sys_role_permission (role_id, permission_id, create_by, update_by, created_at, updated_at) +SELECT 1, id, 'system', 'system', NOW(), NOW() FROM sys_permission +WHERE permission_code LIKE 'system:coach:%' + AND id NOT IN (SELECT permission_id FROM sys_role_permission WHERE role_id = 1); + +-- ============================================ +-- 4. 教练管理菜单(顶级菜单) +-- ============================================ +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(20, '教练管理', 0, 9, 'C', 'system:coach:list', 'system/coach/index', 1, NOW(), NOW()); + +-- 教练管理按钮权限 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) VALUES +(201, '教练查询', 20, 1, 'F', 'system:coach:query', NULL, 1, NOW(), NOW()), +(202, '教练新增', 20, 2, 'F', 'system:coach:add', NULL, 1, NOW(), NOW()), +(203, '教练修改', 20, 3, 'F', 'system:coach:edit', NULL, 1, NOW(), NOW()), +(204, '教练删除', 20, 4, 'F', 'system:coach:remove', NULL, 1, NOW(), NOW()); + +-- ============================================ +-- 5. 重置序列 +-- ============================================ +SELECT setval('sys_menu_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_menu)); +SELECT setval('sys_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_permission)); +SELECT setval('sys_role_permission_id_seq',(SELECT COALESCE(MAX(id), 1) FROM sys_role_permission)); diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Create_coach_violation.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Create_coach_violation.sql new file mode 100644 index 0000000..ce36fd7 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Create_coach_violation.sql @@ -0,0 +1,30 @@ +-- ============================================ +-- 教练违规记录表 +-- 版本: V23 +-- 描述: 创建教练违规记录表 +-- ============================================ + +CREATE TABLE IF NOT EXISTS coach_violation ( + id BIGSERIAL PRIMARY KEY, + coach_id BIGINT NOT NULL, + course_id BIGINT NOT NULL, + violation_time TIMESTAMP NOT NULL, + violation_reason VARCHAR(50) NOT NULL, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +COMMENT ON TABLE coach_violation IS '教练违规记录表'; +COMMENT ON COLUMN coach_violation.id IS '主键ID'; +COMMENT ON COLUMN coach_violation.coach_id IS '教练ID(关联sys_user)'; +COMMENT ON COLUMN coach_violation.course_id IS '团课ID(关联group_course)'; +COMMENT ON COLUMN coach_violation.violation_time IS '违规时间'; +COMMENT ON COLUMN coach_violation.violation_reason IS '违规原因(COACH_LATE-教练迟到, COACH_ABSENT-教练缺席, NOT_MANUAL_END-教练未手动结课)'; +COMMENT ON COLUMN coach_violation.create_by IS '创建人'; +COMMENT ON COLUMN coach_violation.update_by IS '更新人'; +COMMENT ON COLUMN coach_violation.created_at IS '创建时间'; +COMMENT ON COLUMN coach_violation.updated_at IS '更新时间'; +COMMENT ON COLUMN coach_violation.deleted_at IS '删除时间(软删除)'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Insert_coach_data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Insert_coach_data.sql new file mode 100644 index 0000000..8895ea9 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Insert_coach_data.sql @@ -0,0 +1,82 @@ +-- ============================================ +-- V24: 教练测试数据 +-- 描述: 创建5个教练用户并分配团课,每教练负责一种课型 +-- 确保同教练的课程时间不冲突 +-- ============================================ + +-- ============================================ +-- 1. 教练用户数据(密码均为 Test@123) +-- ============================================ +INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by, created_at, updated_at) +VALUES +(11, 'coach_zhang', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'coach_zhang@novalon.com', '13800138011', '张教练(瑜伽)', 1, 'system', 'system', NOW(), NOW()), +(12, 'coach_li', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'coach_li@novalon.com', '13800138012', '李教练(动感单车)', 1, 'system', 'system', NOW(), NOW()), +(13, 'coach_wang', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'coach_wang@novalon.com', '13800138013', '王教练(HIIT)', 1, 'system', 'system', NOW(), NOW()), +(14, 'coach_zhao', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'coach_zhao@novalon.com', '13800138014', '赵教练(普拉提)', 1, 'system', 'system', NOW(), NOW()), +(15, 'coach_chen', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'coach_chen@novalon.com', '13800138015', '陈教练(搏击操)', 1, 'system', 'system', NOW(), NOW()) +ON CONFLICT (username) DO UPDATE SET + password = EXCLUDED.password, + status = EXCLUDED.status; + +SELECT setval('sys_user_id_seq', 15); + +-- ============================================ +-- 2. 分配教练角色(role_id=5) +-- ============================================ +INSERT INTO user_role (user_id, role_id, created_by, created_at) +VALUES +(11, 5, 'system', NOW()), +(12, 5, 'system', NOW()), +(13, 5, 'system', NOW()), +(14, 5, 'system', NOW()), +(15, 5, 'system', NOW()) +ON CONFLICT (user_id, role_id) DO NOTHING; + +-- ============================================ +-- 3. 为团课分配教练(按课型,无时间冲突) +-- coach_id=11 张教练: 瑜伽 5门 +-- coach_id=12 李教练: 动感单车 5门 +-- coach_id=13 王教练: HIIT训练 5门 +-- coach_id=14 赵教练: 普拉提 5门 +-- coach_id=15 陈教练: 搏击操 5门 +-- ============================================ + +-- 瑜伽课程 → 张教练(11) +UPDATE group_course SET coach_id = 11, updated_at = NOW() +WHERE course_type = (SELECT id FROM group_course_type WHERE type_name = '瑜伽') + AND deleted_at IS NULL; + +-- 动感单车课程 → 李教练(12) +UPDATE group_course SET coach_id = 12, updated_at = NOW() +WHERE course_type = (SELECT id FROM group_course_type WHERE type_name = '动感单车') + AND deleted_at IS NULL; + +-- HIIT训练课程 → 王教练(13) +UPDATE group_course SET coach_id = 13, updated_at = NOW() +WHERE course_type = (SELECT id FROM group_course_type WHERE type_name = 'HIIT训练') + AND deleted_at IS NULL; + +-- 普拉提课程 → 赵教练(14) +UPDATE group_course SET coach_id = 14, updated_at = NOW() +WHERE course_type = (SELECT id FROM group_course_type WHERE type_name = '普拉提') + AND deleted_at IS NULL; + +-- 搏击操课程 → 陈教练(15) +UPDATE group_course SET coach_id = 15, updated_at = NOW() +WHERE course_type = (SELECT id FROM group_course_type WHERE type_name = '搏击操') + AND deleted_at IS NULL; + +-- ============================================ +-- 4. 验证:确认每个团课都有教练 +-- ============================================ +DO $$ +DECLARE + null_count INTEGER; +BEGIN + SELECT COUNT(*) INTO null_count FROM group_course WHERE coach_id IS NULL AND deleted_at IS NULL; + IF null_count > 0 THEN + RAISE NOTICE '警告:仍有 % 门团课缺少教练', null_count; + ELSE + RAISE NOTICE '所有团课已成功分配教练'; + END IF; +END $$; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V2__Create_indexes.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V2__Create_indexes.sql new file mode 100644 index 0000000..bc9b5b7 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V2__Create_indexes.sql @@ -0,0 +1,141 @@ +-- ============================================ +-- Novalon管理系统索引优化脚本 +-- 版本: V2 +-- 描述: 为核心系统表创建必要的索引以提升查询性能 +-- ============================================ + +-- ============================================ +-- 用户与角色表索引 +-- ============================================ + +-- 用户表索引 +CREATE INDEX IF NOT EXISTS idx_users_username ON sys_user(username); +CREATE INDEX IF NOT EXISTS idx_users_email ON sys_user(email); +CREATE INDEX IF NOT EXISTS idx_users_status ON sys_user(status); +CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON sys_user(deleted_at); + +-- 角色表索引 +CREATE INDEX IF NOT EXISTS idx_roles_role_key ON sys_role(role_key); +CREATE INDEX IF NOT EXISTS idx_roles_status ON sys_role(status); +CREATE INDEX IF NOT EXISTS idx_roles_deleted_at ON sys_role(deleted_at); + +-- 用户角色关联表索引 +CREATE INDEX IF NOT EXISTS idx_user_role_user_id ON user_role(user_id); +CREATE INDEX IF NOT EXISTS idx_user_role_role_id ON user_role(role_id); + +-- ============================================ +-- 权限表索引 +-- ============================================ + +-- 权限表索引 +CREATE INDEX IF NOT EXISTS idx_permission_code ON sys_permission(permission_code); +CREATE INDEX IF NOT EXISTS idx_permission_resource ON sys_permission(resource); +CREATE INDEX IF NOT EXISTS idx_permission_status ON sys_permission(status); + +-- 角色权限关联表索引 +CREATE INDEX IF NOT EXISTS idx_role_permission_role_id ON sys_role_permission(role_id); +CREATE INDEX IF NOT EXISTS idx_role_permission_permission_id ON sys_role_permission(permission_id); + +-- ============================================ +-- 菜单表索引 +-- ============================================ + +CREATE INDEX IF NOT EXISTS idx_sys_menu_parent_id ON sys_menu(parent_id); +CREATE INDEX IF NOT EXISTS idx_sys_menu_status ON sys_menu(status); +CREATE INDEX IF NOT EXISTS idx_sys_menu_deleted_at ON sys_menu(deleted_at); + +-- ============================================ +-- 字典表索引 +-- ============================================ + +-- 字典类型表索引 +CREATE INDEX IF NOT EXISTS idx_sys_dict_type_dict_type ON sys_dict_type(dict_type); +CREATE INDEX IF NOT EXISTS idx_sys_dict_type_status ON sys_dict_type(status); +CREATE INDEX IF NOT EXISTS idx_sys_dict_type_deleted_at ON sys_dict_type(deleted_at); + +-- 字典数据表索引 +CREATE INDEX IF NOT EXISTS idx_sys_dict_data_dict_type ON sys_dict_data(dict_type); +CREATE INDEX IF NOT EXISTS idx_sys_dict_data_dict_value ON sys_dict_data(dict_value); +CREATE INDEX IF NOT EXISTS idx_sys_dict_data_status ON sys_dict_data(status); +CREATE INDEX IF NOT EXISTS idx_sys_dict_data_deleted_at ON sys_dict_data(deleted_at); + +-- 通用字典表索引 +CREATE INDEX IF NOT EXISTS idx_sys_dictionary_type ON sys_dictionary(type); +CREATE INDEX IF NOT EXISTS idx_sys_dictionary_type_code ON sys_dictionary(type, code); +CREATE INDEX IF NOT EXISTS idx_sys_dictionary_deleted_at ON sys_dictionary(deleted_at); + +-- ============================================ +-- 系统配置表索引 +-- ============================================ + +CREATE INDEX IF NOT EXISTS idx_sys_config_config_key ON sys_config(config_key); +CREATE INDEX IF NOT EXISTS idx_sys_config_config_type ON sys_config(config_type); +CREATE INDEX IF NOT EXISTS idx_sys_config_deleted_at ON sys_config(deleted_at); + +-- ============================================ +-- 日志表索引 +-- ============================================ + +-- 登录日志表索引 +CREATE INDEX IF NOT EXISTS idx_sys_login_log_username ON sys_login_log(username); +CREATE INDEX IF NOT EXISTS idx_sys_login_log_ip ON sys_login_log(ip); +CREATE INDEX IF NOT EXISTS idx_sys_login_log_status ON sys_login_log(status); +CREATE INDEX IF NOT EXISTS idx_sys_login_log_login_time ON sys_login_log(login_time); + +-- 异常日志表索引 +CREATE INDEX IF NOT EXISTS idx_sys_exception_log_username ON sys_exception_log(username); +CREATE INDEX IF NOT EXISTS idx_sys_exception_log_exception_name ON sys_exception_log(exception_name); +CREATE INDEX IF NOT EXISTS idx_sys_exception_log_create_time ON sys_exception_log(create_time); + +-- 操作日志表索引 +CREATE INDEX IF NOT EXISTS idx_operation_log_username ON operation_log(username); +CREATE INDEX IF NOT EXISTS idx_operation_log_operation ON operation_log(operation); +CREATE INDEX IF NOT EXISTS idx_operation_log_created_at ON operation_log(created_at); +CREATE INDEX IF NOT EXISTS idx_operation_log_status ON operation_log(status); +CREATE INDEX IF NOT EXISTS idx_operation_log_deleted_at ON operation_log(deleted_at); + +-- 审计日志表索引 +CREATE INDEX IF NOT EXISTS idx_audit_log_entity_type ON audit_log(entity_type); +CREATE INDEX IF NOT EXISTS idx_audit_log_entity_id ON audit_log(entity_id); +CREATE INDEX IF NOT EXISTS idx_audit_log_operation_type ON audit_log(operation_type); +CREATE INDEX IF NOT EXISTS idx_audit_log_operator ON audit_log(operator); +CREATE INDEX IF NOT EXISTS idx_audit_log_operation_time ON audit_log(operation_time); +CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id); + +-- 审计日志归档表索引 +CREATE INDEX IF NOT EXISTS idx_audit_log_archive_entity_type ON audit_log_archive(entity_type); +CREATE INDEX IF NOT EXISTS idx_audit_log_archive_entity_id ON audit_log_archive(entity_id); +CREATE INDEX IF NOT EXISTS idx_audit_log_archive_operation_type ON audit_log_archive(operation_type); +CREATE INDEX IF NOT EXISTS idx_audit_log_archive_operator ON audit_log_archive(operator); +CREATE INDEX IF NOT EXISTS idx_audit_log_archive_operation_time ON audit_log_archive(operation_time); +CREATE INDEX IF NOT EXISTS idx_audit_log_archive_archived_at ON audit_log_archive(archived_at); + +-- ============================================ +-- 通知与消息表索引 +-- ============================================ + +-- 系统公告表索引 +CREATE INDEX IF NOT EXISTS idx_sys_notice_notice_type ON sys_notice(notice_type); +CREATE INDEX IF NOT EXISTS idx_sys_notice_status ON sys_notice(status); +CREATE INDEX IF NOT EXISTS idx_sys_notice_deleted_at ON sys_notice(deleted_at); + +-- 用户消息表索引 +CREATE INDEX IF NOT EXISTS idx_sys_user_message_user_id ON sys_user_message(user_id); +CREATE INDEX IF NOT EXISTS idx_sys_user_message_notice_id ON sys_user_message(notice_id); +CREATE INDEX IF NOT EXISTS idx_sys_user_message_is_read ON sys_user_message(is_read); +CREATE INDEX IF NOT EXISTS idx_sys_user_message_deleted_at ON sys_user_message(deleted_at); + +-- ============================================ +-- 文件管理表索引 +-- ============================================ + +CREATE INDEX IF NOT EXISTS idx_sys_file_file_type ON sys_file(file_type); +CREATE INDEX IF NOT EXISTS idx_sys_file_deleted_at ON sys_file(deleted_at); + +-- ============================================ +-- OAuth2客户端表索引 +-- ============================================ + +CREATE INDEX IF NOT EXISTS idx_oauth2_client_client_id ON oauth2_client(client_id); +CREATE INDEX IF NOT EXISTS idx_oauth2_client_enabled ON oauth2_client(enabled); +CREATE INDEX IF NOT EXISTS idx_oauth2_client_deleted_at ON oauth2_client(deleted_at); diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V3__Grant_permissions.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V3__Grant_permissions.sql new file mode 100644 index 0000000..e8348d5 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V3__Grant_permissions.sql @@ -0,0 +1,15 @@ +-- ============================================ +-- Novalon管理系统权限授予脚本 +-- 版本: V3 +-- 描述: 为novalon用户授予所有表的访问权限 +-- ============================================ + +-- 授予所有表的SELECT, INSERT, UPDATE, DELETE权限 +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO novalon; + +-- 授予所有序列的使用权限 +GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO novalon; + +-- 设置默认权限,使未来创建的表自动授予novalon用户权限 +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO novalon; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO novalon; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V4__Create_member_tables.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V4__Create_member_tables.sql new file mode 100644 index 0000000..3b9a91a --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V4__Create_member_tables.sql @@ -0,0 +1,238 @@ +-- ============================================ +-- 会员相关表 +-- 版本: V4 +-- 描述: 创建会员、微信用户、会员卡类型、会员卡记录、交易流水、退款申请等表 +-- ============================================ + +-- ============================================ +-- 1. member_user 表(会员表) +-- ============================================ + +CREATE TABLE IF NOT EXISTS member_user ( + id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + member_no VARCHAR(50) NOT NULL UNIQUE, + nickname VARCHAR(100), + phone VARCHAR(255), + gender INTEGER DEFAULT 0, + birthday DATE, + address VARCHAR(500), + avatar VARCHAR(500), + subscribed BOOLEAN DEFAULT FALSE, + last_login_at TIMESTAMP, + + union_id VARCHAR(100), + miniapp_open_id VARCHAR(100), + official_open_id VARCHAR(100), + + is_deleted BOOLEAN DEFAULT FALSE +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_member_user_member_no ON member_user(member_no); +CREATE INDEX IF NOT EXISTS idx_member_user_union_id ON member_user(union_id); +CREATE INDEX IF NOT EXISTS idx_member_user_miniapp_openid ON member_user(miniapp_open_id); +CREATE INDEX IF NOT EXISTS idx_member_user_official_openid ON member_user(official_open_id); +CREATE INDEX IF NOT EXISTS idx_member_user_phone ON member_user(phone); +CREATE INDEX IF NOT EXISTS idx_member_user_is_deleted ON member_user(is_deleted); + +COMMENT ON TABLE member_user IS '会员表'; +COMMENT ON COLUMN member_user.id IS '主键ID'; +COMMENT ON COLUMN member_user.created_at IS '创建时间'; +COMMENT ON COLUMN member_user.updated_at IS '更新时间'; +COMMENT ON COLUMN member_user.member_no IS '会员编号(唯一,格式:MEM + 8位随机字符)'; +COMMENT ON COLUMN member_user.nickname IS '昵称'; +COMMENT ON COLUMN member_user.phone IS '手机号(AES-128-CBC加密存储)'; +COMMENT ON COLUMN member_user.gender IS '性别:0-未知,1-男,2-女'; +COMMENT ON COLUMN member_user.birthday IS '生日'; +COMMENT ON COLUMN member_user.address IS '地址'; +COMMENT ON COLUMN member_user.avatar IS '头像URL'; +COMMENT ON COLUMN member_user.subscribed IS '是否关注服务号:true-已关注,false-未关注'; +COMMENT ON COLUMN member_user.last_login_at IS '最后登录时间'; +COMMENT ON COLUMN member_user.union_id IS '微信UnionID(用户在开放平台的唯一标识,跨应用相同)'; +COMMENT ON COLUMN member_user.miniapp_open_id IS '小程序OpenID(用户在当前小程序的唯一标识)'; +COMMENT ON COLUMN member_user.official_open_id IS '服务号OpenID(用户在当前服务号的唯一标识)'; +COMMENT ON COLUMN member_user.is_deleted IS '是否删除(软删除标记):false-正常,true-已删除'; + +-- ============================================ +-- 2. wechat_user 表(微信用户表) +-- ============================================ + +CREATE TABLE IF NOT EXISTS wechat_user ( + id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + member_id BIGINT NOT NULL, + + union_id VARCHAR(100), + miniapp_openid VARCHAR(100), + mp_openid VARCHAR(100), + + is_subscribed BOOLEAN DEFAULT FALSE, + subscribe_time TIMESTAMP, + unsubscribe_time TIMESTAMP +); + +ALTER TABLE wechat_user + ADD CONSTRAINT fk_wechat_user_member + FOREIGN KEY (member_id) REFERENCES member_user(id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS idx_wechat_user_union_id ON wechat_user(union_id); +CREATE INDEX IF NOT EXISTS idx_wechat_user_miniapp_openid ON wechat_user(miniapp_openid); +CREATE INDEX IF NOT EXISTS idx_wechat_user_mp_openid ON wechat_user(mp_openid); +CREATE INDEX IF NOT EXISTS idx_wechat_user_member_id ON wechat_user(member_id); + +COMMENT ON TABLE wechat_user IS '微信用户表'; +COMMENT ON COLUMN wechat_user.id IS '主键ID'; +COMMENT ON COLUMN wechat_user.created_at IS '创建时间'; +COMMENT ON COLUMN wechat_user.updated_at IS '更新时间'; +COMMENT ON COLUMN wechat_user.member_id IS '会员ID(关联 member_user 表的 id 字段)'; +COMMENT ON COLUMN wechat_user.union_id IS '微信UnionID(用户在开放平台的唯一标识)'; +COMMENT ON COLUMN wechat_user.miniapp_openid IS '小程序OpenID(用户在当前小程序的唯一标识)'; +COMMENT ON COLUMN wechat_user.mp_openid IS '服务号OpenID(用户在当前服务号的唯一标识)'; +COMMENT ON COLUMN wechat_user.is_subscribed IS '是否关注服务号:true-已关注,false-未关注'; +COMMENT ON COLUMN wechat_user.subscribe_time IS '首次关注时间'; +COMMENT ON COLUMN wechat_user.unsubscribe_time IS '最后一次取消关注时间'; + +-- ============================================ +-- 3. member_card 表(会员卡类型表) +-- ============================================ + +CREATE TABLE IF NOT EXISTS member_card ( + id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP, + + member_card_id BIGSERIAL, + member_card_name VARCHAR(100) NOT NULL, + member_card_type VARCHAR(20) NOT NULL, + member_card_price DECIMAL(10, 2) NOT NULL, + member_card_validity_days INTEGER, + member_card_total_times INTEGER, + member_card_amount DECIMAL(10, 2), + member_card_status INTEGER DEFAULT 1 NOT NULL, + extra_config TEXT DEFAULT '{}' +); + +COMMENT ON TABLE member_card IS '会员卡类型表'; +COMMENT ON COLUMN member_card.member_card_id IS '会员卡ID'; +COMMENT ON COLUMN member_card.member_card_name IS '会员卡名称'; +COMMENT ON COLUMN member_card.member_card_type IS '会员卡类型:TIME_CARD-时长卡, COUNT_CARD-次卡, STORED_VALUE_CARD-储值卡'; +COMMENT ON COLUMN member_card.member_card_price IS '会员卡价格'; +COMMENT ON COLUMN member_card.member_card_validity_days IS '有效天数(时长卡用)'; +COMMENT ON COLUMN member_card.member_card_total_times IS '总次数(次卡用)'; +COMMENT ON COLUMN member_card.member_card_amount IS '面额(储值卡用)'; +COMMENT ON COLUMN member_card.member_card_status IS '状态:0-下架, 1-上架'; +COMMENT ON COLUMN member_card.extra_config IS '扩展配置(JSON格式)'; + +-- ============================================ +-- 4. member_card_record 表(会员卡记录表) +-- ============================================ + +CREATE TABLE IF NOT EXISTS member_card_record ( + id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP, + + member_card_record_id BIGSERIAL, + member_id BIGINT NOT NULL, + member_card_id BIGINT NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', + remaining_times INTEGER DEFAULT 0, + remaining_amount DECIMAL(10, 2) DEFAULT 0.00, + expire_time TIMESTAMP, + source_order_id BIGINT, + purchase_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + version INTEGER DEFAULT 0 NOT NULL, + card_composition TEXT +); + +CREATE INDEX IF NOT EXISTS idx_member_card_record_member_id ON member_card_record(member_id); +CREATE INDEX IF NOT EXISTS idx_member_card_record_status ON member_card_record(status); +CREATE INDEX IF NOT EXISTS idx_member_card_record_expire_time ON member_card_record(expire_time); +CREATE INDEX IF NOT EXISTS idx_member_card_record_member_status ON member_card_record(member_id, status); +CREATE INDEX IF NOT EXISTS idx_member_card_record_status_expire ON member_card_record(status, expire_time) + WHERE status = 'ACTIVE'; + +COMMENT ON TABLE member_card_record IS '会员卡记录表(会员持有的卡)'; +COMMENT ON COLUMN member_card_record.member_card_record_id IS '会员卡记录ID'; +COMMENT ON COLUMN member_card_record.member_id IS '会员ID'; +COMMENT ON COLUMN member_card_record.member_card_id IS '会员卡类型ID'; +COMMENT ON COLUMN member_card_record.status IS '状态:ACTIVE-有效, USED_UP-用完, EXPIRED-过期, REFUNDED-已退款'; +COMMENT ON COLUMN member_card_record.remaining_times IS '剩余次数'; +COMMENT ON COLUMN member_card_record.remaining_amount IS '剩余金额'; +COMMENT ON COLUMN member_card_record.expire_time IS '到期时间'; +COMMENT ON COLUMN member_card_record.source_order_id IS '来源订单ID'; +COMMENT ON COLUMN member_card_record.purchase_time IS '购买时间'; +COMMENT ON COLUMN member_card_record.version IS '乐观锁版本号'; +COMMENT ON COLUMN member_card_record.card_composition IS '卡片组成(JSON格式,用于组合卡)'; + +-- ============================================ +-- 5. member_card_transactions 表(会员卡交易流水表) +-- ============================================ + +CREATE TABLE IF NOT EXISTS member_card_transactions ( + id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + member_card_record_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + member_card_id BIGINT NOT NULL, + operation_type VARCHAR(20) NOT NULL, + change_amount INTEGER DEFAULT 0, + change_balance DECIMAL(10, 2) DEFAULT 0.00, + after_remaining_count INTEGER DEFAULT 0, + after_remaining_balance DECIMAL(10, 2) DEFAULT 0.00, + related_biz_type VARCHAR(20), + source_order_id BIGINT, + remark VARCHAR(500), + is_archived BOOLEAN DEFAULT FALSE, + archived_at TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_member_card_transactions_member_id ON member_card_transactions(member_id); +CREATE INDEX IF NOT EXISTS idx_member_card_transactions_record_id ON member_card_transactions(member_card_record_id); +CREATE INDEX IF NOT EXISTS idx_member_card_transactions_created_at ON member_card_transactions(created_at); +CREATE INDEX IF NOT EXISTS idx_member_card_transactions_member_type_time + ON member_card_transactions(member_id, operation_type, created_at); + +COMMENT ON TABLE member_card_transactions IS '会员卡交易流水表'; +COMMENT ON COLUMN member_card_transactions.operation_type IS '操作类型:PURCHASE-购买, DEDUCT-扣次/扣费, RENEW-续费, REFUND-退款, EXPIRE-过期'; +COMMENT ON COLUMN member_card_transactions.change_amount IS '变动次数'; +COMMENT ON COLUMN member_card_transactions.change_balance IS '变动金额'; +COMMENT ON COLUMN member_card_transactions.after_remaining_count IS '变动后剩余次数'; +COMMENT ON COLUMN member_card_transactions.after_remaining_balance IS '变动后剩余金额'; +COMMENT ON COLUMN member_card_transactions.related_biz_type IS '关联业务类型:GROUP_CLASS-团课, PT_CLASS-私教, CHECK_IN-签到'; +COMMENT ON COLUMN member_card_transactions.is_archived IS '是否已归档'; +COMMENT ON COLUMN member_card_transactions.archived_at IS '归档时间'; + +-- ============================================ +-- 6. refund_application 表(退款申请表) +-- ============================================ + +CREATE TABLE IF NOT EXISTS refund_application ( + id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP, + + record_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'PENDING', + reason VARCHAR(500), + apply_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + audit_time TIMESTAMP, + auditor_id BIGINT, + audit_remark VARCHAR(500), + refund_amount DECIMAL(10, 2) +); + +CREATE INDEX IF NOT EXISTS idx_refund_application_record_id ON refund_application(record_id); +CREATE INDEX IF NOT EXISTS idx_refund_application_status ON refund_application(status); + +COMMENT ON TABLE refund_application IS '退款申请表'; +COMMENT ON COLUMN refund_application.status IS '状态:PENDING-待审核, APPROVED-已批准, REJECTED-已拒绝, PROCESSING-处理中, SUCCESS-成功, FAILED-失败'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V5__Create_group_course_tables.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V5__Create_group_course_tables.sql new file mode 100644 index 0000000..d56b99c --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V5__Create_group_course_tables.sql @@ -0,0 +1,81 @@ +-- ============================================ +-- 团课相关表 +-- 版本: V5 +-- 描述: 创建团课课程表和团课预约记录表 +-- ============================================ + +-- 团课课程表 +CREATE TABLE IF NOT EXISTS group_course ( + id BIGSERIAL PRIMARY KEY, + course_name VARCHAR(100) NOT NULL, + coach_id BIGINT, + course_type BIGINT, + start_time TIMESTAMP NOT NULL, + end_time TIMESTAMP NOT NULL, + max_members INTEGER DEFAULT 20, + current_members INTEGER DEFAULT 0, + status VARCHAR(1) DEFAULT '0', + actual_start_time TIMESTAMP, + actual_end_time TIMESTAMP, + location VARCHAR(255), + cover_image VARCHAR(500), + description TEXT, + stored_value_amount DECIMAL(10,2) DEFAULT 0, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 团课预约记录表 +CREATE TABLE IF NOT EXISTS group_course_booking ( + id BIGSERIAL PRIMARY KEY, + course_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + member_card_id BIGINT NOT NULL, + booking_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + status VARCHAR(1) DEFAULT '0', + cancel_time TIMESTAMP, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +COMMENT ON TABLE group_course IS '团课课程表'; +COMMENT ON COLUMN group_course.id IS '主键ID'; +COMMENT ON COLUMN group_course.course_name IS '课程名称'; +COMMENT ON COLUMN group_course.coach_id IS '教练ID(关联sys_user)'; +COMMENT ON COLUMN group_course.course_type IS '课程类型(如瑜伽/普拉提/动感单车)'; +COMMENT ON COLUMN group_course.start_time IS '开始时间'; +COMMENT ON COLUMN group_course.end_time IS '结束时间'; +COMMENT ON COLUMN group_course.max_members IS '最大参与人数'; +COMMENT ON COLUMN group_course.current_members IS '当前参与人数'; +COMMENT ON COLUMN group_course.status IS '状态(0正常 1已取消 2已结束 3进行中 4超时 5教练缺席 6自动结束 7教练迟到)'; +COMMENT ON COLUMN group_course.actual_start_time IS '实际开课时间(教练点击开课时记录)'; +COMMENT ON COLUMN group_course.actual_end_time IS '实际结课时间(教练点击结课时记录)'; +COMMENT ON COLUMN group_course.location IS '上课地点'; +COMMENT ON COLUMN group_course.cover_image IS '封面图URL'; +COMMENT ON COLUMN group_course.description IS '课程描述'; +COMMENT ON COLUMN group_course.stored_value_amount IS '储值卡额度(消耗金额)'; +COMMENT ON COLUMN group_course.create_by IS '创建人'; +COMMENT ON COLUMN group_course.update_by IS '更新人'; +COMMENT ON COLUMN group_course.created_at IS '创建时间'; +COMMENT ON COLUMN group_course.updated_at IS '更新时间'; +COMMENT ON COLUMN group_course.deleted_at IS '删除时间(软删除)'; + +COMMENT ON TABLE group_course_booking IS '团课预约记录表'; +COMMENT ON COLUMN group_course_booking.id IS '主键ID'; +COMMENT ON COLUMN group_course_booking.course_id IS '团课ID'; +COMMENT ON COLUMN group_course_booking.member_id IS '用户ID'; +COMMENT ON COLUMN group_course_booking.member_card_id IS '会员卡ID'; +COMMENT ON COLUMN group_course_booking.booking_time IS '预约时间'; +COMMENT ON COLUMN group_course_booking.status IS '状态(0已预约 1已取消 2已出席 3缺席 4教练缺席 5迟到)'; +COMMENT ON COLUMN group_course_booking.cancel_time IS '取消时间'; +COMMENT ON COLUMN group_course_booking.create_by IS '创建人'; +COMMENT ON COLUMN group_course_booking.update_by IS '更新人'; +COMMENT ON COLUMN group_course_booking.created_at IS '创建时间'; +COMMENT ON COLUMN group_course_booking.updated_at IS '更新时间'; +COMMENT ON COLUMN group_course_booking.deleted_at IS '删除时间(软删除)'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Add_booking_snapshot_fields.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Add_booking_snapshot_fields.sql new file mode 100644 index 0000000..9778517 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Add_booking_snapshot_fields.sql @@ -0,0 +1,16 @@ +-- ============================================ +-- 为团课预约记录表添加课程冗余字段 +-- 版本: V6 +-- 描述: 保存预约时的课程快照信息 +-- ============================================ + +ALTER TABLE group_course_booking + ADD COLUMN IF NOT EXISTS course_name VARCHAR(100), + ADD COLUMN IF NOT EXISTS course_start_time TIMESTAMP, + ADD COLUMN IF NOT EXISTS course_end_time TIMESTAMP, + ADD COLUMN IF NOT EXISTS location VARCHAR(255); + +COMMENT ON COLUMN group_course_booking.course_name IS '课程名称(冗余字段,保存预约时的课程快照)'; +COMMENT ON COLUMN group_course_booking.course_start_time IS '课程开始时间(冗余字段,保存预约时的课程快照)'; +COMMENT ON COLUMN group_course_booking.course_end_time IS '课程结束时间(冗余字段,保存预约时的课程快照)'; +COMMENT ON COLUMN group_course_booking.location IS '上课地点(冗余字段,保存预约时的课程快照)'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V7__Create_sign_in_record.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V7__Create_sign_in_record.sql new file mode 100644 index 0000000..d68c53a --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V7__Create_sign_in_record.sql @@ -0,0 +1,52 @@ +-- ============================================ +-- 会员到店签到记录表 +-- 版本: V7 +-- 描述: 创建sign_in_record表,用于记录会员签到信息 +-- ============================================ + +CREATE TABLE IF NOT EXISTS sign_in_record ( + id BIGSERIAL PRIMARY KEY, + member_id BIGINT NOT NULL, + member_card_id BIGINT, + sign_in_time TIMESTAMP NOT NULL, + sign_in_type VARCHAR(20) NOT NULL, + sign_in_status VARCHAR(20) NOT NULL DEFAULT 'SUCCESS', + verification_details TEXT, + fail_reason VARCHAR(500), + operator_id BIGINT, + operator_name VARCHAR(100), + device_info VARCHAR(200), + ip_address VARCHAR(50), + source VARCHAR(20) NOT NULL, + is_delete BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_id ON sign_in_record(member_id); +CREATE INDEX IF NOT EXISTS idx_sign_in_record_sign_in_time ON sign_in_record(sign_in_time); +CREATE INDEX IF NOT EXISTS idx_sign_in_record_sign_in_status ON sign_in_record(sign_in_status); +CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_card_id ON sign_in_record(member_card_id); +CREATE INDEX IF NOT EXISTS idx_sign_in_record_operator_id ON sign_in_record(operator_id); +CREATE INDEX IF NOT EXISTS idx_sign_in_record_source ON sign_in_record(source); +CREATE INDEX IF NOT EXISTS idx_sign_in_record_is_delete ON sign_in_record(is_delete); +CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_time ON sign_in_record(member_id, sign_in_time); +CREATE INDEX IF NOT EXISTS idx_sign_in_record_status_time ON sign_in_record(sign_in_status, sign_in_time); + +COMMENT ON TABLE sign_in_record IS '会员到店签到记录表'; +COMMENT ON COLUMN sign_in_record.id IS '自增主键'; +COMMENT ON COLUMN sign_in_record.member_id IS '会员ID,关联member表'; +COMMENT ON COLUMN sign_in_record.member_card_id IS '签到时使用的会员卡ID'; +COMMENT ON COLUMN sign_in_record.sign_in_time IS '签到入场时间'; +COMMENT ON COLUMN sign_in_record.sign_in_type IS '签到方式:QR_CODE-扫码签到,MANUAL-手动签到,FACE-人脸识别'; +COMMENT ON COLUMN sign_in_record.sign_in_status IS '签到状态:SUCCESS-成功,FAILED-失败'; +COMMENT ON COLUMN sign_in_record.verification_details IS 'JSON格式,存储会员卡验证时的快照数据'; +COMMENT ON COLUMN sign_in_record.fail_reason IS '失败时的具体原因文案'; +COMMENT ON COLUMN sign_in_record.operator_id IS '操作人ID(前台人员),自助签到时为NULL'; +COMMENT ON COLUMN sign_in_record.operator_name IS '操作人姓名冗余'; +COMMENT ON COLUMN sign_in_record.device_info IS '签到设备标识或型号'; +COMMENT ON COLUMN sign_in_record.ip_address IS '客户端IP地址'; +COMMENT ON COLUMN sign_in_record.source IS '签到来源:MINI_PROGRAM-小程序扫码,PC_BACKEND-后台管理端'; +COMMENT ON COLUMN sign_in_record.is_delete IS '软删除标识:false-未删除,true-已删除'; +COMMENT ON COLUMN sign_in_record.created_at IS '记录创建时间'; +COMMENT ON COLUMN sign_in_record.updated_at IS '记录更新时间'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V8__Create_group_course_type.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V8__Create_group_course_type.sql new file mode 100644 index 0000000..942af5a --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V8__Create_group_course_type.sql @@ -0,0 +1,33 @@ +-- ============================================ +-- 团课类型表 +-- 版本: V8 +-- 描述: 创建团课类型表,用于分类管理团课 +-- ============================================ + +CREATE TABLE IF NOT EXISTS group_course_type ( + id BIGSERIAL PRIMARY KEY, + type_name VARCHAR(100) NOT NULL, + base_difficulty INTEGER DEFAULT 1, + description TEXT, + category VARCHAR(50), + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_group_course_type_type_name ON group_course_type(type_name); +CREATE INDEX IF NOT EXISTS idx_group_course_type_category ON group_course_type(category); + +COMMENT ON TABLE group_course_type IS '团课类型表'; +COMMENT ON COLUMN group_course_type.id IS '主键ID'; +COMMENT ON COLUMN group_course_type.type_name IS '类型名称'; +COMMENT ON COLUMN group_course_type.base_difficulty IS '基础难度(1-10)'; +COMMENT ON COLUMN group_course_type.description IS '类型描述'; +COMMENT ON COLUMN group_course_type.category IS '分类(如:有氧、力量、柔韧等)'; +COMMENT ON COLUMN group_course_type.create_by IS '创建人'; +COMMENT ON COLUMN group_course_type.update_by IS '更新人'; +COMMENT ON COLUMN group_course_type.created_at IS '创建时间'; +COMMENT ON COLUMN group_course_type.updated_at IS '更新时间'; +COMMENT ON COLUMN group_course_type.deleted_at IS '删除时间(软删除)'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V9__Create_course_label_tables.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V9__Create_course_label_tables.sql new file mode 100644 index 0000000..0c193b8 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V9__Create_course_label_tables.sql @@ -0,0 +1,56 @@ +-- ============================================ +-- 团课标签相关表 +-- 版本: V9 +-- 描述: 创建团课标签表和类型-标签关联表 +-- ============================================ + +-- 团课标签表 +CREATE TABLE IF NOT EXISTS course_label ( + id BIGSERIAL PRIMARY KEY, + label_name VARCHAR(50) NOT NULL, + color VARCHAR(20) DEFAULT '#1890ff', + description VARCHAR(200), + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 团课类型-标签关联表 +CREATE TABLE IF NOT EXISTS course_type_label ( + id BIGSERIAL PRIMARY KEY, + type_id BIGINT NOT NULL, + label_id BIGINT NOT NULL, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP, + UNIQUE (type_id, label_id) +); + +CREATE INDEX IF NOT EXISTS idx_course_label_label_name ON course_label(label_name); +CREATE INDEX IF NOT EXISTS idx_course_type_label_type_id ON course_type_label(type_id); +CREATE INDEX IF NOT EXISTS idx_course_type_label_label_id ON course_type_label(label_id); + +COMMENT ON TABLE course_label IS '团课标签表'; +COMMENT ON COLUMN course_label.id IS '主键ID'; +COMMENT ON COLUMN course_label.label_name IS '标签名称'; +COMMENT ON COLUMN course_label.color IS '标签颜色(十六进制)'; +COMMENT ON COLUMN course_label.description IS '标签描述'; +COMMENT ON COLUMN course_label.create_by IS '创建人'; +COMMENT ON COLUMN course_label.update_by IS '更新人'; +COMMENT ON COLUMN course_label.created_at IS '创建时间'; +COMMENT ON COLUMN course_label.updated_at IS '更新时间'; +COMMENT ON COLUMN course_label.deleted_at IS '删除时间(软删除)'; + +COMMENT ON TABLE course_type_label IS '团课类型-标签关联表'; +COMMENT ON COLUMN course_type_label.id IS '主键ID'; +COMMENT ON COLUMN course_type_label.type_id IS '团课类型ID'; +COMMENT ON COLUMN course_type_label.label_id IS '标签ID'; +COMMENT ON COLUMN course_type_label.create_by IS '创建人'; +COMMENT ON COLUMN course_type_label.update_by IS '更新人'; +COMMENT ON COLUMN course_type_label.created_at IS '创建时间'; +COMMENT ON COLUMN course_type_label.updated_at IS '更新时间'; +COMMENT ON COLUMN course_type_label.deleted_at IS '删除时间(软删除)'; diff --git a/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java b/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java index b743860..501026b 100644 --- a/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java +++ b/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java @@ -64,6 +64,7 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory findByDeletedAtIsNull(); + + Flux findActiveBanners(); + + Mono findById(Long id); + + Mono save(Banner banner); + + Mono deleteByIdAndDeletedAtIsNull(Long id); +} diff --git a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/IBannerService.java b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/IBannerService.java new file mode 100644 index 0000000..238d881 --- /dev/null +++ b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/IBannerService.java @@ -0,0 +1,20 @@ +package cn.novalon.gym.manage.notify.core.service; + +import cn.novalon.gym.manage.notify.core.domain.Banner; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface IBannerService { + + Flux getAllBanners(); + + Flux getActiveBanners(); + + Mono getBannerById(Long id); + + Mono createBanner(Banner banner); + + Mono updateBanner(Long id, Banner banner); + + Mono deleteBanner(Long id); +} diff --git a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/impl/BannerServiceImpl.java b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/impl/BannerServiceImpl.java new file mode 100644 index 0000000..227678b --- /dev/null +++ b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/impl/BannerServiceImpl.java @@ -0,0 +1,80 @@ +package cn.novalon.gym.manage.notify.core.service.impl; + +import cn.novalon.gym.manage.notify.core.domain.Banner; +import cn.novalon.gym.manage.notify.core.repository.IBannerRepository; +import cn.novalon.gym.manage.notify.core.service.IBannerService; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; + +@Service +public class BannerServiceImpl implements IBannerService { + + private final IBannerRepository bannerRepository; + + public BannerServiceImpl(IBannerRepository bannerRepository) { + this.bannerRepository = bannerRepository; + } + + @Override + public Flux getAllBanners() { + return bannerRepository.findByDeletedAtIsNull(); + } + + @Override + public Flux getActiveBanners() { + return bannerRepository.findActiveBanners(); + } + + @Override + public Mono getBannerById(Long id) { + return bannerRepository.findById(id) + .filter(banner -> banner.getDeletedAt() == null); + } + + @Override + public Mono createBanner(Banner banner) { + banner.setCreatedAt(LocalDateTime.now()); + return bannerRepository.save(banner); + } + + @Override + public Mono updateBanner(Long id, Banner banner) { + return bannerRepository.findById(id) + .flatMap(existing -> { + if (banner.getImageUrl() != null) { + existing.setImageUrl(banner.getImageUrl()); + } + if (banner.getTitle() != null) { + existing.setTitle(banner.getTitle()); + } + if (banner.getSubtitle() != null) { + existing.setSubtitle(banner.getSubtitle()); + } + if (banner.getSortOrder() != null) { + existing.setSortOrder(banner.getSortOrder()); + } + if (banner.getIsActive() != null) { + existing.setIsActive(banner.getIsActive()); + } + if (banner.getLinkUrl() != null) { + existing.setLinkUrl(banner.getLinkUrl()); + } + existing.setUpdatedAt(LocalDateTime.now()); + return bannerRepository.save(existing); + }); + } + + @Override + public Mono deleteBanner(Long id) { + return bannerRepository.findById(id) + .filter(banner -> banner.getDeletedAt() == null) + .flatMap(banner -> { + banner.setDeletedAt(LocalDateTime.now()); + return bannerRepository.save(banner); + }) + .then(); + } +} diff --git a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/handler/BannerHandler.java b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/handler/BannerHandler.java new file mode 100644 index 0000000..81e621a --- /dev/null +++ b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/handler/BannerHandler.java @@ -0,0 +1,120 @@ +package cn.novalon.gym.manage.notify.handler; + +import cn.novalon.gym.manage.notify.core.domain.Banner; +import cn.novalon.gym.manage.notify.core.service.IBannerService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; +import java.util.Map; + +@Component +@Tag(name = "轮播图管理", description = "首页轮播图相关操作") +public class BannerHandler { + + private final IBannerService bannerService; + + public BannerHandler(IBannerService bannerService) { + this.bannerService = bannerService; + } + + @Operation(summary = "获取所有轮播图", description = "获取系统中所有轮播图列表(按排序升序)") + public Mono getAllBanners(ServerRequest request) { + Flux banners = bannerService.getAllBanners(); + return ServerResponse.ok().body(banners, Banner.class); + } + + @Operation(summary = "获取启用的轮播图", description = "获取所有启用状态的轮播图(按排序升序),供外部页面调用") + public Mono getActiveBanners(ServerRequest request) { + Flux banners = bannerService.getActiveBanners(); + return ServerResponse.ok().body(banners, Banner.class); + } + + @Operation(summary = "根据ID获取轮播图", description = "根据轮播图ID获取详细信息") + public Mono getBannerById(ServerRequest request) { + Long id = Long.parseLong(request.pathVariable("id")); + return bannerService.getBannerById(id) + .flatMap(banner -> ServerResponse.ok().bodyValue(banner)) + .switchIfEmpty(ServerResponse.notFound().build()); + } + + @Operation(summary = "创建轮播图", description = "创建新轮播图") + public Mono createBanner(ServerRequest request) { + return request.bodyToMono(Map.class) + .map(this::mapToBanner) + .filter(banner -> banner.getImageUrl() != null && !banner.getImageUrl().trim().isEmpty()) + .switchIfEmpty(Mono.error(new IllegalArgumentException("轮播图图片地址不能为空"))) + .filter(banner -> banner.getTitle() != null && !banner.getTitle().trim().isEmpty()) + .switchIfEmpty(Mono.error(new IllegalArgumentException("轮播图标题不能为空"))) + .flatMap(banner -> { + if (banner.getSortOrder() == null) { + banner.setSortOrder(0); + } + if (banner.getIsActive() == null) { + banner.setIsActive("1"); + } + return bannerService.createBanner(banner); + }) + .flatMap(banner -> ServerResponse.ok().bodyValue(banner)) + .onErrorResume(IllegalArgumentException.class, ex -> + ServerResponse.badRequest().bodyValue(Map.of( + "code", 400, + "message", ex.getMessage(), + "timestamp", LocalDateTime.now() + )) + ); + } + + @Operation(summary = "更新轮播图", description = "更新轮播图信息") + public Mono updateBanner(ServerRequest request) { + Long id = Long.parseLong(request.pathVariable("id")); + return request.bodyToMono(Map.class) + .map(this::mapToBanner) + .flatMap(banner -> bannerService.updateBanner(id, banner)) + .flatMap(banner -> ServerResponse.ok().bodyValue(banner)) + .switchIfEmpty(ServerResponse.notFound().build()); + } + + @Operation(summary = "删除轮播图", description = "删除指定轮播图(软删除)") + public Mono deleteBanner(ServerRequest request) { + Long id = Long.parseLong(request.pathVariable("id")); + return bannerService.getBannerById(id) + .filter(banner -> banner.getDeletedAt() == null) + .flatMap(banner -> bannerService.deleteBanner(id) + .then(ServerResponse.noContent().build())) + .switchIfEmpty(ServerResponse.notFound().build()); + } + + /** + * 将前端传来的 Map 转换为 Banner 领域对象 + * 兼容 isActive 以 boolean 或 string 形式传入 + */ + private Banner mapToBanner(Map body) { + Banner banner = new Banner(); + banner.setImageUrl((String) body.get("imageUrl")); + banner.setTitle((String) body.get("title")); + banner.setSubtitle((String) body.get("subtitle")); + banner.setLinkUrl((String) body.get("linkUrl")); + + // sortOrder 处理 + Object sortOrderObj = body.get("sortOrder"); + if (sortOrderObj instanceof Number) { + banner.setSortOrder(((Number) sortOrderObj).intValue()); + } + + // isActive 兼容 boolean 和 string + Object isActiveObj = body.get("isActive"); + if (isActiveObj instanceof Boolean) { + banner.setIsActive((Boolean) isActiveObj ? "1" : "0"); + } else if (isActiveObj instanceof String) { + banner.setIsActive((String) isActiveObj); + } + + return banner; + } +} diff --git a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java index 754e243..b52b481 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java @@ -65,7 +65,9 @@ public class SecurityConfig { .pathMatchers("/api/payment/create").permitAll() .pathMatchers("/api/payment/query").permitAll() .pathMatchers("/api/payment/notify").permitAll() - .pathMatchers("/api/payment/refund").permitAll(); + .pathMatchers("/api/payment/refund").permitAll() + .pathMatchers("/api/files/**").permitAll() + .pathMatchers("/api/coach/**").permitAll(); if (isDevOrTest) { spec.pathMatchers("/swagger-ui.html").permitAll() diff --git a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/dto/request/CoachCreateRequest.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/dto/request/CoachCreateRequest.java new file mode 100644 index 0000000..c884fbc --- /dev/null +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/dto/request/CoachCreateRequest.java @@ -0,0 +1,54 @@ +package cn.novalon.gym.manage.sys.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** + * 教练创建请求DTO + * + * @author 张翔 + * @date 2026-07-19 + */ +@Schema(description = "教练创建请求") +public class CoachCreateRequest { + + @Schema(description = "用户名", example = "coach01") + @NotBlank(message = "用户名不能为空") + @Size(min = 3, max = 50, message = "用户名长度必须在3-50之间") + @Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "用户名只能包含字母、数字、下划线和横线") + private String username; + + @Schema(description = "昵称", example = "张教练") + @Size(max = 100, message = "昵称长度不能超过100") + private String nickname; + + @Schema(description = "密码", example = "Coach@123") + @NotBlank(message = "密码不能为空") + @Size(min = 8, max = 20, message = "密码长度必须在8-20之间") + @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$", message = "密码必须包含大小写字母和数字") + private String password; + + @Schema(description = "邮箱", example = "coach@example.com") + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") + private String email; + + @Schema(description = "手机号", example = "13800138001") + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getNickname() { return nickname; } + public void setNickname(String nickname) { this.nickname = nickname; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } +} diff --git a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/dto/request/CoachUpdateRequest.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/dto/request/CoachUpdateRequest.java new file mode 100644 index 0000000..59c078f --- /dev/null +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/dto/request/CoachUpdateRequest.java @@ -0,0 +1,35 @@ +package cn.novalon.gym.manage.sys.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** + * 教练更新请求DTO + * + * @author 张翔 + * @date 2026-07-19 + */ +@Schema(description = "教练更新请求") +public class CoachUpdateRequest { + + @Schema(description = "昵称", example = "张教练") + @Size(max = 100, message = "昵称长度不能超过100") + private String nickname; + + @Schema(description = "邮箱", example = "coach@example.com") + @Email(message = "邮箱格式不正确") + private String email; + + @Schema(description = "手机号", example = "13800138001") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + public String getNickname() { return nickname; } + public void setNickname(String nickname) { this.nickname = nickname; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } +} diff --git a/gym-manage-api/pom.xml b/gym-manage-api/pom.xml index 385258e..9df6ca5 100644 --- a/gym-manage-api/pom.xml +++ b/gym-manage-api/pom.xml @@ -48,6 +48,7 @@ gym-dataCount gym-auth gym-payment + gym-coach diff --git a/gym-manage-coach-uniapp/.gitignore b/gym-manage-coach-uniapp/.gitignore new file mode 100644 index 0000000..2370852 --- /dev/null +++ b/gym-manage-coach-uniapp/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +unpackage/ +.hbuilderx/ +.DS_Store diff --git a/gym-manage-coach-uniapp/App.vue b/gym-manage-coach-uniapp/App.vue new file mode 100644 index 0000000..64dae1b --- /dev/null +++ b/gym-manage-coach-uniapp/App.vue @@ -0,0 +1,25 @@ + + + diff --git a/gym-manage-coach-uniapp/api/coach.js b/gym-manage-coach-uniapp/api/coach.js new file mode 100644 index 0000000..aea4423 --- /dev/null +++ b/gym-manage-coach-uniapp/api/coach.js @@ -0,0 +1,52 @@ +const http = require('../utils/request') + +/** + * 教练登录(账号密码) + */ +function login(username, password) { + return http.post('/auth/login', { username, password }) +} + +/** + * 获取教练负责的团课列表 + */ +function getCoachCourses(coachId) { + return http.get('/coach/' + coachId + '/courses') +} + +/** + * 教练手动开课 + */ +function startCourse(courseId) { + return http.post('/coach/courses/' + courseId + '/start') +} + +/** + * 教练手动结课 + */ +function endCourse(courseId) { + return http.post('/coach/courses/' + courseId + '/end') +} + +/** + * 获取团课详情 + */ +function getCourseDetail(courseId) { + return http.get('/groupCourse/' + courseId + '/detail') +} + +/** + * 获取教练违规记录 + */ +function getCoachViolations(coachId) { + return http.get('/coach/' + coachId + '/violations') +} + +module.exports = { + login, + getCoachCourses, + startCourse, + endCourse, + getCourseDetail, + getCoachViolations +} diff --git a/gym-manage-coach-uniapp/index.html b/gym-manage-coach-uniapp/index.html new file mode 100644 index 0000000..b5d330d --- /dev/null +++ b/gym-manage-coach-uniapp/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + diff --git a/gym-manage-coach-uniapp/main.js b/gym-manage-coach-uniapp/main.js new file mode 100644 index 0000000..20ad8f1 --- /dev/null +++ b/gym-manage-coach-uniapp/main.js @@ -0,0 +1,22 @@ +import App from './App' + +// #ifndef VUE3 +import Vue from 'vue' +import './uni.promisify.adaptor' +Vue.config.productionTip = false +App.mpType = 'app' +const app = new Vue({ + ...App +}) +app.$mount() +// #endif + +// #ifdef VUE3 +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} +// #endif diff --git a/gym-manage-coach-uniapp/manifest.json b/gym-manage-coach-uniapp/manifest.json new file mode 100644 index 0000000..d3a2b2a --- /dev/null +++ b/gym-manage-coach-uniapp/manifest.json @@ -0,0 +1,64 @@ +{ + "name" : "gym-manage-coach-uniapp", + "appid" : "", + "description" : "", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + "app-plus" : { + "usingComponents" : true, + "nvueStyleCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + "modules" : {}, + "distribute" : { + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "ios" : {}, + "sdkConfigs" : {} + } + }, + "quickapp" : {}, + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "2" +} diff --git a/gym-manage-coach-uniapp/package-lock.json b/gym-manage-coach-uniapp/package-lock.json new file mode 100644 index 0000000..cb205bb --- /dev/null +++ b/gym-manage-coach-uniapp/package-lock.json @@ -0,0 +1,45 @@ +{ + "name": "gym-manage-coach-uniapp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gym-manage-coach-uniapp", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@dcloudio/uni-ui": "^1.5.12", + "crypto-js": "^4.2.0", + "luch-request": "^3.1.1" + } + }, + "node_modules/@dcloudio/types": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@dcloudio/types/-/types-2.6.12.tgz", + "integrity": "sha512-mrCMwcINy1IFjU9VUqLeWBkj404yWs5paLDttBcA+eqUjanuUQbBcTVPqlrGgkyzLXDcV2oDDZRSNxNpXi4kMQ==", + "license": "Apache-2.0" + }, + "node_modules/@dcloudio/uni-ui": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/@dcloudio/uni-ui/-/uni-ui-1.5.12.tgz", + "integrity": "sha512-mGDl2OZSz7D8xcUAzJegWDHOqB4MEFBSW9Esb/oJiu2/3Gk9+P/Z4bA4JZ9jv9VWBYbMrYwaTfK1Z728kABdYg==", + "license": "Apache-2.0" + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/luch-request": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/luch-request/-/luch-request-3.1.1.tgz", + "integrity": "sha512-p7+mlcEtgRcd0OfXC4XZbyiwSr1XgCeqNT7LlVUjnk7InYl/8d5Rk7BUqAYNA2WRafI1wRIUQWRWZRpeUwWR0w==", + "license": "MIT", + "dependencies": { + "@dcloudio/types": "^2.0.16" + } + } + } +} diff --git a/gym-manage-coach-uniapp/package.json b/gym-manage-coach-uniapp/package.json new file mode 100644 index 0000000..4bef173 --- /dev/null +++ b/gym-manage-coach-uniapp/package.json @@ -0,0 +1,18 @@ +{ + "name": "gym-manage-coach-uniapp", + "version": "1.0.0", + "description": "", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "@dcloudio/uni-ui": "^1.5.12", + "crypto-js": "^4.2.0", + "luch-request": "^3.1.1" + } +} diff --git a/gym-manage-coach-uniapp/pages.json b/gym-manage-coach-uniapp/pages.json new file mode 100644 index 0000000..468189a --- /dev/null +++ b/gym-manage-coach-uniapp/pages.json @@ -0,0 +1,48 @@ +{ + "pages": [ + { + "path": "pages/login/login", + "style": { + "navigationBarTitleText": "教练登录", + "navigationStyle": "custom" + } + }, + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "教练首页", + "navigationStyle": "custom", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/course-list/course-list", + "style": { + "navigationBarTitleText": "我的团课", + "navigationStyle": "custom", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/course-detail/course-detail", + "style": { + "navigationBarTitleText": "课程详情", + "navigationStyle": "custom" + } + }, + { + "path": "pages/profile/profile", + "style": { + "navigationBarTitleText": "个人中心", + "navigationStyle": "custom" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "white", + "navigationBarTitleText": "Novalon教练端", + "navigationBarBackgroundColor": "#1A1A1A", + "backgroundColor": "#F5F7FA" + }, + "uniIdRouter": {} +} diff --git a/gym-manage-coach-uniapp/pages/course-detail/course-detail.vue b/gym-manage-coach-uniapp/pages/course-detail/course-detail.vue new file mode 100644 index 0000000..1a4ae1b --- /dev/null +++ b/gym-manage-coach-uniapp/pages/course-detail/course-detail.vue @@ -0,0 +1,428 @@ + + + + + diff --git a/gym-manage-coach-uniapp/pages/course-list/course-list.vue b/gym-manage-coach-uniapp/pages/course-list/course-list.vue new file mode 100644 index 0000000..c347003 --- /dev/null +++ b/gym-manage-coach-uniapp/pages/course-list/course-list.vue @@ -0,0 +1,292 @@ + + + + + diff --git a/gym-manage-coach-uniapp/pages/index/index.vue b/gym-manage-coach-uniapp/pages/index/index.vue new file mode 100644 index 0000000..b893864 --- /dev/null +++ b/gym-manage-coach-uniapp/pages/index/index.vue @@ -0,0 +1,282 @@ + + + + + diff --git a/gym-manage-coach-uniapp/pages/login/login.vue b/gym-manage-coach-uniapp/pages/login/login.vue new file mode 100644 index 0000000..0e7b9dc --- /dev/null +++ b/gym-manage-coach-uniapp/pages/login/login.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/gym-manage-coach-uniapp/pages/profile/profile.vue b/gym-manage-coach-uniapp/pages/profile/profile.vue new file mode 100644 index 0000000..e21d449 --- /dev/null +++ b/gym-manage-coach-uniapp/pages/profile/profile.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/gym-manage-coach-uniapp/project.config.json b/gym-manage-coach-uniapp/project.config.json new file mode 100644 index 0000000..759ba4e --- /dev/null +++ b/gym-manage-coach-uniapp/project.config.json @@ -0,0 +1,25 @@ +{ + "setting": { + "es6": true, + "postcss": true, + "minified": true, + "uglifyFileName": false, + "enhance": true, + "packNpmRelationList": [], + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "useCompilerPlugins": false, + "minifyWXML": true + }, + "compileType": "miniprogram", + "simulatorPluginLibVersion": {}, + "packOptions": { + "ignore": [], + "include": [] + }, + "appid": "wxe734af0ea3f96437", + "editorSetting": {} +} \ No newline at end of file diff --git a/gym-manage-coach-uniapp/project.private.config.json b/gym-manage-coach-uniapp/project.private.config.json new file mode 100644 index 0000000..214d71a --- /dev/null +++ b/gym-manage-coach-uniapp/project.private.config.json @@ -0,0 +1,14 @@ +{ + "libVersion": "3.17.0", + "projectname": "gym-manage-coach-uniapp", + "setting": { + "urlCheck": true, + "coverView": true, + "lazyloadPlaceholderEnable": false, + "skylineRenderEnable": false, + "preloadBackgroundData": false, + "autoAudits": false, + "showShadowRootInWxmlPanel": true, + "compileHotReLoad": true + } +} \ No newline at end of file diff --git a/gym-manage-coach-uniapp/static/logo.png b/gym-manage-coach-uniapp/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/gym-manage-coach-uniapp/static/logo.png differ diff --git a/gym-manage-coach-uniapp/store/index.js b/gym-manage-coach-uniapp/store/index.js new file mode 100644 index 0000000..f837f10 --- /dev/null +++ b/gym-manage-coach-uniapp/store/index.js @@ -0,0 +1,68 @@ +/** + * 教练端全局状态管理 — 以 uni.Storage 为唯一数据源 + */ +const TOKEN_KEY = 'coach_token' +const COACH_KEY = 'coach_info' + +const store = { + /** 是否已登录 */ + get isLoggedIn() { + try { + return !!uni.getStorageSync(TOKEN_KEY) + } catch (e) { + return false + } + }, + + /** 获取 token */ + getToken() { + try { + return uni.getStorageSync(TOKEN_KEY) || null + } catch (e) { + return null + } + }, + + /** 获取教练信息 */ + getCoachInfo() { + try { + return uni.getStorageSync(COACH_KEY) || null + } catch (e) { + return null + } + }, + + /** 获取教练 ID */ + getCoachId() { + const info = this.getCoachInfo() + return info ? info.coachId : null + }, + + /** 获取教练用户名 */ + getUsername() { + const info = this.getCoachInfo() + return info ? info.username : '' + }, + + /** 设置登录态 */ + setLogin(token, coachInfo) { + try { + uni.setStorageSync(TOKEN_KEY, token) + if (coachInfo) uni.setStorageSync(COACH_KEY, coachInfo) + } catch (e) { + console.error('存储登录态失败:', e) + } + }, + + /** 清除登录态 */ + clearLogin() { + try { + uni.removeStorageSync(TOKEN_KEY) + uni.removeStorageSync(COACH_KEY) + } catch (e) { + console.error('清除登录态失败:', e) + } + } +} + +module.exports = store diff --git a/gym-manage-coach-uniapp/uni.promisify.adaptor.js b/gym-manage-coach-uniapp/uni.promisify.adaptor.js new file mode 100644 index 0000000..5fec4f3 --- /dev/null +++ b/gym-manage-coach-uniapp/uni.promisify.adaptor.js @@ -0,0 +1,13 @@ +uni.addInterceptor({ + returnValue (res) { + if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) { + return res; + } + return new Promise((resolve, reject) => { + res.then((res) => { + if (!res) return resolve(res) + return res[0] ? reject(res[0]) : resolve(res[1]) + }); + }); + }, +}); \ No newline at end of file diff --git a/gym-manage-coach-uniapp/uni.scss b/gym-manage-coach-uniapp/uni.scss new file mode 100644 index 0000000..b9249e9 --- /dev/null +++ b/gym-manage-coach-uniapp/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16px; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/changelog.md b/gym-manage-coach-uniapp/uni_modules/uni-icons/changelog.md new file mode 100644 index 0000000..62e7682 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-icons/changelog.md @@ -0,0 +1,44 @@ +## 2.0.12(2025-08-26) +- 优化 uni-app x 下 size 类型问题 +## 2.0.11(2025-08-18) +- 修复 图标点击事件返回 +## 2.0.9(2024-01-12) +fix: 修复图标大小默认值错误的问题 +## 2.0.8(2023-12-14) +- 修复 项目未使用 ts 情况下,打包报错的bug +## 2.0.7(2023-12-14) +- 修复 size 属性为 string 时,不加单位导致尺寸异常的bug +## 2.0.6(2023-12-11) +- 优化 兼容老版本icon类型,如 top ,bottom 等 +## 2.0.5(2023-12-11) +- 优化 兼容老版本icon类型,如 top ,bottom 等 +## 2.0.4(2023-12-06) +- 优化 uni-app x 下示例项目图标排序 +## 2.0.3(2023-12-06) +- 修复 nvue下引入组件报错的bug +## 2.0.2(2023-12-05) +-优化 size 属性支持单位 +## 2.0.1(2023-12-05) +- 新增 uni-app x 支持定义图标 +## 1.3.5(2022-01-24) +- 优化 size 属性可以传入不带单位的字符串数值 +## 1.3.4(2022-01-24) +- 优化 size 支持其他单位 +## 1.3.3(2022-01-17) +- 修复 nvue 有些图标不显示的bug,兼容老版本图标 +## 1.3.2(2021-12-01) +- 优化 示例可复制图标名称 +## 1.3.1(2021-11-23) +- 优化 兼容旧组件 type 值 +## 1.3.0(2021-11-19) +- 新增 更多图标 +- 优化 自定义图标使用方式 +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons) +## 1.1.7(2021-11-08) +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.5(2021-05-12) +- 新增 组件示例地址 +## 1.1.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue new file mode 100644 index 0000000..53eb2ea --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue @@ -0,0 +1,91 @@ + + + + + diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue new file mode 100644 index 0000000..1bd3d5e --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css new file mode 100644 index 0000000..0a6b6fe --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css @@ -0,0 +1,664 @@ + +.uniui-cart-filled:before { + content: "\e6d0"; +} + +.uniui-gift-filled:before { + content: "\e6c4"; +} + +.uniui-color:before { + content: "\e6cf"; +} + +.uniui-wallet:before { + content: "\e6b1"; +} + +.uniui-settings-filled:before { + content: "\e6ce"; +} + +.uniui-auth-filled:before { + content: "\e6cc"; +} + +.uniui-shop-filled:before { + content: "\e6cd"; +} + +.uniui-staff-filled:before { + content: "\e6cb"; +} + +.uniui-vip-filled:before { + content: "\e6c6"; +} + +.uniui-plus-filled:before { + content: "\e6c7"; +} + +.uniui-folder-add-filled:before { + content: "\e6c8"; +} + +.uniui-color-filled:before { + content: "\e6c9"; +} + +.uniui-tune-filled:before { + content: "\e6ca"; +} + +.uniui-calendar-filled:before { + content: "\e6c0"; +} + +.uniui-notification-filled:before { + content: "\e6c1"; +} + +.uniui-wallet-filled:before { + content: "\e6c2"; +} + +.uniui-medal-filled:before { + content: "\e6c3"; +} + +.uniui-fire-filled:before { + content: "\e6c5"; +} + +.uniui-refreshempty:before { + content: "\e6bf"; +} + +.uniui-location-filled:before { + content: "\e6af"; +} + +.uniui-person-filled:before { + content: "\e69d"; +} + +.uniui-personadd-filled:before { + content: "\e698"; +} + +.uniui-arrowthinleft:before { + content: "\e6d2"; +} + +.uniui-arrowthinup:before { + content: "\e6d3"; +} + +.uniui-arrowthindown:before { + content: "\e6d4"; +} + +.uniui-back:before { + content: "\e6b9"; +} + +.uniui-forward:before { + content: "\e6ba"; +} + +.uniui-arrow-right:before { + content: "\e6bb"; +} + +.uniui-arrow-left:before { + content: "\e6bc"; +} + +.uniui-arrow-up:before { + content: "\e6bd"; +} + +.uniui-arrow-down:before { + content: "\e6be"; +} + +.uniui-arrowthinright:before { + content: "\e6d1"; +} + +.uniui-down:before { + content: "\e6b8"; +} + +.uniui-bottom:before { + content: "\e6b8"; +} + +.uniui-arrowright:before { + content: "\e6d5"; +} + +.uniui-right:before { + content: "\e6b5"; +} + +.uniui-up:before { + content: "\e6b6"; +} + +.uniui-top:before { + content: "\e6b6"; +} + +.uniui-left:before { + content: "\e6b7"; +} + +.uniui-arrowup:before { + content: "\e6d6"; +} + +.uniui-eye:before { + content: "\e651"; +} + +.uniui-eye-filled:before { + content: "\e66a"; +} + +.uniui-eye-slash:before { + content: "\e6b3"; +} + +.uniui-eye-slash-filled:before { + content: "\e6b4"; +} + +.uniui-info-filled:before { + content: "\e649"; +} + +.uniui-reload:before { + content: "\e6b2"; +} + +.uniui-micoff-filled:before { + content: "\e6b0"; +} + +.uniui-map-pin-ellipse:before { + content: "\e6ac"; +} + +.uniui-map-pin:before { + content: "\e6ad"; +} + +.uniui-location:before { + content: "\e6ae"; +} + +.uniui-starhalf:before { + content: "\e683"; +} + +.uniui-star:before { + content: "\e688"; +} + +.uniui-star-filled:before { + content: "\e68f"; +} + +.uniui-calendar:before { + content: "\e6a0"; +} + +.uniui-fire:before { + content: "\e6a1"; +} + +.uniui-medal:before { + content: "\e6a2"; +} + +.uniui-font:before { + content: "\e6a3"; +} + +.uniui-gift:before { + content: "\e6a4"; +} + +.uniui-link:before { + content: "\e6a5"; +} + +.uniui-notification:before { + content: "\e6a6"; +} + +.uniui-staff:before { + content: "\e6a7"; +} + +.uniui-vip:before { + content: "\e6a8"; +} + +.uniui-folder-add:before { + content: "\e6a9"; +} + +.uniui-tune:before { + content: "\e6aa"; +} + +.uniui-auth:before { + content: "\e6ab"; +} + +.uniui-person:before { + content: "\e699"; +} + +.uniui-email-filled:before { + content: "\e69a"; +} + +.uniui-phone-filled:before { + content: "\e69b"; +} + +.uniui-phone:before { + content: "\e69c"; +} + +.uniui-email:before { + content: "\e69e"; +} + +.uniui-personadd:before { + content: "\e69f"; +} + +.uniui-chatboxes-filled:before { + content: "\e692"; +} + +.uniui-contact:before { + content: "\e693"; +} + +.uniui-chatbubble-filled:before { + content: "\e694"; +} + +.uniui-contact-filled:before { + content: "\e695"; +} + +.uniui-chatboxes:before { + content: "\e696"; +} + +.uniui-chatbubble:before { + content: "\e697"; +} + +.uniui-upload-filled:before { + content: "\e68e"; +} + +.uniui-upload:before { + content: "\e690"; +} + +.uniui-weixin:before { + content: "\e691"; +} + +.uniui-compose:before { + content: "\e67f"; +} + +.uniui-qq:before { + content: "\e680"; +} + +.uniui-download-filled:before { + content: "\e681"; +} + +.uniui-pyq:before { + content: "\e682"; +} + +.uniui-sound:before { + content: "\e684"; +} + +.uniui-trash-filled:before { + content: "\e685"; +} + +.uniui-sound-filled:before { + content: "\e686"; +} + +.uniui-trash:before { + content: "\e687"; +} + +.uniui-videocam-filled:before { + content: "\e689"; +} + +.uniui-spinner-cycle:before { + content: "\e68a"; +} + +.uniui-weibo:before { + content: "\e68b"; +} + +.uniui-videocam:before { + content: "\e68c"; +} + +.uniui-download:before { + content: "\e68d"; +} + +.uniui-help:before { + content: "\e679"; +} + +.uniui-navigate-filled:before { + content: "\e67a"; +} + +.uniui-plusempty:before { + content: "\e67b"; +} + +.uniui-smallcircle:before { + content: "\e67c"; +} + +.uniui-minus-filled:before { + content: "\e67d"; +} + +.uniui-micoff:before { + content: "\e67e"; +} + +.uniui-closeempty:before { + content: "\e66c"; +} + +.uniui-clear:before { + content: "\e66d"; +} + +.uniui-navigate:before { + content: "\e66e"; +} + +.uniui-minus:before { + content: "\e66f"; +} + +.uniui-image:before { + content: "\e670"; +} + +.uniui-mic:before { + content: "\e671"; +} + +.uniui-paperplane:before { + content: "\e672"; +} + +.uniui-close:before { + content: "\e673"; +} + +.uniui-help-filled:before { + content: "\e674"; +} + +.uniui-paperplane-filled:before { + content: "\e675"; +} + +.uniui-plus:before { + content: "\e676"; +} + +.uniui-mic-filled:before { + content: "\e677"; +} + +.uniui-image-filled:before { + content: "\e678"; +} + +.uniui-locked-filled:before { + content: "\e668"; +} + +.uniui-info:before { + content: "\e669"; +} + +.uniui-locked:before { + content: "\e66b"; +} + +.uniui-camera-filled:before { + content: "\e658"; +} + +.uniui-chat-filled:before { + content: "\e659"; +} + +.uniui-camera:before { + content: "\e65a"; +} + +.uniui-circle:before { + content: "\e65b"; +} + +.uniui-checkmarkempty:before { + content: "\e65c"; +} + +.uniui-chat:before { + content: "\e65d"; +} + +.uniui-circle-filled:before { + content: "\e65e"; +} + +.uniui-flag:before { + content: "\e65f"; +} + +.uniui-flag-filled:before { + content: "\e660"; +} + +.uniui-gear-filled:before { + content: "\e661"; +} + +.uniui-home:before { + content: "\e662"; +} + +.uniui-home-filled:before { + content: "\e663"; +} + +.uniui-gear:before { + content: "\e664"; +} + +.uniui-smallcircle-filled:before { + content: "\e665"; +} + +.uniui-map-filled:before { + content: "\e666"; +} + +.uniui-map:before { + content: "\e667"; +} + +.uniui-refresh-filled:before { + content: "\e656"; +} + +.uniui-refresh:before { + content: "\e657"; +} + +.uniui-cloud-upload:before { + content: "\e645"; +} + +.uniui-cloud-download-filled:before { + content: "\e646"; +} + +.uniui-cloud-download:before { + content: "\e647"; +} + +.uniui-cloud-upload-filled:before { + content: "\e648"; +} + +.uniui-redo:before { + content: "\e64a"; +} + +.uniui-images-filled:before { + content: "\e64b"; +} + +.uniui-undo-filled:before { + content: "\e64c"; +} + +.uniui-more:before { + content: "\e64d"; +} + +.uniui-more-filled:before { + content: "\e64e"; +} + +.uniui-undo:before { + content: "\e64f"; +} + +.uniui-images:before { + content: "\e650"; +} + +.uniui-paperclip:before { + content: "\e652"; +} + +.uniui-settings:before { + content: "\e653"; +} + +.uniui-search:before { + content: "\e654"; +} + +.uniui-redo-filled:before { + content: "\e655"; +} + +.uniui-list:before { + content: "\e644"; +} + +.uniui-mail-open-filled:before { + content: "\e63a"; +} + +.uniui-hand-down-filled:before { + content: "\e63c"; +} + +.uniui-hand-down:before { + content: "\e63d"; +} + +.uniui-hand-up-filled:before { + content: "\e63e"; +} + +.uniui-hand-up:before { + content: "\e63f"; +} + +.uniui-heart-filled:before { + content: "\e641"; +} + +.uniui-mail-open:before { + content: "\e643"; +} + +.uniui-heart:before { + content: "\e639"; +} + +.uniui-loop:before { + content: "\e633"; +} + +.uniui-pulldown:before { + content: "\e632"; +} + +.uniui-scan:before { + content: "\e62a"; +} + +.uniui-bars:before { + content: "\e627"; +} + +.uniui-checkbox:before { + content: "\e62b"; +} + +.uniui-checkbox-filled:before { + content: "\e62c"; +} + +.uniui-shop:before { + content: "\e62f"; +} + +.uniui-headphones:before { + content: "\e630"; +} + +.uniui-cart:before { + content: "\e631"; +} diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf new file mode 100644 index 0000000..14696d0 Binary files /dev/null and b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf differ diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts new file mode 100644 index 0000000..98e93aa --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts @@ -0,0 +1,664 @@ + +export type IconsData = { + id : string + name : string + font_family : string + css_prefix_text : string + description : string + glyphs : Array +} + +export type IconsDataItem = { + font_class : string + unicode : string +} + + +export const fontData = [ + { + "font_class": "arrow-down", + "unicode": "\ue6be" + }, + { + "font_class": "arrow-left", + "unicode": "\ue6bc" + }, + { + "font_class": "arrow-right", + "unicode": "\ue6bb" + }, + { + "font_class": "arrow-up", + "unicode": "\ue6bd" + }, + { + "font_class": "auth", + "unicode": "\ue6ab" + }, + { + "font_class": "auth-filled", + "unicode": "\ue6cc" + }, + { + "font_class": "back", + "unicode": "\ue6b9" + }, + { + "font_class": "bars", + "unicode": "\ue627" + }, + { + "font_class": "calendar", + "unicode": "\ue6a0" + }, + { + "font_class": "calendar-filled", + "unicode": "\ue6c0" + }, + { + "font_class": "camera", + "unicode": "\ue65a" + }, + { + "font_class": "camera-filled", + "unicode": "\ue658" + }, + { + "font_class": "cart", + "unicode": "\ue631" + }, + { + "font_class": "cart-filled", + "unicode": "\ue6d0" + }, + { + "font_class": "chat", + "unicode": "\ue65d" + }, + { + "font_class": "chat-filled", + "unicode": "\ue659" + }, + { + "font_class": "chatboxes", + "unicode": "\ue696" + }, + { + "font_class": "chatboxes-filled", + "unicode": "\ue692" + }, + { + "font_class": "chatbubble", + "unicode": "\ue697" + }, + { + "font_class": "chatbubble-filled", + "unicode": "\ue694" + }, + { + "font_class": "checkbox", + "unicode": "\ue62b" + }, + { + "font_class": "checkbox-filled", + "unicode": "\ue62c" + }, + { + "font_class": "checkmarkempty", + "unicode": "\ue65c" + }, + { + "font_class": "circle", + "unicode": "\ue65b" + }, + { + "font_class": "circle-filled", + "unicode": "\ue65e" + }, + { + "font_class": "clear", + "unicode": "\ue66d" + }, + { + "font_class": "close", + "unicode": "\ue673" + }, + { + "font_class": "closeempty", + "unicode": "\ue66c" + }, + { + "font_class": "cloud-download", + "unicode": "\ue647" + }, + { + "font_class": "cloud-download-filled", + "unicode": "\ue646" + }, + { + "font_class": "cloud-upload", + "unicode": "\ue645" + }, + { + "font_class": "cloud-upload-filled", + "unicode": "\ue648" + }, + { + "font_class": "color", + "unicode": "\ue6cf" + }, + { + "font_class": "color-filled", + "unicode": "\ue6c9" + }, + { + "font_class": "compose", + "unicode": "\ue67f" + }, + { + "font_class": "contact", + "unicode": "\ue693" + }, + { + "font_class": "contact-filled", + "unicode": "\ue695" + }, + { + "font_class": "down", + "unicode": "\ue6b8" + }, + { + "font_class": "bottom", + "unicode": "\ue6b8" + }, + { + "font_class": "download", + "unicode": "\ue68d" + }, + { + "font_class": "download-filled", + "unicode": "\ue681" + }, + { + "font_class": "email", + "unicode": "\ue69e" + }, + { + "font_class": "email-filled", + "unicode": "\ue69a" + }, + { + "font_class": "eye", + "unicode": "\ue651" + }, + { + "font_class": "eye-filled", + "unicode": "\ue66a" + }, + { + "font_class": "eye-slash", + "unicode": "\ue6b3" + }, + { + "font_class": "eye-slash-filled", + "unicode": "\ue6b4" + }, + { + "font_class": "fire", + "unicode": "\ue6a1" + }, + { + "font_class": "fire-filled", + "unicode": "\ue6c5" + }, + { + "font_class": "flag", + "unicode": "\ue65f" + }, + { + "font_class": "flag-filled", + "unicode": "\ue660" + }, + { + "font_class": "folder-add", + "unicode": "\ue6a9" + }, + { + "font_class": "folder-add-filled", + "unicode": "\ue6c8" + }, + { + "font_class": "font", + "unicode": "\ue6a3" + }, + { + "font_class": "forward", + "unicode": "\ue6ba" + }, + { + "font_class": "gear", + "unicode": "\ue664" + }, + { + "font_class": "gear-filled", + "unicode": "\ue661" + }, + { + "font_class": "gift", + "unicode": "\ue6a4" + }, + { + "font_class": "gift-filled", + "unicode": "\ue6c4" + }, + { + "font_class": "hand-down", + "unicode": "\ue63d" + }, + { + "font_class": "hand-down-filled", + "unicode": "\ue63c" + }, + { + "font_class": "hand-up", + "unicode": "\ue63f" + }, + { + "font_class": "hand-up-filled", + "unicode": "\ue63e" + }, + { + "font_class": "headphones", + "unicode": "\ue630" + }, + { + "font_class": "heart", + "unicode": "\ue639" + }, + { + "font_class": "heart-filled", + "unicode": "\ue641" + }, + { + "font_class": "help", + "unicode": "\ue679" + }, + { + "font_class": "help-filled", + "unicode": "\ue674" + }, + { + "font_class": "home", + "unicode": "\ue662" + }, + { + "font_class": "home-filled", + "unicode": "\ue663" + }, + { + "font_class": "image", + "unicode": "\ue670" + }, + { + "font_class": "image-filled", + "unicode": "\ue678" + }, + { + "font_class": "images", + "unicode": "\ue650" + }, + { + "font_class": "images-filled", + "unicode": "\ue64b" + }, + { + "font_class": "info", + "unicode": "\ue669" + }, + { + "font_class": "info-filled", + "unicode": "\ue649" + }, + { + "font_class": "left", + "unicode": "\ue6b7" + }, + { + "font_class": "link", + "unicode": "\ue6a5" + }, + { + "font_class": "list", + "unicode": "\ue644" + }, + { + "font_class": "location", + "unicode": "\ue6ae" + }, + { + "font_class": "location-filled", + "unicode": "\ue6af" + }, + { + "font_class": "locked", + "unicode": "\ue66b" + }, + { + "font_class": "locked-filled", + "unicode": "\ue668" + }, + { + "font_class": "loop", + "unicode": "\ue633" + }, + { + "font_class": "mail-open", + "unicode": "\ue643" + }, + { + "font_class": "mail-open-filled", + "unicode": "\ue63a" + }, + { + "font_class": "map", + "unicode": "\ue667" + }, + { + "font_class": "map-filled", + "unicode": "\ue666" + }, + { + "font_class": "map-pin", + "unicode": "\ue6ad" + }, + { + "font_class": "map-pin-ellipse", + "unicode": "\ue6ac" + }, + { + "font_class": "medal", + "unicode": "\ue6a2" + }, + { + "font_class": "medal-filled", + "unicode": "\ue6c3" + }, + { + "font_class": "mic", + "unicode": "\ue671" + }, + { + "font_class": "mic-filled", + "unicode": "\ue677" + }, + { + "font_class": "micoff", + "unicode": "\ue67e" + }, + { + "font_class": "micoff-filled", + "unicode": "\ue6b0" + }, + { + "font_class": "minus", + "unicode": "\ue66f" + }, + { + "font_class": "minus-filled", + "unicode": "\ue67d" + }, + { + "font_class": "more", + "unicode": "\ue64d" + }, + { + "font_class": "more-filled", + "unicode": "\ue64e" + }, + { + "font_class": "navigate", + "unicode": "\ue66e" + }, + { + "font_class": "navigate-filled", + "unicode": "\ue67a" + }, + { + "font_class": "notification", + "unicode": "\ue6a6" + }, + { + "font_class": "notification-filled", + "unicode": "\ue6c1" + }, + { + "font_class": "paperclip", + "unicode": "\ue652" + }, + { + "font_class": "paperplane", + "unicode": "\ue672" + }, + { + "font_class": "paperplane-filled", + "unicode": "\ue675" + }, + { + "font_class": "person", + "unicode": "\ue699" + }, + { + "font_class": "person-filled", + "unicode": "\ue69d" + }, + { + "font_class": "personadd", + "unicode": "\ue69f" + }, + { + "font_class": "personadd-filled", + "unicode": "\ue698" + }, + { + "font_class": "personadd-filled-copy", + "unicode": "\ue6d1" + }, + { + "font_class": "phone", + "unicode": "\ue69c" + }, + { + "font_class": "phone-filled", + "unicode": "\ue69b" + }, + { + "font_class": "plus", + "unicode": "\ue676" + }, + { + "font_class": "plus-filled", + "unicode": "\ue6c7" + }, + { + "font_class": "plusempty", + "unicode": "\ue67b" + }, + { + "font_class": "pulldown", + "unicode": "\ue632" + }, + { + "font_class": "pyq", + "unicode": "\ue682" + }, + { + "font_class": "qq", + "unicode": "\ue680" + }, + { + "font_class": "redo", + "unicode": "\ue64a" + }, + { + "font_class": "redo-filled", + "unicode": "\ue655" + }, + { + "font_class": "refresh", + "unicode": "\ue657" + }, + { + "font_class": "refresh-filled", + "unicode": "\ue656" + }, + { + "font_class": "refreshempty", + "unicode": "\ue6bf" + }, + { + "font_class": "reload", + "unicode": "\ue6b2" + }, + { + "font_class": "right", + "unicode": "\ue6b5" + }, + { + "font_class": "scan", + "unicode": "\ue62a" + }, + { + "font_class": "search", + "unicode": "\ue654" + }, + { + "font_class": "settings", + "unicode": "\ue653" + }, + { + "font_class": "settings-filled", + "unicode": "\ue6ce" + }, + { + "font_class": "shop", + "unicode": "\ue62f" + }, + { + "font_class": "shop-filled", + "unicode": "\ue6cd" + }, + { + "font_class": "smallcircle", + "unicode": "\ue67c" + }, + { + "font_class": "smallcircle-filled", + "unicode": "\ue665" + }, + { + "font_class": "sound", + "unicode": "\ue684" + }, + { + "font_class": "sound-filled", + "unicode": "\ue686" + }, + { + "font_class": "spinner-cycle", + "unicode": "\ue68a" + }, + { + "font_class": "staff", + "unicode": "\ue6a7" + }, + { + "font_class": "staff-filled", + "unicode": "\ue6cb" + }, + { + "font_class": "star", + "unicode": "\ue688" + }, + { + "font_class": "star-filled", + "unicode": "\ue68f" + }, + { + "font_class": "starhalf", + "unicode": "\ue683" + }, + { + "font_class": "trash", + "unicode": "\ue687" + }, + { + "font_class": "trash-filled", + "unicode": "\ue685" + }, + { + "font_class": "tune", + "unicode": "\ue6aa" + }, + { + "font_class": "tune-filled", + "unicode": "\ue6ca" + }, + { + "font_class": "undo", + "unicode": "\ue64f" + }, + { + "font_class": "undo-filled", + "unicode": "\ue64c" + }, + { + "font_class": "up", + "unicode": "\ue6b6" + }, + { + "font_class": "top", + "unicode": "\ue6b6" + }, + { + "font_class": "upload", + "unicode": "\ue690" + }, + { + "font_class": "upload-filled", + "unicode": "\ue68e" + }, + { + "font_class": "videocam", + "unicode": "\ue68c" + }, + { + "font_class": "videocam-filled", + "unicode": "\ue689" + }, + { + "font_class": "vip", + "unicode": "\ue6a8" + }, + { + "font_class": "vip-filled", + "unicode": "\ue6c6" + }, + { + "font_class": "wallet", + "unicode": "\ue6b1" + }, + { + "font_class": "wallet-filled", + "unicode": "\ue6c2" + }, + { + "font_class": "weibo", + "unicode": "\ue68b" + }, + { + "font_class": "weixin", + "unicode": "\ue691" + } +] as IconsDataItem[] + +// export const fontData = JSON.parse(fontDataJson) diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js new file mode 100644 index 0000000..1cd11e1 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js @@ -0,0 +1,649 @@ + +export const fontData = [ + { + "font_class": "arrow-down", + "unicode": "\ue6be" + }, + { + "font_class": "arrow-left", + "unicode": "\ue6bc" + }, + { + "font_class": "arrow-right", + "unicode": "\ue6bb" + }, + { + "font_class": "arrow-up", + "unicode": "\ue6bd" + }, + { + "font_class": "auth", + "unicode": "\ue6ab" + }, + { + "font_class": "auth-filled", + "unicode": "\ue6cc" + }, + { + "font_class": "back", + "unicode": "\ue6b9" + }, + { + "font_class": "bars", + "unicode": "\ue627" + }, + { + "font_class": "calendar", + "unicode": "\ue6a0" + }, + { + "font_class": "calendar-filled", + "unicode": "\ue6c0" + }, + { + "font_class": "camera", + "unicode": "\ue65a" + }, + { + "font_class": "camera-filled", + "unicode": "\ue658" + }, + { + "font_class": "cart", + "unicode": "\ue631" + }, + { + "font_class": "cart-filled", + "unicode": "\ue6d0" + }, + { + "font_class": "chat", + "unicode": "\ue65d" + }, + { + "font_class": "chat-filled", + "unicode": "\ue659" + }, + { + "font_class": "chatboxes", + "unicode": "\ue696" + }, + { + "font_class": "chatboxes-filled", + "unicode": "\ue692" + }, + { + "font_class": "chatbubble", + "unicode": "\ue697" + }, + { + "font_class": "chatbubble-filled", + "unicode": "\ue694" + }, + { + "font_class": "checkbox", + "unicode": "\ue62b" + }, + { + "font_class": "checkbox-filled", + "unicode": "\ue62c" + }, + { + "font_class": "checkmarkempty", + "unicode": "\ue65c" + }, + { + "font_class": "circle", + "unicode": "\ue65b" + }, + { + "font_class": "circle-filled", + "unicode": "\ue65e" + }, + { + "font_class": "clear", + "unicode": "\ue66d" + }, + { + "font_class": "close", + "unicode": "\ue673" + }, + { + "font_class": "closeempty", + "unicode": "\ue66c" + }, + { + "font_class": "cloud-download", + "unicode": "\ue647" + }, + { + "font_class": "cloud-download-filled", + "unicode": "\ue646" + }, + { + "font_class": "cloud-upload", + "unicode": "\ue645" + }, + { + "font_class": "cloud-upload-filled", + "unicode": "\ue648" + }, + { + "font_class": "color", + "unicode": "\ue6cf" + }, + { + "font_class": "color-filled", + "unicode": "\ue6c9" + }, + { + "font_class": "compose", + "unicode": "\ue67f" + }, + { + "font_class": "contact", + "unicode": "\ue693" + }, + { + "font_class": "contact-filled", + "unicode": "\ue695" + }, + { + "font_class": "down", + "unicode": "\ue6b8" + }, + { + "font_class": "bottom", + "unicode": "\ue6b8" + }, + { + "font_class": "download", + "unicode": "\ue68d" + }, + { + "font_class": "download-filled", + "unicode": "\ue681" + }, + { + "font_class": "email", + "unicode": "\ue69e" + }, + { + "font_class": "email-filled", + "unicode": "\ue69a" + }, + { + "font_class": "eye", + "unicode": "\ue651" + }, + { + "font_class": "eye-filled", + "unicode": "\ue66a" + }, + { + "font_class": "eye-slash", + "unicode": "\ue6b3" + }, + { + "font_class": "eye-slash-filled", + "unicode": "\ue6b4" + }, + { + "font_class": "fire", + "unicode": "\ue6a1" + }, + { + "font_class": "fire-filled", + "unicode": "\ue6c5" + }, + { + "font_class": "flag", + "unicode": "\ue65f" + }, + { + "font_class": "flag-filled", + "unicode": "\ue660" + }, + { + "font_class": "folder-add", + "unicode": "\ue6a9" + }, + { + "font_class": "folder-add-filled", + "unicode": "\ue6c8" + }, + { + "font_class": "font", + "unicode": "\ue6a3" + }, + { + "font_class": "forward", + "unicode": "\ue6ba" + }, + { + "font_class": "gear", + "unicode": "\ue664" + }, + { + "font_class": "gear-filled", + "unicode": "\ue661" + }, + { + "font_class": "gift", + "unicode": "\ue6a4" + }, + { + "font_class": "gift-filled", + "unicode": "\ue6c4" + }, + { + "font_class": "hand-down", + "unicode": "\ue63d" + }, + { + "font_class": "hand-down-filled", + "unicode": "\ue63c" + }, + { + "font_class": "hand-up", + "unicode": "\ue63f" + }, + { + "font_class": "hand-up-filled", + "unicode": "\ue63e" + }, + { + "font_class": "headphones", + "unicode": "\ue630" + }, + { + "font_class": "heart", + "unicode": "\ue639" + }, + { + "font_class": "heart-filled", + "unicode": "\ue641" + }, + { + "font_class": "help", + "unicode": "\ue679" + }, + { + "font_class": "help-filled", + "unicode": "\ue674" + }, + { + "font_class": "home", + "unicode": "\ue662" + }, + { + "font_class": "home-filled", + "unicode": "\ue663" + }, + { + "font_class": "image", + "unicode": "\ue670" + }, + { + "font_class": "image-filled", + "unicode": "\ue678" + }, + { + "font_class": "images", + "unicode": "\ue650" + }, + { + "font_class": "images-filled", + "unicode": "\ue64b" + }, + { + "font_class": "info", + "unicode": "\ue669" + }, + { + "font_class": "info-filled", + "unicode": "\ue649" + }, + { + "font_class": "left", + "unicode": "\ue6b7" + }, + { + "font_class": "link", + "unicode": "\ue6a5" + }, + { + "font_class": "list", + "unicode": "\ue644" + }, + { + "font_class": "location", + "unicode": "\ue6ae" + }, + { + "font_class": "location-filled", + "unicode": "\ue6af" + }, + { + "font_class": "locked", + "unicode": "\ue66b" + }, + { + "font_class": "locked-filled", + "unicode": "\ue668" + }, + { + "font_class": "loop", + "unicode": "\ue633" + }, + { + "font_class": "mail-open", + "unicode": "\ue643" + }, + { + "font_class": "mail-open-filled", + "unicode": "\ue63a" + }, + { + "font_class": "map", + "unicode": "\ue667" + }, + { + "font_class": "map-filled", + "unicode": "\ue666" + }, + { + "font_class": "map-pin", + "unicode": "\ue6ad" + }, + { + "font_class": "map-pin-ellipse", + "unicode": "\ue6ac" + }, + { + "font_class": "medal", + "unicode": "\ue6a2" + }, + { + "font_class": "medal-filled", + "unicode": "\ue6c3" + }, + { + "font_class": "mic", + "unicode": "\ue671" + }, + { + "font_class": "mic-filled", + "unicode": "\ue677" + }, + { + "font_class": "micoff", + "unicode": "\ue67e" + }, + { + "font_class": "micoff-filled", + "unicode": "\ue6b0" + }, + { + "font_class": "minus", + "unicode": "\ue66f" + }, + { + "font_class": "minus-filled", + "unicode": "\ue67d" + }, + { + "font_class": "more", + "unicode": "\ue64d" + }, + { + "font_class": "more-filled", + "unicode": "\ue64e" + }, + { + "font_class": "navigate", + "unicode": "\ue66e" + }, + { + "font_class": "navigate-filled", + "unicode": "\ue67a" + }, + { + "font_class": "notification", + "unicode": "\ue6a6" + }, + { + "font_class": "notification-filled", + "unicode": "\ue6c1" + }, + { + "font_class": "paperclip", + "unicode": "\ue652" + }, + { + "font_class": "paperplane", + "unicode": "\ue672" + }, + { + "font_class": "paperplane-filled", + "unicode": "\ue675" + }, + { + "font_class": "person", + "unicode": "\ue699" + }, + { + "font_class": "person-filled", + "unicode": "\ue69d" + }, + { + "font_class": "personadd", + "unicode": "\ue69f" + }, + { + "font_class": "personadd-filled", + "unicode": "\ue698" + }, + { + "font_class": "personadd-filled-copy", + "unicode": "\ue6d1" + }, + { + "font_class": "phone", + "unicode": "\ue69c" + }, + { + "font_class": "phone-filled", + "unicode": "\ue69b" + }, + { + "font_class": "plus", + "unicode": "\ue676" + }, + { + "font_class": "plus-filled", + "unicode": "\ue6c7" + }, + { + "font_class": "plusempty", + "unicode": "\ue67b" + }, + { + "font_class": "pulldown", + "unicode": "\ue632" + }, + { + "font_class": "pyq", + "unicode": "\ue682" + }, + { + "font_class": "qq", + "unicode": "\ue680" + }, + { + "font_class": "redo", + "unicode": "\ue64a" + }, + { + "font_class": "redo-filled", + "unicode": "\ue655" + }, + { + "font_class": "refresh", + "unicode": "\ue657" + }, + { + "font_class": "refresh-filled", + "unicode": "\ue656" + }, + { + "font_class": "refreshempty", + "unicode": "\ue6bf" + }, + { + "font_class": "reload", + "unicode": "\ue6b2" + }, + { + "font_class": "right", + "unicode": "\ue6b5" + }, + { + "font_class": "scan", + "unicode": "\ue62a" + }, + { + "font_class": "search", + "unicode": "\ue654" + }, + { + "font_class": "settings", + "unicode": "\ue653" + }, + { + "font_class": "settings-filled", + "unicode": "\ue6ce" + }, + { + "font_class": "shop", + "unicode": "\ue62f" + }, + { + "font_class": "shop-filled", + "unicode": "\ue6cd" + }, + { + "font_class": "smallcircle", + "unicode": "\ue67c" + }, + { + "font_class": "smallcircle-filled", + "unicode": "\ue665" + }, + { + "font_class": "sound", + "unicode": "\ue684" + }, + { + "font_class": "sound-filled", + "unicode": "\ue686" + }, + { + "font_class": "spinner-cycle", + "unicode": "\ue68a" + }, + { + "font_class": "staff", + "unicode": "\ue6a7" + }, + { + "font_class": "staff-filled", + "unicode": "\ue6cb" + }, + { + "font_class": "star", + "unicode": "\ue688" + }, + { + "font_class": "star-filled", + "unicode": "\ue68f" + }, + { + "font_class": "starhalf", + "unicode": "\ue683" + }, + { + "font_class": "trash", + "unicode": "\ue687" + }, + { + "font_class": "trash-filled", + "unicode": "\ue685" + }, + { + "font_class": "tune", + "unicode": "\ue6aa" + }, + { + "font_class": "tune-filled", + "unicode": "\ue6ca" + }, + { + "font_class": "undo", + "unicode": "\ue64f" + }, + { + "font_class": "undo-filled", + "unicode": "\ue64c" + }, + { + "font_class": "up", + "unicode": "\ue6b6" + }, + { + "font_class": "top", + "unicode": "\ue6b6" + }, + { + "font_class": "upload", + "unicode": "\ue690" + }, + { + "font_class": "upload-filled", + "unicode": "\ue68e" + }, + { + "font_class": "videocam", + "unicode": "\ue68c" + }, + { + "font_class": "videocam-filled", + "unicode": "\ue689" + }, + { + "font_class": "vip", + "unicode": "\ue6a8" + }, + { + "font_class": "vip-filled", + "unicode": "\ue6c6" + }, + { + "font_class": "wallet", + "unicode": "\ue6b1" + }, + { + "font_class": "wallet-filled", + "unicode": "\ue6c2" + }, + { + "font_class": "weibo", + "unicode": "\ue68b" + }, + { + "font_class": "weixin", + "unicode": "\ue691" + } +] + +// export const fontData = JSON.parse(fontDataJson) diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/package.json b/gym-manage-coach-uniapp/uni_modules/uni-icons/package.json new file mode 100644 index 0000000..60e45f0 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-icons/package.json @@ -0,0 +1,111 @@ +{ + "id": "uni-icons", + "displayName": "uni-icons 图标", + "version": "2.0.12", + "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。", + "keywords": [ + "uni-ui", + "uniui", + "icon", + "图标" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.2.14", + "uni-app": "^4.08", + "uni-app-x": "^4.61" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x" + }, + "uni_modules": { + "dependencies": [ + "uni-scss" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app": { + "vue": { + "vue2": "√", + "vue3": "√" + }, + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "vue": "√", + "nvue": "-", + "android": { + "extVersion": "", + "minVersion": "29" + }, + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√", + "alipay": "√", + "toutiao": "√", + "baidu": "√", + "kuaishou": "-", + "jd": "-", + "harmony": "-", + "qq": "√", + "lark": "-" + }, + "quickapp": { + "huawei": "√", + "union": "√" + } + }, + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": { + "extVersion": "", + "minVersion": "29" + }, + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} \ No newline at end of file diff --git a/gym-manage-coach-uniapp/uni_modules/uni-icons/readme.md b/gym-manage-coach-uniapp/uni_modules/uni-icons/readme.md new file mode 100644 index 0000000..86234ba --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-icons/readme.md @@ -0,0 +1,8 @@ +## Icons 图标 +> **组件名:uni-icons** +> 代码块: `uIcons` + +用于展示 icons 图标 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/changelog.md b/gym-manage-coach-uniapp/uni_modules/uni-scss/changelog.md new file mode 100644 index 0000000..b863bb0 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/changelog.md @@ -0,0 +1,8 @@ +## 1.0.3(2022-01-21) +- 优化 组件示例 +## 1.0.2(2021-11-22) +- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题 +## 1.0.1(2021-11-22) +- 修复 vue3中scss语法兼容问题 +## 1.0.0(2021-11-18) +- init diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/index.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/index.scss new file mode 100644 index 0000000..1744a5f --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/index.scss @@ -0,0 +1 @@ +@import './styles/index.scss'; diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/package.json b/gym-manage-coach-uniapp/uni_modules/uni-scss/package.json new file mode 100644 index 0000000..7cc0ccb --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-scss", + "displayName": "uni-scss 辅助样式", + "version": "1.0.3", + "description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。", + "keywords": [ + "uni-scss", + "uni-ui", + "辅助样式" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/readme.md b/gym-manage-coach-uniapp/uni_modules/uni-scss/readme.md new file mode 100644 index 0000000..b7d1c25 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/readme.md @@ -0,0 +1,4 @@ +`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/index.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/index.scss new file mode 100644 index 0000000..ffac4fe --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/index.scss @@ -0,0 +1,7 @@ +@import './setting/_variables.scss'; +@import './setting/_border.scss'; +@import './setting/_color.scss'; +@import './setting/_space.scss'; +@import './setting/_radius.scss'; +@import './setting/_text.scss'; +@import './setting/_styles.scss'; diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_border.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_border.scss new file mode 100644 index 0000000..12a11c3 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_border.scss @@ -0,0 +1,3 @@ +.uni-border { + border: 1px $uni-border-1 solid; +} \ No newline at end of file diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_color.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_color.scss new file mode 100644 index 0000000..1ededd9 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_color.scss @@ -0,0 +1,66 @@ + +// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐 +// @mixin get-styles($k,$c) { +// @if $k == size or $k == weight{ +// font-#{$k}:#{$c} +// }@else{ +// #{$k}:#{$c} +// } +// } +$uni-ui-color:( + // 主色 + primary: $uni-primary, + primary-disable: $uni-primary-disable, + primary-light: $uni-primary-light, + // 辅助色 + success: $uni-success, + success-disable: $uni-success-disable, + success-light: $uni-success-light, + warning: $uni-warning, + warning-disable: $uni-warning-disable, + warning-light: $uni-warning-light, + error: $uni-error, + error-disable: $uni-error-disable, + error-light: $uni-error-light, + info: $uni-info, + info-disable: $uni-info-disable, + info-light: $uni-info-light, + // 中性色 + main-color: $uni-main-color, + base-color: $uni-base-color, + secondary-color: $uni-secondary-color, + extra-color: $uni-extra-color, + // 背景色 + bg-color: $uni-bg-color, + // 边框颜色 + border-1: $uni-border-1, + border-2: $uni-border-2, + border-3: $uni-border-3, + border-4: $uni-border-4, + // 黑色 + black:$uni-black, + // 白色 + white:$uni-white, + // 透明 + transparent:$uni-transparent +) !default; +@each $key, $child in $uni-ui-color { + .uni-#{"" + $key} { + color: $child; + } + .uni-#{"" + $key}-bg { + background-color: $child; + } +} +.uni-shadow-sm { + box-shadow: $uni-shadow-sm; +} +.uni-shadow-base { + box-shadow: $uni-shadow-base; +} +.uni-shadow-lg { + box-shadow: $uni-shadow-lg; +} +.uni-mask { + background-color:$uni-mask; +} diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss new file mode 100644 index 0000000..9a0428b --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss @@ -0,0 +1,55 @@ +@mixin radius($r,$d:null ,$important: false){ + $radius-value:map-get($uni-radius, $r) if($important, !important, null); + // Key exists within the $uni-radius variable + @if (map-has-key($uni-radius, $r) and $d){ + @if $d == t { + border-top-left-radius:$radius-value; + border-top-right-radius:$radius-value; + }@else if $d == r { + border-top-right-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == b { + border-bottom-left-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == l { + border-top-left-radius:$radius-value; + border-bottom-left-radius:$radius-value; + }@else if $d == tl { + border-top-left-radius:$radius-value; + }@else if $d == tr { + border-top-right-radius:$radius-value; + }@else if $d == br { + border-bottom-right-radius:$radius-value; + }@else if $d == bl { + border-bottom-left-radius:$radius-value; + } + }@else{ + border-radius:$radius-value; + } +} + +@each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $key} { + @include radius($key) + } + }@else{ + .uni-radius { + @include radius($key) + } + } +} + +@each $direction in t, r, b, l,tl, tr, br, bl { + @each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $direction}-#{"" + $key} { + @include radius($key,$direction,false) + } + }@else{ + .uni-radius-#{$direction} { + @include radius($key,$direction,false) + } + } + } +} diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_space.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_space.scss new file mode 100644 index 0000000..3c89528 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_space.scss @@ -0,0 +1,56 @@ + +@mixin fn($space,$direction,$size,$n) { + @if $n { + #{$space}-#{$direction}: #{$size*$uni-space-root}px + } @else { + #{$space}-#{$direction}: #{-$size*$uni-space-root}px + } +} +@mixin get-styles($direction,$i,$space,$n){ + @if $direction == t { + @include fn($space, top,$i,$n); + } + @if $direction == r { + @include fn($space, right,$i,$n); + } + @if $direction == b { + @include fn($space, bottom,$i,$n); + } + @if $direction == l { + @include fn($space, left,$i,$n); + } + @if $direction == x { + @include fn($space, left,$i,$n); + @include fn($space, right,$i,$n); + } + @if $direction == y { + @include fn($space, top,$i,$n); + @include fn($space, bottom,$i,$n); + } + @if $direction == a { + @if $n { + #{$space}:#{$i*$uni-space-root}px; + } @else { + #{$space}:#{-$i*$uni-space-root}px; + } + } +} + +@each $orientation in m,p { + $space: margin; + @if $orientation == m { + $space: margin; + } @else { + $space: padding; + } + @for $i from 0 through 16 { + @each $direction in t, r, b, l, x, y, a { + .uni-#{$orientation}#{$direction}-#{$i} { + @include get-styles($direction,$i,$space,true); + } + .uni-#{$orientation}#{$direction}-n#{$i} { + @include get-styles($direction,$i,$space,false); + } + } + } +} \ No newline at end of file diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss new file mode 100644 index 0000000..689afec --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss @@ -0,0 +1,167 @@ +/* #ifndef APP-NVUE */ + +$-color-white:#fff; +$-color-black:#000; +@mixin base-style($color) { + color: #fff; + background-color: $color; + border-color: mix($-color-black, $color, 8%); + &:not([hover-class]):active { + background: mix($-color-black, $color, 10%); + border-color: mix($-color-black, $color, 20%); + color: $-color-white; + outline: none; + } +} +@mixin is-color($color) { + @include base-style($color); + &[loading] { + @include base-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &[loading], + &:not([hover-class]):active { + color: $-color-white; + border-color: mix(darken($color,10%), $-color-white); + background-color: mix($color, $-color-white); + } + } + +} +@mixin base-plain-style($color) { + color:$color; + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 70%); + &:not([hover-class]):active { + background: mix($-color-white, $color, 80%); + color: $color; + outline: none; + border-color: mix($-color-white, $color, 50%); + } +} +@mixin is-plain($color){ + &[plain] { + @include base-plain-style($color); + &[loading] { + @include base-plain-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &:active { + color: mix($-color-white, $color, 40%); + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 80%); + } + } + } +} + + +.uni-btn { + margin: 5px; + color: #393939; + border:1px solid #ccc; + font-size: 16px; + font-weight: 200; + background-color: #F9F9F9; + // TODO 暂时处理边框隐藏一边的问题 + overflow: visible; + &::after{ + border: none; + } + + &:not([type]),&[type=default] { + color: #999; + &[loading] { + background: none; + &::before { + margin-right:5px; + } + } + + + + &[disabled]{ + color: mix($-color-white, #999, 60%); + &, + &[loading], + &:active { + color: mix($-color-white, #999, 60%); + background-color: mix($-color-white,$-color-black , 98%); + border-color: mix($-color-white, #999, 85%); + } + } + + &[plain] { + color: #999; + background: none; + border-color: $uni-border-1; + &:not([hover-class]):active { + background: none; + color: mix($-color-white, $-color-black, 80%); + border-color: mix($-color-white, $-color-black, 90%); + outline: none; + } + &[disabled]{ + &, + &[loading], + &:active { + background: none; + color: mix($-color-white, #999, 60%); + border-color: mix($-color-white, #999, 85%); + } + } + } + } + + &:not([hover-class]):active { + color: mix($-color-white, $-color-black, 50%); + } + + &[size=mini] { + font-size: 16px; + font-weight: 200; + border-radius: 8px; + } + + + + &.uni-btn-small { + font-size: 14px; + } + &.uni-btn-mini { + font-size: 12px; + } + + &.uni-btn-radius { + border-radius: 999px; + } + &[type=primary] { + @include is-color($uni-primary); + @include is-plain($uni-primary) + } + &[type=success] { + @include is-color($uni-success); + @include is-plain($uni-success) + } + &[type=error] { + @include is-color($uni-error); + @include is-plain($uni-error) + } + &[type=warning] { + @include is-color($uni-warning); + @include is-plain($uni-warning) + } + &[type=info] { + @include is-color($uni-info); + @include is-plain($uni-info) + } +} +/* #endif */ diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_text.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_text.scss new file mode 100644 index 0000000..a34d08f --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_text.scss @@ -0,0 +1,24 @@ +@mixin get-styles($k,$c) { + @if $k == size or $k == weight{ + font-#{$k}:#{$c} + }@else{ + #{$k}:#{$c} + } +} + +@each $key, $child in $uni-headings { + /* #ifndef APP-NVUE */ + .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ + /* #ifdef APP-NVUE */ + .container .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ +} diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss new file mode 100644 index 0000000..557d3d7 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss @@ -0,0 +1,146 @@ +// @use "sass:math"; +@import '../tools/functions.scss'; +// 间距基础倍数 +$uni-space-root: 2 !default; +// 边框半径默认值 +$uni-radius-root:5px !default; +$uni-radius: () !default; +// 边框半径断点 +$uni-radius: map-deep-merge( + ( + 0: 0, + // TODO 当前版本暂时不支持 sm 属性 + // 'sm': math.div($uni-radius-root, 2), + null: $uni-radius-root, + 'lg': $uni-radius-root * 2, + 'xl': $uni-radius-root * 6, + 'pill': 9999px, + 'circle': 50% + ), + $uni-radius +); +// 字体家族 +$body-font-family: 'Roboto', sans-serif !default; +// 文本 +$heading-font-family: $body-font-family !default; +$uni-headings: () !default; +$letterSpacing: -0.01562em; +$uni-headings: map-deep-merge( + ( + 'h1': ( + size: 32px, + weight: 300, + line-height: 50px, + // letter-spacing:-0.01562em + ), + 'h2': ( + size: 28px, + weight: 300, + line-height: 40px, + // letter-spacing: -0.00833em + ), + 'h3': ( + size: 24px, + weight: 400, + line-height: 32px, + // letter-spacing: normal + ), + 'h4': ( + size: 20px, + weight: 400, + line-height: 30px, + // letter-spacing: 0.00735em + ), + 'h5': ( + size: 16px, + weight: 400, + line-height: 24px, + // letter-spacing: normal + ), + 'h6': ( + size: 14px, + weight: 500, + line-height: 18px, + // letter-spacing: 0.0125em + ), + 'subtitle': ( + size: 12px, + weight: 400, + line-height: 20px, + // letter-spacing: 0.00937em + ), + 'body': ( + font-size: 14px, + font-weight: 400, + line-height: 22px, + // letter-spacing: 0.03125em + ), + 'caption': ( + 'size': 12px, + 'weight': 400, + 'line-height': 20px, + // 'letter-spacing': 0.03333em, + // 'text-transform': false + ) + ), + $uni-headings +); + + + +// 主色 +$uni-primary: #2979ff !default; +$uni-primary-disable:lighten($uni-primary,20%) !default; +$uni-primary-light: lighten($uni-primary,25%) !default; + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37 !default; +$uni-success-disable:lighten($uni-success,20%) !default; +$uni-success-light: lighten($uni-success,25%) !default; + +$uni-warning: #f3a73f !default; +$uni-warning-disable:lighten($uni-warning,20%) !default; +$uni-warning-light: lighten($uni-warning,25%) !default; + +$uni-error: #e43d33 !default; +$uni-error-disable:lighten($uni-error,20%) !default; +$uni-error-light: lighten($uni-error,25%) !default; + +$uni-info: #8f939c !default; +$uni-info-disable:lighten($uni-info,20%) !default; +$uni-info-light: lighten($uni-info,25%) !default; + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a !default; // 主要文字 +$uni-base-color: #6a6a6a !default; // 常规文字 +$uni-secondary-color: #909399 !default; // 次要文字 +$uni-extra-color: #c7c7c7 !default; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0 !default; +$uni-border-2: #EDEDED !default; +$uni-border-3: #DCDCDC !default; +$uni-border-4: #B9B9B9 !default; + +// 常规色 +$uni-black: #000000 !default; +$uni-white: #ffffff !default; +$uni-transparent: rgba($color: #000000, $alpha: 0) !default; + +// 背景色 +$uni-bg-color: #f7f7f7 !default; + +/* 水平间距 */ +$uni-spacing-sm: 8px !default; +$uni-spacing-base: 15px !default; +$uni-spacing-lg: 30px !default; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default; +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default; +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default; + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4) !default; diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/tools/functions.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/tools/functions.scss new file mode 100644 index 0000000..ac6f63e --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/styles/tools/functions.scss @@ -0,0 +1,19 @@ +// 合并 map +@function map-deep-merge($parent-map, $child-map){ + $result: $parent-map; + @each $key, $child in $child-map { + $parent-has-key: map-has-key($result, $key); + $parent-value: map-get($result, $key); + $parent-type: type-of($parent-value); + $child-type: type-of($child); + $parent-is-map: $parent-type == map; + $child-is-map: $child-type == map; + + @if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){ + $result: map-merge($result, ( $key: $child )); + }@else { + $result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) )); + } + } + @return $result; +}; diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/theme.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/theme.scss new file mode 100644 index 0000000..80ee62f --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/theme.scss @@ -0,0 +1,31 @@ +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; +// 主色 +$uni-primary: #2979ff; +// 辅助色 +$uni-success: #4cd964; +// 警告色 +$uni-warning: #f0ad4e; +// 错误色 +$uni-error: #dd524d; +// 描述色 +$uni-info: #909399; +// 中性色 +$uni-main-color: #303133; +$uni-base-color: #606266; +$uni-secondary-color: #909399; +$uni-extra-color: #C0C4CC; +// 背景色 +$uni-bg-color: #f5f5f5; +// 边框颜色 +$uni-border-1: #DCDFE6; +$uni-border-2: #E4E7ED; +$uni-border-3: #EBEEF5; +$uni-border-4: #F2F6FC; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); diff --git a/gym-manage-coach-uniapp/uni_modules/uni-scss/variables.scss b/gym-manage-coach-uniapp/uni_modules/uni-scss/variables.scss new file mode 100644 index 0000000..1c062d4 --- /dev/null +++ b/gym-manage-coach-uniapp/uni_modules/uni-scss/variables.scss @@ -0,0 +1,62 @@ +@import './styles/setting/_variables.scss'; +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; + +// 主色 +$uni-primary: #2979ff; +$uni-primary-disable:mix(#fff,$uni-primary,50%); +$uni-primary-light: mix(#fff,$uni-primary,80%); + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37; +$uni-success-disable:mix(#fff,$uni-success,50%); +$uni-success-light: mix(#fff,$uni-success,80%); + +$uni-warning: #f3a73f; +$uni-warning-disable:mix(#fff,$uni-warning,50%); +$uni-warning-light: mix(#fff,$uni-warning,80%); + +$uni-error: #e43d33; +$uni-error-disable:mix(#fff,$uni-error,50%); +$uni-error-light: mix(#fff,$uni-error,80%); + +$uni-info: #8f939c; +$uni-info-disable:mix(#fff,$uni-info,50%); +$uni-info-light: mix(#fff,$uni-info,80%); + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a; // 主要文字 +$uni-base-color: #6a6a6a; // 常规文字 +$uni-secondary-color: #909399; // 次要文字 +$uni-extra-color: #c7c7c7; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0; +$uni-border-2: #EDEDED; +$uni-border-3: #DCDCDC; +$uni-border-4: #B9B9B9; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); + +// 背景色 +$uni-bg-color: #f7f7f7; + +/* 水平间距 */ +$uni-spacing-sm: 8px; +$uni-spacing-base: 15px; +$uni-spacing-lg: 30px; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5); +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2); +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4); diff --git a/gym-manage-coach-uniapp/utils/request.js b/gym-manage-coach-uniapp/utils/request.js new file mode 100644 index 0000000..dbbd73b --- /dev/null +++ b/gym-manage-coach-uniapp/utils/request.js @@ -0,0 +1,76 @@ +const luchRequest = require('luch-request') +const Request = luchRequest.default || luchRequest +const { generateSignatureHeaders } = require('./signature') +const store = require('../store/index') + +const BASE_URL = 'http://192.168.101.5:8084/api' + +const http = new Request({ + baseURL: BASE_URL, + timeout: 15000, + header: { + 'Content-Type': 'application/json' + } +}) + +// 请求拦截器 +http.interceptors.request.use( + (config) => { + // 注入 JWT token + const token = store.getToken() + if (token) { + config.header = config.header || {} + config.header.Authorization = 'Bearer ' + token + } + + // 注入签名头 + const method = (config.method || 'GET').toUpperCase() + const url = config.url || '' + const body = config.data + + const signatureHeaders = generateSignatureHeaders(method, url, body) + config.header = config.header || {} + Object.assign(config.header, signatureHeaders) + + return config + }, + (error) => Promise.reject(error) +) + +// 响应拦截器 +http.interceptors.response.use( + (response) => { + if (response.statusCode === 200) { + return response.data + } + return response + }, + (error) => { + if (error.statusCode === 401) { + store.clearLogin() + const pages = getCurrentPages() + const currentPage = pages[pages.length - 1] + if (currentPage && currentPage.route !== 'pages/login/login') { + uni.reLaunch({ url: '/pages/login/login' }) + } + } + return Promise.reject(error) + } +) + +/** + * 将 coverImage 字段值解析为完整图片 URL + */ +function resolveCoverUrl(coverImage) { + if (!coverImage) return '' + if (/^\/api\/files\/\d+\/preview$/.test(coverImage)) { + return BASE_URL + coverImage.substring(4) + } + if (/^\d+$/.test(coverImage)) { + return BASE_URL + '/files/' + coverImage + '/preview' + } + return coverImage +} + +module.exports = http +module.exports.resolveCoverUrl = resolveCoverUrl diff --git a/gym-manage-coach-uniapp/utils/signature.js b/gym-manage-coach-uniapp/utils/signature.js new file mode 100644 index 0000000..fa87beb --- /dev/null +++ b/gym-manage-coach-uniapp/utils/signature.js @@ -0,0 +1,59 @@ +const CryptoJS = require('crypto-js') + +const SIGNATURE_SECRET = 'NovalonManageSystemSecretKey2026' + +/** + * 生成 HMAC-SHA256 签名 + */ +function generateSignature(method, path, query, body, timestamp, nonce) { + const stringToSign = [method, path, query || '', body || '', String(timestamp), nonce].join('\n') + const signature = CryptoJS.HmacSHA256(stringToSign, SIGNATURE_SECRET) + return CryptoJS.enc.Base64.stringify(signature) +} + +/** + * 生成随机 nonce + */ +function generateNonce() { + const timestamp = Date.now().toString(36) + const randomPart = Math.random().toString(36).substring(2, 15) + return timestamp + '-' + randomPart +} + +/** + * 从 URL 解析 path 和 query + */ +function parseUrl(url) { + if (url.startsWith('http://') || url.startsWith('https://')) { + const withoutProtocol = url.substring(url.indexOf('://') + 3) + const pathStart = withoutProtocol.indexOf('/') + if (pathStart === -1) return { path: '/', query: '' } + const pathAndQuery = withoutProtocol.substring(pathStart) + const queryIndex = pathAndQuery.indexOf('?') + if (queryIndex === -1) return { path: pathAndQuery, query: '' } + return { path: pathAndQuery.substring(0, queryIndex), query: pathAndQuery.substring(queryIndex + 1) } + } + const queryIndex = url.indexOf('?') + if (queryIndex === -1) return { path: url, query: '' } + return { path: url.substring(0, queryIndex), query: url.substring(queryIndex + 1) } +} + +/** + * 生成签名请求头 + */ +function generateSignatureHeaders(method, url, body) { + const timestamp = Date.now() + const nonce = generateNonce() + const { path, query } = parseUrl(url) + const bodyStr = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : '' + + const signature = generateSignature(method.toUpperCase(), path, query, bodyStr, timestamp, nonce) + + return { + 'X-Signature': signature, + 'X-Timestamp': String(timestamp), + 'X-Nonce': nonce + } +} + +module.exports = { generateSignatureHeaders } diff --git a/gym-manage-uniapp/.gitignore b/gym-manage-uniapp/.gitignore new file mode 100644 index 0000000..2370852 --- /dev/null +++ b/gym-manage-uniapp/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +unpackage/ +.hbuilderx/ +.DS_Store diff --git a/gym-manage-uniapp/App.vue b/gym-manage-uniapp/App.vue new file mode 100644 index 0000000..589851a --- /dev/null +++ b/gym-manage-uniapp/App.vue @@ -0,0 +1,25 @@ + + + diff --git a/gym-manage-uniapp/api/auth.js b/gym-manage-uniapp/api/auth.js new file mode 100644 index 0000000..7159d87 --- /dev/null +++ b/gym-manage-uniapp/api/auth.js @@ -0,0 +1,12 @@ +const http = require('../utils/request') + +/** + * 微信小程序登录 + * @param {string} code - wx.login() 返回的 code + * @param {string} [phoneCode] - 可选,手机号 code + */ +function miniappLogin(code, phoneCode) { + return http.post('/member/auth/miniapp/login', { code, phoneCode }) +} + +module.exports = { miniappLogin } diff --git a/gym-manage-uniapp/api/banner.js b/gym-manage-uniapp/api/banner.js new file mode 100644 index 0000000..cdaefbe --- /dev/null +++ b/gym-manage-uniapp/api/banner.js @@ -0,0 +1,16 @@ +const http = require('../utils/request') + +/** 获取启用的轮播图列表(按 sortOrder 升序) */ +function getActiveBanners() { + return http.get('/banners/active').then(res => { + console.log('[Banner API] 响应数据:', JSON.stringify(res)) + return res + }).catch(err => { + console.error('[Banner API] 请求失败:', err) + throw err + }) +} + +module.exports = { + getActiveBanners +} diff --git a/gym-manage-uniapp/api/booking.js b/gym-manage-uniapp/api/booking.js new file mode 100644 index 0000000..d8be151 --- /dev/null +++ b/gym-manage-uniapp/api/booking.js @@ -0,0 +1,40 @@ +const http = require('../utils/request') + +/** 预约课程 */ +function bookCourse(params) { + return http.post('/groupCourse/book', params) +} + +/** 取消预约 */ +function cancelBooking(bookingId, memberId) { + return http.post('/groupCourse/booking/' + bookingId + '/cancel', { memberId }) +} + +/** 获取会员的预约列表 */ +function getMemberBookings(memberId) { + return http.get('/groupCourse/bookings/member/' + memberId) +} + +/** 获取课程的预约列表 */ +function getCourseBookings(courseId) { + return http.get('/groupCourse/bookings/course/' + courseId) +} + +/** 获取预约详情 */ +function getBookingDetail(bookingId) { + return http.get('/groupCourse/bookings/' + bookingId) +} + +/** 扫码签到 */ +function signIn(courseId, memberId) { + return http.post('/groupCourse/signin/' + memberId, { courseId }) +} + +module.exports = { + bookCourse, + cancelBooking, + getMemberBookings, + getCourseBookings, + getBookingDetail, + signIn +} diff --git a/gym-manage-uniapp/api/course.js b/gym-manage-uniapp/api/course.js new file mode 100644 index 0000000..a5a4622 --- /dev/null +++ b/gym-manage-uniapp/api/course.js @@ -0,0 +1,40 @@ +const http = require('../utils/request') + +/** 分页查询团课 */ +function getCoursesByPage(params) { + return http.post('/groupCourse/page', params) +} + +/** 获取课程详情 */ +function getCourseDetail(id) { + return http.get('/groupCourse/' + id + '/detail') +} + +/** 获取团课类型列表 */ +function getCourseTypes() { + return http.get('/groupCourse/types') +} + +/** 获取课程标签 */ +function getCourseLabels() { + return http.get('/groupCourse/labels') +} + +/** 获取活跃推荐课程 */ +function getActiveRecommendations() { + return http.get('/groupCourse/recommend/active') +} + +/** 搜索团课 */ +function searchCourses(params) { + return http.post('/groupCourse/search', params) +} + +module.exports = { + getCoursesByPage, + getCourseDetail, + getCourseTypes, + getCourseLabels, + getActiveRecommendations, + searchCourses +} diff --git a/gym-manage-uniapp/api/member.js b/gym-manage-uniapp/api/member.js new file mode 100644 index 0000000..c2135d3 --- /dev/null +++ b/gym-manage-uniapp/api/member.js @@ -0,0 +1,34 @@ +const http = require('../utils/request') + +/** 获取当前会员信息 */ +function getMemberInfo() { + return http.get('/member/info') +} + +/** 更新会员信息 */ +function updateMemberInfo(data) { + return http.put('/member/info', data) +} + +/** 获取活跃会员卡 */ +function getActiveCards() { + return http.get('/member-cards/active') +} + +/** 获取会员签到统计 */ +function getCheckInStatistics() { + return http.get('/checkIn/statistics') +} + +/** 获取会员签到记录 */ +function getCheckInRecords(params) { + return http.get('/checkIn/records', { params }) +} + +module.exports = { + getMemberInfo, + updateMemberInfo, + getActiveCards, + getCheckInStatistics, + getCheckInRecords +} diff --git a/gym-manage-uniapp/common/img/20200414210134_qbeyi.jpg b/gym-manage-uniapp/common/img/20200414210134_qbeyi.jpg new file mode 100644 index 0000000..6749e9c Binary files /dev/null and b/gym-manage-uniapp/common/img/20200414210134_qbeyi.jpg differ diff --git a/gym-manage-uniapp/common/img/icon/jianshen.png b/gym-manage-uniapp/common/img/icon/jianshen.png new file mode 100644 index 0000000..df33933 Binary files /dev/null and b/gym-manage-uniapp/common/img/icon/jianshen.png differ diff --git a/gym-manage-uniapp/common/img/icon/jianshen2.png b/gym-manage-uniapp/common/img/icon/jianshen2.png new file mode 100644 index 0000000..c032879 Binary files /dev/null and b/gym-manage-uniapp/common/img/icon/jianshen2.png differ diff --git a/gym-manage-uniapp/common/img/icon/shouye.png b/gym-manage-uniapp/common/img/icon/shouye.png new file mode 100644 index 0000000..ac38d7d Binary files /dev/null and b/gym-manage-uniapp/common/img/icon/shouye.png differ diff --git a/gym-manage-uniapp/common/img/icon/wode.png b/gym-manage-uniapp/common/img/icon/wode.png new file mode 100644 index 0000000..04128be Binary files /dev/null and b/gym-manage-uniapp/common/img/icon/wode.png differ diff --git a/gym-manage-uniapp/exmp/style1.html b/gym-manage-uniapp/exmp/style1.html new file mode 100644 index 0000000..f907f13 --- /dev/null +++ b/gym-manage-uniapp/exmp/style1.html @@ -0,0 +1,624 @@ + + + + + + 健身房预约 · 配色方案展示 + + + + + + + +
+ +
+ 9:41 + 📶 📶 🔋 +
+ + + + + +
+ + +
+
+ 👋 你好,Jason + 🔥 今日 420 kcal +
+
+
+ 68 + 总课时 +
+
+ 12 + 连续打卡 +
+
+ 4.8 + ⭐ 评分 +
+
+
+ + +
+ 全部 + 力量区 + 有氧 + 瑜伽 + 私教 + 团课 +
+ + +
+
+ 12 + 周日 +
+
+ 13 + 周一 +
+
+ 14 + 周二 +
+
+ 15 + 周三 +
+
+ 16 + 周四 +
+
+ + +
+
🏋️
+
+
综合力量训练
+
+ ⏱️ 45 min + 🔥 热门 + 📍 力量区 +
+
+ +
+ + +
+
🧘
+
+
流瑜伽 · 中级
+
+ ⏱️ 60 min + 🧘 瑜伽 + 📍 3号教室 +
+
+ +
+ + +
+
🚴
+
+
动感单车 · 冲刺
+
+ ⏱️ 40 min + ⚡ 有氧 + 📍 单车房 +
+
+ +
+ + +
+
+
J
+
+

Jason Wang

+

🏆 高级会员 · 已练 68 课时

+
+
+ +
+ +
配色方案 · 能量黑 + 荧光绿 · 预约小程序
+
+ + +
+
+ 🏠 + 首页 +
+
+ 📅 + 课程 +
+ +
+ + 预约 +
+
+ 💬 + 消息 +
+
+ 👤 + 我的 +
+
+
+ + \ No newline at end of file diff --git a/gym-manage-uniapp/exmp/style2.html b/gym-manage-uniapp/exmp/style2.html new file mode 100644 index 0000000..14d9474 --- /dev/null +++ b/gym-manage-uniapp/exmp/style2.html @@ -0,0 +1,590 @@ + + + + + + 健身房预约 · 极光紫配色 + + + + + + +
+ +
+ 9:41 + 📶 📶 🔋 +
+ + + + + +
+ +
+
+ 👋 你好,Jason + 🔥 今日 420 kcal +
+
+
+ 68 + 总课时 +
+
+ 12 + 连续打卡 +
+
+ 4.8 + ⭐ 评分 +
+
+
+ + +
+ 全部 + 力量区 + 有氧 + 瑜伽 + 私教 + 团课 +
+ + +
+
+ 12 + 周日 +
+
+ 13 + 周一 +
+
+ 14 + 周二 +
+
+ 15 + 周三 +
+
+ 16 + 周四 +
+
+ + +
+
🏋️
+
+
综合力量训练
+
+ ⏱️ 45 min + 🔥 热门 + 📍 力量区 +
+
+ +
+ + +
+
🧘
+
+
流瑜伽 · 中级
+
+ ⏱️ 60 min + 🧘 瑜伽 + 📍 3号教室 +
+
+ +
+ + +
+
🚴
+
+
动感单车 · 冲刺
+
+ ⏱️ 40 min + ⚡ 有氧 + 📍 单车房 +
+
+ +
+ + +
+
+
J
+
+

Jason Wang

+

🏆 高级会员 · 已练 68 课时

+
+
+ +
+ +
配色方案 · 极光紫 · 科技感预约小程序
+
+ + +
+
+ 🏠 + 首页 +
+
+ 📅 + 课程 +
+
+ + 预约 +
+
+ 💬 + 消息 +
+
+ 👤 + 我的 +
+
+
+ + \ No newline at end of file diff --git a/gym-manage-uniapp/exmp/style3.html b/gym-manage-uniapp/exmp/style3.html new file mode 100644 index 0000000..cf3fb71 --- /dev/null +++ b/gym-manage-uniapp/exmp/style3.html @@ -0,0 +1,591 @@ + + + + + + 健身房预约 · 珊瑚橙配色 + + + + + + +
+ +
+ 9:41 + 📶 📶 🔋 +
+ + + + + +
+ +
+
+ 👋 你好,Jason + 🔥 今日 420 kcal +
+
+
+ 68 + 总课时 +
+
+ 12 + 连续打卡 +
+
+ 4.8 + ⭐ 评分 +
+
+
+ + +
+ 全部 + 力量区 + 有氧 + 瑜伽 + 私教 + 团课 +
+ + +
+
+ 12 + 周日 +
+
+ 13 + 周一 +
+
+ 14 + 周二 +
+
+ 15 + 周三 +
+
+ 16 + 周四 +
+
+ + +
+
🏋️
+
+
综合力量训练
+
+ ⏱️ 45 min + 🔥 热门 + 📍 力量区 +
+
+ +
+ + +
+
🧘
+
+
流瑜伽 · 中级
+
+ ⏱️ 60 min + 🧘 瑜伽 + 📍 3号教室 +
+
+ +
+ + +
+
🚴
+
+
动感单车 · 冲刺
+
+ ⏱️ 40 min + ⚡ 有氧 + 📍 单车房 +
+
+ +
+ + +
+
+
J
+
+

Jason Wang

+

🏆 高级会员 · 已练 68 课时

+
+
+ +
+ +
配色方案 · 珊瑚橙 · 温暖活力预约小程序
+
+ + +
+
+ 🏠 + 首页 +
+
+ 📅 + 课程 +
+
+ + 预约 +
+
+ 💬 + 消息 +
+
+ 👤 + 我的 +
+
+
+ + \ No newline at end of file diff --git a/gym-manage-uniapp/index.html b/gym-manage-uniapp/index.html new file mode 100644 index 0000000..b5d330d --- /dev/null +++ b/gym-manage-uniapp/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + diff --git a/gym-manage-uniapp/main.js b/gym-manage-uniapp/main.js new file mode 100644 index 0000000..c1caf36 --- /dev/null +++ b/gym-manage-uniapp/main.js @@ -0,0 +1,22 @@ +import App from './App' + +// #ifndef VUE3 +import Vue from 'vue' +import './uni.promisify.adaptor' +Vue.config.productionTip = false +App.mpType = 'app' +const app = new Vue({ + ...App +}) +app.$mount() +// #endif + +// #ifdef VUE3 +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} +// #endif \ No newline at end of file diff --git a/gym-manage-uniapp/manifest.json b/gym-manage-uniapp/manifest.json index 39bd84f..66c6a64 100644 --- a/gym-manage-uniapp/manifest.json +++ b/gym-manage-uniapp/manifest.json @@ -1,64 +1,72 @@ { - "name": "gym-manage-uniapp", - "appid": "", - "description": "Gym Management System Mobile App", - "versionName": "1.0.0", - "versionCode": "100", - "transformPx": false, - "app-plus": { - "usingComponents": true, - "nvueStyleCompiler": "uni-app", - "compilerVersion": 3, - "splashscreen": { - "alwaysShowBeforeRender": true, - "waiting": true, - "autoclose": true, - "delay": 0 + "name" : "gym-manage-uniapp", + "appid" : "", + "description" : "", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueStyleCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : {}, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + /* ios打包配置 */ + "ios" : {}, + /* SDK配置 */ + "sdkConfigs" : {} + } }, - "modules": {}, - "distribute": { - "android": { - "permissions": [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ] - }, - "ios": {}, - "sdkConfigs": {} - } - }, - "quickapp": {}, - "mp-weixin": { - "appid": "", - "setting": { - "urlCheck": false + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true }, - "usingComponents": true - }, - "mp-alipay": { - "usingComponents": true - }, - "mp-baidu": { - "usingComponents": true - }, - "mp-toutiao": { - "usingComponents": true - }, - "uniStatistics": { - "enable": false - }, - "vueVersion": "3" + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "2" } diff --git a/gym-manage-uniapp/package-lock.json b/gym-manage-uniapp/package-lock.json new file mode 100644 index 0000000..49988ab --- /dev/null +++ b/gym-manage-uniapp/package-lock.json @@ -0,0 +1,45 @@ +{ + "name": "gym-manage-uniapp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gym-manage-uniapp", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@dcloudio/uni-ui": "^1.5.12", + "crypto-js": "^4.2.0", + "luch-request": "^3.1.1" + } + }, + "node_modules/@dcloudio/types": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@dcloudio/types/-/types-2.6.12.tgz", + "integrity": "sha512-mrCMwcINy1IFjU9VUqLeWBkj404yWs5paLDttBcA+eqUjanuUQbBcTVPqlrGgkyzLXDcV2oDDZRSNxNpXi4kMQ==", + "license": "Apache-2.0" + }, + "node_modules/@dcloudio/uni-ui": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/@dcloudio/uni-ui/-/uni-ui-1.5.12.tgz", + "integrity": "sha512-mGDl2OZSz7D8xcUAzJegWDHOqB4MEFBSW9Esb/oJiu2/3Gk9+P/Z4bA4JZ9jv9VWBYbMrYwaTfK1Z728kABdYg==", + "license": "Apache-2.0" + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/luch-request": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/luch-request/-/luch-request-3.1.1.tgz", + "integrity": "sha512-p7+mlcEtgRcd0OfXC4XZbyiwSr1XgCeqNT7LlVUjnk7InYl/8d5Rk7BUqAYNA2WRafI1wRIUQWRWZRpeUwWR0w==", + "license": "MIT", + "dependencies": { + "@dcloudio/types": "^2.0.16" + } + } + } +} diff --git a/gym-manage-uniapp/package.json b/gym-manage-uniapp/package.json index c3506cd..0b0957d 100644 --- a/gym-manage-uniapp/package.json +++ b/gym-manage-uniapp/package.json @@ -1,19 +1,18 @@ { "name": "gym-manage-uniapp", "version": "1.0.0", - "description": "Gym Management System Mobile App", + "description": "", "main": "main.js", "scripts": { - "dev:mp-weixin": "uni -p mp-weixin", - "build:mp-weixin": "uni build -p mp-weixin", - "dev:h5": "uni", - "build:h5": "uni build" + "test": "echo \"Error: no test specified\" && exit 1" }, - "keywords": [ - "uniapp", - "gym", - "management" - ], - "author": "Novalon", - "license": "MIT" + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "@dcloudio/uni-ui": "^1.5.12", + "crypto-js": "^4.2.0", + "luch-request": "^3.1.1" + } } diff --git a/gym-manage-uniapp/pages.json b/gym-manage-uniapp/pages.json index bd13bd5..a4046b7 100644 --- a/gym-manage-uniapp/pages.json +++ b/gym-manage-uniapp/pages.json @@ -1,28 +1,91 @@ { - "pages": [ - { - "path": "pages/index/index", - "style": { - "navigationBarTitleText": "首页" - } - } - ], - "globalStyle": { - "navigationBarTextStyle": "black", - "navigationBarTitleText": "健身房管理系统", - "navigationBarBackgroundColor": "#F8F8F8", - "backgroundColor": "#F8F8F8" - }, - "tabBar": { - "color": "#7A7E83", - "selectedColor": "#007AFF", - "borderStyle": "black", - "backgroundColor": "#F8F8F8", - "list": [ - { - "pagePath": "pages/index/index", - "text": "首页" - } - ] - } + "pages": [ + { + "path": "pages/login/login", + "style": { + "navigationBarTitleText": "登录", + "navigationStyle": "custom" + } + }, + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "首页", + "navigationStyle": "custom", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/search/search", + "style": { + "navigationBarTitleText": "团课搜索", + "navigationStyle": "custom", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/course-detail/course-detail", + "style": { + "navigationBarTitleText": "课程详情", + "navigationStyle": "custom" + } + }, + { + "path": "pages/profile/profile", + "style": { + "navigationBarTitleText": "个人中心", + "navigationStyle": "custom", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/my-courses/my-courses", + "style": { + "navigationBarTitleText": "我的课程", + "navigationStyle": "custom", + "enablePullDownRefresh": true + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "white", + "navigationBarTitleText": "Novalon健身房", + "navigationBarBackgroundColor": "#1A1A1A", + "backgroundColor": "#F5F7FA" + }, + "tabBar": { + "color": "#7A7E84", + "selectedColor": "#00E676", + "backgroundColor": "#1A1A1A", + "borderStyle": "black", + "iconWidth": "24px", + "iconHeight": "24px", + "list": [ + { + "pagePath": "pages/index/index", + "text": "首页", + "iconPath": "static/icon/shouye.png", + "selectedIconPath": "static/icon/shouye.png" + }, + { + "pagePath": "pages/search/search", + "text": "课程", + "iconPath": "static/icon/jianshen.png", + "selectedIconPath": "static/icon/jianshen.png" + }, + { + "pagePath": "pages/my-courses/my-courses", + "text": "我的课程", + "iconPath": "static/icon/jianshen2.png", + "selectedIconPath": "static/icon/jianshen2.png" + }, + { + "pagePath": "pages/profile/profile", + "text": "我的", + "iconPath": "static/icon/wode.png", + "selectedIconPath": "static/icon/wode.png" + } + ] + }, + "uniIdRouter": {} } diff --git a/gym-manage-uniapp/pages/course-detail/course-detail.vue b/gym-manage-uniapp/pages/course-detail/course-detail.vue new file mode 100644 index 0000000..f8ee2de --- /dev/null +++ b/gym-manage-uniapp/pages/course-detail/course-detail.vue @@ -0,0 +1,416 @@ + + + + + diff --git a/gym-manage-uniapp/pages/index/index.vue b/gym-manage-uniapp/pages/index/index.vue new file mode 100644 index 0000000..2283632 --- /dev/null +++ b/gym-manage-uniapp/pages/index/index.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/gym-manage-uniapp/pages/login/login.vue b/gym-manage-uniapp/pages/login/login.vue new file mode 100644 index 0000000..130eec6 --- /dev/null +++ b/gym-manage-uniapp/pages/login/login.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/gym-manage-uniapp/pages/my-courses/my-courses.vue b/gym-manage-uniapp/pages/my-courses/my-courses.vue new file mode 100644 index 0000000..d32901d --- /dev/null +++ b/gym-manage-uniapp/pages/my-courses/my-courses.vue @@ -0,0 +1,312 @@ + + + + + diff --git a/gym-manage-uniapp/pages/profile/profile.vue b/gym-manage-uniapp/pages/profile/profile.vue new file mode 100644 index 0000000..5377ac8 --- /dev/null +++ b/gym-manage-uniapp/pages/profile/profile.vue @@ -0,0 +1,417 @@ + + + + + diff --git a/gym-manage-uniapp/pages/search/search.vue b/gym-manage-uniapp/pages/search/search.vue new file mode 100644 index 0000000..6876b29 --- /dev/null +++ b/gym-manage-uniapp/pages/search/search.vue @@ -0,0 +1,519 @@ + + + + + diff --git a/gym-manage-uniapp/src/api/index.js b/gym-manage-uniapp/src/api/index.js deleted file mode 100644 index f29ddad..0000000 --- a/gym-manage-uniapp/src/api/index.js +++ /dev/null @@ -1,29 +0,0 @@ -const BASE_URL = 'http://localhost:8080/api' - -export const request = (options) => { - return new Promise((resolve, reject) => { - uni.request({ - url: BASE_URL + options.url, - method: options.method || 'GET', - data: options.data || {}, - header: { - 'Content-Type': 'application/json', - ...options.header - }, - success: (res) => { - if (res.statusCode === 200) { - resolve(res.data) - } else { - reject(res) - } - }, - fail: (err) => { - reject(err) - } - }) - }) -} - -export default { - request -} diff --git a/gym-manage-uniapp/src/pages/index/index.vue b/gym-manage-uniapp/src/pages/index/index.vue deleted file mode 100644 index 5b7705f..0000000 --- a/gym-manage-uniapp/src/pages/index/index.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/gym-manage-uniapp/src/store/index.ts b/gym-manage-uniapp/src/store/index.ts deleted file mode 100644 index 8a05980..0000000 --- a/gym-manage-uniapp/src/store/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createPinia } from 'pinia' - -const pinia = createPinia() - -export default pinia diff --git a/gym-manage-uniapp/src/store/user.ts b/gym-manage-uniapp/src/store/user.ts deleted file mode 100644 index 6b67a0b..0000000 --- a/gym-manage-uniapp/src/store/user.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { defineStore } from 'pinia' - -export const useUserStore = defineStore('user', { - state: () => ({ - token: '', - userInfo: null - }), - - getters: { - isLoggedIn: (state) => !!state.token - }, - - actions: { - setToken(token) { - this.token = token - }, - - setUserInfo(userInfo) { - this.userInfo = userInfo - }, - - logout() { - this.token = '' - this.userInfo = null - } - } -}) diff --git a/gym-manage-uniapp/static/icon/jianshen.png b/gym-manage-uniapp/static/icon/jianshen.png new file mode 100644 index 0000000..df33933 Binary files /dev/null and b/gym-manage-uniapp/static/icon/jianshen.png differ diff --git a/gym-manage-uniapp/static/icon/jianshen2.png b/gym-manage-uniapp/static/icon/jianshen2.png new file mode 100644 index 0000000..c032879 Binary files /dev/null and b/gym-manage-uniapp/static/icon/jianshen2.png differ diff --git a/gym-manage-uniapp/static/icon/shouye.png b/gym-manage-uniapp/static/icon/shouye.png new file mode 100644 index 0000000..ac38d7d Binary files /dev/null and b/gym-manage-uniapp/static/icon/shouye.png differ diff --git a/gym-manage-uniapp/static/icon/wode.png b/gym-manage-uniapp/static/icon/wode.png new file mode 100644 index 0000000..04128be Binary files /dev/null and b/gym-manage-uniapp/static/icon/wode.png differ diff --git a/gym-manage-uniapp/static/logo.png b/gym-manage-uniapp/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/gym-manage-uniapp/static/logo.png differ diff --git a/gym-manage-uniapp/store/index.js b/gym-manage-uniapp/store/index.js new file mode 100644 index 0000000..6a508a8 --- /dev/null +++ b/gym-manage-uniapp/store/index.js @@ -0,0 +1,66 @@ +/** + * 全局状态管理 — 以 uni.Storage 为唯一数据源,确保跨页面/跨模块共享 + */ +const TOKEN_KEY = 'gym_token' +const MEMBER_KEY = 'gym_member' + +const store = { + /** 是否已登录 */ + get isLoggedIn() { + try { + return !!uni.getStorageSync(TOKEN_KEY) + } catch (e) { + return false + } + }, + + /** 获取 token */ + getToken() { + try { + return uni.getStorageSync(TOKEN_KEY) || null + } catch (e) { + return null + } + }, + + /** 获取会员信息 */ + getMemberInfo() { + try { + return uni.getStorageSync(MEMBER_KEY) || null + } catch (e) { + return null + } + }, + + /** 设置登录态 */ + setLogin(token, memberInfo) { + try { + uni.setStorageSync(TOKEN_KEY, token) + if (memberInfo) uni.setStorageSync(MEMBER_KEY, memberInfo) + } catch (e) { + console.error('存储登录态失败:', e) + } + }, + + /** 更新会员信息(合并而非覆盖,保留 memberId 等关键字段) */ + updateMemberInfo(memberInfo) { + try { + const existing = uni.getStorageSync(MEMBER_KEY) || {} + uni.setStorageSync(MEMBER_KEY, { ...existing, ...memberInfo }) + } catch (e) { + console.error('更新会员信息失败:', e) + } + }, + + /** 清除登录态 */ + clearLogin() { + try { + uni.removeStorageSync(TOKEN_KEY) + uni.removeStorageSync(MEMBER_KEY) + } catch (e) { + console.error('清除登录态失败:', e) + } + } +} + +module.exports = store diff --git a/gym-manage-uniapp/style/base/style1.css b/gym-manage-uniapp/style/base/style1.css new file mode 100644 index 0000000..d0ec271 --- /dev/null +++ b/gym-manage-uniapp/style/base/style1.css @@ -0,0 +1,96 @@ +/** + * ============================================================ + * 健身房预约小程序 · 全局配色方案 (CSS Variables) + * ============================================================ + * + * 主题风格:能量黑 + 荧光绿 + 科技灰 + * 适用场景:健身/运动/预约类小程序或移动端H5 + * 设计理念:硬核力量感 + 现代科技感 + 清晰易用 + * + * 使用方式:在项目入口文件中引入本文件,或在 :root 中复制变量 + * 示例:color: var(--primary); background: var(--bg-primary); + * ============================================================ + */ + +:root { + /* ========================================================== + 1. 品牌主色 (Primary) —— 荧光绿 / 能量绿 + 用于核心操作按钮、选中状态、品牌高亮 + ========================================================== */ + + /** 主色:明亮荧光绿,代表活力与健康指标 */ + --primary: #00E676; + + /** 主色按压态:用于按钮点击、加载进度条等反馈 */ + --primary-dark: #00C853; + + /** 主色浅色背景:用于标签、选中态背景、轻量高亮 (半透明) */ + --primary-light: rgba(0, 230, 118, 0.15); + + /** 主色渐变终点:用于数据卡片、图表渐变的辅助色 (蓝绿) */ + --primary-gradient-end: #00BFA5; + + /* ========================================================== + 2. 中性色 (Neutral) —— 深色/灰色/白色 + 用于背景、文字、分割线等基础界面 + ========================================================== */ + + /** 深空黑:顶部导航、底部TabBar、卡片深色背景 */ + --secondary: #1A1A1A; + + /** 全局背景色:浅灰,使主色更突出,视觉干净 */ + --bg-primary: #F5F7FA; + + /** 纯白色:卡片、浮层、按钮文字 */ + --white: #FFFFFF; + + /** 深灰色:用于重要标题、用户名等主要文字 */ + --text-primary: #1E1E1E; + + /** 中灰色:用于次要描述、场馆地址、日期等辅助文字 */ + --text-secondary: #7A7E84; + + /** 浅灰色:用于禁用状态文字、占位符 */ + --text-disabled: #BDBDBD; + + /** 极浅灰:分割线、边框、轻量背景区分 */ + --border-light: #EEEEEE; + + /** 卡片阴影:轻度投影,提升层次感 */ + --shadow-card: 0 4px 12px rgba(0, 0, 0, 0.06); + + /* ========================================================== + 3. 功能色 (Functional) —— 反馈/状态/警告 + 用于提示、错误、成功等交互反馈 + ========================================================== */ + + /** 高亮警告红:未读消息红点、取消预约、倒计时、危险操作 */ + --highlight: #FF5252; + + /** 成功状态:通常复用主色,但独立定义便于统一调整 */ + --success: var(--primary); + + /** 禁用/不可用状态:背景色和文字色 */ + --disabled-bg: #E0E0E0; + --disabled-text: #BDBDBD; + + /* ========================================================== + 4. 圆角 & 尺寸 (Radius & Size) + 便于统一界面风格 + ========================================================== */ + + /** 大圆角:卡片、弹窗、主要容器 */ + --radius-large: 20px; + + /** 中圆角:按钮、输入框、次级卡片 */ + --radius-medium: 12px; + + /** 小圆角:标签、头像、小元素 */ + --radius-small: 8px; + + /** 全圆角:胶囊按钮、标签 */ + --radius-full: 40px; + + /** 底部TabBar高度 */ + --tab-height: 72px; +} diff --git a/gym-manage-uniapp/style/base/style2.css b/gym-manage-uniapp/style/base/style2.css new file mode 100644 index 0000000..8a23849 --- /dev/null +++ b/gym-manage-uniapp/style/base/style2.css @@ -0,0 +1,129 @@ +/** + * ============================================================ + * 健身房预约小程序 · 珊瑚橙配色方案 (CSS Variables) + * ============================================================ + * + * 主题风格:珊瑚橙 (Coral Orange) + * 主色:温暖珊瑚橙 + 柔和米白 + * 性格:亲切、阳光、活力、温暖 + * 适用场景:轻运动、瑜伽、女性友好、社区型健身房 + * + * 使用方式:在项目入口文件中引入本文件,或在 :root 中复制变量 + * 示例:color: var(--primary); background: var(--bg-primary); + * ============================================================ + */ + +:root { + /* ========================================================== + 1. 品牌主色 (Primary) —— 珊瑚橙 / 暖橙渐变 + 用于核心操作按钮、品牌高亮、渐变背景 + ========================================================== */ + + /** 主色:温暖珊瑚橙,代表活力与亲切感 */ + --primary: #FF6B4A; + + /** 主色按压态:深橙,用于按钮点击反馈 */ + --primary-dark: #E55A3A; + + /** 主色浅色背景:半透明橙,用于标签、选中态背景 */ + --primary-light: rgba(255, 107, 74, 0.15); + + /** 主色渐变:珊瑚橙到浅橙红,用于导航栏、欢迎卡片、按钮 */ + --primary-gradient: linear-gradient(135deg, #FF6B4A, #FF8A65); + + /** 渐变辅助色:用于数据卡片或图表的渐变更丰富 */ + --primary-gradient-start: #FF6B4A; + --primary-gradient-end: #FF8A65; + + /* ========================================================== + 2. 中性色 (Neutral) —— 暖色系/米白/暖灰 + 用于背景、文字、分割线等基础界面 + ========================================================== */ + + /** 暖棕黑:底部TabBar、导航栏背景、重要文字 */ + --secondary: #2D1F1A; + + /** 全局背景色:暖白米色,营造温馨舒适感 */ + --bg-primary: #FDF8F5; + + /** 纯白色:卡片、浮层、按钮文字 */ + --white: #FFFFFF; + + /** 暖棕黑:用于重要标题、用户名等主要文字 */ + --text-primary: #2D1F1A; + + /** 暖灰色:用于次要描述、场馆地址、日期等辅助文字 */ + --text-secondary: #8A7A72; + + /** 浅暖灰:用于禁用状态文字、占位符 */ + --text-disabled: #C5B5AD; + + /** 暖色分割线:用于列表、卡片分割 */ + --border-light: #F0E8E3; + + /** 卡片阴影:带有橙色调的轻度投影 */ + --shadow-card: 0 4px 16px rgba(255, 107, 74, 0.12); + + /* ========================================================== + 3. 功能色 (Functional) —— 反馈/状态/警告 + 用于提示、错误、成功等交互反馈 + ========================================================== */ + + /** 高亮警告红:未读消息红点、取消预约、危险操作 */ + --highlight: #FF4757; + + /** 成功状态:翠绿色,用于完成、通过等正向反馈 */ + --success: #2ED573; + + /** 警告状态:琥珀色,用于提醒、注意 */ + --warning: #FFA502; + + /** 信息状态:淡蓝色,用于一般提示 */ + --info: #4A90D9; + + /** 禁用/不可用状态:背景色和文字色 */ + --disabled-bg: #F0E8E3; + --disabled-text: #C5B5AD; + + /* ========================================================== + 4. 圆角 & 尺寸 (Radius & Size) + 便于统一界面风格 + ========================================================== */ + + /** 大圆角:卡片、弹窗、主要容器 */ + --radius-large: 20px; + + /** 中圆角:按钮、输入框、次级卡片 */ + --radius-medium: 12px; + + /** 小圆角:标签、头像、小元素 */ + --radius-small: 8px; + + /** 全圆角:胶囊按钮、标签、头像 */ + --radius-full: 40px; + + /** 底部TabBar高度 */ + --tab-height: 72px; + + /** 导航栏高度 */ + --nav-height: 56px; + + /* ========================================================== + 5. 额外辅助变量 (Extra Utilities) + ========================================================== */ + + /** 主色文字颜色 (通常为白色) */ + --primary-text-color: #FFFFFF; + + /** 渐变背景的文本颜色 (通常为白色) */ + --gradient-text-color: #FFFFFF; + + /** 卡片内边距 (统一) */ + --card-padding: 16px; + + /** 页面左右安全间距 */ + --page-padding: 16px; + + /** 字体家族 */ + --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} diff --git a/gym-manage-uniapp/style/base/style3.css b/gym-manage-uniapp/style/base/style3.css new file mode 100644 index 0000000..c22b80a --- /dev/null +++ b/gym-manage-uniapp/style/base/style3.css @@ -0,0 +1,126 @@ +/** + * ============================================================ + * 健身房预约小程序 · 极光紫配色方案 (CSS Variables) + * ============================================================ + * + * 主题风格:极光紫 (Aurora Purple) + * 主色:深邃紫罗兰 + 霓虹蓝紫渐变 + * 性格:科技感、神秘、高级、时尚 + * 适用场景:高端健身、时尚运动、科技感预约类小程序 + * + * 使用方式:在项目入口文件中引入本文件,或在 :root 中复制变量 + * 示例:color: var(--primary); background: var(--bg-primary); + * ============================================================ + */ + +:root { + /* ========================================================== + 1. 品牌主色 (Primary) —— 极光紫 / 蓝紫渐变 + 用于核心操作按钮、品牌高亮、渐变背景 + ========================================================== */ + + /** 主色:深邃紫罗兰,代表神秘与高级感 */ + --primary: #7C3AED; + + /** 主色按压态:深紫,用于按钮点击反馈 */ + --primary-dark: #5B21B6; + + /** 主色浅色背景:半透明紫,用于标签、选中态背景 */ + --primary-light: rgba(124, 58, 237, 0.15); + + /** 主色渐变:紫罗兰到靛蓝,用于导航栏、欢迎卡片、按钮 */ + --primary-gradient: linear-gradient(135deg, #7C3AED, #4F46E5); + + /** 渐变辅助色:用于数据卡片或图表的渐变更丰富 */ + --primary-gradient-start: #7C3AED; + --primary-gradient-end: #4F46E5; + + /* ========================================================== + 2. 中性色 (Neutral) —— 深色/灰色/白色 + 用于背景、文字、分割线等基础界面 + ========================================================== */ + + /** 深紫黑:底部TabBar、导航栏背景、重要文字 */ + --secondary: #1E1028; + + /** 全局背景色:极浅灰紫,营造柔和科技感 */ + --bg-primary: #F8F7FC; + + /** 纯白色:卡片、浮层、按钮文字 */ + --white: #FFFFFF; + + /** 深紫黑:用于重要标题、用户名等主要文字 */ + --text-primary: #1E1028; + + /** 紫灰色:用于次要描述、场馆地址、日期等辅助文字 */ + --text-secondary: #6B5E7E; + + /** 浅灰紫:用于禁用状态文字、占位符 */ + --text-disabled: #B8AED0; + + /** 浅紫分割线:用于列表、卡片分割 */ + --border-light: #EDE9F5; + + /** 卡片阴影:带有紫色调的轻度投影 */ + --shadow-card: 0 4px 16px rgba(94, 48, 176, 0.10); + + /* ========================================================== + 3. 功能色 (Functional) —— 反馈/状态/警告 + 用于提示、错误、成功等交互反馈 + ========================================================== */ + + /** 高亮警告红:未读消息红点、取消预约、危险操作 */ + --highlight: #EF4444; + + /** 成功状态:翠绿色,用于完成、通过等正向反馈 */ + --success: #10B981; + + /** 警告状态:琥珀色,用于提醒、注意 */ + --warning: #F59E0B; + + /** 信息状态:淡蓝色,用于一般提示 */ + --info: #3B82F6; + + /** 禁用/不可用状态:背景色和文字色 */ + --disabled-bg: #E8E3F0; + --disabled-text: #B8AED0; + + /* ========================================================== + 4. 圆角 & 尺寸 (Radius & Size) + 便于统一界面风格 + ========================================================== */ + + /** 大圆角:卡片、弹窗、主要容器 */ + --radius-large: 20px; + + /** 中圆角:按钮、输入框、次级卡片 */ + --radius-medium: 12px; + + /** 小圆角:标签、头像、小元素 */ + --radius-small: 8px; + + /** 全圆角:胶囊按钮、标签、头像 */ + --radius-full: 40px; + + /** 底部TabBar高度 */ + --tab-height: 72px; + + /** 导航栏高度 */ + --nav-height: 56px; + + /* ========================================================== + 5. 额外辅助变量 (Extra Utilities) + ========================================================== */ + + /** 主色文字颜色 (通常为白色) */ + --primary-text-color: #FFFFFF; + + /** 渐变背景的文本颜色 (通常为白色) */ + --gradient-text-color: #FFFFFF; + + /** 卡片内边距 (统一) */ + --card-padding: 16px; + + /** 页面左右安全间距 */ + --page-padding: 16px; +} diff --git a/gym-manage-uniapp/uni.promisify.adaptor.js b/gym-manage-uniapp/uni.promisify.adaptor.js new file mode 100644 index 0000000..5fec4f3 --- /dev/null +++ b/gym-manage-uniapp/uni.promisify.adaptor.js @@ -0,0 +1,13 @@ +uni.addInterceptor({ + returnValue (res) { + if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) { + return res; + } + return new Promise((resolve, reject) => { + res.then((res) => { + if (!res) return resolve(res) + return res[0] ? reject(res[0]) : resolve(res[1]) + }); + }); + }, +}); \ No newline at end of file diff --git a/gym-manage-uniapp/uni.scss b/gym-manage-uniapp/uni.scss new file mode 100644 index 0000000..b9249e9 --- /dev/null +++ b/gym-manage-uniapp/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16px; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/gym-manage-uniapp/uni_modules/uni-icons/changelog.md b/gym-manage-uniapp/uni_modules/uni-icons/changelog.md new file mode 100644 index 0000000..62e7682 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-icons/changelog.md @@ -0,0 +1,44 @@ +## 2.0.12(2025-08-26) +- 优化 uni-app x 下 size 类型问题 +## 2.0.11(2025-08-18) +- 修复 图标点击事件返回 +## 2.0.9(2024-01-12) +fix: 修复图标大小默认值错误的问题 +## 2.0.8(2023-12-14) +- 修复 项目未使用 ts 情况下,打包报错的bug +## 2.0.7(2023-12-14) +- 修复 size 属性为 string 时,不加单位导致尺寸异常的bug +## 2.0.6(2023-12-11) +- 优化 兼容老版本icon类型,如 top ,bottom 等 +## 2.0.5(2023-12-11) +- 优化 兼容老版本icon类型,如 top ,bottom 等 +## 2.0.4(2023-12-06) +- 优化 uni-app x 下示例项目图标排序 +## 2.0.3(2023-12-06) +- 修复 nvue下引入组件报错的bug +## 2.0.2(2023-12-05) +-优化 size 属性支持单位 +## 2.0.1(2023-12-05) +- 新增 uni-app x 支持定义图标 +## 1.3.5(2022-01-24) +- 优化 size 属性可以传入不带单位的字符串数值 +## 1.3.4(2022-01-24) +- 优化 size 支持其他单位 +## 1.3.3(2022-01-17) +- 修复 nvue 有些图标不显示的bug,兼容老版本图标 +## 1.3.2(2021-12-01) +- 优化 示例可复制图标名称 +## 1.3.1(2021-11-23) +- 优化 兼容旧组件 type 值 +## 1.3.0(2021-11-19) +- 新增 更多图标 +- 优化 自定义图标使用方式 +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons) +## 1.1.7(2021-11-08) +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.5(2021-05-12) +- 新增 组件示例地址 +## 1.1.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue new file mode 100644 index 0000000..53eb2ea --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue @@ -0,0 +1,91 @@ + + + + + diff --git a/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue new file mode 100644 index 0000000..1bd3d5e --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css new file mode 100644 index 0000000..0a6b6fe --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css @@ -0,0 +1,664 @@ + +.uniui-cart-filled:before { + content: "\e6d0"; +} + +.uniui-gift-filled:before { + content: "\e6c4"; +} + +.uniui-color:before { + content: "\e6cf"; +} + +.uniui-wallet:before { + content: "\e6b1"; +} + +.uniui-settings-filled:before { + content: "\e6ce"; +} + +.uniui-auth-filled:before { + content: "\e6cc"; +} + +.uniui-shop-filled:before { + content: "\e6cd"; +} + +.uniui-staff-filled:before { + content: "\e6cb"; +} + +.uniui-vip-filled:before { + content: "\e6c6"; +} + +.uniui-plus-filled:before { + content: "\e6c7"; +} + +.uniui-folder-add-filled:before { + content: "\e6c8"; +} + +.uniui-color-filled:before { + content: "\e6c9"; +} + +.uniui-tune-filled:before { + content: "\e6ca"; +} + +.uniui-calendar-filled:before { + content: "\e6c0"; +} + +.uniui-notification-filled:before { + content: "\e6c1"; +} + +.uniui-wallet-filled:before { + content: "\e6c2"; +} + +.uniui-medal-filled:before { + content: "\e6c3"; +} + +.uniui-fire-filled:before { + content: "\e6c5"; +} + +.uniui-refreshempty:before { + content: "\e6bf"; +} + +.uniui-location-filled:before { + content: "\e6af"; +} + +.uniui-person-filled:before { + content: "\e69d"; +} + +.uniui-personadd-filled:before { + content: "\e698"; +} + +.uniui-arrowthinleft:before { + content: "\e6d2"; +} + +.uniui-arrowthinup:before { + content: "\e6d3"; +} + +.uniui-arrowthindown:before { + content: "\e6d4"; +} + +.uniui-back:before { + content: "\e6b9"; +} + +.uniui-forward:before { + content: "\e6ba"; +} + +.uniui-arrow-right:before { + content: "\e6bb"; +} + +.uniui-arrow-left:before { + content: "\e6bc"; +} + +.uniui-arrow-up:before { + content: "\e6bd"; +} + +.uniui-arrow-down:before { + content: "\e6be"; +} + +.uniui-arrowthinright:before { + content: "\e6d1"; +} + +.uniui-down:before { + content: "\e6b8"; +} + +.uniui-bottom:before { + content: "\e6b8"; +} + +.uniui-arrowright:before { + content: "\e6d5"; +} + +.uniui-right:before { + content: "\e6b5"; +} + +.uniui-up:before { + content: "\e6b6"; +} + +.uniui-top:before { + content: "\e6b6"; +} + +.uniui-left:before { + content: "\e6b7"; +} + +.uniui-arrowup:before { + content: "\e6d6"; +} + +.uniui-eye:before { + content: "\e651"; +} + +.uniui-eye-filled:before { + content: "\e66a"; +} + +.uniui-eye-slash:before { + content: "\e6b3"; +} + +.uniui-eye-slash-filled:before { + content: "\e6b4"; +} + +.uniui-info-filled:before { + content: "\e649"; +} + +.uniui-reload:before { + content: "\e6b2"; +} + +.uniui-micoff-filled:before { + content: "\e6b0"; +} + +.uniui-map-pin-ellipse:before { + content: "\e6ac"; +} + +.uniui-map-pin:before { + content: "\e6ad"; +} + +.uniui-location:before { + content: "\e6ae"; +} + +.uniui-starhalf:before { + content: "\e683"; +} + +.uniui-star:before { + content: "\e688"; +} + +.uniui-star-filled:before { + content: "\e68f"; +} + +.uniui-calendar:before { + content: "\e6a0"; +} + +.uniui-fire:before { + content: "\e6a1"; +} + +.uniui-medal:before { + content: "\e6a2"; +} + +.uniui-font:before { + content: "\e6a3"; +} + +.uniui-gift:before { + content: "\e6a4"; +} + +.uniui-link:before { + content: "\e6a5"; +} + +.uniui-notification:before { + content: "\e6a6"; +} + +.uniui-staff:before { + content: "\e6a7"; +} + +.uniui-vip:before { + content: "\e6a8"; +} + +.uniui-folder-add:before { + content: "\e6a9"; +} + +.uniui-tune:before { + content: "\e6aa"; +} + +.uniui-auth:before { + content: "\e6ab"; +} + +.uniui-person:before { + content: "\e699"; +} + +.uniui-email-filled:before { + content: "\e69a"; +} + +.uniui-phone-filled:before { + content: "\e69b"; +} + +.uniui-phone:before { + content: "\e69c"; +} + +.uniui-email:before { + content: "\e69e"; +} + +.uniui-personadd:before { + content: "\e69f"; +} + +.uniui-chatboxes-filled:before { + content: "\e692"; +} + +.uniui-contact:before { + content: "\e693"; +} + +.uniui-chatbubble-filled:before { + content: "\e694"; +} + +.uniui-contact-filled:before { + content: "\e695"; +} + +.uniui-chatboxes:before { + content: "\e696"; +} + +.uniui-chatbubble:before { + content: "\e697"; +} + +.uniui-upload-filled:before { + content: "\e68e"; +} + +.uniui-upload:before { + content: "\e690"; +} + +.uniui-weixin:before { + content: "\e691"; +} + +.uniui-compose:before { + content: "\e67f"; +} + +.uniui-qq:before { + content: "\e680"; +} + +.uniui-download-filled:before { + content: "\e681"; +} + +.uniui-pyq:before { + content: "\e682"; +} + +.uniui-sound:before { + content: "\e684"; +} + +.uniui-trash-filled:before { + content: "\e685"; +} + +.uniui-sound-filled:before { + content: "\e686"; +} + +.uniui-trash:before { + content: "\e687"; +} + +.uniui-videocam-filled:before { + content: "\e689"; +} + +.uniui-spinner-cycle:before { + content: "\e68a"; +} + +.uniui-weibo:before { + content: "\e68b"; +} + +.uniui-videocam:before { + content: "\e68c"; +} + +.uniui-download:before { + content: "\e68d"; +} + +.uniui-help:before { + content: "\e679"; +} + +.uniui-navigate-filled:before { + content: "\e67a"; +} + +.uniui-plusempty:before { + content: "\e67b"; +} + +.uniui-smallcircle:before { + content: "\e67c"; +} + +.uniui-minus-filled:before { + content: "\e67d"; +} + +.uniui-micoff:before { + content: "\e67e"; +} + +.uniui-closeempty:before { + content: "\e66c"; +} + +.uniui-clear:before { + content: "\e66d"; +} + +.uniui-navigate:before { + content: "\e66e"; +} + +.uniui-minus:before { + content: "\e66f"; +} + +.uniui-image:before { + content: "\e670"; +} + +.uniui-mic:before { + content: "\e671"; +} + +.uniui-paperplane:before { + content: "\e672"; +} + +.uniui-close:before { + content: "\e673"; +} + +.uniui-help-filled:before { + content: "\e674"; +} + +.uniui-paperplane-filled:before { + content: "\e675"; +} + +.uniui-plus:before { + content: "\e676"; +} + +.uniui-mic-filled:before { + content: "\e677"; +} + +.uniui-image-filled:before { + content: "\e678"; +} + +.uniui-locked-filled:before { + content: "\e668"; +} + +.uniui-info:before { + content: "\e669"; +} + +.uniui-locked:before { + content: "\e66b"; +} + +.uniui-camera-filled:before { + content: "\e658"; +} + +.uniui-chat-filled:before { + content: "\e659"; +} + +.uniui-camera:before { + content: "\e65a"; +} + +.uniui-circle:before { + content: "\e65b"; +} + +.uniui-checkmarkempty:before { + content: "\e65c"; +} + +.uniui-chat:before { + content: "\e65d"; +} + +.uniui-circle-filled:before { + content: "\e65e"; +} + +.uniui-flag:before { + content: "\e65f"; +} + +.uniui-flag-filled:before { + content: "\e660"; +} + +.uniui-gear-filled:before { + content: "\e661"; +} + +.uniui-home:before { + content: "\e662"; +} + +.uniui-home-filled:before { + content: "\e663"; +} + +.uniui-gear:before { + content: "\e664"; +} + +.uniui-smallcircle-filled:before { + content: "\e665"; +} + +.uniui-map-filled:before { + content: "\e666"; +} + +.uniui-map:before { + content: "\e667"; +} + +.uniui-refresh-filled:before { + content: "\e656"; +} + +.uniui-refresh:before { + content: "\e657"; +} + +.uniui-cloud-upload:before { + content: "\e645"; +} + +.uniui-cloud-download-filled:before { + content: "\e646"; +} + +.uniui-cloud-download:before { + content: "\e647"; +} + +.uniui-cloud-upload-filled:before { + content: "\e648"; +} + +.uniui-redo:before { + content: "\e64a"; +} + +.uniui-images-filled:before { + content: "\e64b"; +} + +.uniui-undo-filled:before { + content: "\e64c"; +} + +.uniui-more:before { + content: "\e64d"; +} + +.uniui-more-filled:before { + content: "\e64e"; +} + +.uniui-undo:before { + content: "\e64f"; +} + +.uniui-images:before { + content: "\e650"; +} + +.uniui-paperclip:before { + content: "\e652"; +} + +.uniui-settings:before { + content: "\e653"; +} + +.uniui-search:before { + content: "\e654"; +} + +.uniui-redo-filled:before { + content: "\e655"; +} + +.uniui-list:before { + content: "\e644"; +} + +.uniui-mail-open-filled:before { + content: "\e63a"; +} + +.uniui-hand-down-filled:before { + content: "\e63c"; +} + +.uniui-hand-down:before { + content: "\e63d"; +} + +.uniui-hand-up-filled:before { + content: "\e63e"; +} + +.uniui-hand-up:before { + content: "\e63f"; +} + +.uniui-heart-filled:before { + content: "\e641"; +} + +.uniui-mail-open:before { + content: "\e643"; +} + +.uniui-heart:before { + content: "\e639"; +} + +.uniui-loop:before { + content: "\e633"; +} + +.uniui-pulldown:before { + content: "\e632"; +} + +.uniui-scan:before { + content: "\e62a"; +} + +.uniui-bars:before { + content: "\e627"; +} + +.uniui-checkbox:before { + content: "\e62b"; +} + +.uniui-checkbox-filled:before { + content: "\e62c"; +} + +.uniui-shop:before { + content: "\e62f"; +} + +.uniui-headphones:before { + content: "\e630"; +} + +.uniui-cart:before { + content: "\e631"; +} diff --git a/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf new file mode 100644 index 0000000..14696d0 Binary files /dev/null and b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf differ diff --git a/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts new file mode 100644 index 0000000..98e93aa --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts @@ -0,0 +1,664 @@ + +export type IconsData = { + id : string + name : string + font_family : string + css_prefix_text : string + description : string + glyphs : Array +} + +export type IconsDataItem = { + font_class : string + unicode : string +} + + +export const fontData = [ + { + "font_class": "arrow-down", + "unicode": "\ue6be" + }, + { + "font_class": "arrow-left", + "unicode": "\ue6bc" + }, + { + "font_class": "arrow-right", + "unicode": "\ue6bb" + }, + { + "font_class": "arrow-up", + "unicode": "\ue6bd" + }, + { + "font_class": "auth", + "unicode": "\ue6ab" + }, + { + "font_class": "auth-filled", + "unicode": "\ue6cc" + }, + { + "font_class": "back", + "unicode": "\ue6b9" + }, + { + "font_class": "bars", + "unicode": "\ue627" + }, + { + "font_class": "calendar", + "unicode": "\ue6a0" + }, + { + "font_class": "calendar-filled", + "unicode": "\ue6c0" + }, + { + "font_class": "camera", + "unicode": "\ue65a" + }, + { + "font_class": "camera-filled", + "unicode": "\ue658" + }, + { + "font_class": "cart", + "unicode": "\ue631" + }, + { + "font_class": "cart-filled", + "unicode": "\ue6d0" + }, + { + "font_class": "chat", + "unicode": "\ue65d" + }, + { + "font_class": "chat-filled", + "unicode": "\ue659" + }, + { + "font_class": "chatboxes", + "unicode": "\ue696" + }, + { + "font_class": "chatboxes-filled", + "unicode": "\ue692" + }, + { + "font_class": "chatbubble", + "unicode": "\ue697" + }, + { + "font_class": "chatbubble-filled", + "unicode": "\ue694" + }, + { + "font_class": "checkbox", + "unicode": "\ue62b" + }, + { + "font_class": "checkbox-filled", + "unicode": "\ue62c" + }, + { + "font_class": "checkmarkempty", + "unicode": "\ue65c" + }, + { + "font_class": "circle", + "unicode": "\ue65b" + }, + { + "font_class": "circle-filled", + "unicode": "\ue65e" + }, + { + "font_class": "clear", + "unicode": "\ue66d" + }, + { + "font_class": "close", + "unicode": "\ue673" + }, + { + "font_class": "closeempty", + "unicode": "\ue66c" + }, + { + "font_class": "cloud-download", + "unicode": "\ue647" + }, + { + "font_class": "cloud-download-filled", + "unicode": "\ue646" + }, + { + "font_class": "cloud-upload", + "unicode": "\ue645" + }, + { + "font_class": "cloud-upload-filled", + "unicode": "\ue648" + }, + { + "font_class": "color", + "unicode": "\ue6cf" + }, + { + "font_class": "color-filled", + "unicode": "\ue6c9" + }, + { + "font_class": "compose", + "unicode": "\ue67f" + }, + { + "font_class": "contact", + "unicode": "\ue693" + }, + { + "font_class": "contact-filled", + "unicode": "\ue695" + }, + { + "font_class": "down", + "unicode": "\ue6b8" + }, + { + "font_class": "bottom", + "unicode": "\ue6b8" + }, + { + "font_class": "download", + "unicode": "\ue68d" + }, + { + "font_class": "download-filled", + "unicode": "\ue681" + }, + { + "font_class": "email", + "unicode": "\ue69e" + }, + { + "font_class": "email-filled", + "unicode": "\ue69a" + }, + { + "font_class": "eye", + "unicode": "\ue651" + }, + { + "font_class": "eye-filled", + "unicode": "\ue66a" + }, + { + "font_class": "eye-slash", + "unicode": "\ue6b3" + }, + { + "font_class": "eye-slash-filled", + "unicode": "\ue6b4" + }, + { + "font_class": "fire", + "unicode": "\ue6a1" + }, + { + "font_class": "fire-filled", + "unicode": "\ue6c5" + }, + { + "font_class": "flag", + "unicode": "\ue65f" + }, + { + "font_class": "flag-filled", + "unicode": "\ue660" + }, + { + "font_class": "folder-add", + "unicode": "\ue6a9" + }, + { + "font_class": "folder-add-filled", + "unicode": "\ue6c8" + }, + { + "font_class": "font", + "unicode": "\ue6a3" + }, + { + "font_class": "forward", + "unicode": "\ue6ba" + }, + { + "font_class": "gear", + "unicode": "\ue664" + }, + { + "font_class": "gear-filled", + "unicode": "\ue661" + }, + { + "font_class": "gift", + "unicode": "\ue6a4" + }, + { + "font_class": "gift-filled", + "unicode": "\ue6c4" + }, + { + "font_class": "hand-down", + "unicode": "\ue63d" + }, + { + "font_class": "hand-down-filled", + "unicode": "\ue63c" + }, + { + "font_class": "hand-up", + "unicode": "\ue63f" + }, + { + "font_class": "hand-up-filled", + "unicode": "\ue63e" + }, + { + "font_class": "headphones", + "unicode": "\ue630" + }, + { + "font_class": "heart", + "unicode": "\ue639" + }, + { + "font_class": "heart-filled", + "unicode": "\ue641" + }, + { + "font_class": "help", + "unicode": "\ue679" + }, + { + "font_class": "help-filled", + "unicode": "\ue674" + }, + { + "font_class": "home", + "unicode": "\ue662" + }, + { + "font_class": "home-filled", + "unicode": "\ue663" + }, + { + "font_class": "image", + "unicode": "\ue670" + }, + { + "font_class": "image-filled", + "unicode": "\ue678" + }, + { + "font_class": "images", + "unicode": "\ue650" + }, + { + "font_class": "images-filled", + "unicode": "\ue64b" + }, + { + "font_class": "info", + "unicode": "\ue669" + }, + { + "font_class": "info-filled", + "unicode": "\ue649" + }, + { + "font_class": "left", + "unicode": "\ue6b7" + }, + { + "font_class": "link", + "unicode": "\ue6a5" + }, + { + "font_class": "list", + "unicode": "\ue644" + }, + { + "font_class": "location", + "unicode": "\ue6ae" + }, + { + "font_class": "location-filled", + "unicode": "\ue6af" + }, + { + "font_class": "locked", + "unicode": "\ue66b" + }, + { + "font_class": "locked-filled", + "unicode": "\ue668" + }, + { + "font_class": "loop", + "unicode": "\ue633" + }, + { + "font_class": "mail-open", + "unicode": "\ue643" + }, + { + "font_class": "mail-open-filled", + "unicode": "\ue63a" + }, + { + "font_class": "map", + "unicode": "\ue667" + }, + { + "font_class": "map-filled", + "unicode": "\ue666" + }, + { + "font_class": "map-pin", + "unicode": "\ue6ad" + }, + { + "font_class": "map-pin-ellipse", + "unicode": "\ue6ac" + }, + { + "font_class": "medal", + "unicode": "\ue6a2" + }, + { + "font_class": "medal-filled", + "unicode": "\ue6c3" + }, + { + "font_class": "mic", + "unicode": "\ue671" + }, + { + "font_class": "mic-filled", + "unicode": "\ue677" + }, + { + "font_class": "micoff", + "unicode": "\ue67e" + }, + { + "font_class": "micoff-filled", + "unicode": "\ue6b0" + }, + { + "font_class": "minus", + "unicode": "\ue66f" + }, + { + "font_class": "minus-filled", + "unicode": "\ue67d" + }, + { + "font_class": "more", + "unicode": "\ue64d" + }, + { + "font_class": "more-filled", + "unicode": "\ue64e" + }, + { + "font_class": "navigate", + "unicode": "\ue66e" + }, + { + "font_class": "navigate-filled", + "unicode": "\ue67a" + }, + { + "font_class": "notification", + "unicode": "\ue6a6" + }, + { + "font_class": "notification-filled", + "unicode": "\ue6c1" + }, + { + "font_class": "paperclip", + "unicode": "\ue652" + }, + { + "font_class": "paperplane", + "unicode": "\ue672" + }, + { + "font_class": "paperplane-filled", + "unicode": "\ue675" + }, + { + "font_class": "person", + "unicode": "\ue699" + }, + { + "font_class": "person-filled", + "unicode": "\ue69d" + }, + { + "font_class": "personadd", + "unicode": "\ue69f" + }, + { + "font_class": "personadd-filled", + "unicode": "\ue698" + }, + { + "font_class": "personadd-filled-copy", + "unicode": "\ue6d1" + }, + { + "font_class": "phone", + "unicode": "\ue69c" + }, + { + "font_class": "phone-filled", + "unicode": "\ue69b" + }, + { + "font_class": "plus", + "unicode": "\ue676" + }, + { + "font_class": "plus-filled", + "unicode": "\ue6c7" + }, + { + "font_class": "plusempty", + "unicode": "\ue67b" + }, + { + "font_class": "pulldown", + "unicode": "\ue632" + }, + { + "font_class": "pyq", + "unicode": "\ue682" + }, + { + "font_class": "qq", + "unicode": "\ue680" + }, + { + "font_class": "redo", + "unicode": "\ue64a" + }, + { + "font_class": "redo-filled", + "unicode": "\ue655" + }, + { + "font_class": "refresh", + "unicode": "\ue657" + }, + { + "font_class": "refresh-filled", + "unicode": "\ue656" + }, + { + "font_class": "refreshempty", + "unicode": "\ue6bf" + }, + { + "font_class": "reload", + "unicode": "\ue6b2" + }, + { + "font_class": "right", + "unicode": "\ue6b5" + }, + { + "font_class": "scan", + "unicode": "\ue62a" + }, + { + "font_class": "search", + "unicode": "\ue654" + }, + { + "font_class": "settings", + "unicode": "\ue653" + }, + { + "font_class": "settings-filled", + "unicode": "\ue6ce" + }, + { + "font_class": "shop", + "unicode": "\ue62f" + }, + { + "font_class": "shop-filled", + "unicode": "\ue6cd" + }, + { + "font_class": "smallcircle", + "unicode": "\ue67c" + }, + { + "font_class": "smallcircle-filled", + "unicode": "\ue665" + }, + { + "font_class": "sound", + "unicode": "\ue684" + }, + { + "font_class": "sound-filled", + "unicode": "\ue686" + }, + { + "font_class": "spinner-cycle", + "unicode": "\ue68a" + }, + { + "font_class": "staff", + "unicode": "\ue6a7" + }, + { + "font_class": "staff-filled", + "unicode": "\ue6cb" + }, + { + "font_class": "star", + "unicode": "\ue688" + }, + { + "font_class": "star-filled", + "unicode": "\ue68f" + }, + { + "font_class": "starhalf", + "unicode": "\ue683" + }, + { + "font_class": "trash", + "unicode": "\ue687" + }, + { + "font_class": "trash-filled", + "unicode": "\ue685" + }, + { + "font_class": "tune", + "unicode": "\ue6aa" + }, + { + "font_class": "tune-filled", + "unicode": "\ue6ca" + }, + { + "font_class": "undo", + "unicode": "\ue64f" + }, + { + "font_class": "undo-filled", + "unicode": "\ue64c" + }, + { + "font_class": "up", + "unicode": "\ue6b6" + }, + { + "font_class": "top", + "unicode": "\ue6b6" + }, + { + "font_class": "upload", + "unicode": "\ue690" + }, + { + "font_class": "upload-filled", + "unicode": "\ue68e" + }, + { + "font_class": "videocam", + "unicode": "\ue68c" + }, + { + "font_class": "videocam-filled", + "unicode": "\ue689" + }, + { + "font_class": "vip", + "unicode": "\ue6a8" + }, + { + "font_class": "vip-filled", + "unicode": "\ue6c6" + }, + { + "font_class": "wallet", + "unicode": "\ue6b1" + }, + { + "font_class": "wallet-filled", + "unicode": "\ue6c2" + }, + { + "font_class": "weibo", + "unicode": "\ue68b" + }, + { + "font_class": "weixin", + "unicode": "\ue691" + } +] as IconsDataItem[] + +// export const fontData = JSON.parse(fontDataJson) diff --git a/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js new file mode 100644 index 0000000..1cd11e1 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js @@ -0,0 +1,649 @@ + +export const fontData = [ + { + "font_class": "arrow-down", + "unicode": "\ue6be" + }, + { + "font_class": "arrow-left", + "unicode": "\ue6bc" + }, + { + "font_class": "arrow-right", + "unicode": "\ue6bb" + }, + { + "font_class": "arrow-up", + "unicode": "\ue6bd" + }, + { + "font_class": "auth", + "unicode": "\ue6ab" + }, + { + "font_class": "auth-filled", + "unicode": "\ue6cc" + }, + { + "font_class": "back", + "unicode": "\ue6b9" + }, + { + "font_class": "bars", + "unicode": "\ue627" + }, + { + "font_class": "calendar", + "unicode": "\ue6a0" + }, + { + "font_class": "calendar-filled", + "unicode": "\ue6c0" + }, + { + "font_class": "camera", + "unicode": "\ue65a" + }, + { + "font_class": "camera-filled", + "unicode": "\ue658" + }, + { + "font_class": "cart", + "unicode": "\ue631" + }, + { + "font_class": "cart-filled", + "unicode": "\ue6d0" + }, + { + "font_class": "chat", + "unicode": "\ue65d" + }, + { + "font_class": "chat-filled", + "unicode": "\ue659" + }, + { + "font_class": "chatboxes", + "unicode": "\ue696" + }, + { + "font_class": "chatboxes-filled", + "unicode": "\ue692" + }, + { + "font_class": "chatbubble", + "unicode": "\ue697" + }, + { + "font_class": "chatbubble-filled", + "unicode": "\ue694" + }, + { + "font_class": "checkbox", + "unicode": "\ue62b" + }, + { + "font_class": "checkbox-filled", + "unicode": "\ue62c" + }, + { + "font_class": "checkmarkempty", + "unicode": "\ue65c" + }, + { + "font_class": "circle", + "unicode": "\ue65b" + }, + { + "font_class": "circle-filled", + "unicode": "\ue65e" + }, + { + "font_class": "clear", + "unicode": "\ue66d" + }, + { + "font_class": "close", + "unicode": "\ue673" + }, + { + "font_class": "closeempty", + "unicode": "\ue66c" + }, + { + "font_class": "cloud-download", + "unicode": "\ue647" + }, + { + "font_class": "cloud-download-filled", + "unicode": "\ue646" + }, + { + "font_class": "cloud-upload", + "unicode": "\ue645" + }, + { + "font_class": "cloud-upload-filled", + "unicode": "\ue648" + }, + { + "font_class": "color", + "unicode": "\ue6cf" + }, + { + "font_class": "color-filled", + "unicode": "\ue6c9" + }, + { + "font_class": "compose", + "unicode": "\ue67f" + }, + { + "font_class": "contact", + "unicode": "\ue693" + }, + { + "font_class": "contact-filled", + "unicode": "\ue695" + }, + { + "font_class": "down", + "unicode": "\ue6b8" + }, + { + "font_class": "bottom", + "unicode": "\ue6b8" + }, + { + "font_class": "download", + "unicode": "\ue68d" + }, + { + "font_class": "download-filled", + "unicode": "\ue681" + }, + { + "font_class": "email", + "unicode": "\ue69e" + }, + { + "font_class": "email-filled", + "unicode": "\ue69a" + }, + { + "font_class": "eye", + "unicode": "\ue651" + }, + { + "font_class": "eye-filled", + "unicode": "\ue66a" + }, + { + "font_class": "eye-slash", + "unicode": "\ue6b3" + }, + { + "font_class": "eye-slash-filled", + "unicode": "\ue6b4" + }, + { + "font_class": "fire", + "unicode": "\ue6a1" + }, + { + "font_class": "fire-filled", + "unicode": "\ue6c5" + }, + { + "font_class": "flag", + "unicode": "\ue65f" + }, + { + "font_class": "flag-filled", + "unicode": "\ue660" + }, + { + "font_class": "folder-add", + "unicode": "\ue6a9" + }, + { + "font_class": "folder-add-filled", + "unicode": "\ue6c8" + }, + { + "font_class": "font", + "unicode": "\ue6a3" + }, + { + "font_class": "forward", + "unicode": "\ue6ba" + }, + { + "font_class": "gear", + "unicode": "\ue664" + }, + { + "font_class": "gear-filled", + "unicode": "\ue661" + }, + { + "font_class": "gift", + "unicode": "\ue6a4" + }, + { + "font_class": "gift-filled", + "unicode": "\ue6c4" + }, + { + "font_class": "hand-down", + "unicode": "\ue63d" + }, + { + "font_class": "hand-down-filled", + "unicode": "\ue63c" + }, + { + "font_class": "hand-up", + "unicode": "\ue63f" + }, + { + "font_class": "hand-up-filled", + "unicode": "\ue63e" + }, + { + "font_class": "headphones", + "unicode": "\ue630" + }, + { + "font_class": "heart", + "unicode": "\ue639" + }, + { + "font_class": "heart-filled", + "unicode": "\ue641" + }, + { + "font_class": "help", + "unicode": "\ue679" + }, + { + "font_class": "help-filled", + "unicode": "\ue674" + }, + { + "font_class": "home", + "unicode": "\ue662" + }, + { + "font_class": "home-filled", + "unicode": "\ue663" + }, + { + "font_class": "image", + "unicode": "\ue670" + }, + { + "font_class": "image-filled", + "unicode": "\ue678" + }, + { + "font_class": "images", + "unicode": "\ue650" + }, + { + "font_class": "images-filled", + "unicode": "\ue64b" + }, + { + "font_class": "info", + "unicode": "\ue669" + }, + { + "font_class": "info-filled", + "unicode": "\ue649" + }, + { + "font_class": "left", + "unicode": "\ue6b7" + }, + { + "font_class": "link", + "unicode": "\ue6a5" + }, + { + "font_class": "list", + "unicode": "\ue644" + }, + { + "font_class": "location", + "unicode": "\ue6ae" + }, + { + "font_class": "location-filled", + "unicode": "\ue6af" + }, + { + "font_class": "locked", + "unicode": "\ue66b" + }, + { + "font_class": "locked-filled", + "unicode": "\ue668" + }, + { + "font_class": "loop", + "unicode": "\ue633" + }, + { + "font_class": "mail-open", + "unicode": "\ue643" + }, + { + "font_class": "mail-open-filled", + "unicode": "\ue63a" + }, + { + "font_class": "map", + "unicode": "\ue667" + }, + { + "font_class": "map-filled", + "unicode": "\ue666" + }, + { + "font_class": "map-pin", + "unicode": "\ue6ad" + }, + { + "font_class": "map-pin-ellipse", + "unicode": "\ue6ac" + }, + { + "font_class": "medal", + "unicode": "\ue6a2" + }, + { + "font_class": "medal-filled", + "unicode": "\ue6c3" + }, + { + "font_class": "mic", + "unicode": "\ue671" + }, + { + "font_class": "mic-filled", + "unicode": "\ue677" + }, + { + "font_class": "micoff", + "unicode": "\ue67e" + }, + { + "font_class": "micoff-filled", + "unicode": "\ue6b0" + }, + { + "font_class": "minus", + "unicode": "\ue66f" + }, + { + "font_class": "minus-filled", + "unicode": "\ue67d" + }, + { + "font_class": "more", + "unicode": "\ue64d" + }, + { + "font_class": "more-filled", + "unicode": "\ue64e" + }, + { + "font_class": "navigate", + "unicode": "\ue66e" + }, + { + "font_class": "navigate-filled", + "unicode": "\ue67a" + }, + { + "font_class": "notification", + "unicode": "\ue6a6" + }, + { + "font_class": "notification-filled", + "unicode": "\ue6c1" + }, + { + "font_class": "paperclip", + "unicode": "\ue652" + }, + { + "font_class": "paperplane", + "unicode": "\ue672" + }, + { + "font_class": "paperplane-filled", + "unicode": "\ue675" + }, + { + "font_class": "person", + "unicode": "\ue699" + }, + { + "font_class": "person-filled", + "unicode": "\ue69d" + }, + { + "font_class": "personadd", + "unicode": "\ue69f" + }, + { + "font_class": "personadd-filled", + "unicode": "\ue698" + }, + { + "font_class": "personadd-filled-copy", + "unicode": "\ue6d1" + }, + { + "font_class": "phone", + "unicode": "\ue69c" + }, + { + "font_class": "phone-filled", + "unicode": "\ue69b" + }, + { + "font_class": "plus", + "unicode": "\ue676" + }, + { + "font_class": "plus-filled", + "unicode": "\ue6c7" + }, + { + "font_class": "plusempty", + "unicode": "\ue67b" + }, + { + "font_class": "pulldown", + "unicode": "\ue632" + }, + { + "font_class": "pyq", + "unicode": "\ue682" + }, + { + "font_class": "qq", + "unicode": "\ue680" + }, + { + "font_class": "redo", + "unicode": "\ue64a" + }, + { + "font_class": "redo-filled", + "unicode": "\ue655" + }, + { + "font_class": "refresh", + "unicode": "\ue657" + }, + { + "font_class": "refresh-filled", + "unicode": "\ue656" + }, + { + "font_class": "refreshempty", + "unicode": "\ue6bf" + }, + { + "font_class": "reload", + "unicode": "\ue6b2" + }, + { + "font_class": "right", + "unicode": "\ue6b5" + }, + { + "font_class": "scan", + "unicode": "\ue62a" + }, + { + "font_class": "search", + "unicode": "\ue654" + }, + { + "font_class": "settings", + "unicode": "\ue653" + }, + { + "font_class": "settings-filled", + "unicode": "\ue6ce" + }, + { + "font_class": "shop", + "unicode": "\ue62f" + }, + { + "font_class": "shop-filled", + "unicode": "\ue6cd" + }, + { + "font_class": "smallcircle", + "unicode": "\ue67c" + }, + { + "font_class": "smallcircle-filled", + "unicode": "\ue665" + }, + { + "font_class": "sound", + "unicode": "\ue684" + }, + { + "font_class": "sound-filled", + "unicode": "\ue686" + }, + { + "font_class": "spinner-cycle", + "unicode": "\ue68a" + }, + { + "font_class": "staff", + "unicode": "\ue6a7" + }, + { + "font_class": "staff-filled", + "unicode": "\ue6cb" + }, + { + "font_class": "star", + "unicode": "\ue688" + }, + { + "font_class": "star-filled", + "unicode": "\ue68f" + }, + { + "font_class": "starhalf", + "unicode": "\ue683" + }, + { + "font_class": "trash", + "unicode": "\ue687" + }, + { + "font_class": "trash-filled", + "unicode": "\ue685" + }, + { + "font_class": "tune", + "unicode": "\ue6aa" + }, + { + "font_class": "tune-filled", + "unicode": "\ue6ca" + }, + { + "font_class": "undo", + "unicode": "\ue64f" + }, + { + "font_class": "undo-filled", + "unicode": "\ue64c" + }, + { + "font_class": "up", + "unicode": "\ue6b6" + }, + { + "font_class": "top", + "unicode": "\ue6b6" + }, + { + "font_class": "upload", + "unicode": "\ue690" + }, + { + "font_class": "upload-filled", + "unicode": "\ue68e" + }, + { + "font_class": "videocam", + "unicode": "\ue68c" + }, + { + "font_class": "videocam-filled", + "unicode": "\ue689" + }, + { + "font_class": "vip", + "unicode": "\ue6a8" + }, + { + "font_class": "vip-filled", + "unicode": "\ue6c6" + }, + { + "font_class": "wallet", + "unicode": "\ue6b1" + }, + { + "font_class": "wallet-filled", + "unicode": "\ue6c2" + }, + { + "font_class": "weibo", + "unicode": "\ue68b" + }, + { + "font_class": "weixin", + "unicode": "\ue691" + } +] + +// export const fontData = JSON.parse(fontDataJson) diff --git a/gym-manage-uniapp/uni_modules/uni-icons/package.json b/gym-manage-uniapp/uni_modules/uni-icons/package.json new file mode 100644 index 0000000..60e45f0 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-icons/package.json @@ -0,0 +1,111 @@ +{ + "id": "uni-icons", + "displayName": "uni-icons 图标", + "version": "2.0.12", + "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。", + "keywords": [ + "uni-ui", + "uniui", + "icon", + "图标" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.2.14", + "uni-app": "^4.08", + "uni-app-x": "^4.61" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x" + }, + "uni_modules": { + "dependencies": [ + "uni-scss" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app": { + "vue": { + "vue2": "√", + "vue3": "√" + }, + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "vue": "√", + "nvue": "-", + "android": { + "extVersion": "", + "minVersion": "29" + }, + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√", + "alipay": "√", + "toutiao": "√", + "baidu": "√", + "kuaishou": "-", + "jd": "-", + "harmony": "-", + "qq": "√", + "lark": "-" + }, + "quickapp": { + "huawei": "√", + "union": "√" + } + }, + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": { + "extVersion": "", + "minVersion": "29" + }, + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} \ No newline at end of file diff --git a/gym-manage-uniapp/uni_modules/uni-icons/readme.md b/gym-manage-uniapp/uni_modules/uni-icons/readme.md new file mode 100644 index 0000000..86234ba --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-icons/readme.md @@ -0,0 +1,8 @@ +## Icons 图标 +> **组件名:uni-icons** +> 代码块: `uIcons` + +用于展示 icons 图标 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/gym-manage-uniapp/uni_modules/uni-scss/changelog.md b/gym-manage-uniapp/uni_modules/uni-scss/changelog.md new file mode 100644 index 0000000..b863bb0 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/changelog.md @@ -0,0 +1,8 @@ +## 1.0.3(2022-01-21) +- 优化 组件示例 +## 1.0.2(2021-11-22) +- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题 +## 1.0.1(2021-11-22) +- 修复 vue3中scss语法兼容问题 +## 1.0.0(2021-11-18) +- init diff --git a/gym-manage-uniapp/uni_modules/uni-scss/index.scss b/gym-manage-uniapp/uni_modules/uni-scss/index.scss new file mode 100644 index 0000000..1744a5f --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/index.scss @@ -0,0 +1 @@ +@import './styles/index.scss'; diff --git a/gym-manage-uniapp/uni_modules/uni-scss/package.json b/gym-manage-uniapp/uni_modules/uni-scss/package.json new file mode 100644 index 0000000..7cc0ccb --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-scss", + "displayName": "uni-scss 辅助样式", + "version": "1.0.3", + "description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。", + "keywords": [ + "uni-scss", + "uni-ui", + "辅助样式" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/gym-manage-uniapp/uni_modules/uni-scss/readme.md b/gym-manage-uniapp/uni_modules/uni-scss/readme.md new file mode 100644 index 0000000..b7d1c25 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/readme.md @@ -0,0 +1,4 @@ +`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/index.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/index.scss new file mode 100644 index 0000000..ffac4fe --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/index.scss @@ -0,0 +1,7 @@ +@import './setting/_variables.scss'; +@import './setting/_border.scss'; +@import './setting/_color.scss'; +@import './setting/_space.scss'; +@import './setting/_radius.scss'; +@import './setting/_text.scss'; +@import './setting/_styles.scss'; diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_border.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_border.scss new file mode 100644 index 0000000..12a11c3 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_border.scss @@ -0,0 +1,3 @@ +.uni-border { + border: 1px $uni-border-1 solid; +} \ No newline at end of file diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_color.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_color.scss new file mode 100644 index 0000000..1ededd9 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_color.scss @@ -0,0 +1,66 @@ + +// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐 +// @mixin get-styles($k,$c) { +// @if $k == size or $k == weight{ +// font-#{$k}:#{$c} +// }@else{ +// #{$k}:#{$c} +// } +// } +$uni-ui-color:( + // 主色 + primary: $uni-primary, + primary-disable: $uni-primary-disable, + primary-light: $uni-primary-light, + // 辅助色 + success: $uni-success, + success-disable: $uni-success-disable, + success-light: $uni-success-light, + warning: $uni-warning, + warning-disable: $uni-warning-disable, + warning-light: $uni-warning-light, + error: $uni-error, + error-disable: $uni-error-disable, + error-light: $uni-error-light, + info: $uni-info, + info-disable: $uni-info-disable, + info-light: $uni-info-light, + // 中性色 + main-color: $uni-main-color, + base-color: $uni-base-color, + secondary-color: $uni-secondary-color, + extra-color: $uni-extra-color, + // 背景色 + bg-color: $uni-bg-color, + // 边框颜色 + border-1: $uni-border-1, + border-2: $uni-border-2, + border-3: $uni-border-3, + border-4: $uni-border-4, + // 黑色 + black:$uni-black, + // 白色 + white:$uni-white, + // 透明 + transparent:$uni-transparent +) !default; +@each $key, $child in $uni-ui-color { + .uni-#{"" + $key} { + color: $child; + } + .uni-#{"" + $key}-bg { + background-color: $child; + } +} +.uni-shadow-sm { + box-shadow: $uni-shadow-sm; +} +.uni-shadow-base { + box-shadow: $uni-shadow-base; +} +.uni-shadow-lg { + box-shadow: $uni-shadow-lg; +} +.uni-mask { + background-color:$uni-mask; +} diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss new file mode 100644 index 0000000..9a0428b --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss @@ -0,0 +1,55 @@ +@mixin radius($r,$d:null ,$important: false){ + $radius-value:map-get($uni-radius, $r) if($important, !important, null); + // Key exists within the $uni-radius variable + @if (map-has-key($uni-radius, $r) and $d){ + @if $d == t { + border-top-left-radius:$radius-value; + border-top-right-radius:$radius-value; + }@else if $d == r { + border-top-right-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == b { + border-bottom-left-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == l { + border-top-left-radius:$radius-value; + border-bottom-left-radius:$radius-value; + }@else if $d == tl { + border-top-left-radius:$radius-value; + }@else if $d == tr { + border-top-right-radius:$radius-value; + }@else if $d == br { + border-bottom-right-radius:$radius-value; + }@else if $d == bl { + border-bottom-left-radius:$radius-value; + } + }@else{ + border-radius:$radius-value; + } +} + +@each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $key} { + @include radius($key) + } + }@else{ + .uni-radius { + @include radius($key) + } + } +} + +@each $direction in t, r, b, l,tl, tr, br, bl { + @each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $direction}-#{"" + $key} { + @include radius($key,$direction,false) + } + }@else{ + .uni-radius-#{$direction} { + @include radius($key,$direction,false) + } + } + } +} diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_space.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_space.scss new file mode 100644 index 0000000..3c89528 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_space.scss @@ -0,0 +1,56 @@ + +@mixin fn($space,$direction,$size,$n) { + @if $n { + #{$space}-#{$direction}: #{$size*$uni-space-root}px + } @else { + #{$space}-#{$direction}: #{-$size*$uni-space-root}px + } +} +@mixin get-styles($direction,$i,$space,$n){ + @if $direction == t { + @include fn($space, top,$i,$n); + } + @if $direction == r { + @include fn($space, right,$i,$n); + } + @if $direction == b { + @include fn($space, bottom,$i,$n); + } + @if $direction == l { + @include fn($space, left,$i,$n); + } + @if $direction == x { + @include fn($space, left,$i,$n); + @include fn($space, right,$i,$n); + } + @if $direction == y { + @include fn($space, top,$i,$n); + @include fn($space, bottom,$i,$n); + } + @if $direction == a { + @if $n { + #{$space}:#{$i*$uni-space-root}px; + } @else { + #{$space}:#{-$i*$uni-space-root}px; + } + } +} + +@each $orientation in m,p { + $space: margin; + @if $orientation == m { + $space: margin; + } @else { + $space: padding; + } + @for $i from 0 through 16 { + @each $direction in t, r, b, l, x, y, a { + .uni-#{$orientation}#{$direction}-#{$i} { + @include get-styles($direction,$i,$space,true); + } + .uni-#{$orientation}#{$direction}-n#{$i} { + @include get-styles($direction,$i,$space,false); + } + } + } +} \ No newline at end of file diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss new file mode 100644 index 0000000..689afec --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss @@ -0,0 +1,167 @@ +/* #ifndef APP-NVUE */ + +$-color-white:#fff; +$-color-black:#000; +@mixin base-style($color) { + color: #fff; + background-color: $color; + border-color: mix($-color-black, $color, 8%); + &:not([hover-class]):active { + background: mix($-color-black, $color, 10%); + border-color: mix($-color-black, $color, 20%); + color: $-color-white; + outline: none; + } +} +@mixin is-color($color) { + @include base-style($color); + &[loading] { + @include base-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &[loading], + &:not([hover-class]):active { + color: $-color-white; + border-color: mix(darken($color,10%), $-color-white); + background-color: mix($color, $-color-white); + } + } + +} +@mixin base-plain-style($color) { + color:$color; + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 70%); + &:not([hover-class]):active { + background: mix($-color-white, $color, 80%); + color: $color; + outline: none; + border-color: mix($-color-white, $color, 50%); + } +} +@mixin is-plain($color){ + &[plain] { + @include base-plain-style($color); + &[loading] { + @include base-plain-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &:active { + color: mix($-color-white, $color, 40%); + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 80%); + } + } + } +} + + +.uni-btn { + margin: 5px; + color: #393939; + border:1px solid #ccc; + font-size: 16px; + font-weight: 200; + background-color: #F9F9F9; + // TODO 暂时处理边框隐藏一边的问题 + overflow: visible; + &::after{ + border: none; + } + + &:not([type]),&[type=default] { + color: #999; + &[loading] { + background: none; + &::before { + margin-right:5px; + } + } + + + + &[disabled]{ + color: mix($-color-white, #999, 60%); + &, + &[loading], + &:active { + color: mix($-color-white, #999, 60%); + background-color: mix($-color-white,$-color-black , 98%); + border-color: mix($-color-white, #999, 85%); + } + } + + &[plain] { + color: #999; + background: none; + border-color: $uni-border-1; + &:not([hover-class]):active { + background: none; + color: mix($-color-white, $-color-black, 80%); + border-color: mix($-color-white, $-color-black, 90%); + outline: none; + } + &[disabled]{ + &, + &[loading], + &:active { + background: none; + color: mix($-color-white, #999, 60%); + border-color: mix($-color-white, #999, 85%); + } + } + } + } + + &:not([hover-class]):active { + color: mix($-color-white, $-color-black, 50%); + } + + &[size=mini] { + font-size: 16px; + font-weight: 200; + border-radius: 8px; + } + + + + &.uni-btn-small { + font-size: 14px; + } + &.uni-btn-mini { + font-size: 12px; + } + + &.uni-btn-radius { + border-radius: 999px; + } + &[type=primary] { + @include is-color($uni-primary); + @include is-plain($uni-primary) + } + &[type=success] { + @include is-color($uni-success); + @include is-plain($uni-success) + } + &[type=error] { + @include is-color($uni-error); + @include is-plain($uni-error) + } + &[type=warning] { + @include is-color($uni-warning); + @include is-plain($uni-warning) + } + &[type=info] { + @include is-color($uni-info); + @include is-plain($uni-info) + } +} +/* #endif */ diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_text.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_text.scss new file mode 100644 index 0000000..a34d08f --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_text.scss @@ -0,0 +1,24 @@ +@mixin get-styles($k,$c) { + @if $k == size or $k == weight{ + font-#{$k}:#{$c} + }@else{ + #{$k}:#{$c} + } +} + +@each $key, $child in $uni-headings { + /* #ifndef APP-NVUE */ + .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ + /* #ifdef APP-NVUE */ + .container .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ +} diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss new file mode 100644 index 0000000..557d3d7 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss @@ -0,0 +1,146 @@ +// @use "sass:math"; +@import '../tools/functions.scss'; +// 间距基础倍数 +$uni-space-root: 2 !default; +// 边框半径默认值 +$uni-radius-root:5px !default; +$uni-radius: () !default; +// 边框半径断点 +$uni-radius: map-deep-merge( + ( + 0: 0, + // TODO 当前版本暂时不支持 sm 属性 + // 'sm': math.div($uni-radius-root, 2), + null: $uni-radius-root, + 'lg': $uni-radius-root * 2, + 'xl': $uni-radius-root * 6, + 'pill': 9999px, + 'circle': 50% + ), + $uni-radius +); +// 字体家族 +$body-font-family: 'Roboto', sans-serif !default; +// 文本 +$heading-font-family: $body-font-family !default; +$uni-headings: () !default; +$letterSpacing: -0.01562em; +$uni-headings: map-deep-merge( + ( + 'h1': ( + size: 32px, + weight: 300, + line-height: 50px, + // letter-spacing:-0.01562em + ), + 'h2': ( + size: 28px, + weight: 300, + line-height: 40px, + // letter-spacing: -0.00833em + ), + 'h3': ( + size: 24px, + weight: 400, + line-height: 32px, + // letter-spacing: normal + ), + 'h4': ( + size: 20px, + weight: 400, + line-height: 30px, + // letter-spacing: 0.00735em + ), + 'h5': ( + size: 16px, + weight: 400, + line-height: 24px, + // letter-spacing: normal + ), + 'h6': ( + size: 14px, + weight: 500, + line-height: 18px, + // letter-spacing: 0.0125em + ), + 'subtitle': ( + size: 12px, + weight: 400, + line-height: 20px, + // letter-spacing: 0.00937em + ), + 'body': ( + font-size: 14px, + font-weight: 400, + line-height: 22px, + // letter-spacing: 0.03125em + ), + 'caption': ( + 'size': 12px, + 'weight': 400, + 'line-height': 20px, + // 'letter-spacing': 0.03333em, + // 'text-transform': false + ) + ), + $uni-headings +); + + + +// 主色 +$uni-primary: #2979ff !default; +$uni-primary-disable:lighten($uni-primary,20%) !default; +$uni-primary-light: lighten($uni-primary,25%) !default; + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37 !default; +$uni-success-disable:lighten($uni-success,20%) !default; +$uni-success-light: lighten($uni-success,25%) !default; + +$uni-warning: #f3a73f !default; +$uni-warning-disable:lighten($uni-warning,20%) !default; +$uni-warning-light: lighten($uni-warning,25%) !default; + +$uni-error: #e43d33 !default; +$uni-error-disable:lighten($uni-error,20%) !default; +$uni-error-light: lighten($uni-error,25%) !default; + +$uni-info: #8f939c !default; +$uni-info-disable:lighten($uni-info,20%) !default; +$uni-info-light: lighten($uni-info,25%) !default; + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a !default; // 主要文字 +$uni-base-color: #6a6a6a !default; // 常规文字 +$uni-secondary-color: #909399 !default; // 次要文字 +$uni-extra-color: #c7c7c7 !default; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0 !default; +$uni-border-2: #EDEDED !default; +$uni-border-3: #DCDCDC !default; +$uni-border-4: #B9B9B9 !default; + +// 常规色 +$uni-black: #000000 !default; +$uni-white: #ffffff !default; +$uni-transparent: rgba($color: #000000, $alpha: 0) !default; + +// 背景色 +$uni-bg-color: #f7f7f7 !default; + +/* 水平间距 */ +$uni-spacing-sm: 8px !default; +$uni-spacing-base: 15px !default; +$uni-spacing-lg: 30px !default; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default; +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default; +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default; + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4) !default; diff --git a/gym-manage-uniapp/uni_modules/uni-scss/styles/tools/functions.scss b/gym-manage-uniapp/uni_modules/uni-scss/styles/tools/functions.scss new file mode 100644 index 0000000..ac6f63e --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/styles/tools/functions.scss @@ -0,0 +1,19 @@ +// 合并 map +@function map-deep-merge($parent-map, $child-map){ + $result: $parent-map; + @each $key, $child in $child-map { + $parent-has-key: map-has-key($result, $key); + $parent-value: map-get($result, $key); + $parent-type: type-of($parent-value); + $child-type: type-of($child); + $parent-is-map: $parent-type == map; + $child-is-map: $child-type == map; + + @if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){ + $result: map-merge($result, ( $key: $child )); + }@else { + $result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) )); + } + } + @return $result; +}; diff --git a/gym-manage-uniapp/uni_modules/uni-scss/theme.scss b/gym-manage-uniapp/uni_modules/uni-scss/theme.scss new file mode 100644 index 0000000..80ee62f --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/theme.scss @@ -0,0 +1,31 @@ +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; +// 主色 +$uni-primary: #2979ff; +// 辅助色 +$uni-success: #4cd964; +// 警告色 +$uni-warning: #f0ad4e; +// 错误色 +$uni-error: #dd524d; +// 描述色 +$uni-info: #909399; +// 中性色 +$uni-main-color: #303133; +$uni-base-color: #606266; +$uni-secondary-color: #909399; +$uni-extra-color: #C0C4CC; +// 背景色 +$uni-bg-color: #f5f5f5; +// 边框颜色 +$uni-border-1: #DCDFE6; +$uni-border-2: #E4E7ED; +$uni-border-3: #EBEEF5; +$uni-border-4: #F2F6FC; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); diff --git a/gym-manage-uniapp/uni_modules/uni-scss/variables.scss b/gym-manage-uniapp/uni_modules/uni-scss/variables.scss new file mode 100644 index 0000000..1c062d4 --- /dev/null +++ b/gym-manage-uniapp/uni_modules/uni-scss/variables.scss @@ -0,0 +1,62 @@ +@import './styles/setting/_variables.scss'; +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; + +// 主色 +$uni-primary: #2979ff; +$uni-primary-disable:mix(#fff,$uni-primary,50%); +$uni-primary-light: mix(#fff,$uni-primary,80%); + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37; +$uni-success-disable:mix(#fff,$uni-success,50%); +$uni-success-light: mix(#fff,$uni-success,80%); + +$uni-warning: #f3a73f; +$uni-warning-disable:mix(#fff,$uni-warning,50%); +$uni-warning-light: mix(#fff,$uni-warning,80%); + +$uni-error: #e43d33; +$uni-error-disable:mix(#fff,$uni-error,50%); +$uni-error-light: mix(#fff,$uni-error,80%); + +$uni-info: #8f939c; +$uni-info-disable:mix(#fff,$uni-info,50%); +$uni-info-light: mix(#fff,$uni-info,80%); + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a; // 主要文字 +$uni-base-color: #6a6a6a; // 常规文字 +$uni-secondary-color: #909399; // 次要文字 +$uni-extra-color: #c7c7c7; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0; +$uni-border-2: #EDEDED; +$uni-border-3: #DCDCDC; +$uni-border-4: #B9B9B9; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); + +// 背景色 +$uni-bg-color: #f7f7f7; + +/* 水平间距 */ +$uni-spacing-sm: 8px; +$uni-spacing-base: 15px; +$uni-spacing-lg: 30px; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5); +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2); +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4); diff --git a/gym-manage-uniapp/utils/request.js b/gym-manage-uniapp/utils/request.js new file mode 100644 index 0000000..b545527 --- /dev/null +++ b/gym-manage-uniapp/utils/request.js @@ -0,0 +1,87 @@ +const luchRequest = require('luch-request') +const Request = luchRequest.default || luchRequest +const { generateSignatureHeaders } = require('./signature') +const store = require('../store/index') + +const BASE_URL = 'http://192.168.101.5:8084/api' + +const http = new Request({ + baseURL: BASE_URL, + timeout: 15000, + header: { + 'Content-Type': 'application/json' + } +}) + +// 请求拦截器 +http.interceptors.request.use( + (config) => { + // 注入 JWT token + const token = store.getToken() + if (token) { + config.header = config.header || {} + config.header.Authorization = 'Bearer ' + token + } + + // 注入签名头 + const method = (config.method || 'GET').toUpperCase() + const url = config.url || '' + const body = config.data + + const signatureHeaders = generateSignatureHeaders(method, url, body) + config.header = config.header || {} + Object.assign(config.header, signatureHeaders) + + return config + }, + (error) => Promise.reject(error) +) + +// 响应拦截器 +http.interceptors.response.use( + (response) => { + // 直接返回 data,统一解包 + if (response.statusCode === 200) { + return response.data + } + // 非200状态码:构造错误对象,确保 catch 块能正确读取 message + const error = new Error(response.data?.message || '请求失败') + error.statusCode = response.statusCode + error.data = response.data + return Promise.reject(error) + }, + (error) => { + // 401 处理:清除登录态,跳转登录页 + if (error.statusCode === 401) { + store.clearLogin() + const pages = getCurrentPages() + const currentPage = pages[pages.length - 1] + if (currentPage && currentPage.route !== 'pages/login/login') { + uni.reLaunch({ url: '/pages/login/login' }) + } + } + return Promise.reject(error) + } +) + +/** + * 将 coverImage 字段值解析为完整图片 URL + * - /api/files/{id}/preview → 拼接 baseURL 后返回 + * - 纯数字 → 构造 /files/{id}/preview 后拼接 baseURL + * - 其他 → 原样返回 + */ +function resolveCoverUrl(coverImage) { + if (!coverImage) return '' + // 新格式:/api/files/{id}/preview + if (/^\/api\/files\/\d+\/preview$/.test(coverImage)) { + return BASE_URL + coverImage.substring(4) // 去掉 /api + } + // 旧格式:纯数字 ID + if (/^\d+$/.test(coverImage)) { + return BASE_URL + '/files/' + coverImage + '/preview' + } + return coverImage +} + +module.exports = http +module.exports.resolveCoverUrl = resolveCoverUrl diff --git a/gym-manage-uniapp/utils/signature.js b/gym-manage-uniapp/utils/signature.js new file mode 100644 index 0000000..f7d4e2d --- /dev/null +++ b/gym-manage-uniapp/utils/signature.js @@ -0,0 +1,60 @@ +const CryptoJS = require('crypto-js') + +const SIGNATURE_SECRET = 'NovalonManageSystemSecretKey2026' + +/** + * 生成 HMAC-SHA256 签名 + */ +function generateSignature(method, path, query, body, timestamp, nonce) { + const stringToSign = [method, path, query || '', body || '', String(timestamp), nonce].join('\n') + const signature = CryptoJS.HmacSHA256(stringToSign, SIGNATURE_SECRET) + return CryptoJS.enc.Base64.stringify(signature) +} + +/** + * 生成随机 nonce + */ +function generateNonce() { + const timestamp = Date.now().toString(36) + const randomPart = Math.random().toString(36).substring(2, 15) + return timestamp + '-' + randomPart +} + +/** + * 从 URL 解析 path 和 query + */ +function parseUrl(url) { + if (url.startsWith('http://') || url.startsWith('https://')) { + // 小程序环境无 URL 构造函数,手动解析 + const withoutProtocol = url.substring(url.indexOf('://') + 3) + const pathStart = withoutProtocol.indexOf('/') + if (pathStart === -1) return { path: '/', query: '' } + const pathAndQuery = withoutProtocol.substring(pathStart) + const queryIndex = pathAndQuery.indexOf('?') + if (queryIndex === -1) return { path: pathAndQuery, query: '' } + return { path: pathAndQuery.substring(0, queryIndex), query: pathAndQuery.substring(queryIndex + 1) } + } + const queryIndex = url.indexOf('?') + if (queryIndex === -1) return { path: url, query: '' } + return { path: url.substring(0, queryIndex), query: url.substring(queryIndex + 1) } +} + +/** + * 生成签名请求头 + */ +function generateSignatureHeaders(method, url, body) { + const timestamp = Date.now() + const nonce = generateNonce() + const { path, query } = parseUrl(url) + const bodyStr = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : '' + + const signature = generateSignature(method.toUpperCase(), path, query, bodyStr, timestamp, nonce) + + return { + 'X-Signature': signature, + 'X-Timestamp': String(timestamp), + 'X-Nonce': nonce + } +} + +module.exports = { generateSignatureHeaders } diff --git a/gym-manage-web/src/api/banner.api.ts b/gym-manage-web/src/api/banner.api.ts new file mode 100644 index 0000000..6ff8835 --- /dev/null +++ b/gym-manage-web/src/api/banner.api.ts @@ -0,0 +1,33 @@ +import request from '@/utils/request' + +export interface Banner { + id?: number + imageUrl: string + title: string + subtitle?: string + sortOrder?: number + isActive?: boolean + linkUrl?: string + createdAt?: string + updatedAt?: string +} + +export const bannerApi = { + getAll: () => + request.get('/banners'), + + getActive: () => + request.get('/banners/active'), + + getById: (id: number) => + request.get(`/banners/${id}`), + + create: (data: Banner) => + request.post('/banners', data), + + update: (id: number, data: Banner) => + request.put(`/banners/${id}`, data), + + delete: (id: number) => + request.delete(`/banners/${id}`), +} diff --git a/gym-manage-web/src/api/coach.api.ts b/gym-manage-web/src/api/coach.api.ts new file mode 100644 index 0000000..2a293da --- /dev/null +++ b/gym-manage-web/src/api/coach.api.ts @@ -0,0 +1,73 @@ +import request from '@/utils/request' +export { type GroupCourse } from '@/api/groupCourse.api' + +export interface Coach { + id: string + username: string + nickname: string + email: string + phone: string + avatar: string + status: number + createdAt: string + updatedAt: string +} + +export interface CoachCreateRequest { + username: string + password: string + nickname: string + email: string + phone: string +} + +export interface CoachUpdateRequest { + nickname?: string + email?: string + phone?: string +} + +export interface ViolationCountItem { + coach_id: number + count: number +} + +export interface ViolationRecord { + id: number + coach_id: number + course_id: number + violation_time: string + violation_reason: string + course_name?: string + created_at: string +} + +export const coachApi = { + /** 获取所有教练列表 */ + getAll: () => + request.get('/coach/list'), + + /** 创建教练(自动分配教练角色) */ + create: (data: CoachCreateRequest) => + request.post('/coach', data), + + /** 更新教练信息 */ + update: (id: string, data: CoachUpdateRequest) => + request.put(`/coach/${id}`, data), + + /** 禁用教练 */ + disable: (id: string) => + request.post(`/coach/${id}/disable`), + + /** 获取教练教授的团课列表 */ + getCourses: (id: string) => + request.get(`/coach/${id}/courses`), + + /** 获取所有教练的违规次数统计 */ + getViolationCounts: () => + request.get('/coach/violation-counts'), + + /** 获取指定教练的违规记录 */ + getViolations: (id: string) => + request.get(`/coach/${id}/violations`), +} diff --git a/gym-manage-web/src/api/groupCourse.api.ts b/gym-manage-web/src/api/groupCourse.api.ts index f1a061d..2b6db6e 100644 --- a/gym-manage-web/src/api/groupCourse.api.ts +++ b/gym-manage-web/src/api/groupCourse.api.ts @@ -5,7 +5,8 @@ import request from '@/utils/request' export interface GroupCourse { id?: number courseName: string - coachId?: number + coachId?: number | string + coachName?: string courseType?: number startTime: string endTime: string @@ -31,7 +32,7 @@ export interface GroupCoursePageRequest { keyword?: string status?: string courseType?: number - coachId?: number + coachId?: number | string startTime?: string endTime?: string sortBy?: string @@ -75,6 +76,18 @@ export const groupCourseApi = { search: (params: any) => request.post('/groupCourse/search', params), + + checkCoachConflict: (params: { + coachId: number | string + startTime: string + endTime: string + excludeCourseId?: number | string + }) => + request.post<{ + success: boolean + hasConflict: boolean + conflicts: GroupCourse[] + }>('/groupCourse/check-coach-conflict', params), } // ==================== 团课类型 ==================== diff --git a/gym-manage-web/src/api/statistics.api.ts b/gym-manage-web/src/api/statistics.api.ts index 53f36e4..e19ffd5 100644 --- a/gym-manage-web/src/api/statistics.api.ts +++ b/gym-manage-web/src/api/statistics.api.ts @@ -36,11 +36,22 @@ export interface SignInStatistics { faceSignIns: number } +export interface CoachStatistics { + totalCoaches: number + totalViolations: number + lateCount: number + absentCount: number + notManualEndCount: number + violatedCoaches: number + totalCourses: number +} + export interface StatisticsSummary { statDate: string memberStatistics: MemberStatistics bookingStatistics: BookingStatistics signInStatistics: SignInStatistics + coachStatistics: CoachStatistics generatedAt: string } diff --git a/gym-manage-web/src/router/index.ts b/gym-manage-web/src/router/index.ts index 303e2ea..5ca01bb 100644 --- a/gym-manage-web/src/router/index.ts +++ b/gym-manage-web/src/router/index.ts @@ -40,6 +40,12 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/system/UserManagement.vue'), meta: { title: '用户管理' } }, + { + path: 'coach', + name: 'CoachManagement', + component: () => import('@/views/system/CoachManagement.vue'), + meta: { title: '教练管理' } + }, { path: 'roles', name: 'RoleManagement', @@ -76,6 +82,12 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/notify/NoticeManagement.vue'), meta: { title: '通知公告' } }, + { + path: 'banners', + name: 'BannerManagement', + component: () => import('@/views/banner/BannerManagement.vue'), + meta: { title: '轮播图管理' } + }, { path: 'loginlog', name: 'LoginLog', diff --git a/gym-manage-web/src/stores/permission.ts b/gym-manage-web/src/stores/permission.ts index 513427e..dc063a2 100644 --- a/gym-manage-web/src/stores/permission.ts +++ b/gym-manage-web/src/stores/permission.ts @@ -31,6 +31,7 @@ function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] { 'system/user/index': '/users', 'system/role/index': '/roles', 'system/menu/index': '/menus', + 'system/coach/index': '/coach', 'system/dict/index': '/dict', 'system/config/index': '/sys/config', 'system/notice/index': '/notice', @@ -45,6 +46,7 @@ function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] { 'member/member/index': '/members', 'member/card/index': '/member-cards', 'statistics/dashboard/index': '/statistics', + 'banner/index': '/banners', } const filteredMenus = backendMenus.filter(menu => menu.menuType !== 'F') @@ -91,6 +93,7 @@ function getMenuIcon(menuName: string): string { '审计日志': 'Document', '系统监控': 'Monitor', '用户管理': 'User', + '教练管理': 'Medal', '角色管理': 'UserFilled', '菜单管理': 'Menu', '字典管理': 'Collection', @@ -107,6 +110,7 @@ function getMenuIcon(menuName: string): string { '会员管理': 'Avatar', '会员卡管理': 'CreditCard', '数据统计': 'DataAnalysis', + '轮播图管理': 'Picture', } return iconMap[menuName] || 'Document' } diff --git a/gym-manage-web/src/views/banner/BannerManagement.vue b/gym-manage-web/src/views/banner/BannerManagement.vue new file mode 100644 index 0000000..3d6e56a --- /dev/null +++ b/gym-manage-web/src/views/banner/BannerManagement.vue @@ -0,0 +1,454 @@ + + + + + diff --git a/gym-manage-web/src/views/groupcourse/GroupCourseManagement.vue b/gym-manage-web/src/views/groupcourse/GroupCourseManagement.vue index 570695d..e479e0e 100644 --- a/gym-manage-web/src/views/groupcourse/GroupCourseManagement.vue +++ b/gym-manage-web/src/views/groupcourse/GroupCourseManagement.vue @@ -27,11 +27,16 @@ - + + + 搜索 + + + 新增团课 @@ -64,6 +69,11 @@ {{ getCourseTypeName(row.courseType) }} + + + - + + + + + + {{ detailRow.id }} + {{ detailRow.courseName }} + {{ getCourseTypeName(detailRow.courseType) }} + {{ getCoachDisplayName(detailRow.coachId) || detailRow.coachName || '-' }} + + + {{ statusMap[detailRow.status || '']?.label || detailRow.status }} + + + {{ detailRow.location || '-' }} + {{ formatDateTime(detailRow.startTime) }} + {{ formatDateTime(detailRow.actualStartTime) || '-' }} + {{ formatDateTime(detailRow.endTime) }} + {{ formatDateTime(detailRow.actualEndTime) || '-' }} + {{ detailRow.currentMembers || 0 }} / {{ detailRow.maxMembers || 0 }} + {{ detailRow.storedValueAmount ?? '-' }} + + + - + + {{ detailRow.description || '-' }} + {{ detailRow.createBy || '-' }} + {{ detailRow.updateBy || '-' }} + {{ formatDateTime(detailRow.createdAt) }} + {{ formatDateTime(detailRow.updatedAt) }} + + + @@ -707,4 +910,11 @@ onMounted(() => { color: #909399; font-size: 14px; } + +.detail-cover { + width: 120px; + height: 80px; + object-fit: cover; + border-radius: 4px; +} diff --git a/gym-manage-web/src/views/statistics/StatisticsDashboard.vue b/gym-manage-web/src/views/statistics/StatisticsDashboard.vue index 42efa28..6cb96ef 100644 --- a/gym-manage-web/src/views/statistics/StatisticsDashboard.vue +++ b/gym-manage-web/src/views/statistics/StatisticsDashboard.vue @@ -50,9 +50,9 @@ - -
签到总数
-
{{ summary?.signInStatistics?.totalSignIns ?? '-' }}
+ +
教练违规
+
{{ summary?.coachStatistics?.totalViolations ?? '-' }}
@@ -192,12 +192,65 @@ + + + + + + +
+ 教练总数 + {{ summary?.coachStatistics?.totalCoaches ?? '-' }} +
+
+ +
+ 违规教练 + + {{ summary?.coachStatistics?.violatedCoaches ?? '-' }} + +
+
+ +
+ 开课总数 + {{ summary?.coachStatistics?.totalCourses ?? '-' }} +
+
+ +
+ 迟到 + + {{ summary?.coachStatistics?.lateCount ?? '-' }} + +
+
+ +
+ 缺席 + + {{ summary?.coachStatistics?.absentCount ?? '-' }} + +
+
+ +
+ 未手动结课 + + {{ summary?.coachStatistics?.notManualEndCount ?? '-' }} + +
+
+
+
@@ -350,6 +415,7 @@ onMounted(() => { .stat-card--success .stat-card__value { color: #67c23a; } .stat-card--warning .stat-card__value { color: #e6a23c; } .stat-card--info .stat-card__value { color: #909399; } +.stat-card--danger .stat-card__value { color: #f56c6c; } /* ---- 明细分区 ---- */ .section-card { @@ -380,6 +446,14 @@ onMounted(() => { color: #303133; } +.metric-value--warn { + color: #e6a23c; +} + +.metric-value--danger { + color: #f56c6c; +} + .rate-group { display: flex; flex-direction: column; diff --git a/gym-manage-web/src/views/system/CoachManagement.vue b/gym-manage-web/src/views/system/CoachManagement.vue new file mode 100644 index 0000000..d809efa --- /dev/null +++ b/gym-manage-web/src/views/system/CoachManagement.vue @@ -0,0 +1,522 @@ + + + + + diff --git a/scripts/add_banner_menu.sql b/scripts/add_banner_menu.sql new file mode 100644 index 0000000..19f46b2 --- /dev/null +++ b/scripts/add_banner_menu.sql @@ -0,0 +1,29 @@ +-- ============================================ +-- 轮播图管理 - 菜单数据插入(可独立执行) +-- 使用方法: 直接在数据库中运行此 SQL +-- ============================================ + +-- 轮播图管理菜单项(顶级菜单 parent_id=0) +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) +SELECT 19, '轮播图管理', 0, 8, 'C', 'system:banner:list', 'banner/index', 1, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE id = 19); + +-- 按钮权限 +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) +SELECT 191, '轮播图查询', 19, 1, 'F', 'system:banner:query', NULL, 1, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE id = 191); + +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) +SELECT 192, '轮播图新增', 19, 2, 'F', 'system:banner:add', NULL, 1, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE id = 192); + +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) +SELECT 193, '轮播图修改', 19, 3, 'F', 'system:banner:edit', NULL, 1, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE id = 193); + +INSERT INTO sys_menu (id, menu_name, parent_id, order_num, menu_type, perms, component, status, created_at, updated_at) +SELECT 194, '轮播图删除', 19, 4, 'F', 'system:banner:remove', NULL, 1, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE id = 194); + +-- 重置序列 +SELECT setval('sys_menu_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_menu)); diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V10__Create_GroupCourse_Type_table.sql b/temp_migration_backup/V10__Create_GroupCourse_Type_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V10__Create_GroupCourse_Type_table.sql rename to temp_migration_backup/V10__Create_GroupCourse_Type_table.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V11__Create_Course_Label_tables.sql b/temp_migration_backup/V11__Create_Course_Label_tables.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V11__Create_Course_Label_tables.sql rename to temp_migration_backup/V11__Create_Course_Label_tables.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V12__Create_GroupCourse_Recommend_table.sql b/temp_migration_backup/V12__Create_GroupCourse_Recommend_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V12__Create_GroupCourse_Recommend_table.sql rename to temp_migration_backup/V12__Create_GroupCourse_Recommend_table.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql b/temp_migration_backup/V13__Consolidated_Test_Data.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql rename to temp_migration_backup/V13__Consolidated_Test_Data.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V19__Add_GroupCourse_QRCode_Path.sql b/temp_migration_backup/V19__Add_GroupCourse_QRCode_Path.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V19__Add_GroupCourse_QRCode_Path.sql rename to temp_migration_backup/V19__Add_GroupCourse_QRCode_Path.sql diff --git a/temp_migration_backup/V1__Create_all_tables.sql b/temp_migration_backup/V1__Create_all_tables.sql new file mode 100644 index 0000000..ab538c2 --- /dev/null +++ b/temp_migration_backup/V1__Create_all_tables.sql @@ -0,0 +1,407 @@ +-- Novalon管理系统数据库初始化脚本 +-- 版本: V1 +-- 描述: 创建所有核心表结构(合并版) + +-- ============================================ +-- 用户与角色相关表 +-- ============================================ + +-- 用户表 +CREATE TABLE IF NOT EXISTS sys_user ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + email VARCHAR(100), + phone VARCHAR(20), + nickname VARCHAR(100), + status INTEGER DEFAULT 1, + role_id BIGINT, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 角色表 +CREATE TABLE IF NOT EXISTS sys_role ( + id BIGSERIAL PRIMARY KEY, + role_name VARCHAR(100) NOT NULL, + role_key VARCHAR(100) NOT NULL UNIQUE, + role_sort INTEGER DEFAULT 0, + status INTEGER DEFAULT 1, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 用户角色关联表(支持多对多关系) +CREATE TABLE IF NOT EXISTS user_role ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by VARCHAR(50), + CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id) ON DELETE CASCADE, + CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE, + CONSTRAINT uk_user_role UNIQUE (user_id, role_id) +); + +-- ============================================ +-- 权限相关表 +-- ============================================ + +-- 权限表 +CREATE TABLE IF NOT EXISTS sys_permission ( + id BIGSERIAL PRIMARY KEY, + permission_name VARCHAR(100) NOT NULL, + permission_code VARCHAR(100) NOT NULL UNIQUE, + resource VARCHAR(200) NOT NULL, + action VARCHAR(50) NOT NULL, + description VARCHAR(500), + status INTEGER DEFAULT 1, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 角色权限关联表 +CREATE TABLE IF NOT EXISTS sys_role_permission ( + id BIGSERIAL PRIMARY KEY, + role_id BIGINT NOT NULL, + permission_id BIGINT NOT NULL, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE, + FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE, + UNIQUE (role_id, permission_id) +); + +-- ============================================ +-- 菜单相关表 +-- ============================================ + +-- 菜单表 +CREATE TABLE IF NOT EXISTS sys_menu ( + id BIGSERIAL PRIMARY KEY, + menu_name VARCHAR(50) NOT NULL, + parent_id BIGINT DEFAULT 0, + order_num INTEGER DEFAULT 0, + menu_type VARCHAR(1) DEFAULT 'C', + perms VARCHAR(100), + component VARCHAR(200), + status INTEGER DEFAULT 1, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- ============================================ +-- 字典相关表 +-- ============================================ + +-- 字典类型表 +CREATE TABLE IF NOT EXISTS sys_dict_type ( + id BIGSERIAL PRIMARY KEY, + dict_name VARCHAR(100) NOT NULL, + dict_type VARCHAR(100) NOT NULL UNIQUE, + status VARCHAR(1) DEFAULT '0', + remark VARCHAR(500), + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 字典数据表 +CREATE TABLE IF NOT EXISTS sys_dict_data ( + id BIGSERIAL PRIMARY KEY, + dict_sort INTEGER DEFAULT 0, + dict_label VARCHAR(100) NOT NULL, + dict_value VARCHAR(100) NOT NULL, + dict_type VARCHAR(100) NOT NULL, + css_class VARCHAR(100), + list_class VARCHAR(100), + is_default VARCHAR(1) DEFAULT 'N', + status VARCHAR(1) DEFAULT '0', + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 字典表(通用字典) +CREATE TABLE IF NOT EXISTS sys_dictionary ( + id BIGSERIAL PRIMARY KEY, + type VARCHAR(100) NOT NULL, + code VARCHAR(100) NOT NULL, + name VARCHAR(100) NOT NULL, + value VARCHAR(500), + remark VARCHAR(500), + sort INTEGER DEFAULT 0, + create_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- ============================================ +-- 系统配置表 +-- ============================================ + +-- 系统配置表 +CREATE TABLE IF NOT EXISTS sys_config ( + id BIGSERIAL PRIMARY KEY, + config_name VARCHAR(100) NOT NULL, + config_key VARCHAR(100) NOT NULL UNIQUE, + config_value VARCHAR(500) NOT NULL, + config_type VARCHAR(1) DEFAULT 'N', + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- ============================================ +-- 日志相关表 +-- ============================================ + +-- 登录日志表 +CREATE TABLE IF NOT EXISTS sys_login_log ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50), + ip VARCHAR(50), + location VARCHAR(255), + browser VARCHAR(50), + os VARCHAR(50), + status VARCHAR(1), + message VARCHAR(255), + login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 异常日志表 +CREATE TABLE IF NOT EXISTS sys_exception_log ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50), + title VARCHAR(100), + exception_name VARCHAR(100), + method_name VARCHAR(255), + method_params TEXT, + exception_msg TEXT, + exception_stack TEXT, + ip VARCHAR(50), + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 操作日志表 +CREATE TABLE IF NOT EXISTS operation_log ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50), + operation VARCHAR(100), + method VARCHAR(200), + params TEXT, + result TEXT, + ip VARCHAR(50), + duration BIGINT, + status VARCHAR(1) DEFAULT '0', + error_msg TEXT, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 审计日志表 +CREATE TABLE IF NOT EXISTS audit_log ( + id BIGSERIAL PRIMARY KEY, + entity_type VARCHAR(100) NOT NULL, + entity_id BIGINT, + operation_type VARCHAR(20) NOT NULL, + operator VARCHAR(100), + operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + before_data JSONB, + after_data JSONB, + changed_fields TEXT[], + ip_address VARCHAR(50), + user_agent TEXT, + description TEXT, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 审计日志归档表 +CREATE TABLE IF NOT EXISTS audit_log_archive ( + id BIGSERIAL PRIMARY KEY, + entity_type VARCHAR(100) NOT NULL, + entity_id BIGINT, + operation_type VARCHAR(20) NOT NULL, + operator VARCHAR(100), + operation_time TIMESTAMP, + before_data JSONB, + after_data JSONB, + changed_fields TEXT[], + ip_address VARCHAR(50), + user_agent TEXT, + description TEXT, + created_at TIMESTAMP, + archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ============================================ +-- 通知与消息表 +-- ============================================ + +-- 系统公告表 +CREATE TABLE IF NOT EXISTS sys_notice ( + id BIGSERIAL PRIMARY KEY, + notice_title VARCHAR(50) NOT NULL, + notice_type VARCHAR(1) NOT NULL, + notice_content TEXT, + status VARCHAR(1) DEFAULT '0', + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 用户消息表 +CREATE TABLE IF NOT EXISTS sys_user_message ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + notice_id BIGINT, + message_title VARCHAR(255), + message_content TEXT, + is_read VARCHAR(1) DEFAULT '0', + read_time TIMESTAMP, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- ============================================ +-- 文件管理表 +-- ============================================ + +-- 文件管理表 +CREATE TABLE IF NOT EXISTS sys_file ( + id BIGSERIAL PRIMARY KEY, + file_name VARCHAR(255) NOT NULL, + file_path VARCHAR(500) NOT NULL, + file_size BIGINT, + file_type VARCHAR(100), + file_extension VARCHAR(10), + storage_type VARCHAR(50), + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- ============================================ +-- OAuth2相关表 +-- ============================================ + +-- OAuth2客户端表 +CREATE TABLE IF NOT EXISTS oauth2_client ( + id BIGSERIAL PRIMARY KEY, + client_id VARCHAR(100) NOT NULL UNIQUE, + client_secret VARCHAR(255) NOT NULL, + client_name VARCHAR(100), + web_server_redirect_uri VARCHAR(500), + scope VARCHAR(500), + authorized_grant_types VARCHAR(500), + access_token_validity_seconds INTEGER, + refresh_token_validity_seconds INTEGER, + auto_approve VARCHAR(1) DEFAULT 'false', + enabled VARCHAR(1) DEFAULT 'true', + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- ============================================ +-- 表注释 +-- ============================================ + +COMMENT ON TABLE sys_user IS '系统用户表'; +COMMENT ON TABLE sys_role IS '系统角色表'; +COMMENT ON TABLE user_role IS '用户角色关联表'; +COMMENT ON TABLE sys_permission IS '系统权限表'; +COMMENT ON TABLE sys_role_permission IS '角色权限关联表'; +COMMENT ON TABLE sys_menu IS '系统菜单表'; +COMMENT ON TABLE sys_dict_type IS '字典类型表'; +COMMENT ON TABLE sys_dict_data IS '字典数据表'; +COMMENT ON TABLE sys_dictionary IS '通用字典表'; +COMMENT ON TABLE sys_config IS '系统配置表'; +COMMENT ON TABLE sys_login_log IS '登录日志表'; +COMMENT ON TABLE sys_exception_log IS '异常日志表'; +COMMENT ON TABLE operation_log IS '操作日志表'; +COMMENT ON TABLE audit_log IS '审计日志表'; +COMMENT ON TABLE audit_log_archive IS '审计日志归档表'; +COMMENT ON TABLE sys_notice IS '系统公告表'; +COMMENT ON TABLE sys_user_message IS '用户消息表'; +COMMENT ON TABLE sys_file IS '文件管理表'; +COMMENT ON TABLE oauth2_client IS 'OAuth2客户端表'; + +COMMENT ON TABLE sys_exception_log IS '异常日志表'; +COMMENT ON COLUMN sys_exception_log.id IS '主键ID'; +COMMENT ON COLUMN sys_exception_log.username IS '操作用户'; +COMMENT ON COLUMN sys_exception_log.title IS '异常标题'; +COMMENT ON COLUMN sys_exception_log.exception_name IS '异常名称'; +COMMENT ON COLUMN sys_exception_log.method_name IS '方法名称'; +COMMENT ON COLUMN sys_exception_log.method_params IS '方法参数'; +COMMENT ON COLUMN sys_exception_log.exception_msg IS '异常消息'; +COMMENT ON COLUMN sys_exception_log.exception_stack IS '异常堆栈'; +COMMENT ON COLUMN sys_exception_log.ip IS 'IP地址'; +COMMENT ON COLUMN sys_exception_log.create_time IS '创建时间'; + +COMMENT ON TABLE audit_log IS '审计日志表'; +COMMENT ON COLUMN audit_log.id IS '主键ID'; +COMMENT ON COLUMN audit_log.entity_type IS '实体类型(如User, Role等)'; +COMMENT ON COLUMN audit_log.entity_id IS '实体ID'; +COMMENT ON COLUMN audit_log.operation_type IS '操作类型(CREATE, UPDATE, DELETE)'; +COMMENT ON COLUMN audit_log.operator IS '操作人'; +COMMENT ON COLUMN audit_log.operation_time IS '操作时间'; +COMMENT ON COLUMN audit_log.before_data IS '变更前数据(JSON格式)'; +COMMENT ON COLUMN audit_log.after_data IS '变更后数据(JSON格式)'; +COMMENT ON COLUMN audit_log.changed_fields IS '变更字段列表'; +COMMENT ON COLUMN audit_log.ip_address IS 'IP地址'; +COMMENT ON COLUMN audit_log.description IS '操作描述'; +COMMENT ON COLUMN audit_log.created_at IS '记录创建时间'; + +COMMENT ON TABLE audit_log_archive IS '审计日志归档表'; +COMMENT ON COLUMN audit_log_archive.id IS '主键ID'; +COMMENT ON COLUMN audit_log_archive.entity_type IS '实体类型(如User, Role等)'; +COMMENT ON COLUMN audit_log_archive.entity_id IS '实体ID'; +COMMENT ON COLUMN audit_log_archive.operation_type IS '操作类型(CREATE, UPDATE, DELETE)'; +COMMENT ON COLUMN audit_log_archive.operator IS '操作人'; +COMMENT ON COLUMN audit_log_archive.operation_time IS '操作时间'; +COMMENT ON COLUMN audit_log_archive.before_data IS '变更前数据(JSON格式)'; +COMMENT ON COLUMN audit_log_archive.after_data IS '变更后数据(JSON格式)'; +COMMENT ON COLUMN audit_log_archive.changed_fields IS '变更字段列表'; +COMMENT ON COLUMN audit_log_archive.ip_address IS 'IP地址'; +COMMENT ON COLUMN audit_log_archive.user_agent IS '用户代理'; +COMMENT ON COLUMN audit_log_archive.description IS '操作描述'; +COMMENT ON COLUMN audit_log_archive.created_at IS '记录创建时间'; +COMMENT ON COLUMN audit_log_archive.archived_at IS '归档时间'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_deleted_at_to_member_user.sql b/temp_migration_backup/V20__Add_deleted_at_to_member_user.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_deleted_at_to_member_user.sql rename to temp_migration_backup/V20__Add_deleted_at_to_member_user.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V21__Add_updated_at_to_member_card_transactions.sql b/temp_migration_backup/V21__Add_updated_at_to_member_card_transactions.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V21__Add_updated_at_to_member_card_transactions.sql rename to temp_migration_backup/V21__Add_updated_at_to_member_card_transactions.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_Business_Permissions.sql b/temp_migration_backup/V22__Add_Business_Permissions.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_Business_Permissions.sql rename to temp_migration_backup/V22__Add_Business_Permissions.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql b/temp_migration_backup/V23__Add_Coach_Test_Data.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql rename to temp_migration_backup/V23__Add_Coach_Test_Data.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql b/temp_migration_backup/V24__Add_Second_Coach_Test_Data.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql rename to temp_migration_backup/V24__Add_Second_Coach_Test_Data.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V25__Create_Payment_Order_table.sql b/temp_migration_backup/V25__Create_Payment_Order_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V25__Create_Payment_Order_table.sql rename to temp_migration_backup/V25__Create_Payment_Order_table.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V26__Add_party_order_id_to_payment_order.sql b/temp_migration_backup/V26__Add_party_order_id_to_payment_order.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V26__Add_party_order_id_to_payment_order.sql rename to temp_migration_backup/V26__Add_party_order_id_to_payment_order.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V27__Create_StoredCard_And_PayPassword_table.sql b/temp_migration_backup/V27__Create_StoredCard_And_PayPassword_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V27__Create_StoredCard_And_PayPassword_table.sql rename to temp_migration_backup/V27__Create_StoredCard_And_PayPassword_table.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V28__Add_Business_Menus.sql b/temp_migration_backup/V28__Add_Business_Menus.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V28__Add_Business_Menus.sql rename to temp_migration_backup/V28__Add_Business_Menus.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V29__Add_Member_Management_Menu.sql b/temp_migration_backup/V29__Add_Member_Management_Menu.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V29__Add_Member_Management_Menu.sql rename to temp_migration_backup/V29__Add_Member_Management_Menu.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V2__Insert_initial_data.sql b/temp_migration_backup/V2__Insert_initial_data.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V2__Insert_initial_data.sql rename to temp_migration_backup/V2__Insert_initial_data.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V30__Add_Member_Card_Management_Menu.sql b/temp_migration_backup/V30__Add_Member_Card_Management_Menu.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V30__Add_Member_Card_Management_Menu.sql rename to temp_migration_backup/V30__Add_Member_Card_Management_Menu.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V31__Add_Statistics_Menu.sql b/temp_migration_backup/V31__Add_Statistics_Menu.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V31__Add_Statistics_Menu.sql rename to temp_migration_backup/V31__Add_Statistics_Menu.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V32__Make_member_card_id_nullable.sql b/temp_migration_backup/V32__Make_member_card_id_nullable.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V32__Make_member_card_id_nullable.sql rename to temp_migration_backup/V32__Make_member_card_id_nullable.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V3__Create_indexes.sql b/temp_migration_backup/V3__Create_indexes.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V3__Create_indexes.sql rename to temp_migration_backup/V3__Create_indexes.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V4__Grant_permissions.sql b/temp_migration_backup/V4__Grant_permissions.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V4__Grant_permissions.sql rename to temp_migration_backup/V4__Grant_permissions.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V5__Create_operation_log_table.sql b/temp_migration_backup/V5__Create_operation_log_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V5__Create_operation_log_table.sql rename to temp_migration_backup/V5__Create_operation_log_table.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql b/temp_migration_backup/V6__Create_GroupCourse_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql rename to temp_migration_backup/V6__Create_GroupCourse_table.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V7__Create_Member_And_MemberCard.sql b/temp_migration_backup/V7__Create_Member_And_MemberCard.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V7__Create_Member_And_MemberCard.sql rename to temp_migration_backup/V7__Create_Member_And_MemberCard.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V8__Add_GroupCourse_Booking_Snapshot_Fields.sql b/temp_migration_backup/V8__Add_GroupCourse_Booking_Snapshot_Fields.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V8__Add_GroupCourse_Booking_Snapshot_Fields.sql rename to temp_migration_backup/V8__Add_GroupCourse_Booking_Snapshot_Fields.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V9__Create_sign_in_record_table.sql b/temp_migration_backup/V9__Create_sign_in_record_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V9__Create_sign_in_record_table.sql rename to temp_migration_backup/V9__Create_sign_in_record_table.sql