Skip to content

Commit 1e8e941

Browse files
committed
feat(课程分组): 实现课程分组功能并支持前端展示
添加课程分组列表接口及相关服务逻辑 在前端课程、听书和电子书页面增加分组展示和导航功能 支持分组内课程列表展示和返回上级操作
1 parent a462ebd commit 1e8e941

9 files changed

Lines changed: 326 additions & 71 deletions

File tree

backend/app/course.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ func CourseList(category, order string, page, limit int) (list *services.CourseL
1818
return
1919
}
2020

21+
func CourseGroupList(category, order string, groupID, page, limit int) (list *services.CourseList, err error) {
22+
list, err = getService().CourseGroupList(category, order, groupID, page, limit)
23+
if err != nil {
24+
return
25+
}
26+
return
27+
}
28+
2129
// CourseDetail 已购课程详情
2230
func CourseDetail(category string, id int) (detail *services.Course, err error) {
2331
detail, err = getService().CourseDetail(category, id)

backend/course.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ func (a *App) CourseList(category, order string, page, limit int) (list *service
2424
return
2525
}
2626

27+
func (a *App) CourseGroupList(category, order string, groupID, page, limit int) (list *services.CourseList, err error) {
28+
list, err = app.CourseGroupList(category, order, groupID, page, limit)
29+
if err != nil {
30+
return
31+
}
32+
return
33+
}
34+
2735
func (a *App) CourseInfo(enid string) (info *services.CourseInfo, err error) {
2836
info, err = app.CourseInfoByEnid(enid)
2937
return

backend/services/course.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,62 @@ func (s *Service) CourseDetail(category string, id int) (detail *Course, err err
624624
return
625625
}
626626

627+
// CourseGroupList fetches a single page of items within a specific group.
628+
// 获取分组内的课程列表(单页)
629+
func (s *Service) CourseGroupList(category, order string, groupID, page, limit int) (response *CourseList, err error) {
630+
body, err := s.reqCourseGroupList(category, order, groupID, page, limit)
631+
if err != nil {
632+
return
633+
}
634+
defer body.Close()
635+
if err = handleJSONParse(body, &response); err != nil {
636+
return
637+
}
638+
return
639+
}
640+
641+
// CourseGroupListAll fetches all items within a specific group across all pages.
642+
// It handles pagination automatically and aggregates results.
643+
// 获取分组内的所有课程列表(自动处理分页)
644+
func (s *Service) CourseGroupListAll(category, order string, groupID int) (data *CourseList, err error) {
645+
resp, err := s.CourseGroupList(category, order, groupID, 1, 18)
646+
if err != nil {
647+
return
648+
}
649+
650+
if resp.Total == 0 {
651+
data = resp
652+
return
653+
}
654+
655+
total := resp.Total
656+
limit := 18
657+
totalPages := int(math.Ceil(float64(total) / float64(limit)))
658+
659+
// 已经获取第一页数据
660+
var allCourses []Course
661+
allCourses = append(allCourses, resp.List...)
662+
663+
// 获取剩余页面数据
664+
for page := 2; page <= totalPages; page++ {
665+
pageResp, err := s.CourseGroupList(category, order, groupID, page, limit)
666+
if err != nil {
667+
return data, err
668+
}
669+
allCourses = append(allCourses, pageResp.List...)
670+
}
671+
672+
// 构建完整结果
673+
data = &CourseList{
674+
List: allCourses,
675+
Total: total,
676+
IsMore: 0, // 已获取全部,没有更多
677+
HasSingleBook: resp.HasSingleBook,
678+
}
679+
680+
return
681+
}
682+
627683
// CourseInfo get course info
628684
func (s *Service) CourseInfo(enid string) (info *CourseInfo, err error) {
629685

backend/services/requester.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,24 @@ func (s *Service) reqCourseList(category, order string, page, limit int) (io.Rea
9797
return handleHTTPResponse(resp, err)
9898
}
9999

100+
// reqCourseGroupList makes an HTTP request to fetch items within a specific group.
101+
// It uses the /api/hades/v2/product/group/list endpoint with display_group=false to prevent nesting.
102+
// 请求分组内的课程列表
103+
func (s *Service) reqCourseGroupList(category, order string, groupID, page, limit int) (io.ReadCloser, error) {
104+
resp, err := s.client.R().SetBody(map[string]interface{}{
105+
"category": category,
106+
"display_group": false, // Prevent nested groups
107+
"filter": "group",
108+
"group_id": groupID,
109+
"order": order,
110+
"filter_complete": 0,
111+
"page": page,
112+
"page_size": limit,
113+
"sort_type": "desc",
114+
}).Post("/api/hades/v2/product/group/list")
115+
return handleHTTPResponse(resp, err)
116+
}
117+
100118
// reqOutsideDetail 请求名家讲书课程详情
101119
func (s *Service) reqOutsideDetail(enid string) (io.ReadCloser, error) {
102120
resp, err := s.client.R().SetBody(map[string]interface{}{

frontend/src/views/Course.vue

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
11
<template>
2+
<div v-if="groupMode.active" style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 8px;">
3+
<div style="display:flex; align-items:center; gap: 8px;">
4+
<el-button type="primary" link @click="exitGroup">返回</el-button>
5+
<span>{{ groupMode.title }}</span>
6+
</div>
7+
</div>
28
<el-table :data="tableData.list" v-loading="loading" height="97%" style="width: 100%"
39
:cell-style="{ textAlign: 'left' }" :header-cell-style="{ textAlign: 'left' }"
410
table-layout="auto">
511
<!-- <el-table-column prop="class_id" label="ID" width="80" /> -->
6-
<el-table-column prop="title" label="标题" width="280" />
12+
<el-table-column prop="title" label="标题" width="280">
13+
<template #default="scope">
14+
<div style="display:flex; align-items:center; gap: 8px;">
15+
<span>{{ scope.row.title }}</span>
16+
<el-tag v-if="scope.row.is_group" type="info" size="small">分组</el-tag>
17+
<span v-if="scope.row.is_group">共{{ scope.row.course_num || 0 }}本</span>
18+
<el-button v-if="scope.row.is_group" type="primary" link @click.stop="enterGroup(scope.row)">进入</el-button>
19+
</div>
20+
</template>
21+
</el-table-column>
722
<el-table-column prop="icon" label="封面" width="80">
823
<template #default="scope">
9-
<el-image
10-
:src="scope.row.icon"
24+
<el-image
25+
v-if="scope.row.icon"
26+
:src="scope.row.icon"
1127
:preview-teleported="true"
12-
:preview-src-list="[scope.row.icon]"
28+
:preview-src-list="[scope.row.icon]"
1329
style="width: 32px;"
14-
>
15-
</el-image>
30+
/>
31+
<span v-else>-</span>
1632
</template>
1733
</el-table-column>
1834
<el-table-column prop="intro" label="简介" width="280" />
@@ -21,24 +37,27 @@
2137

2238
<el-table-column prop="publish_num" label="已更新" width="100" >
2339
<template #default="scope">
24-
<span>{{ scope.row.publish_num }}/{{scope.row.course_num}}</span>
40+
<span v-if="!scope.row.is_group">{{ scope.row.publish_num || 0 }}/{{ scope.row.course_num || 0 }}</span>
41+
<span v-else>-</span>
2542
</template>
2643
</el-table-column>
2744
<el-table-column prop="progress" label="已学%" width="80" />
2845
<el-table-column fixed="right" label="操作" width="240">
2946
<template #default="scope">
30-
<el-button icon="list" size="small" type="primary" link @click="gotoArticleList(scope.row)">章节列表
31-
</el-button>
32-
<el-button icon="view" size="small" type="primary" link @click="handleProd(scope.row.enid)">详情
33-
</el-button>
34-
<el-button icon="download" size="small" type="primary" link @click="openDownloadDialog(scope.row)">下载
35-
</el-button>
47+
<template v-if="!scope.row.is_group">
48+
<el-button icon="list" size="small" type="primary" link @click="gotoArticleList(scope.row)">章节列表
49+
</el-button>
50+
<el-button icon="view" size="small" type="primary" link @click="handleProd(scope.row.enid)">详情
51+
</el-button>
52+
<el-button icon="download" size="small" type="primary" link @click="openDownloadDialog(scope.row)">下载
53+
</el-button>
54+
</template>
3655

3756
</template>
3857
</el-table-column>
3958
</el-table>
4059

41-
<pagination :total="total" @pageChange="handleChangePage"></pagination>
60+
<pagination :key="paginationKey" :total="total" @pageChange="handleChangePage"></pagination>
4261
<course-info v-if="dialogVisible" :enid= "prodEnid" :dialog-visible="dialogVisible" @close="closeDialog"></course-info>
4362
<download-dialog
4463
v-if="dialogDownloadVisible"
@@ -54,7 +73,7 @@
5473
<script lang="ts" setup>
5574
import { ref, reactive, onMounted } from 'vue'
5675
import { ElTable, ElMessage } from 'element-plus'
57-
import {CourseList, CourseCategory, SetDir} from '../../wailsjs/go/backend/App'
76+
import {CourseList, CourseCategory, CourseGroupList, SetDir} from '../../wailsjs/go/backend/App'
5877
import { services } from '../../wailsjs/go/models'
5978
import { userStore } from '../stores/user';
6079
import { settingStore } from '../stores/setting';
@@ -71,10 +90,17 @@ const { pushLogin, pushCourseDetail, pushSetting } = useAppRouter()
7190
const loading = ref(true)
7291
const page = ref(1)
7392
const total = ref(0)
93+
const outerTotal = ref(0)
7494
const pageSize = ref(15)
95+
const paginationKey = ref(0)
7596
const dialogVisible = ref(false)
7697
const prodEnid = ref("")
7798
99+
const groupMode = reactive({
100+
active: false,
101+
groupId: 0,
102+
title: '',
103+
})
78104
79105
const dialogDownloadVisible = ref(false)
80106
const downloadType = ref(1)
@@ -93,7 +119,8 @@ onMounted(() => {
93119
CourseCategory().then(result => {
94120
result.forEach((item, key) => {
95121
if (item.category == "bauhinia") {
96-
total.value = item.count
122+
outerTotal.value = item.count
123+
if (!groupMode.active) total.value = item.count
97124
}
98125
})
99126
}).catch((error) => {
@@ -115,10 +142,15 @@ const handleChangePage = (item: any) => {
115142
116143
117144
const getTableData = async () => {
118-
await CourseList("bauhinia", "study", page.value, pageSize.value).then((table) => {
145+
loading.value = true
146+
const fetcher = groupMode.active
147+
? CourseGroupList("bauhinia", "study", groupMode.groupId, page.value, pageSize.value)
148+
: CourseList("bauhinia", "study", page.value, pageSize.value)
149+
150+
await fetcher.then((table) => {
119151
loading.value = false
120152
Object.assign(tableData, table)
121-
// console.log(tableData)
153+
total.value = groupMode.active ? (table.total || 0) : outerTotal.value
122154
}).catch((error) => {
123155
loading.value = false
124156
ElMessage({
@@ -143,6 +175,27 @@ const gotoArticleList = (row: any) => {
143175
})
144176
}
145177
178+
const enterGroup = (row: any) => {
179+
const groupId = Number(row?.group_id || 0)
180+
if (!groupId) return
181+
groupMode.active = true
182+
groupMode.groupId = groupId
183+
groupMode.title = String(row?.title || '')
184+
page.value = 1
185+
paginationKey.value += 1
186+
getTableData()
187+
}
188+
189+
const exitGroup = () => {
190+
groupMode.active = false
191+
groupMode.groupId = 0
192+
groupMode.title = ''
193+
page.value = 1
194+
total.value = outerTotal.value
195+
paginationKey.value += 1
196+
getTableData()
197+
}
198+
146199
const openDialog = () => {
147200
dialogVisible.value = true
148201
}
@@ -201,4 +254,4 @@ const closeDownloadDialog = () => {
201254
/* height: auto; */
202255
text-align: center;
203256
}
204-
</style>
257+
</style>

0 commit comments

Comments
 (0)