feat: 套餐审核管理 - 审核页面、驳回可重新编辑提交、审核页已驳回不允许审核
Made-with: Cursor
This commit is contained in:
parent
f5de4e613d
commit
87899886d1
@ -57,7 +57,7 @@ request.interceptors.response.use(
|
|||||||
message.error('请求的资源不存在');
|
message.error('请求的资源不存在');
|
||||||
break;
|
break;
|
||||||
case 500:
|
case 500:
|
||||||
message.error('服务器错误');
|
message.error(data?.message || '服务器错误');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
message.error(data?.message || '请求失败');
|
message.error(data?.message || '请求失败');
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export interface CoursePackage {
|
|||||||
publishedAt?: string;
|
publishedAt?: string;
|
||||||
submittedAt?: string;
|
submittedAt?: string;
|
||||||
reviewedAt?: string;
|
reviewedAt?: string;
|
||||||
|
reviewComment?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
courses?: PackageCourse[];
|
courses?: PackageCourse[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,6 +82,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/admin/packages/PackageListView.vue'),
|
component: () => import('@/views/admin/packages/PackageListView.vue'),
|
||||||
meta: { title: '套餐管理' },
|
meta: { title: '套餐管理' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'packages/review',
|
||||||
|
name: 'AdminPackageReview',
|
||||||
|
component: () => import('@/views/admin/packages/PackageReviewView.vue'),
|
||||||
|
meta: { title: '套餐审核管理' },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'packages/create',
|
path: 'packages/create',
|
||||||
name: 'AdminPackageCreate',
|
name: 'AdminPackageCreate',
|
||||||
|
|||||||
@ -8,7 +8,12 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<a-button @click="router.back()">返回</a-button>
|
<a-button @click="router.back()">返回</a-button>
|
||||||
<a-button v-if="pkg?.status === 'DRAFT'" type="primary" @click="handleEdit">编辑</a-button>
|
<a-button v-if="pkg?.status === 'DRAFT'" type="primary" @click="handleEdit">编辑</a-button>
|
||||||
<a-button v-if="pkg?.status === 'DRAFT'" @click="handleSubmit">提交审核</a-button>
|
<a-tooltip v-if="pkg?.status === 'DRAFT' && (pkg?.courseCount || 0) === 0" title="请先添加至少一个课程包">
|
||||||
|
<span>
|
||||||
|
<a-button disabled>提交审核</a-button>
|
||||||
|
</span>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-button v-else-if="pkg?.status === 'DRAFT'" @click="handleSubmit">提交审核</a-button>
|
||||||
<a-button v-if="pkg?.status === 'APPROVED'" type="primary" @click="handlePublish">发布</a-button>
|
<a-button v-if="pkg?.status === 'APPROVED'" type="primary" @click="handlePublish">发布</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
@ -121,12 +126,16 @@ const handleEdit = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if ((pkg.value?.courseCount || 0) === 0) {
|
||||||
|
message.warning('套餐必须包含至少一个课程包,请先编辑添加');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await submitPackage(route.params.id as string);
|
await submitPackage(route.params.id as string);
|
||||||
message.success('提交成功');
|
message.success('提交成功');
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
message.error('提交失败');
|
message.error(error.response?.data?.message || '提交失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,10 +5,16 @@
|
|||||||
<span>课程套餐管理</span>
|
<span>课程套餐管理</span>
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="$router.push('/admin/packages/review')">
|
||||||
|
<AuditOutlined /> 审核管理
|
||||||
|
<a-badge v-if="pendingCount > 0" :count="pendingCount" :offset="[10, 0]" />
|
||||||
|
</a-button>
|
||||||
<a-button type="primary" @click="handleCreate">
|
<a-button type="primary" @click="handleCreate">
|
||||||
<template #icon><PlusOutlined /></template>
|
<template #icon><PlusOutlined /></template>
|
||||||
新建套餐
|
新建套餐
|
||||||
</a-button>
|
</a-button>
|
||||||
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 筛选 -->
|
<!-- 筛选 -->
|
||||||
@ -54,19 +60,32 @@
|
|||||||
<a-button
|
<a-button
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
v-if="record.status === 'DRAFT'"
|
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
||||||
@click="handleEdit(record)"
|
@click="handleEdit(record)"
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-tooltip v-if="(record.status === 'DRAFT' || record.status === 'REJECTED') && (record.courseCount || 0) === 0" title="请先添加至少一个课程包">
|
||||||
|
<span>
|
||||||
|
<a-button type="link" size="small" disabled>提交</a-button>
|
||||||
|
</span>
|
||||||
|
</a-tooltip>
|
||||||
<a-button
|
<a-button
|
||||||
|
v-else-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
v-if="record.status === 'DRAFT'"
|
|
||||||
@click="handleSubmit(record)"
|
@click="handleSubmit(record)"
|
||||||
>
|
>
|
||||||
提交
|
提交
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
v-if="record.status === 'PENDING_REVIEW'"
|
||||||
|
@click="handleReview(record)"
|
||||||
|
>
|
||||||
|
审核
|
||||||
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
@ -94,7 +113,7 @@
|
|||||||
import { ref, reactive, onMounted } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined, AuditOutlined } from '@ant-design/icons-vue';
|
||||||
import { getPackageList, deletePackage, submitPackage, publishPackage } from '@/api/package';
|
import { getPackageList, deletePackage, submitPackage, publishPackage } from '@/api/package';
|
||||||
import type { CoursePackage } from '@/api/package';
|
import type { CoursePackage } from '@/api/package';
|
||||||
|
|
||||||
@ -102,6 +121,7 @@ const router = useRouter();
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const dataSource = ref<CoursePackage[]>([]);
|
const dataSource = ref<CoursePackage[]>([]);
|
||||||
|
const pendingCount = ref(0);
|
||||||
const filters = reactive({
|
const filters = reactive({
|
||||||
status: undefined as string | undefined,
|
status: undefined as string | undefined,
|
||||||
});
|
});
|
||||||
@ -159,9 +179,15 @@ const fetchData = async () => {
|
|||||||
pageNum: pagination.current,
|
pageNum: pagination.current,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
}) as any;
|
}) as any;
|
||||||
// 后端返回 PageResult 格式:{ list, total, pageNum, pageSize, pages }
|
|
||||||
dataSource.value = res.list || [];
|
dataSource.value = res.list || [];
|
||||||
pagination.total = res.total || 0;
|
pagination.total = res.total || 0;
|
||||||
|
// 获取待审核数量
|
||||||
|
try {
|
||||||
|
const pendingRes = await getPackageList({ status: 'PENDING_REVIEW', pageNum: 1, pageSize: 1 }) as any;
|
||||||
|
pendingCount.value = pendingRes.total || 0;
|
||||||
|
} catch {
|
||||||
|
pendingCount.value = 0;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取套餐列表失败', error);
|
console.error('获取套餐列表失败', error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -187,13 +213,21 @@ const handleEdit = (record: any) => {
|
|||||||
router.push(`/admin/packages/${record.id}/edit`);
|
router.push(`/admin/packages/${record.id}/edit`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleReview = (record: any) => {
|
||||||
|
router.push('/admin/packages/review');
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async (record: any) => {
|
const handleSubmit = async (record: any) => {
|
||||||
|
if ((record.courseCount || 0) === 0) {
|
||||||
|
message.warning('套餐必须包含至少一个课程包,请先编辑添加');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await submitPackage(record.id);
|
await submitPackage(record.id);
|
||||||
message.success('提交成功');
|
message.success('提交成功');
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
message.error('提交失败');
|
message.error(error.response?.data?.message || '提交失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,319 @@
|
|||||||
|
<template>
|
||||||
|
<div class="package-review">
|
||||||
|
<div class="page-header">
|
||||||
|
<a-page-header title="套餐审核管理" sub-title="审核待发布的课程套餐" @back="$router.back()">
|
||||||
|
<template #extra>
|
||||||
|
<a-space>
|
||||||
|
<a-select v-model:value="filters.status" placeholder="全部状态" style="width: 120px" @change="fetchPackages">
|
||||||
|
<a-select-option value="PENDING_REVIEW">待审核</a-select-option>
|
||||||
|
<a-select-option value="REJECTED">已驳回</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-button @click="fetchPackages">
|
||||||
|
<ReloadOutlined /> 刷新
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-page-header>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="packages"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
row-key="id"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'name'">
|
||||||
|
<div class="package-name">
|
||||||
|
<a @click="showReviewModal(record)">{{ record.name }}</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'price'">
|
||||||
|
¥{{ ((record.price || 0) / 100).toFixed(2) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'gradeLevels'">
|
||||||
|
<a-tag v-for="grade in parseGradeLevels(record.gradeLevels)" :key="grade" size="small">
|
||||||
|
{{ grade }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'status'">
|
||||||
|
<a-tag :color="record.status === 'PENDING_REVIEW' ? 'processing' : 'error'">
|
||||||
|
{{ record.status === 'PENDING_REVIEW' ? '待审核' : '已驳回' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'submittedAt'">
|
||||||
|
{{ formatDate(record.submittedAt) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'actions'">
|
||||||
|
<a-space>
|
||||||
|
<a-button v-if="record.status === 'PENDING_REVIEW'" type="primary" size="small" @click="showReviewModal(record)">
|
||||||
|
审核
|
||||||
|
</a-button>
|
||||||
|
<a-button v-if="record.status === 'REJECTED'" size="small" @click="viewRejectReason(record)">
|
||||||
|
查看原因
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
<!-- 审核弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="reviewModalVisible"
|
||||||
|
:title="`审核: ${currentPackage?.name || ''}`"
|
||||||
|
width="700px"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="closeReviewModal"
|
||||||
|
>
|
||||||
|
<a-spin :spinning="loadingDetail">
|
||||||
|
<div v-if="currentPackage" class="review-content">
|
||||||
|
<a-descriptions title="套餐信息" bordered size="small" :column="2" style="margin-bottom: 16px">
|
||||||
|
<a-descriptions-item label="套餐名称">{{ currentPackage.name }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="价格">
|
||||||
|
¥{{ ((currentPackage.price || 0) / 100).toFixed(2) }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="适用年级">
|
||||||
|
<a-tag v-for="grade in parseGradeLevels(currentPackage.gradeLevels)" :key="grade" size="small">
|
||||||
|
{{ grade }}
|
||||||
|
</a-tag>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="课程数">{{ currentPackage.courseCount }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="描述" :span="2">
|
||||||
|
{{ currentPackage.description || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
|
||||||
|
<a-divider>包含课程包</a-divider>
|
||||||
|
<a-table
|
||||||
|
v-if="currentPackage.courses && currentPackage.courses.length > 0"
|
||||||
|
:columns="courseColumns"
|
||||||
|
:data-source="currentPackage.courses"
|
||||||
|
:pagination="false"
|
||||||
|
size="small"
|
||||||
|
row-key="id"
|
||||||
|
/>
|
||||||
|
<a-empty v-else description="暂无课程包" />
|
||||||
|
|
||||||
|
<a-form layout="vertical" style="margin-top: 16px">
|
||||||
|
<a-form-item label="审核意见">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="reviewComment"
|
||||||
|
placeholder="驳回时必填,通过时可选"
|
||||||
|
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a-space v-if="currentPackage.status === 'PENDING_REVIEW'">
|
||||||
|
<a-button @click="closeReviewModal">取消</a-button>
|
||||||
|
<a-button type="default" danger :loading="reviewing" @click="rejectPackage">
|
||||||
|
驳回
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" :loading="reviewing" @click="approvePackage">
|
||||||
|
通过并发布
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
<template v-else>
|
||||||
|
<a-alert type="info" message="已驳回的套餐请到套餐管理页面重新编辑并提交" show-icon style="flex: 1" />
|
||||||
|
<a-button @click="closeReviewModal">关闭</a-button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 查看驳回原因弹窗 -->
|
||||||
|
<a-modal v-model:open="rejectReasonVisible" title="驳回原因" :footer="null">
|
||||||
|
<a-alert type="error" :message="rejectReasonPackage?.reviewComment" show-icon />
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { ReloadOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { getPackageList, getPackageDetail, reviewPackage } from '@/api/package';
|
||||||
|
import type { CoursePackage } from '@/api/package';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const loadingDetail = ref(false);
|
||||||
|
const packages = ref<CoursePackage[]>([]);
|
||||||
|
|
||||||
|
const filters = reactive<{ status?: string }>({
|
||||||
|
status: 'PENDING_REVIEW',
|
||||||
|
});
|
||||||
|
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: '套餐名称', key: 'name', width: 200 },
|
||||||
|
{ title: '价格', key: 'price', width: 100 },
|
||||||
|
{ title: '适用年级', key: 'gradeLevels', width: 150 },
|
||||||
|
{ title: '课程数', key: 'courseCount', width: 80 },
|
||||||
|
{ title: '状态', key: 'status', width: 100 },
|
||||||
|
{ title: '提交时间', key: 'submittedAt', width: 150 },
|
||||||
|
{ title: '操作', key: 'actions', width: 150, fixed: 'right' as const },
|
||||||
|
];
|
||||||
|
|
||||||
|
const courseColumns = [
|
||||||
|
{ title: '课程名称', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '年级', dataIndex: 'gradeLevel', key: 'gradeLevel', width: 80 },
|
||||||
|
{ title: '排序', dataIndex: 'sortOrder', key: 'sortOrder', width: 60 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const reviewModalVisible = ref(false);
|
||||||
|
const reviewing = ref(false);
|
||||||
|
const currentPackage = ref<CoursePackage | null>(null);
|
||||||
|
const reviewComment = ref('');
|
||||||
|
|
||||||
|
const rejectReasonVisible = ref(false);
|
||||||
|
const rejectReasonPackage = ref<CoursePackage | null>(null);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchPackages();
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchPackages = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getPackageList({
|
||||||
|
status: filters.status,
|
||||||
|
pageNum: pagination.current,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
}) as any;
|
||||||
|
packages.value = res.list || [];
|
||||||
|
pagination.total = res.total || 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取套餐列表失败:', error);
|
||||||
|
message.error('获取套餐列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTableChange = (pag: any) => {
|
||||||
|
pagination.current = pag.current;
|
||||||
|
pagination.pageSize = pag.pageSize;
|
||||||
|
fetchPackages();
|
||||||
|
};
|
||||||
|
|
||||||
|
const showReviewModal = async (record: CoursePackage) => {
|
||||||
|
currentPackage.value = record;
|
||||||
|
reviewComment.value = '';
|
||||||
|
reviewModalVisible.value = true;
|
||||||
|
|
||||||
|
loadingDetail.value = true;
|
||||||
|
try {
|
||||||
|
const detail = await getPackageDetail(record.id);
|
||||||
|
currentPackage.value = detail as CoursePackage;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取套餐详情失败:', error);
|
||||||
|
message.error('获取套餐详情失败');
|
||||||
|
} finally {
|
||||||
|
loadingDetail.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeReviewModal = () => {
|
||||||
|
reviewModalVisible.value = false;
|
||||||
|
currentPackage.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const approvePackage = async () => {
|
||||||
|
if (!currentPackage.value) return;
|
||||||
|
|
||||||
|
reviewing.value = true;
|
||||||
|
try {
|
||||||
|
await reviewPackage(currentPackage.value.id, {
|
||||||
|
approved: true,
|
||||||
|
comment: reviewComment.value || '审核通过',
|
||||||
|
});
|
||||||
|
message.success('审核通过,套餐已发布');
|
||||||
|
closeReviewModal();
|
||||||
|
fetchPackages();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.response?.data?.message || '审核失败');
|
||||||
|
} finally {
|
||||||
|
reviewing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectPackage = async () => {
|
||||||
|
if (!currentPackage.value) return;
|
||||||
|
if (!reviewComment.value.trim()) {
|
||||||
|
message.warning('请填写驳回原因');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reviewing.value = true;
|
||||||
|
try {
|
||||||
|
await reviewPackage(currentPackage.value.id, {
|
||||||
|
approved: false,
|
||||||
|
comment: reviewComment.value,
|
||||||
|
});
|
||||||
|
message.success('已驳回');
|
||||||
|
closeReviewModal();
|
||||||
|
fetchPackages();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.response?.data?.message || '驳回失败');
|
||||||
|
} finally {
|
||||||
|
reviewing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewRejectReason = (record: CoursePackage) => {
|
||||||
|
rejectReasonPackage.value = record;
|
||||||
|
rejectReasonVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseGradeLevels = (gradeLevels: string | string[]) => {
|
||||||
|
if (Array.isArray(gradeLevels)) return gradeLevels;
|
||||||
|
try {
|
||||||
|
return JSON.parse(gradeLevels || '[]');
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (date?: string) => {
|
||||||
|
if (!date) return '-';
|
||||||
|
return new Date(date).toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.package-review {
|
||||||
|
.page-header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-name a {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-content {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -3,6 +3,7 @@ package com.reading.platform.controller.admin;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.reading.platform.common.annotation.RequireRole;
|
import com.reading.platform.common.annotation.RequireRole;
|
||||||
import com.reading.platform.common.enums.UserRole;
|
import com.reading.platform.common.enums.UserRole;
|
||||||
|
import com.reading.platform.common.security.SecurityUtils;
|
||||||
import com.reading.platform.common.response.PageResult;
|
import com.reading.platform.common.response.PageResult;
|
||||||
import com.reading.platform.common.response.Result;
|
import com.reading.platform.common.response.Result;
|
||||||
import com.reading.platform.dto.request.PackageCreateRequest;
|
import com.reading.platform.dto.request.PackageCreateRequest;
|
||||||
@ -98,7 +99,7 @@ public class AdminPackageController {
|
|||||||
@Operation(summary = "提交审核")
|
@Operation(summary = "提交审核")
|
||||||
@RequireRole(UserRole.ADMIN)
|
@RequireRole(UserRole.ADMIN)
|
||||||
public Result<Void> submit(@PathVariable Long id) {
|
public Result<Void> submit(@PathVariable Long id) {
|
||||||
packageService.submitPackage(id, 1L); // TODO: 从token获取userId
|
packageService.submitPackage(id, SecurityUtils.getCurrentUserId());
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ public class AdminPackageController {
|
|||||||
public Result<Void> review(
|
public Result<Void> review(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody ReviewRequest request) {
|
@RequestBody ReviewRequest request) {
|
||||||
packageService.reviewPackage(id, 1L, request.getApproved(), request.getComment());
|
packageService.reviewPackage(id, SecurityUtils.getCurrentUserId(), request.getApproved(), request.getComment());
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user