feat: 🎸 增加课程难度、课程所属专题字段&课程列表查询增加条件

This commit is contained in:
GoCo 2025-07-26 18:51:25 +08:00
parent 0ba0bda7f8
commit fc7d1f2ee1
14 changed files with 150 additions and 20 deletions

View File

@ -11,8 +11,6 @@ RUN npm install -g pnpm
# 设置工作目录
WORKDIR /app
# 复制后端
COPY ./server ./server
# 复制前端
COPY ./web ./web
@ -21,6 +19,9 @@ WORKDIR /app/web
RUN echo "y" |pnpm install
RUN pnpm run build
# 复制后端
COPY ./server ./server
# 构建后端项目
WORKDIR /app/server

View File

@ -10,7 +10,8 @@ import (
type LessonListReq struct {
g.Meta `path:"/lesson/list" method:"get" tags:"课程" summary:"获取课程列表"`
CategoryId int64 `json:"categoryId" dc:"分类ID"`
Tag string `json:"tag" dc:"标签"`
Difficuty int `json:"difficuty" dc:"难度"`
Subject string `json:"subject" dc:"专题"`
}
// 获取课程列表响应

View File

@ -9,23 +9,33 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
func (c *ControllerV1) LessonList(ctx context.Context, req *v1.LessonListReq) (res *v1.LessonListRes, err error) {
// 构建查询条件
query := dao.Lesson.Ctx(ctx)
// 分类ID过滤
if req.CategoryId > 0 {
// 分类过滤
if req.CategoryId != -1 && req.CategoryId > 0 {
query = query.Where("category_id = ?", req.CategoryId)
}
// TODO 按标签过滤
// 难度过滤
if req.Difficuty != -1 {
query = query.Where("difficulty = ?", req.Difficuty)
}
// 专题过滤subject为all时不过滤否则用JSON_CONTAINS
if req.Subject != "" && req.Subject != "all" {
query = query.WhereLike("subject", "%"+req.Subject+"%")
}
// 查询课程列表
var list []*entity.Lesson
err = query.Scan(&list)
if err != nil {
g.Log().Error(ctx, err)
return nil, gerror.NewCode(gcode.CodeDbOperationError, "数据库错误")
}

View File

@ -41,6 +41,8 @@ type LessonColumns struct {
CreatedTime string // 创建时间
UpdatedBy string // 更新人
UpdatedTime string // 更新时间
Difficulty string // 课程难度
Subject string // 所属专题
}
// lessonColumns holds the columns for the table hg_lesson.
@ -65,6 +67,8 @@ var lessonColumns = LessonColumns{
CreatedTime: "created_time",
UpdatedBy: "updated_by",
UpdatedTime: "updated_time",
Difficulty: "difficulty",
Subject: "subject",
}
// NewLessonDao creates and returns a new DAO object for table data access.

View File

@ -27,7 +27,7 @@ type LessonSectionColumns struct {
Name string // 章节名
SortOrder string // 排序号
ParentId string // 父章节id
Level string // 章节层级;1=章2=节
Level string // 章节层级
Revision string // 乐观锁
CreatedBy string // 创建人
CreatedTime string // 创建时间

View File

@ -52,6 +52,11 @@ func (s *sSysLesson) List(ctx context.Context, in *sysin.LessonListInp) (list []
// 字段过滤
mod = mod.Fields(sysin.LessonListModel{})
// 查询id
if in.Id > 0 {
mod = mod.Where(dao.Lesson.Columns().Id, in.Id)
}
// 查询课程名
if in.Name != "" {
mod = mod.WhereLike(dao.Lesson.Columns().Name, in.Name)

View File

@ -32,4 +32,6 @@ type Lesson struct {
CreatedTime *gtime.Time // 创建时间
UpdatedBy interface{} // 更新人
UpdatedTime *gtime.Time // 更新时间
Difficulty interface{} // 课程难度
Subject interface{} // 所属专题
}

View File

@ -18,7 +18,7 @@ type LessonSection struct {
Name interface{} // 章节名
SortOrder interface{} // 排序号
ParentId interface{} // 父章节id
Level interface{} // 章节层级;1=章2=节
Level interface{} // 章节层级
Revision interface{} // 乐观锁
CreatedBy interface{} // 创建人
CreatedTime *gtime.Time // 创建时间

View File

@ -30,4 +30,6 @@ type Lesson struct {
CreatedTime *gtime.Time `json:"createdTime" orm:"created_time" description:"创建时间"`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:"更新人"`
UpdatedTime *gtime.Time `json:"updatedTime" orm:"updated_time" description:"更新时间"`
Difficulty int `json:"difficulty" orm:"difficulty" description:"课程难度"`
Subject string `json:"subject" orm:"subject" description:"所属专题"`
}

View File

@ -16,7 +16,7 @@ type LessonSection struct {
Name string `json:"name" orm:"name" description:"章节名"`
SortOrder int `json:"sortOrder" orm:"sort_order" description:"排序号"`
ParentId int `json:"parentId" orm:"parent_id" description:"父章节id"`
Level int `json:"level" orm:"level" description:"章节层级;1=章2=节"`
Level int `json:"level" orm:"level" description:"章节层级"`
Revision int `json:"revision" orm:"revision" description:"乐观锁"`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:"创建人"`
CreatedTime *gtime.Time `json:"createdTime" orm:"created_time" description:"创建时间"`

View File

@ -11,6 +11,7 @@ import (
"hotgo/internal/model/entity"
"hotgo/internal/model/input/form"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
@ -18,7 +19,7 @@ import (
type LessonUpdateFields struct {
Name string `json:"name" dc:"课程名"`
Cover string `json:"cover" dc:"封面图"`
CategoryId int `json:"categoryId" dc:"所属分类"`
CategoryId int64 `json:"categoryId" dc:"所属分类"`
Video string `json:"video" dc:"介绍视频"`
School string `json:"school" dc:"所属学校"`
Description string `json:"description" dc:"课程概述"`
@ -30,14 +31,16 @@ type LessonUpdateFields struct {
StartTime *gtime.Time `json:"startTime" dc:"开课时间"`
EndTime *gtime.Time `json:"endTime" dc:"结课时间"`
Question string `json:"question" dc:"常见问题"`
UpdatedBy int `json:"updatedBy" dc:"更新人"`
UpdatedBy int64 `json:"updatedBy" dc:"更新人"`
Difficulty int `json:"difficulty" dc:"课程难度"`
Subject string `json:"subject" dc:"所属专题"`
}
// LessonInsertFields 新增课程管理字段过滤
type LessonInsertFields struct {
Name string `json:"name" dc:"课程名"`
Cover string `json:"cover" dc:"封面图"`
CategoryId int `json:"categoryId" dc:"所属分类"`
CategoryId int64 `json:"categoryId" dc:"所属分类"`
Video string `json:"video" dc:"介绍视频"`
School string `json:"school" dc:"所属学校"`
Description string `json:"description" dc:"课程概述"`
@ -49,7 +52,9 @@ type LessonInsertFields struct {
StartTime *gtime.Time `json:"startTime" dc:"开课时间"`
EndTime *gtime.Time `json:"endTime" dc:"结课时间"`
Question string `json:"question" dc:"常见问题"`
CreatedBy int `json:"createdBy" dc:"创建人"`
CreatedBy int64 `json:"createdBy" dc:"创建人"`
Difficulty int `json:"difficulty" dc:"课程难度"`
Subject string `json:"subject" dc:"所属专题"`
}
// LessonEditInp 修改/新增课程管理
@ -58,6 +63,20 @@ type LessonEditInp struct {
}
func (in *LessonEditInp) Filter(ctx context.Context) (err error) {
// 验证课程名
if err := g.Validator().Rules("required").Data(in.Name).Messages("课程名不能为空").Run(ctx); err != nil {
return err.Current()
}
// 验证封面图
if err := g.Validator().Rules("required").Data(in.Cover).Messages("封面图不能为空").Run(ctx); err != nil {
return err.Current()
}
// 验证所属分类
if err := g.Validator().Rules("required").Data(in.CategoryId).Messages("所属分类不能为空").Run(ctx); err != nil {
return err.Current()
}
return
}
@ -77,7 +96,7 @@ type LessonDeleteModel struct{}
// LessonViewInp 获取指定课程管理信息
type LessonViewInp struct {
Id int `json:"id" v:"required#id不能为空" dc:"id"`
Id int64 `json:"id" v:"required#id不能为空" dc:"id"`
}
func (in *LessonViewInp) Filter(ctx context.Context) (err error) {
@ -91,6 +110,7 @@ type LessonViewModel struct {
// LessonListInp 获取课程管理列表
type LessonListInp struct {
form.PageReq
Id int64 `json:"id" dc:"id"`
Name string `json:"name" dc:"课程名"`
School string `json:"school" dc:"所属学校"`
}
@ -100,22 +120,24 @@ func (in *LessonListInp) Filter(ctx context.Context) (err error) {
}
type LessonListModel struct {
Id int `json:"id" dc:"id"`
Id int64 `json:"id" dc:"id"`
Name string `json:"name" dc:"课程名"`
Cover string `json:"cover" dc:"封面图"`
CategoryId int `json:"categoryId" dc:"所属分类"`
CategoryId int64 `json:"categoryId" dc:"所属分类"`
Video string `json:"video" dc:"介绍视频"`
School string `json:"school" dc:"所属学校"`
StartTime *gtime.Time `json:"startTime" dc:"开课时间"`
EndTime *gtime.Time `json:"endTime" dc:"结课时间"`
Difficulty int `json:"difficulty" dc:"课程难度"`
Subject string `json:"subject" dc:"所属专题"`
}
// LessonExportModel 导出课程管理
type LessonExportModel struct {
Id int `json:"id" dc:"id"`
Id int64 `json:"id" dc:"id"`
Name string `json:"name" dc:"课程名"`
Cover string `json:"cover" dc:"封面图"`
CategoryId int `json:"categoryId" dc:"所属分类"`
CategoryId int64 `json:"categoryId" dc:"所属分类"`
Video string `json:"video" dc:"介绍视频"`
School string `json:"school" dc:"所属学校"`
Description string `json:"description" dc:"课程概述"`
@ -127,4 +149,6 @@ type LessonExportModel struct {
StartTime *gtime.Time `json:"startTime" dc:"开课时间"`
EndTime *gtime.Time `json:"endTime" dc:"结课时间"`
Question string `json:"question" dc:"常见问题"`
Difficulty int `json:"difficulty" dc:"课程难度"`
Subject string `json:"subject" dc:"所属专题"`
}

View File

@ -16,6 +16,7 @@
<n-form
ref="formRef"
:model="formValue"
:rules="rules"
:label-placement="settingStore.isMobile ? 'top' : 'left'"
:label-width="100"
class="py-4"
@ -91,6 +92,16 @@
<Editor style="height: 450px" id="question" v-model:value="formValue.question" />
</n-form-item>
</n-gi>
<n-gi span="1">
<n-form-item label="课程难度" path="difficulty">
<n-select v-model:value="formValue.difficulty" :options="dict.getOptionUnRef('lesson_difficulty')" />
</n-form-item>
</n-gi>
<n-gi span="1">
<n-form-item label="所属专题" path="subject">
<n-select multiple v-model:value="formValue.subject" :options="dict.getOptionUnRef('lesson_subject')" />
</n-form-item>
</n-gi>
</n-grid>
</n-form>
</n-spin>
@ -113,7 +124,7 @@
import { ref, computed } from 'vue';
import { useDictStore } from '@/store/modules/dict';
import { Edit, View } from '@/api/lesson';
import { State, newState } from './model';
import { State, newState, rules } from './model';
import UploadImage from '@/components/Upload/uploadImage.vue';
import UploadFile from '@/components/Upload/uploadFile.vue';
import Editor from '@/components/Editor/editor.vue';

View File

@ -1,6 +1,8 @@
import { h, ref } from 'vue';
import { NTag } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { isNullObject } from '@/utils/is';
import { renderImage, renderOptionTag, renderFile } from '@/utils';
import { useDictStore } from '@/store/modules/dict';
@ -27,6 +29,8 @@ export class State {
public createdTime = ''; // 创建时间
public updatedBy = 0; // 更新人
public updatedTime = ''; // 更新时间
public difficulty = null; // 课程难度
public subject = null; // 所属专题
constructor(state?: Partial<State>) {
if (state) {
@ -46,9 +50,40 @@ export function newState(state: State | Record<string, any> | null): State {
}
// 表单验证规则
export const rules = {
name: {
required: true,
trigger: ['blur', 'input'],
type: 'string',
message: '请输入课程名',
},
cover: {
required: true,
trigger: ['blur', 'input'],
type: 'string',
message: '请输入封面图',
},
categoryId: {
required: true,
trigger: ['blur', 'input'],
type: 'number',
message: '请输入所属分类',
},
};
// 表格搜索表单
export const schemas = ref<FormSchema[]>([
{
field: 'id',
component: 'NInputNumber',
label: 'id',
componentProps: {
placeholder: '请输入id',
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'name',
component: 'NInput',
@ -132,9 +167,32 @@ export const columns = [
align: 'left',
width: -1,
},
{
title: '课程难度',
key: 'difficulty',
align: 'left',
width: -1,
render(row: State) {
return renderOptionTag('lesson_difficulty', row.difficulty);
},
},
{
title: '所属专题',
key: 'subject',
align: 'left',
width: -1,
render(row: State) {
if (isNullObject(row.subject) || !isArray(row.subject)) {
return ``;
}
return row.subject.map((tagKey) => {
return renderOptionTag('lesson_subject', row.tagKey)
});
},
},
];
// 加载字典数据选项
export function loadOptions() {
dict.loadOptions(['lessonCategoryOption']);
dict.loadOptions(['lesson_subject', 'lesson_difficulty', 'lessonCategoryOption']);
}

View File

@ -97,6 +97,18 @@
</template>
<span v-html="formValue.question"></span>
</n-descriptions-item>
<n-descriptions-item label="课程难度">
<n-tag :type="dict.getType('lesson_difficulty', formValue.difficulty)" size="small" class="min-left-space">
{{ dict.getLabel('lesson_difficulty', formValue.difficulty) }}
</n-tag>
</n-descriptions-item>
<n-descriptions-item label="所属专题">
<template v-for="(item, key) in formValue.subject" :key="key">
<n-tag :type="dict.getType('lesson_subject', item)" size="small" class="min-left-space">
{{ dict.getLabel('lesson_subject', item) }}
</n-tag>
</template>
</n-descriptions-item>
</n-descriptions>
</n-spin>
</n-drawer-content>