feat(课程包): 未提交状态与草稿分流,草稿不可发布审核;保存草稿跳过校验

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-24 15:30:31 +08:00
parent 40782a8905
commit b551af1355
8 changed files with 68 additions and 14 deletions

View File

@ -232,7 +232,7 @@ export function submitCourse(id: number | string): Promise<any> {
// 撤销审核 (暂时使用更新接口,需要确认后端是否有此功能)
export function withdrawCourse(id: number): Promise<any> {
return api.updateCourse(id, { status: "DRAFT" }) as any;
return api.updateCourse(id, { status: "UNSUBMITTED" }) as any;
}
// 审核通过
@ -304,6 +304,7 @@ export const COURSE_STATUS_MAP: Record<
{ label: string; color: string }
> = {
DRAFT: { label: "草稿", color: "default" },
UNSUBMITTED: { label: "未提交", color: "geekblue" },
PENDING: { label: "审核中", color: "processing" },
REJECTED: { label: "已驳回", color: "error" },
PUBLISHED: { label: "已发布", color: "success" },

View File

@ -400,6 +400,7 @@ export function getStepTypeStyle(type: string): {
// 课程状态映射(英文 → 中文)
export const COURSE_STATUS_MAP: Record<string, string> = {
DRAFT: "草稿",
UNSUBMITTED: "未提交",
PENDING: "审核中",
APPROVED: "已通过",
REJECTED: "已驳回",
@ -423,6 +424,7 @@ export const COURSE_STATUS_COLORS: Record<
{ bg: string; text: string }
> = {
稿: { bg: "#F5F5F5", text: "#666666" },
: { bg: "#E8EAF6", text: "#5C6BC0" },
: { bg: "#E3F2FD", text: "#1976D2" },
: { bg: "#FFEBEE", text: "#E53935" },
:{ bg: "#E8F5E9", text: "#43A047" },

View File

@ -10,7 +10,7 @@
<a-button @click="viewStats">
<BarChartOutlined /> 数据
</a-button>
<a-popconfirm v-if="course.status === 'DRAFT' || course.status === 'ARCHIVED'" title="确定删除此课程包吗?"
<a-popconfirm v-if="course.status === 'DRAFT' || course.status === 'UNSUBMITTED' || course.status === 'ARCHIVED'" title="确定删除此课程包吗?"
@confirm="deleteCourse">
<a-button danger>
<DeleteOutlined /> 删除
@ -509,7 +509,7 @@ const themeTagStyle = computed(() =>
// 稿//
const canEdit = computed(() => {
const s = course.value.status;
return s === 'DRAFT' || s === 'REJECTED' || s === 'ARCHIVED';
return s === 'DRAFT' || s === 'UNSUBMITTED' || s === 'REJECTED' || s === 'ARCHIVED';
});
//
@ -658,6 +658,7 @@ const getStatusStyle = (status: string) => {
const translateStatus = (status: string) => {
const map: Record<string, string> = {
'DRAFT': '草稿',
'UNSUBMITTED': '未提交',
'PENDING': '待审核',
'PUBLISHED': '已发布',
'ARCHIVED': '已下架',

View File

@ -316,9 +316,11 @@ const handleSaveDraft = async () => {
//
const handleSave = async (isDraft = false) => {
//
// 稿
if (!isDraft) {
const ok = await validateCurrentStep();
if (!ok) return;
}
//
if (saving.value) return;
@ -351,7 +353,15 @@ const handleSave = async (isDraft = false) => {
scheduleRefData: formData.scheduleRefData,
//
environmentConstruction: formData.environmentConstruction,
};
} as Record<string, unknown>;
if (isDraft) {
courseData.status = 'DRAFT';
} else if (currentStep.value === 6) {
// / /
courseData.status = 'UNSUBMITTED';
}
// status稿
console.log('Saving course data...', { isDraft, isEdit: isEdit.value });

View File

@ -14,6 +14,7 @@
<a-select v-model:value="filters.status" placeholder="状态" style="width: 120px" allow-clear
@change="fetchCourses">
<a-select-option value="DRAFT">草稿</a-select-option>
<a-select-option value="UNSUBMITTED">未提交</a-select-option>
<a-select-option value="PENDING">审核中</a-select-option>
<a-select-option value="REJECTED">已驳回</a-select-option>
<a-select-option value="PUBLISHED">已发布</a-select-option>
@ -91,8 +92,16 @@
<template v-else-if="column.key === 'actions'">
<a-space>
<!-- 草稿状态 -->
<!-- 草稿未完成保存为未提交不允许发布/提交审核 -->
<template v-if="record.status === 'DRAFT'">
<a-button size="small" @click="editCourse(record.id)">编辑</a-button>
<a-popconfirm title="确定删除此课程包吗?" @confirm="deleteCourseHandler(record.id)">
<a-button size="small" danger>删除</a-button>
</a-popconfirm>
</template>
<!-- 未提交可提交审核或直接发布 -->
<template v-else-if="record.status === 'UNSUBMITTED'">
<a-button size="small" @click="editCourse(record.id)">编辑</a-button>
<a-dropdown>
<a-button type="primary" size="small">

View File

@ -9,6 +9,8 @@ import lombok.Getter;
public enum CourseStatus {
DRAFT("DRAFT", "草稿"),
/** 表单已完整保存、可提交审核或直接发布,与草稿区分 */
UNSUBMITTED("UNSUBMITTED", "未提交"),
PENDING("PENDING", "待审核"),
REJECTED("REJECTED", "已驳回"),
PUBLISHED("PUBLISHED", "已发布"),

View File

@ -138,4 +138,7 @@ public class CourseCreateRequest {
@Schema(description = "是否有集体课")
private Boolean hasCollectiveLesson;
@Schema(description = "初始状态DRAFT 草稿、UNSUBMITTED 未提交,默认草稿")
private String status;
}

View File

@ -52,7 +52,7 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
CoursePackage entity = buildEntityFromRequest(request);
entity.setTenantId(tenantId);
entity.setIsSystem(0);
entity.setStatus(CourseStatus.DRAFT.getCode());
entity.setStatus(resolveInitialStatus(request));
entity.setUsageCount(0);
coursePackageMapper.insert(entity);
log.info("课程创建成功id={}", entity.getId());
@ -66,7 +66,7 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
CoursePackage entity = buildEntityFromRequest(request);
entity.setTenantId(null);
entity.setIsSystem(1);
entity.setStatus(CourseStatus.DRAFT.getCode());
entity.setStatus(resolveInitialStatus(request));
entity.setUsageCount(0);
coursePackageMapper.insert(entity);
log.info("系统课程创建成功id={}", entity.getId());
@ -204,10 +204,15 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
@Transactional(rollbackFor = Exception.class)
public void publishCourse(Long id) {
CoursePackage entity = getCourseById(id);
String st = entity.getStatus();
if (CourseStatus.UNSUBMITTED.getCode().equals(st) || CourseStatus.PENDING.getCode().equals(st)) {
entity.setStatus(CourseStatus.PUBLISHED.getCode());
entity.setPublishedAt(LocalDateTime.now());
coursePackageMapper.updateById(entity);
log.info("课程发布成功id={}", id);
return;
}
throw new BusinessException("仅「未提交」或「待审核」状态的课程包可发布,当前状态不允许发布");
}
@Override
@ -239,6 +244,16 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
@Transactional(rollbackFor = Exception.class)
public void submitCourse(Long id) {
CoursePackage entity = getCourseById(id);
String st = entity.getStatus();
if (CourseStatus.DRAFT.getCode().equals(st)) {
throw new BusinessException("草稿状态请先完成全部步骤并保存为「未提交」后再提交审核");
}
if (CourseStatus.PENDING.getCode().equals(st)) {
throw new BusinessException("课程已在审核中");
}
if (CourseStatus.PUBLISHED.getCode().equals(st)) {
throw new BusinessException("课程已发布,无需提交审核");
}
entity.setStatus(CourseStatus.PENDING.getCode());
entity.setSubmittedAt(LocalDateTime.now());
coursePackageMapper.updateById(entity);
@ -411,6 +426,17 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
return coursePackageMapper.selectPage(page, wrapper);
}
private static String resolveInitialStatus(CourseCreateRequest request) {
if (!StringUtils.hasText(request.getStatus())) {
return CourseStatus.DRAFT.getCode();
}
String code = request.getStatus();
if (CourseStatus.UNSUBMITTED.getCode().equals(code) || CourseStatus.DRAFT.getCode().equals(code)) {
return code;
}
return CourseStatus.DRAFT.getCode();
}
private CoursePackage buildEntityFromRequest(CourseCreateRequest request) {
CoursePackage entity = new CoursePackage();
entity.setName(request.getName());