From 17ecdad42f7ce0a872dd003033fed8bc7f021b9e Mon Sep 17 00:00:00 2001 From: mayiming <1627832236@qq.com> Date: Sun, 2 Nov 2025 12:31:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=AF=BE=E7=A8=8B=E8=B5=84?= =?UTF-8?q?=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onlinecourse/onlineCourseView.vue | 591 ++++++++++++------ .../course_content/deletecontenthandler.go | 14 +- .../course_content/getcontenthandler.go | 14 +- .../course_content/getcontentlistlogic.go | 112 +++- .../course_content/internal/types/types.go | 35 +- .../deletecourseresourcehandler.go | 14 +- .../getcourseresourcehandler.go | 15 +- .../course_resource/internal/types/types.go | 16 +- 8 files changed, 552 insertions(+), 259 deletions(-) diff --git a/management/src/views/caseresource/onlinecourse/onlineCourseView.vue b/management/src/views/caseresource/onlinecourse/onlineCourseView.vue index 8b5ef3f4..a91a4794 100644 --- a/management/src/views/caseresource/onlinecourse/onlineCourseView.vue +++ b/management/src/views/caseresource/onlinecourse/onlineCourseView.vue @@ -1,10 +1,9 @@ @@ -57,7 +67,6 @@ - +

课程标题: {{ currentCourse.title }}

