fix: 课程包下架后需重新提交审核才能发布
- ARCHIVED 状态改为「提交审核」按钮,移除直接重新发布 - CollectionEditView/PackageEditView: 选择弹窗回显与分页保持选中 Made-with: Cursor
This commit is contained in:
parent
fdf34e9352
commit
0e77fae390
@ -100,6 +100,7 @@
|
|||||||
v-model:open="showPackageSelector"
|
v-model:open="showPackageSelector"
|
||||||
title="选择课程包"
|
title="选择课程包"
|
||||||
width="800px"
|
width="800px"
|
||||||
|
:confirm-loading="addingPackages"
|
||||||
@ok="handleAddPackages"
|
@ok="handleAddPackages"
|
||||||
>
|
>
|
||||||
<a-table
|
<a-table
|
||||||
@ -109,6 +110,18 @@
|
|||||||
row-key="id"
|
row-key="id"
|
||||||
size="small"
|
size="small"
|
||||||
:loading="loadingPackages"
|
:loading="loadingPackages"
|
||||||
|
:pagination="{
|
||||||
|
current: selectorPagination.current,
|
||||||
|
pageSize: selectorPagination.pageSize,
|
||||||
|
total: selectorPagination.total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: (t: number) => `共 ${t} 条`,
|
||||||
|
onChange: (page: number, size: number) => {
|
||||||
|
selectorPagination.current = page;
|
||||||
|
selectorPagination.pageSize = size;
|
||||||
|
fetchAvailablePackages();
|
||||||
|
},
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'gradeTags'">
|
<template v-if="column.key === 'gradeTags'">
|
||||||
@ -121,13 +134,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue';
|
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import type { FormInstance } from 'ant-design-vue';
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import { getCollectionDetail, createCollection, updateCollection, setCollectionPackages } from '@/api/package';
|
import { getCollectionDetail, createCollection, updateCollection, setCollectionPackages, getCoursePackageList, getCoursePackage } from '@/api/package';
|
||||||
import { getCoursePackageList } from '@/api/package';
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -141,6 +153,8 @@ const loadingPackages = ref(false);
|
|||||||
const showPackageSelector = ref(false);
|
const showPackageSelector = ref(false);
|
||||||
const availablePackages = ref<any[]>([]);
|
const availablePackages = ref<any[]>([]);
|
||||||
const selectedRowKeys = ref<(number | string)[]>([]);
|
const selectedRowKeys = ref<(number | string)[]>([]);
|
||||||
|
const selectorPagination = reactive({ current: 1, pageSize: 10, total: 0 });
|
||||||
|
const addingPackages = ref(false);
|
||||||
|
|
||||||
const formState = ref({
|
const formState = ref({
|
||||||
name: '',
|
name: '',
|
||||||
@ -268,9 +282,13 @@ const fetchCollectionDetail = async () => {
|
|||||||
const fetchAvailablePackages = async () => {
|
const fetchAvailablePackages = async () => {
|
||||||
loadingPackages.value = true;
|
loadingPackages.value = true;
|
||||||
try {
|
try {
|
||||||
// 获取已发布的课程包列表
|
const res = await getCoursePackageList({
|
||||||
const res = await getCoursePackageList({ pageNum: 1, pageSize: 100, status: 'PUBLISHED' });
|
pageNum: selectorPagination.current,
|
||||||
|
pageSize: selectorPagination.pageSize,
|
||||||
|
status: 'PUBLISHED',
|
||||||
|
});
|
||||||
availablePackages.value = res.list || [];
|
availablePackages.value = res.list || [];
|
||||||
|
selectorPagination.total = res.total || 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取课程包列表失败', error);
|
console.error('获取课程包列表失败', error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -278,18 +296,50 @@ const fetchAvailablePackages = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddPackages = () => {
|
// 打开选择弹窗时:回显已选课程包,加载第一页数据
|
||||||
|
watch(showPackageSelector, (visible) => {
|
||||||
|
if (visible) {
|
||||||
|
selectedRowKeys.value = selectedPackages.value.map((p) => p.packageId);
|
||||||
|
selectorPagination.current = 1;
|
||||||
|
fetchAvailablePackages();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAddPackages = async () => {
|
||||||
const existingIds = new Set(selectedPackages.value.map((p) => p.packageId));
|
const existingIds = new Set(selectedPackages.value.map((p) => p.packageId));
|
||||||
const newPackages = availablePackages.value
|
const idsToAdd = selectedRowKeys.value.filter((id) => !existingIds.has(id));
|
||||||
.filter((p) => selectedRowKeys.value.includes(p.id) && !existingIds.has(p.id))
|
if (idsToAdd.length === 0) {
|
||||||
.map((p) => ({
|
showPackageSelector.value = false;
|
||||||
packageId: p.id,
|
return;
|
||||||
packageName: p.name,
|
}
|
||||||
gradeLevel: parseGradeTags(p.gradeTags)?.[0] || '小班',
|
|
||||||
sortOrder: selectedPackages.value.length,
|
addingPackages.value = true;
|
||||||
}));
|
try {
|
||||||
|
const pkgMap = new Map(availablePackages.value.map((p) => [p.id, p]));
|
||||||
|
const newPackages: { packageId: number | string; packageName: string; gradeLevel: string; sortOrder: number }[] = [];
|
||||||
|
let sortOrder = selectedPackages.value.length;
|
||||||
|
|
||||||
|
for (const id of idsToAdd) {
|
||||||
|
let pkg = pkgMap.get(id);
|
||||||
|
if (!pkg) {
|
||||||
|
try {
|
||||||
|
pkg = await getCoursePackage(id);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPackages.push({
|
||||||
|
packageId: pkg.id,
|
||||||
|
packageName: pkg.name,
|
||||||
|
gradeLevel: parseGradeTags(pkg.gradeTags)?.[0] || '小班',
|
||||||
|
sortOrder: sortOrder++,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
selectedPackages.value.push(...newPackages);
|
selectedPackages.value.push(...newPackages);
|
||||||
|
} finally {
|
||||||
|
addingPackages.value = false;
|
||||||
|
}
|
||||||
selectedRowKeys.value = [];
|
selectedRowKeys.value = [];
|
||||||
showPackageSelector.value = false;
|
showPackageSelector.value = false;
|
||||||
};
|
};
|
||||||
@ -374,7 +424,6 @@ const handleCancel = () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchCollectionDetail();
|
fetchCollectionDetail();
|
||||||
fetchAvailablePackages();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -148,11 +148,11 @@
|
|||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 已下架状态 -->
|
<!-- 已下架状态:需重新提交审核后才能发布 -->
|
||||||
<template v-else-if="record.status === 'ARCHIVED'">
|
<template v-else-if="record.status === 'ARCHIVED'">
|
||||||
<a-button size="small" @click="viewCourse(record.id)">查看</a-button>
|
<a-button size="small" @click="viewCourse(record.id)">查看</a-button>
|
||||||
<a-button size="small" @click="viewStats(record.id)">数据</a-button>
|
<a-button size="small" @click="viewStats(record.id)">数据</a-button>
|
||||||
<a-button type="primary" size="small" @click="republishCourse(record.id)">重新发布</a-button>
|
<a-button type="primary" size="small" @click="submitForReview(record)">提交审核</a-button>
|
||||||
<a-popconfirm title="确定删除此课程包吗?删除后无法恢复" @confirm="deleteCourseHandler(record.id)">
|
<a-popconfirm title="确定删除此课程包吗?删除后无法恢复" @confirm="deleteCourseHandler(record.id)">
|
||||||
<a-button size="small" danger>删除</a-button>
|
<a-button size="small" danger>删除</a-button>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
@ -494,23 +494,6 @@ const unpublishCourse = async (id: number) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重新发布
|
|
||||||
const republishCourse = async (id: number) => {
|
|
||||||
Modal.confirm({
|
|
||||||
title: '确认重新发布',
|
|
||||||
content: '重新发布后所有活跃租户将可以查看并使用此课程包,确认继续?',
|
|
||||||
onOk: async () => {
|
|
||||||
try {
|
|
||||||
await courseApi.republishCourse(id);
|
|
||||||
message.success('重新发布成功');
|
|
||||||
fetchCourses();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('重新发布失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const iterateCourse = (id: number) => {
|
const iterateCourse = (id: number) => {
|
||||||
router.push(`/admin/packages/${id}/iterate`);
|
router.push(`/admin/packages/${id}/iterate`);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -103,6 +103,7 @@
|
|||||||
v-model:open="showPackageSelector"
|
v-model:open="showPackageSelector"
|
||||||
title="选择课程包"
|
title="选择课程包"
|
||||||
width="800px"
|
width="800px"
|
||||||
|
:confirm-loading="addingPackages"
|
||||||
@ok="handleAddPackages"
|
@ok="handleAddPackages"
|
||||||
>
|
>
|
||||||
<a-table
|
<a-table
|
||||||
@ -112,6 +113,18 @@
|
|||||||
row-key="id"
|
row-key="id"
|
||||||
size="small"
|
size="small"
|
||||||
:loading="loadingPackages"
|
:loading="loadingPackages"
|
||||||
|
:pagination="{
|
||||||
|
current: selectorPagination.current,
|
||||||
|
pageSize: selectorPagination.pageSize,
|
||||||
|
total: selectorPagination.total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: (t: number) => `共 ${t} 条`,
|
||||||
|
onChange: (page: number, size: number) => {
|
||||||
|
selectorPagination.current = page;
|
||||||
|
selectorPagination.pageSize = size;
|
||||||
|
fetchAvailablePackages();
|
||||||
|
},
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'gradeTags'">
|
<template v-if="column.key === 'gradeTags'">
|
||||||
@ -129,8 +142,7 @@ import { useRouter, useRoute } from 'vue-router';
|
|||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import type { FormInstance } from 'ant-design-vue';
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import { getCollectionDetail, createCollection, updateCollection, setCollectionPackages } from '@/api/package';
|
import { getCollectionDetail, createCollection, updateCollection, setCollectionPackages, getCoursePackageList, getCoursePackage } from '@/api/package';
|
||||||
import { getCoursePackageList } from '@/api/package';
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -144,6 +156,8 @@ const loadingPackages = ref(false);
|
|||||||
const showPackageSelector = ref(false);
|
const showPackageSelector = ref(false);
|
||||||
const availablePackages = ref<any[]>([]);
|
const availablePackages = ref<any[]>([]);
|
||||||
const selectedRowKeys = ref<(number | string)[]>([]);
|
const selectedRowKeys = ref<(number | string)[]>([]);
|
||||||
|
const selectorPagination = reactive({ current: 1, pageSize: 10, total: 0 });
|
||||||
|
const addingPackages = ref(false);
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
@ -280,9 +294,13 @@ const fetchPackageDetail = async () => {
|
|||||||
const fetchAvailablePackages = async () => {
|
const fetchAvailablePackages = async () => {
|
||||||
loadingPackages.value = true;
|
loadingPackages.value = true;
|
||||||
try {
|
try {
|
||||||
// 获取已发布的课程包列表
|
const res = await getCoursePackageList({
|
||||||
const res = await getCoursePackageList({ pageNum: 1, pageSize: 100, status: 'PUBLISHED' });
|
pageNum: selectorPagination.current,
|
||||||
|
pageSize: selectorPagination.pageSize,
|
||||||
|
status: 'PUBLISHED',
|
||||||
|
});
|
||||||
availablePackages.value = res.list || [];
|
availablePackages.value = res.list || [];
|
||||||
|
selectorPagination.total = res.total || 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取课程包列表失败', error);
|
console.error('获取课程包列表失败', error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -290,21 +308,51 @@ const fetchAvailablePackages = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddPackages = () => {
|
// 打开选择弹窗时:回显已选课程包,加载第一页数据
|
||||||
|
watch(showPackageSelector, (visible) => {
|
||||||
|
if (visible) {
|
||||||
|
selectedRowKeys.value = selectedPackages.value.map((p) => p.packageId);
|
||||||
|
selectorPagination.current = 1;
|
||||||
|
fetchAvailablePackages();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAddPackages = async () => {
|
||||||
const existingIds = new Set(selectedPackages.value.map((p) => p.packageId));
|
const existingIds = new Set(selectedPackages.value.map((p) => p.packageId));
|
||||||
const newPackages = availablePackages.value
|
const idsToAdd = selectedRowKeys.value.filter((id) => !existingIds.has(id));
|
||||||
.filter((p) => selectedRowKeys.value.includes(p.id) && !existingIds.has(p.id))
|
if (idsToAdd.length === 0) {
|
||||||
.map((p) => {
|
showPackageSelector.value = false;
|
||||||
const tags = parseGradeTags(p.gradeTags);
|
return;
|
||||||
return {
|
}
|
||||||
packageId: p.id,
|
|
||||||
packageName: p.name,
|
addingPackages.value = true;
|
||||||
|
try {
|
||||||
|
const pkgMap = new Map(availablePackages.value.map((p) => [p.id, p]));
|
||||||
|
const newPackages: { packageId: number | string; packageName: string; gradeLevels: string[]; sortOrder: number }[] = [];
|
||||||
|
let sortOrder = selectedPackages.value.length;
|
||||||
|
|
||||||
|
for (const id of idsToAdd) {
|
||||||
|
let pkg = pkgMap.get(id);
|
||||||
|
if (!pkg) {
|
||||||
|
try {
|
||||||
|
pkg = await getCoursePackage(id);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tags = parseGradeTags(pkg.gradeTags);
|
||||||
|
newPackages.push({
|
||||||
|
packageId: pkg.id,
|
||||||
|
packageName: pkg.name,
|
||||||
gradeLevels: Array.isArray(tags) && tags.length > 0 ? tags : ['小班'],
|
gradeLevels: Array.isArray(tags) && tags.length > 0 ? tags : ['小班'],
|
||||||
sortOrder: selectedPackages.value.length,
|
sortOrder: sortOrder++,
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
selectedPackages.value.push(...newPackages);
|
selectedPackages.value.push(...newPackages);
|
||||||
|
} finally {
|
||||||
|
addingPackages.value = false;
|
||||||
|
}
|
||||||
selectedRowKeys.value = [];
|
selectedRowKeys.value = [];
|
||||||
showPackageSelector.value = false;
|
showPackageSelector.value = false;
|
||||||
};
|
};
|
||||||
@ -384,7 +432,6 @@ const handleSave = async () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchPackageDetail();
|
fetchPackageDetail();
|
||||||
fetchAvailablePackages();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user