Procházet zdrojové kódy

Phase1: 模块化基础——train_module表+track字段

liaoxg před 1 týdnem
rodič
revize
8b2d5dad5d

+ 3 - 0
train-backend/src/main/java/com/train/controller/admin/AdminCourseController.java

@@ -105,6 +105,9 @@ public class AdminCourseController {
         if (c.getMemberPrice() != null) {
             exist.setMemberPrice(c.getMemberPrice());
         }
+        if (c.getTrack() != null) {
+            exist.setTrack(c.getTrack());
+        }
         trainCourseMapper.updateById(exist);
         return Result.success(true);
     }

+ 148 - 0
train-backend/src/main/java/com/train/controller/admin/AdminModuleController.java

@@ -0,0 +1,148 @@
+package com.train.controller.admin;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.train.common.Result;
+import com.train.entity.TrainCourse;
+import com.train.entity.TrainModule;
+import com.train.mapper.TrainCourseMapper;
+import com.train.mapper.TrainModuleMapper;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 管理端-课程模块(模块化教学:L1/L2 按三本账方向拆模块 + 插班制)。
+ * <p>模块挂课程(course_id),管理端维护模块增删改/排序/启用;
+ * 模块价(分)为插班按模块收费的支付来源。
+ */
+@Tag(name = "管理端-课程模块", description = "模块列表、新建、编辑、删除、排序")
+@RestController
+@RequestMapping("/api/admin/module")
+public class AdminModuleController {
+
+    @Resource
+    private TrainModuleMapper trainModuleMapper;
+    @Resource
+    private TrainCourseMapper trainCourseMapper;
+
+    @Operation(summary = "模块列表(按课程,可按学习路径过滤)")
+    @PostMapping("/list")
+    public Result<List<TrainModule>> list(@RequestBody Map<String, Object> body) {
+        if (body == null || body.get("courseId") == null) {
+            return Result.error("缺少课程ID");
+        }
+        Long courseId = Long.valueOf(body.get("courseId").toString());
+        LambdaQueryWrapper<TrainModule> wrapper = new LambdaQueryWrapper<TrainModule>()
+                .eq(TrainModule::getCourseId, courseId);
+        if (body.get("track") != null && StringUtils.hasText(body.get("track").toString())) {
+            wrapper.eq(TrainModule::getTrack, body.get("track").toString());
+        }
+        wrapper.orderByAsc(TrainModule::getSort).orderByAsc(TrainModule::getId);
+        return Result.success(trainModuleMapper.selectList(wrapper));
+    }
+
+    @Operation(summary = "新建课程模块")
+    @PostMapping("/create")
+    public Result<TrainModule> create(@RequestBody TrainModule module) {
+        if (module.getCourseId() == null) {
+            return Result.error("缺少课程ID");
+        }
+        TrainCourse exist = trainCourseMapper.selectById(module.getCourseId());
+        if (exist == null) {
+            return Result.error("课程不存在");
+        }
+        if (!StringUtils.hasText(module.getName())) {
+            return Result.error("模块名称不能为空");
+        }
+        module.setName(module.getName().trim());
+        if (module.getPrice() == null) {
+            module.setPrice(0);
+        }
+        if (module.getSort() == null) {
+            module.setSort(0);
+        }
+        if (module.getEnabled() == null) {
+            module.setEnabled(1);
+        }
+        trainModuleMapper.insert(module);
+        return Result.success(module);
+    }
+
+    @Operation(summary = "编辑课程模块(名称/说明/路径/价格/排序/启用)")
+    @PostMapping("/update")
+    public Result<Boolean> update(@RequestBody TrainModule module) {
+        if (module.getId() == null) {
+            return Result.error("缺少模块ID");
+        }
+        TrainModule exist = trainModuleMapper.selectById(module.getId());
+        if (exist == null) {
+            return Result.error("模块不存在");
+        }
+        // 仅更新允许编辑的字段,其余保持原值
+        if (StringUtils.hasText(module.getName())) {
+            exist.setName(module.getName().trim());
+        }
+        if (module.getDescription() != null) {
+            exist.setDescription(module.getDescription());
+        }
+        if (module.getTrack() != null) {
+            exist.setTrack(module.getTrack());
+        }
+        if (module.getPrice() != null) {
+            exist.setPrice(module.getPrice());
+        }
+        if (module.getSort() != null) {
+            exist.setSort(module.getSort());
+        }
+        if (module.getEnabled() != null) {
+            exist.setEnabled(module.getEnabled());
+        }
+        trainModuleMapper.updateById(exist);
+        return Result.success(true);
+    }
+
+    @Operation(summary = "删除课程模块")
+    @PostMapping("/delete")
+    public Result<Boolean> delete(@RequestBody Map<String, Object> body) {
+        if (body == null || body.get("id") == null) {
+            return Result.error("缺少模块ID");
+        }
+        Long id = Long.valueOf(body.get("id").toString());
+        trainModuleMapper.deleteById(id);
+        return Result.success(true);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Operation(summary = "模块排序(items: [{id, sort}])")
+    @PostMapping("/sort")
+    public Result<Boolean> sort(@RequestBody Map<String, Object> body) {
+        if (body == null || body.get("items") == null) {
+            return Result.error("缺少排序列表");
+        }
+        List<Map<String, Object>> items = (List<Map<String, Object>>) body.get("items");
+        if (items.isEmpty()) {
+            return Result.error("排序列表不能为空");
+        }
+        for (Map<String, Object> item : items) {
+            Object idObj = item.get("id");
+            Object sortObj = item.get("sort");
+            if (idObj == null || sortObj == null) {
+                continue;
+            }
+            TrainModule exist = trainModuleMapper.selectById(Long.valueOf(idObj.toString()));
+            if (exist != null) {
+                exist.setSort(Integer.valueOf(sortObj.toString()));
+                trainModuleMapper.updateById(exist);
+            }
+        }
+        return Result.success(true);
+    }
+}

+ 3 - 0
train-backend/src/main/java/com/train/entity/TrainCourse.java

@@ -44,6 +44,9 @@ public class TrainCourse implements Serializable {
     /** 课程会员价(分) */
     private Integer memberPrice;
 
+    /** 学习路径/轨道:wealth/health/growth(模块化方向,空=主线通用课程如 L0) */
+    private String track;
+
     /** 计算字段(不落库):按时间推导的有效状态 upcoming/active/finished;draft/finished 为管理员手动覆盖 */
     @com.baomidou.mybatisplus.annotation.TableField(exist = false)
     private String effectiveStatus;

+ 47 - 0
train-backend/src/main/java/com/train/entity/TrainModule.java

@@ -0,0 +1,47 @@
+package com.train.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 课程模块(模块化教学)。
+ * 一个课程可拆分为多个模块(如 L1-A1 财富诊断、L1-A2 财富自动化),
+ * 模块挂课程(course_id),按三本账方向(wealth/health/growth)分组,
+ * 支持模块化插班与按模块收费(price 单位分,展示时除以 100)。
+ */
+@Data
+@TableName("train_module")
+public class TrainModule implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 所属课程ID(train_course.id) */
+    private Long courseId;
+
+    /** 学习路径/轨道:wealth/health/growth(空=通用模块) */
+    private String track;
+
+    /** 模块名称 */
+    private String name;
+
+    /** 模块说明 */
+    private String description;
+
+    /** 模块报名价(分,插班按模块收费;展示时除以 100) */
+    private Integer price;
+
+    /** 排序(升序) */
+    private Integer sort;
+
+    /** 1=启用,0=禁用 */
+    private Integer enabled;
+
+    private Date createdAt;
+    private Date updatedAt;
+}

+ 7 - 0
train-backend/src/main/java/com/train/mapper/TrainModuleMapper.java

@@ -0,0 +1,7 @@
+package com.train.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.train.entity.TrainModule;
+
+public interface TrainModuleMapper extends BaseMapper<TrainModule> {
+}

+ 36 - 0
train-backend/src/main/resources/schema.sql

@@ -7,6 +7,7 @@
 CREATE TABLE IF NOT EXISTS train_course (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,
     level VARCHAR(20) NOT NULL COMMENT '课程等级:L0/L1/L2/L3/L4',
+    track VARCHAR(20) COMMENT '学习路径/轨道:wealth/health/growth(模块化方向,空=主线通用课程如 L0)',
     name VARCHAR(100) NOT NULL COMMENT '课程名称(如 L0 = 家立方-AI管家成长营)',
     description VARCHAR(500) COMMENT '课程简介',
     status VARCHAR(20) DEFAULT 'draft' COMMENT '手动覆盖状态:draft(下架)/active(强制上架)/finished(强制结束)',
@@ -14,6 +15,7 @@ CREATE TABLE IF NOT EXISTS train_course (
     end_time DATETIME COMMENT '课程结束时间(状态自动切换依据)',
     price INT DEFAULT 0 COMMENT '课程报名价(分,报名=报课程,价格挂在课程上)',
     member_price INT DEFAULT 0 COMMENT '课程会员价(分)',
+    track VARCHAR(20) COMMENT '学习路径/轨道:wealth/health/growth(模块化方向,空=主线通用课程如L0)',
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     UNIQUE KEY uk_level (level),
@@ -34,6 +36,22 @@ CREATE TABLE IF NOT EXISTS train_course_section (
     INDEX idx_sort (sort)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程版块(管理端维护)';
 
+-- 课程模块表(模块化教学:课程按模块组织内容,支持学习路径 track 归类与排序)
+CREATE TABLE IF NOT EXISTS train_module (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    course_id BIGINT NOT NULL COMMENT '所属课程ID(train_course.id)',
+    track VARCHAR(20) COMMENT '学习路径/轨道:wealth/health/growth(null=通用/无方向)',
+    title VARCHAR(100) NOT NULL COMMENT '模块标题',
+    description VARCHAR(500) COMMENT '模块简介',
+    sort INT DEFAULT 0 COMMENT '排序(升序)',
+    enabled TINYINT DEFAULT 1 COMMENT '1=启用,0=禁用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_course_id (course_id),
+    INDEX idx_track (track),
+    INDEX idx_sort (sort)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程模块(模块化教学)';
+
 -- 班次表
 CREATE TABLE IF NOT EXISTS train_class (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,
@@ -87,6 +105,8 @@ ALTER TABLE train_course ADD COLUMN start_time DATETIME COMMENT '课程开始时
 ALTER TABLE train_course ADD COLUMN end_time DATETIME COMMENT '课程结束时间(状态自动切换依据)';
 ALTER TABLE train_course ADD COLUMN price INT DEFAULT 0 COMMENT '课程报名价(分,报名=报课程,价格挂在课程上)';
 ALTER TABLE train_course ADD COLUMN member_price INT DEFAULT 0 COMMENT '课程会员价(分)';
+ALTER TABLE train_course ADD COLUMN track VARCHAR(20) COMMENT '学习路径/轨道:wealth/health/growth(模块化方向,空=主线通用课程如 L0)';
+ALTER TABLE train_course ADD COLUMN track VARCHAR(20) COMMENT '学习路径/轨道:wealth/health/growth(模块化方向,空=主线通用课程如L0)';
 ALTER TABLE train_enrollment ADD COLUMN course_id BIGINT COMMENT '报名所属课程 ID(train_course.id,报名=报课程;班次仅确定上课期次)';
 
 -- 小组表
@@ -568,3 +588,19 @@ INSERT INTO train_badge(badge_key,category,name,color,carrier,condition_desc,sor
 ('lec_t3','lecturer','讲师·金','#F59E0B','电子','T3认证',31),
 ('lec_t4','lecturer','讲师·珊瑚金','#F97316','电子','T4认证',32)
 ON DUPLICATE KEY UPDATE name=VALUES(name);
+
+-- 课程模块表(模块化教学:L1/L2 按三本账方向拆模块 + 插班制)
+CREATE TABLE IF NOT EXISTS train_module (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    course_id BIGINT NOT NULL COMMENT '所属课程ID(train_course.id)',
+    track VARCHAR(20) COMMENT '学习路径/轨道:wealth/health/growth(空=通用模块)',
+    name VARCHAR(100) NOT NULL COMMENT '模块名称(如:L1-A1 财富诊断)',
+    description VARCHAR(500) COMMENT '模块说明',
+    price INT DEFAULT 0 COMMENT '模块报名价(分,插班按模块收费;展示除以100)',
+    sort INT DEFAULT 0 COMMENT '排序(升序)',
+    enabled TINYINT DEFAULT 1 COMMENT '1=启用,0=禁用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_course_id (course_id),
+    INDEX idx_sort (sort)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程模块(模块化教学/插班)';