@@ -83,31 +94,55 @@
+
- +
- - + +
+
-
-
- - 封面 + + 封面
-
+ + +
+
+ + + 添加章节 + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + \ No newline at end of file +.content-table .el-table__row .cell { line-height: 1.4; } + diff --git a/server/internal/course_content/handler/course_content/deletecontenthandler.go b/server/internal/course_content/handler/course_content/deletecontenthandler.go index 2eb68fd5..e1f7279a 100644 --- a/server/internal/course_content/handler/course_content/deletecontenthandler.go +++ b/server/internal/course_content/handler/course_content/deletecontenthandler.go @@ -6,6 +6,8 @@ package course_content import ( "fmt" "net/http" + "strconv" + "strings" "github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/internal/course_content/internal/logic/course_content" @@ -18,10 +20,18 @@ import ( func DeleteContentHandler(cfg *config.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.DeleteContentReq - if err := httpx.Parse(r, &req); err != nil { - httpx.ErrorCtx(r.Context(), w, err) + pathParts := strings.Split(r.URL.Path, "/") + if len(pathParts) < 3 { // 确保路径格式正确 + httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid path format")) return } + idStr := pathParts[3] + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid meeting ID")) + return + } + req.Id = int(id) mysqlCfg := cfg.MySQL dsn := fmt.Sprintf( "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=Local", diff --git a/server/internal/course_content/handler/course_content/getcontenthandler.go b/server/internal/course_content/handler/course_content/getcontenthandler.go index 271553cb..93d43296 100644 --- a/server/internal/course_content/handler/course_content/getcontenthandler.go +++ b/server/internal/course_content/handler/course_content/getcontenthandler.go @@ -6,6 +6,8 @@ package course_content import ( "fmt" "net/http" + "strconv" + "strings" "github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/internal/course_content/internal/logic/course_content" @@ -18,10 +20,18 @@ import ( func GetContentHandler(cfg *config.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.GetContentReq - if err := httpx.Parse(r, &req); err != nil { - httpx.ErrorCtx(r.Context(), w, err) + pathParts := strings.Split(r.URL.Path, "/") + if len(pathParts) < 3 { // 确保路径格式正确 + httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid path format")) return } + idStr := pathParts[3] + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid meeting ID")) + return + } + req.Id = int(id) mysqlCfg := cfg.MySQL dsn := fmt.Sprintf( diff --git a/server/internal/course_content/internal/logic/course_content/getcontentlistlogic.go b/server/internal/course_content/internal/logic/course_content/getcontentlistlogic.go index 6758b81a..61898145 100644 --- a/server/internal/course_content/internal/logic/course_content/getcontentlistlogic.go +++ b/server/internal/course_content/internal/logic/course_content/getcontentlistlogic.go @@ -10,6 +10,7 @@ import ( "github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/internal/course_content/internal/model" "github.com/JACKYMYPERSON/hldrCenter/internal/course_content/internal/types" + "github.com/JACKYMYPERSON/hldrCenter/util" "github.com/zeromicro/go-zero/core/logx" ) @@ -31,46 +32,109 @@ func NewGetContentListLogic(ctx context.Context, cfg *config.Config, model model } func (l *GetContentListLogic) GetContentList(req *types.GetContentListReq) (resp *types.GetContentListResp, err error) { - // 1. 参数校验:确保课程ID有效 + // 1. 参数校验(增强校验,确保CourseId必填) if req.CourseId <= 0 { return nil, fmt.Errorf("参数错误:课程ID必须为正整数") } - // ParentId 可选,允许为0(表示章节)或其他正数(表示小节),此处仅校验非负 if req.ParentId < 0 { return nil, fmt.Errorf("参数错误:父级ID不能为负数") } - // 2. 调用 Model 层查询符合条件的内容列表 - // 假设 Model 层有查询方法:根据 course_id 和 parent_id 筛选,按 sort 排序 - // 若 Model 层无此方法,可构造 SQL:SELECT * FROM course_content WHERE course_id=? AND parent_id=? ORDER BY sort - contentModels, err := l.model.FindByCourseAndParent(l.ctx, - int64(req.CourseId), int64(req.ParentId)) - if err != nil { - return nil, fmt.Errorf("查询课程内容列表失败:%w", err) - } - - // 3. 转换模型数据为响应结构体(适配前端需要的格式) + // 2. 区分查询场景:parentId=0 查树形结构(章节+子章节),否则查平级内容 var contentList []types.CourseContent - for _, modelItem := range contentModels { - contentList = append(contentList, types.CourseContent{ - Id: int(modelItem.Id), // 数据库ID可能为int64,转换为int - CourseId: int(modelItem.CourseId), // 课程ID转换 - ParentId: int(modelItem.ParentId), // 父级ID转换 - Title: modelItem.Title, - // 若数据库中Content是sql.NullString,需转换为普通string(复用工具函数) - Content: NullStringToString(modelItem.Content), - Sort: int(modelItem.Sort), - }) + if req.ParentId == 0 { + // 场景1:查询所有章节(parentId=0)及其子章节 + contentList, err = l.queryTreeContent(req.CourseId) + } else { + // 场景2:查询指定父级的平级内容(兼容原有逻辑) + contentList, err = l.queryFlatContent(req.CourseId, req.ParentId) + } + if err != nil { + return nil, fmt.Errorf("查询课程内容失败:%w", err) } - // 4. 构造响应(包含空列表的情况,前端可正常处理) + // 3. 构造响应 resp = &types.GetContentListResp{ BaseResp: types.BaseResp{ Code: 0, Message: "查询课程内容列表成功", }, - Data: contentList, // 即使为空列表,也正常返回(避免前端处理null的麻烦) + Data: contentList, } return resp, nil } + +// queryTreeContent 查询树形结构:章节(parentId=0)+ 子章节 +func (l *GetContentListLogic) queryTreeContent(courseId int) ([]types.CourseContent, error) { + // 第一步:查询所有章节(parentId=0),按sort升序 + chapterModels, err := l.model.FindByCourseAndParent(l.ctx, int64(courseId), 0) + if err != nil { + return nil, err + } + + // 第二步:遍历每个章节,查询对应的子章节(parentId=章节ID) + var treeList []types.CourseContent + for _, chapter := range chapterModels { + // 转换章节为响应格式 + respChapter := types.CourseContent{ + Id: int(chapter.Id), + CourseId: int(chapter.CourseId), + ParentId: int(chapter.ParentId), + Title: chapter.Title, + Content: util.NullStringToString(chapter.Content), + Sort: int(chapter.Sort), + Children: []types.CourseContent{}, // 初始化子章节切片 + } + + // 查询当前章节的子章节(parentId=章节ID) + childModels, err := l.model.FindByCourseAndParent(l.ctx, int64(courseId), chapter.Id) + if err != nil { + return nil, fmt.Errorf("查询子章节失败(章节ID:%d):%w", chapter.Id, err) + } + + // 转换子章节为响应格式,添加到章节的Children字段 + for _, child := range childModels { + respChild := types.CourseContent{ + Id: int(child.Id), + CourseId: int(child.CourseId), + ParentId: int(child.ParentId), + Title: child.Title, + Content: NullStringToString(child.Content), + Sort: int(child.Sort), + Children: []types.CourseContent{}, // 子章节暂无下级,默认空切片 + } + respChapter.Children = append(respChapter.Children, respChild) + } + + // 添加章节到树形列表 + treeList = append(treeList, respChapter) + } + + return treeList, nil +} + +// queryFlatContent 查询平级内容(兼容原有逻辑:按courseId和parentId查指定层级) +func (l *GetContentListLogic) queryFlatContent(courseId, parentId int) ([]types.CourseContent, error) { + // 调用数据层查询平级内容 + contentModels, err := l.model.FindByCourseAndParent(l.ctx, int64(courseId), int64(parentId)) + if err != nil { + return nil, err + } + + // 转换为响应格式 + var flatList []types.CourseContent + for _, modelItem := range contentModels { + flatList = append(flatList, types.CourseContent{ + Id: int(modelItem.Id), + CourseId: int(modelItem.CourseId), + ParentId: int(modelItem.ParentId), + Title: modelItem.Title, + Content: NullStringToString(modelItem.Content), + Sort: int(modelItem.Sort), + Children: []types.CourseContent{}, // 平级查询时子章节为空 + }) + } + + return flatList, nil +} diff --git a/server/internal/course_content/internal/types/types.go b/server/internal/course_content/internal/types/types.go index 28bb7a00..c3b51838 100644 --- a/server/internal/course_content/internal/types/types.go +++ b/server/internal/course_content/internal/types/types.go @@ -4,11 +4,11 @@ package types type AddContentReq struct { - CourseId int `json:"course_id" form:"course_id"` // 课程ID,必填 - ParentId int `json:"parent_id" form:"parent_id"` // 父级ID,必填 - Title string `json:"title" form:"title"` // 标题,必填 - Content string `json:"content" form:"content"` // 内容详情,可选 - Sort int `json:"sort" form:"sort"` // 排序,可选 + CourseId int `json:"course_id"` // 课程ID,必填 + ParentId int `json:"parent_id"` // 父级ID,必填 + Title string `json:"title"` // 标题,必填 + Content string `json:"content"` // 内容详情,可选 + Sort int `json:"sort"` // 排序,可选 } type AddContentResp struct { @@ -23,12 +23,13 @@ type BaseResp struct { } type CourseContent struct { - Id int `json:"id"` // 内容ID(主键) - CourseId int `json:"course_id"` // 关联课程ID - ParentId int `json:"parent_id"` // 父级ID(0表示章节,>0表示小节) - Title string `json:"title"` // 章节/小节标题 - Content string `json:"content"` // 内容详情 - Sort int `json:"sort"` // 排序 + Id int `json:"id"` // 内容ID(主键) + CourseId int `json:"course_id"` // 关联课程ID + ParentId int `json:"parent_id"` // 父级ID(0表示章节,>0表示小节) + Title string `json:"title"` // 章节/小节标题 + Content string `json:"content"` // 内容详情 + Sort int `json:"sort"` // 排序 + Children []CourseContent `json:"children"` // 子章节列表(仅章节有值) } type DeleteContentReq struct { @@ -40,8 +41,8 @@ type DeleteContentResp struct { } type GetContentListReq struct { - CourseId int `json:"course_id" form:"course_id"` // 课程ID,必填 - ParentId int `json:"parent_id" form:"parent_id"` // 父级ID,可选,0表示获取章节 + CourseId int `json:"course_id"` // 课程ID,必填 + ParentId int `json:"parent_id"` // 父级ID,可选,0表示获取章节 } type GetContentListResp struct { @@ -59,10 +60,10 @@ type GetContentResp struct { } type UpdateContentReq struct { - Id int `json:"id" form:"id"` // 内容ID,必填 - Title string `json:"title" form:"title"` // 标题,可选 - Content string `json:"content" form:"content"` // 内容详情,可选 - Sort int `json:"sort" form:"sort"` // 排序,可选 + Id int `json:"id" ` // 内容ID,必填 + Title string `json:"title" ` // 标题,可选 + Content string `json:"content" ` // 内容详情,可选 + Sort int `json:"sort" ` // 排序,可选 } type UpdateContentResp struct { diff --git a/server/internal/course_resource/handler/course_resource/deletecourseresourcehandler.go b/server/internal/course_resource/handler/course_resource/deletecourseresourcehandler.go index ab65a23c..9be4a2fc 100644 --- a/server/internal/course_resource/handler/course_resource/deletecourseresourcehandler.go +++ b/server/internal/course_resource/handler/course_resource/deletecourseresourcehandler.go @@ -6,6 +6,8 @@ package course_resource import ( "fmt" "net/http" + "strconv" + "strings" "github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/internal/course_resource/internal/logic/course_resource" @@ -18,10 +20,18 @@ import ( func DeleteCourseResourceHandler(cfg *config.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.DeleteCourseResourceReq - if err := httpx.Parse(r, &req); err != nil { - httpx.ErrorCtx(r.Context(), w, err) + pathParts := strings.Split(r.URL.Path, "/") + if len(pathParts) < 3 { // 确保路径格式正确 + httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid path format")) return } + idStr := pathParts[3] + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid meeting ID")) + return + } + req.Id = int(id) mysqlCfg := cfg.MySQL dsn := fmt.Sprintf( diff --git a/server/internal/course_resource/handler/course_resource/getcourseresourcehandler.go b/server/internal/course_resource/handler/course_resource/getcourseresourcehandler.go index 55474f36..e1e2a1d9 100644 --- a/server/internal/course_resource/handler/course_resource/getcourseresourcehandler.go +++ b/server/internal/course_resource/handler/course_resource/getcourseresourcehandler.go @@ -6,6 +6,8 @@ package course_resource import ( "fmt" "net/http" + "strconv" + "strings" "github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/internal/course_resource/internal/logic/course_resource" @@ -18,10 +20,19 @@ import ( func GetCourseResourceHandler(cfg *config.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.GetCourseResourceReq - if err := httpx.Parse(r, &req); err != nil { - httpx.ErrorCtx(r.Context(), w, err) + pathParts := strings.Split(r.URL.Path, "/") + fmt.Println(pathParts) + if len(pathParts) < 3 { // 确保路径格式正确 + httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid path format")) return } + idStr := pathParts[3] + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid meeting ID")) + return + } + req.Id = int(id) mysqlCfg := cfg.MySQL dsn := fmt.Sprintf( diff --git a/server/internal/course_resource/internal/types/types.go b/server/internal/course_resource/internal/types/types.go index f0bb3825..a443a5cd 100644 --- a/server/internal/course_resource/internal/types/types.go +++ b/server/internal/course_resource/internal/types/types.go @@ -4,11 +4,11 @@ package types type CreateCourseResourceReq struct { - CourseId int `json:"course_id" form:"course_id" validate:"required"` - Title string `json:"title" form:"title" validate:"required,max=255"` - ResourceUrl string `json:"resource_url" form:"resource_url" validate:"required,max=512"` - Size int `json:"size" form:"size" validate:"omitempty,min=0"` - Sort int `json:"sort" form:"sort" validate:"omitempty,min=0"` + CourseId int `json:"course_id" validate:"required"` + Title string `json:"title" validate:"required,max=255"` + ResourceUrl string `json:"resource_url" validate:"required,max=512"` + Size int `json:"size" validate:"omitempty,min=0"` + Sort int `json:"sort" validate:"omitempty,min=0"` } type CreateCourseResourceResp struct { @@ -38,9 +38,9 @@ type GetCourseResourceResp struct { } type ListCourseResourceReq struct { - CourseId int `form:"course_id" validate:"omitempty"` - Page int `form:"page" validate:"required,min=1"` - PageSize int `form:"page_size" validate:"required,min=1,max=100"` + CourseId int `json:"course_id" validate:"omitempty"` + Page int `json:"page" validate:"required,min=1"` + PageSize int `json:"page_size" validate:"required,min=1,max=100"` } type ListCourseResourceResp struct {