feat(课程包): 未提交状态与草稿分流,草稿不可发布审核;保存草稿跳过校验
Made-with: Cursor
This commit is contained in:
parent
40782a8905
commit
b551af1355
@ -232,7 +232,7 @@ export function submitCourse(id: number | string): Promise<any> {
|
|||||||
|
|
||||||
// 撤销审核 (暂时使用更新接口,需要确认后端是否有此功能)
|
// 撤销审核 (暂时使用更新接口,需要确认后端是否有此功能)
|
||||||
export function withdrawCourse(id: number): 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 }
|
{ label: string; color: string }
|
||||||
> = {
|
> = {
|
||||||
DRAFT: { label: "草稿", color: "default" },
|
DRAFT: { label: "草稿", color: "default" },
|
||||||
|
UNSUBMITTED: { label: "未提交", color: "geekblue" },
|
||||||
PENDING: { label: "审核中", color: "processing" },
|
PENDING: { label: "审核中", color: "processing" },
|
||||||
REJECTED: { label: "已驳回", color: "error" },
|
REJECTED: { label: "已驳回", color: "error" },
|
||||||
PUBLISHED: { label: "已发布", color: "success" },
|
PUBLISHED: { label: "已发布", color: "success" },
|
||||||
|
|||||||
@ -400,6 +400,7 @@ export function getStepTypeStyle(type: string): {
|
|||||||
// 课程状态映射(英文 → 中文)
|
// 课程状态映射(英文 → 中文)
|
||||||
export const COURSE_STATUS_MAP: Record<string, string> = {
|
export const COURSE_STATUS_MAP: Record<string, string> = {
|
||||||
DRAFT: "草稿",
|
DRAFT: "草稿",
|
||||||
|
UNSUBMITTED: "未提交",
|
||||||
PENDING: "审核中",
|
PENDING: "审核中",
|
||||||
APPROVED: "已通过",
|
APPROVED: "已通过",
|
||||||
REJECTED: "已驳回",
|
REJECTED: "已驳回",
|
||||||
@ -423,6 +424,7 @@ export const COURSE_STATUS_COLORS: Record<
|
|||||||
{ bg: string; text: string }
|
{ bg: string; text: string }
|
||||||
> = {
|
> = {
|
||||||
草稿: { bg: "#F5F5F5", text: "#666666" },
|
草稿: { bg: "#F5F5F5", text: "#666666" },
|
||||||
|
未提交: { bg: "#E8EAF6", text: "#5C6BC0" },
|
||||||
审核中: { bg: "#E3F2FD", text: "#1976D2" },
|
审核中: { bg: "#E3F2FD", text: "#1976D2" },
|
||||||
已驳回: { bg: "#FFEBEE", text: "#E53935" },
|
已驳回: { bg: "#FFEBEE", text: "#E53935" },
|
||||||
已通过:{ bg: "#E8F5E9", text: "#43A047" },
|
已通过:{ bg: "#E8F5E9", text: "#43A047" },
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<a-button @click="viewStats">
|
<a-button @click="viewStats">
|
||||||
<BarChartOutlined /> 数据
|
<BarChartOutlined /> 数据
|
||||||
</a-button>
|
</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">
|
@confirm="deleteCourse">
|
||||||
<a-button danger>
|
<a-button danger>
|
||||||
<DeleteOutlined /> 删除
|
<DeleteOutlined /> 删除
|
||||||
@ -509,7 +509,7 @@ const themeTagStyle = computed(() =>
|
|||||||
// 审核中(待审核)不可编辑,仅草稿/已驳回/已下架可编辑
|
// 审核中(待审核)不可编辑,仅草稿/已驳回/已下架可编辑
|
||||||
const canEdit = computed(() => {
|
const canEdit = computed(() => {
|
||||||
const s = course.value.status;
|
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 translateStatus = (status: string) => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
'DRAFT': '草稿',
|
'DRAFT': '草稿',
|
||||||
|
'UNSUBMITTED': '未提交',
|
||||||
'PENDING': '待审核',
|
'PENDING': '待审核',
|
||||||
'PUBLISHED': '已发布',
|
'PUBLISHED': '已发布',
|
||||||
'ARCHIVED': '已下架',
|
'ARCHIVED': '已下架',
|
||||||
|
|||||||
@ -316,9 +316,11 @@ const handleSaveDraft = async () => {
|
|||||||
|
|
||||||
// 保存
|
// 保存
|
||||||
const handleSave = async (isDraft = false) => {
|
const handleSave = async (isDraft = false) => {
|
||||||
// 保存前校验当前步骤
|
// 正式保存才校验当前步骤;保存草稿允许未完成配置,不校验
|
||||||
const ok = await validateCurrentStep();
|
if (!isDraft) {
|
||||||
if (!ok) return;
|
const ok = await validateCurrentStep();
|
||||||
|
if (!ok) return;
|
||||||
|
}
|
||||||
|
|
||||||
// 防止重复提交
|
// 防止重复提交
|
||||||
if (saving.value) return;
|
if (saving.value) return;
|
||||||
@ -351,7 +353,15 @@ const handleSave = async (isDraft = false) => {
|
|||||||
scheduleRefData: formData.scheduleRefData,
|
scheduleRefData: formData.scheduleRefData,
|
||||||
// 环创建设
|
// 环创建设
|
||||||
environmentConstruction: formData.environmentConstruction,
|
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 });
|
console.log('Saving course data...', { isDraft, isEdit: isEdit.value });
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
<a-select v-model:value="filters.status" placeholder="状态" style="width: 120px" allow-clear
|
<a-select v-model:value="filters.status" placeholder="状态" style="width: 120px" allow-clear
|
||||||
@change="fetchCourses">
|
@change="fetchCourses">
|
||||||
<a-select-option value="DRAFT">草稿</a-select-option>
|
<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="PENDING">审核中</a-select-option>
|
||||||
<a-select-option value="REJECTED">已驳回</a-select-option>
|
<a-select-option value="REJECTED">已驳回</a-select-option>
|
||||||
<a-select-option value="PUBLISHED">已发布</a-select-option>
|
<a-select-option value="PUBLISHED">已发布</a-select-option>
|
||||||
@ -91,8 +92,16 @@
|
|||||||
|
|
||||||
<template v-else-if="column.key === 'actions'">
|
<template v-else-if="column.key === 'actions'">
|
||||||
<a-space>
|
<a-space>
|
||||||
<!-- 草稿状态 -->
|
<!-- 草稿:未完成保存为未提交,不允许发布/提交审核 -->
|
||||||
<template v-if="record.status === 'DRAFT'">
|
<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-button size="small" @click="editCourse(record.id)">编辑</a-button>
|
||||||
<a-dropdown>
|
<a-dropdown>
|
||||||
<a-button type="primary" size="small">
|
<a-button type="primary" size="small">
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import lombok.Getter;
|
|||||||
public enum CourseStatus {
|
public enum CourseStatus {
|
||||||
|
|
||||||
DRAFT("DRAFT", "草稿"),
|
DRAFT("DRAFT", "草稿"),
|
||||||
|
/** 表单已完整保存、可提交审核或直接发布,与草稿区分 */
|
||||||
|
UNSUBMITTED("UNSUBMITTED", "未提交"),
|
||||||
PENDING("PENDING", "待审核"),
|
PENDING("PENDING", "待审核"),
|
||||||
REJECTED("REJECTED", "已驳回"),
|
REJECTED("REJECTED", "已驳回"),
|
||||||
PUBLISHED("PUBLISHED", "已发布"),
|
PUBLISHED("PUBLISHED", "已发布"),
|
||||||
|
|||||||
@ -138,4 +138,7 @@ public class CourseCreateRequest {
|
|||||||
@Schema(description = "是否有集体课")
|
@Schema(description = "是否有集体课")
|
||||||
private Boolean hasCollectiveLesson;
|
private Boolean hasCollectiveLesson;
|
||||||
|
|
||||||
|
@Schema(description = "初始状态:DRAFT 草稿、UNSUBMITTED 未提交,默认草稿")
|
||||||
|
private String status;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
|||||||
CoursePackage entity = buildEntityFromRequest(request);
|
CoursePackage entity = buildEntityFromRequest(request);
|
||||||
entity.setTenantId(tenantId);
|
entity.setTenantId(tenantId);
|
||||||
entity.setIsSystem(0);
|
entity.setIsSystem(0);
|
||||||
entity.setStatus(CourseStatus.DRAFT.getCode());
|
entity.setStatus(resolveInitialStatus(request));
|
||||||
entity.setUsageCount(0);
|
entity.setUsageCount(0);
|
||||||
coursePackageMapper.insert(entity);
|
coursePackageMapper.insert(entity);
|
||||||
log.info("课程创建成功,id={}", entity.getId());
|
log.info("课程创建成功,id={}", entity.getId());
|
||||||
@ -66,7 +66,7 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
|||||||
CoursePackage entity = buildEntityFromRequest(request);
|
CoursePackage entity = buildEntityFromRequest(request);
|
||||||
entity.setTenantId(null);
|
entity.setTenantId(null);
|
||||||
entity.setIsSystem(1);
|
entity.setIsSystem(1);
|
||||||
entity.setStatus(CourseStatus.DRAFT.getCode());
|
entity.setStatus(resolveInitialStatus(request));
|
||||||
entity.setUsageCount(0);
|
entity.setUsageCount(0);
|
||||||
coursePackageMapper.insert(entity);
|
coursePackageMapper.insert(entity);
|
||||||
log.info("系统课程创建成功,id={}", entity.getId());
|
log.info("系统课程创建成功,id={}", entity.getId());
|
||||||
@ -204,10 +204,15 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void publishCourse(Long id) {
|
public void publishCourse(Long id) {
|
||||||
CoursePackage entity = getCourseById(id);
|
CoursePackage entity = getCourseById(id);
|
||||||
entity.setStatus(CourseStatus.PUBLISHED.getCode());
|
String st = entity.getStatus();
|
||||||
entity.setPublishedAt(LocalDateTime.now());
|
if (CourseStatus.UNSUBMITTED.getCode().equals(st) || CourseStatus.PENDING.getCode().equals(st)) {
|
||||||
coursePackageMapper.updateById(entity);
|
entity.setStatus(CourseStatus.PUBLISHED.getCode());
|
||||||
log.info("课程发布成功,id={}", id);
|
entity.setPublishedAt(LocalDateTime.now());
|
||||||
|
coursePackageMapper.updateById(entity);
|
||||||
|
log.info("课程发布成功,id={}", id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new BusinessException("仅「未提交」或「待审核」状态的课程包可发布,当前状态不允许发布");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -239,6 +244,16 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void submitCourse(Long id) {
|
public void submitCourse(Long id) {
|
||||||
CoursePackage entity = getCourseById(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.setStatus(CourseStatus.PENDING.getCode());
|
||||||
entity.setSubmittedAt(LocalDateTime.now());
|
entity.setSubmittedAt(LocalDateTime.now());
|
||||||
coursePackageMapper.updateById(entity);
|
coursePackageMapper.updateById(entity);
|
||||||
@ -411,6 +426,17 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
|||||||
return coursePackageMapper.selectPage(page, wrapper);
|
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) {
|
private CoursePackage buildEntityFromRequest(CourseCreateRequest request) {
|
||||||
CoursePackage entity = new CoursePackage();
|
CoursePackage entity = new CoursePackage();
|
||||||
entity.setName(request.getName());
|
entity.setName(request.getName());
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user