fix: course package detail page - permission gate, status logic, preview lock
This commit is contained in:
@@ -24,31 +24,43 @@
|
||||
<div class="section">
|
||||
<h2>包含课节</h2>
|
||||
<div v-if="courseGroups.length === 0" class="empty">暂无课节</div>
|
||||
<van-cell v-for="g in courseGroups" :key="g.id" :title="g.group_name">
|
||||
<template #label>
|
||||
<div v-if="g.course_sections?.length">
|
||||
{{ g.course_sections.map((s: any) => (s.course_name || '') + '(' + s.section_name + ')').join('、') }}
|
||||
<div v-for="g in courseGroups" :key="g.id" class="group-sections">
|
||||
<div class="group-name">{{ g.group_name }}</div>
|
||||
<div v-if="g.course_sections?.length" class="section-list">
|
||||
<div v-for="s in g.course_sections" :key="s.id" class="section-item" :class="{ locked: !isPreviewable(s) }" @click="handleSectionClick(s)">
|
||||
<span class="section-label">{{ s.course_name || '' }}({{ s.section_name }})</span>
|
||||
<span v-if="isPreviewable(s)" class="section-action">
|
||||
<span class="preview-badge">试看</span>
|
||||
<span class="section-arrow">></span>
|
||||
</span>
|
||||
<span v-else class="section-action">
|
||||
<span class="lock-label">已锁定</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else style="color: #999;">无课程</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</div>
|
||||
<div v-else style="color: #999; padding: 0.08rem 0.32rem; font-size: 0.26rem;">无课程</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<van-button v-if="!isPublished" type="primary" @click="onEdit">编辑</van-button>
|
||||
<van-button v-if="!isPublished" type="danger" @click="onDelete">删除</van-button>
|
||||
<van-button v-if="!isPublished" type="success" @click="onPublish">发布上架</van-button>
|
||||
<van-button v-if="isPublished" type="warning" @click="onUnpublish">下架</van-button>
|
||||
<div class="footer" v-if="isOwner">
|
||||
<van-button v-if="!isActive" type="primary" @click="onEdit">编辑</van-button>
|
||||
<van-button v-if="!isActive" type="danger" @click="onDelete">删除</van-button>
|
||||
<van-button v-if="!isActive" type="success" @click="onPublish">发布上架</van-button>
|
||||
<van-button v-if="isActive" type="warning" @click="onUnpublish">下架</van-button>
|
||||
</div>
|
||||
<div class="footer" v-else-if="isLoggedIn">
|
||||
<van-button type="default" @click="goBack">返回</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent, onMounted, ref} from 'vue';
|
||||
import {Button, Cell, CellGroup, Tag, showConfirmDialog, showFailToast} from 'vant';
|
||||
import {Button, Cell, CellGroup, Tag, showConfirmDialog, showDialog, showFailToast} from 'vant';
|
||||
import {useRoute, useRouter} from 'vue-router';
|
||||
import useCoursePackage from '~/store/course-package';
|
||||
import {CourseGroup} from '~/types';
|
||||
import useUser from '~/store/user';
|
||||
import {CourseGroup, CourseSection} from '~/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'course-package-detail',
|
||||
@@ -62,12 +74,19 @@ export default defineComponent({
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const {store, findById, remove, listPackageItems, publish, unpublish} = useCoursePackage();
|
||||
const {store: userStore} = useUser();
|
||||
|
||||
const pkg = ref<any>(null);
|
||||
const courseGroups = ref<CourseGroup[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const isActive = computed(() => pkg.value?.package_status === 'ACTIVE');
|
||||
const isPublished = computed(() => !!pkg.value?.published_at);
|
||||
const isLoggedIn = computed(() => !!localStorage.getItem('Authorization'));
|
||||
const isOwner = computed(() => {
|
||||
if (!pkg.value?.created_by || !userStore.current?.hty_id) return false;
|
||||
return pkg.value.created_by === userStore.current.hty_id;
|
||||
});
|
||||
|
||||
const loadData = async () => {
|
||||
const id = route.query.id as string;
|
||||
@@ -95,6 +114,8 @@ export default defineComponent({
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const goBack = () => router.go(-1);
|
||||
|
||||
const onEdit = () => {
|
||||
router.push(`/course/course-package/edit?id=${pkg.value?.id}`);
|
||||
};
|
||||
@@ -136,11 +157,44 @@ export default defineComponent({
|
||||
} catch { /* cancelled */ }
|
||||
};
|
||||
|
||||
const previewSection = (section: CourseSection) => {
|
||||
if (section.id) {
|
||||
router.push(`/course/section/detail?id=${section.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Non-owner can only preview the first section; owner can preview all
|
||||
const firstSectionId = computed(() => {
|
||||
for (const g of courseGroups.value) {
|
||||
if (g.course_sections?.length) return g.course_sections[0].id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const isPreviewable = (section: CourseSection) => {
|
||||
if (!section.id) return false;
|
||||
if (isOwner.value) return true;
|
||||
return section.id === firstSectionId.value;
|
||||
};
|
||||
|
||||
const handleSectionClick = (section: CourseSection) => {
|
||||
if (isPreviewable(section)) {
|
||||
previewSection(section);
|
||||
} else {
|
||||
showDialog({
|
||||
title: '提示',
|
||||
message: '请购买课包后查看完整内容',
|
||||
confirmButtonText: '知道了',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(loadData);
|
||||
|
||||
return {
|
||||
pkg, courseGroups, loading, isPublished,
|
||||
onEdit, onDelete, onPublish, onUnpublish,
|
||||
pkg, courseGroups, loading, isActive, isPublished, isOwner, isLoggedIn,
|
||||
onEdit, onDelete, onPublish, onUnpublish, previewSection, goBack,
|
||||
isPreviewable, handleSectionClick,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -181,6 +235,77 @@ export default defineComponent({
|
||||
padding: 0.32rem;
|
||||
}
|
||||
|
||||
.group-sections {
|
||||
margin-bottom: 0.16rem;
|
||||
|
||||
.group-name {
|
||||
font-size: 0.28rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding: 0.16rem 0.32rem 0.08rem;
|
||||
}
|
||||
|
||||
.section-list {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
margin: 0 0.24rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.2rem 0.32rem;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
cursor: pointer;
|
||||
font-size: 0.26rem;
|
||||
color: #333;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
&.locked {
|
||||
color: #bbb;
|
||||
cursor: default;
|
||||
|
||||
&:active {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.section-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.08rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.preview-badge {
|
||||
font-size: 0.2rem;
|
||||
color: #fff;
|
||||
background: #1989fa;
|
||||
border-radius: 4px;
|
||||
padding: 0.02rem 0.1rem;
|
||||
}
|
||||
|
||||
.lock-label {
|
||||
font-size: 0.2rem;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.section-arrow {
|
||||
color: #ccc;
|
||||
font-size: 0.28rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0.16rem;
|
||||
|
||||
Reference in New Issue
Block a user