|
@@ -1,50 +1,59 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="list">
|
|
|
|
|
- <div class="offset">
|
|
|
|
|
- <div class="item" v-for="item in data" :key="item.courseID" @click="handleChange('change', item)">
|
|
|
|
|
- <!-- 骨架屏 -->
|
|
|
|
|
- <ElSkeleton animated :loading="loading" style="width: 100%; height: 100%">
|
|
|
|
|
- <template #template>
|
|
|
|
|
- <div class="top">
|
|
|
|
|
- <ElSkeletonItem variant="image" style="width: 100%; height: 100%; border-radius: 10px" />
|
|
|
|
|
- <div style="padding: 16px 0">
|
|
|
|
|
- <ElSkeletonItem variant="p" style="width: 80%" />
|
|
|
|
|
- <ElSkeletonItem variant="p" style="width: 40%; margin-top: 10px" />
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- <template #default>
|
|
|
|
|
- <div class="top">
|
|
|
|
|
- <ElImage class="cover" :src="getCover(item)" lazy fit="cover">
|
|
|
|
|
- <template #error>
|
|
|
|
|
- <div class="image-slot">
|
|
|
|
|
- <ElIcon><icon-picture /></ElIcon>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </ElImage>
|
|
|
|
|
-
|
|
|
|
|
- <div class="type">
|
|
|
|
|
- <div class="type-tag">{{ isSelected(item) ? '已选择' : '未选择' }}</div>
|
|
|
|
|
- <!-- <el-checkbox class="" v-model="isSelected(item)" @change="handleChange('change', item)" /> -->
|
|
|
|
|
- <div class="type-tag">{{ useDateFormat(item.createTime, 'YYYY-MM-DD') }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <!-- 卡片列表 骨架屏-->
|
|
|
|
|
+ <div v-if="loading" class="mt-4 grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
|
|
|
|
+ <!-- 骨架屏 -->
|
|
|
|
|
+ <ElSkeleton animated :loading="loading" style="width: 100%; height: 100%" v-for="item in Array(8)" :key="item">
|
|
|
|
|
+ <template #template>
|
|
|
|
|
+ <div class="course-card">
|
|
|
|
|
+ <ElSkeletonItem class="cover" variant="image" style="width: 100%; height: 100%; border-radius: 10px" />
|
|
|
|
|
+ <div style="padding: 16px 0" class="space-y-2">
|
|
|
|
|
+ <ElSkeletonItem variant="p" style="width: 40%" />
|
|
|
|
|
+ <ElSkeletonItem variant="p" style="width: 80%" />
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="bottom space-y-2">
|
|
|
|
|
- <h2>{{ item.courseName }}</h2>
|
|
|
|
|
- <div class="desc">{{ item.courseDesc || '暂无描述' }}</div>
|
|
|
|
|
- <div>
|
|
|
|
|
- <el-button size="small" type="primary" :icon="Edit" @click.stop="handleChange('edit', item)">编辑</el-button>
|
|
|
|
|
- <el-button size="small" type="danger" :icon="Delete" @click.stop="handleChange('delete', item)">删除</el-button>
|
|
|
|
|
- <el-button size="small" type="info" :icon="CopyDocument" @click.stop="handleChange('copy', item)">复制</el-button>
|
|
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ElSkeleton>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 卡片列表 骨架屏-->
|
|
|
|
|
+ <div v-else class="mt-4 grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="course-card cursor-pointer rounded-md border p-[16px] transition-all duration-200"
|
|
|
|
|
+ :class="{
|
|
|
|
|
+ 'border-primary bg-blue-50': checkedCouerse.includes(String(item.courseID)),
|
|
|
|
|
+ 'border-gray-200': !checkedCouerse.includes(String(item.courseID))
|
|
|
|
|
+ }"
|
|
|
|
|
+ v-for="item in data"
|
|
|
|
|
+ :key="item.courseID"
|
|
|
|
|
+ @click="toggleCheckItem(String(item.courseID))"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="relative">
|
|
|
|
|
+ <ElImage class="cover" :src="getCover(item)" lazy fit="cover" :preview-src-list="[]" :hide-on-click-modal="true" draggable="false" @click.prevent @dragstart.prevent>
|
|
|
|
|
+ <template #error>
|
|
|
|
|
+ <div class="image-slot">
|
|
|
|
|
+ <ElIcon><icon-picture /></ElIcon>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </ElSkeleton>
|
|
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ElImage>
|
|
|
|
|
+ <div class="type">
|
|
|
|
|
+ <el-checkbox :model-value="checkedCouerse.includes(String(item.courseID))" class="hidden" />
|
|
|
|
|
+ <div class="type-tag">{{ useDateFormat(item.createTime, 'YYYY-MM-DD') }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-bottom space-y-2">
|
|
|
|
|
+ <h2>{{ item.courseName }}</h2>
|
|
|
|
|
+ <div class="desc">{{ item.courseDesc || '暂无描述' }}</div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <el-button size="small" type="primary" :icon="Edit" @click.stop="handleChange('edit', item)">编辑</el-button>
|
|
|
|
|
+ <el-button size="small" type="danger" :icon="Delete" @click.stop="handleChange('delete', item)">删除</el-button>
|
|
|
|
|
+ <el-button size="small" type="info" :icon="CopyDocument" @click.stop="handleChange('copy', item)">复制</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div style="margin-top: 16vh" v-if="data.length === 0 && !props.loading">
|
|
<div style="margin-top: 16vh" v-if="data.length === 0 && !props.loading">
|
|
|
- <ElEmpty :description="`未找到课程数据`" />
|
|
|
|
|
|
|
+ <ElEmpty :description="`暂无课程数据`" />
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
@@ -56,16 +65,12 @@
|
|
|
import { type Course } from '@/api/course.api'
|
|
import { type Course } from '@/api/course.api'
|
|
|
import { ElMessageBox } from 'element-plus'
|
|
import { ElMessageBox } from 'element-plus'
|
|
|
|
|
|
|
|
- const props = withDefaults(defineProps<{ data: Course[]; loading: boolean }>(), {
|
|
|
|
|
- data: () => [],
|
|
|
|
|
- loading: true
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- const selectCourses = ref<string[]>([])
|
|
|
|
|
|
|
+ // 参数
|
|
|
|
|
+ const props = withDefaults(defineProps<{ data: Course[]; loading: boolean; checkedCouerse: string[] }>(), {})
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
const emit = defineEmits<{
|
|
|
- handle: [type: string, item: Course]
|
|
|
|
|
- change: [item: string[]]
|
|
|
|
|
|
|
+ (e: 'update:checkedList', value: string[]): void
|
|
|
|
|
+ (e: 'handle', type: string, item: Course): void
|
|
|
}>()
|
|
}>()
|
|
|
|
|
|
|
|
const getCover = (item: Course) => {
|
|
const getCover = (item: Course) => {
|
|
@@ -75,19 +80,20 @@
|
|
|
return cover2
|
|
return cover2
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 选择课程
|
|
|
|
|
+ const toggleCheckItem = (item: string) => {
|
|
|
|
|
+ const newChecked = [...props.checkedCouerse]
|
|
|
|
|
+ const index = newChecked.indexOf(item)
|
|
|
|
|
+ if (index > -1) {
|
|
|
|
|
+ newChecked.splice(index, 1)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ newChecked.push(item)
|
|
|
|
|
+ }
|
|
|
|
|
+ emit('update:checkedList', newChecked)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const handleChange = (type: string, item: Course) => {
|
|
const handleChange = (type: string, item: Course) => {
|
|
|
- const courseId = String(item.courseID) // 确保 ID 是字符串
|
|
|
|
|
- const exist = selectCourses.value.includes(courseId)
|
|
|
|
|
switch (type) {
|
|
switch (type) {
|
|
|
- case 'change':
|
|
|
|
|
- if (exist) {
|
|
|
|
|
- selectCourses.value = selectCourses.value.filter((id) => id !== courseId)
|
|
|
|
|
- } else {
|
|
|
|
|
- selectCourses.value.push(courseId)
|
|
|
|
|
- }
|
|
|
|
|
- console.log(selectCourses.value) // 调试输出
|
|
|
|
|
- emit('change', selectCourses.value)
|
|
|
|
|
- break
|
|
|
|
|
case 'delete':
|
|
case 'delete':
|
|
|
ElMessageBox.confirm('确定删除课程吗?', '提示', {
|
|
ElMessageBox.confirm('确定删除课程吗?', '提示', {
|
|
|
confirmButtonText: '确定',
|
|
confirmButtonText: '确定',
|
|
@@ -101,182 +107,97 @@
|
|
|
}
|
|
}
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- const isSelected = (item: Course) => {
|
|
|
|
|
- return selectCourses.value.includes(String(item.courseID))
|
|
|
|
|
- }
|
|
|
|
|
</script>
|
|
</script>
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
|
- .list {
|
|
|
|
|
- margin-top: 20px;
|
|
|
|
|
-
|
|
|
|
|
- .custom-segmented .el-segmented {
|
|
|
|
|
- height: 40px;
|
|
|
|
|
- padding: 6px;
|
|
|
|
|
-
|
|
|
|
|
- --el-border-radius-base: 8px;
|
|
|
|
|
|
|
+ .course-card {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ .cover {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ aspect-ratio: 16/7;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ background: var(--art-gray-200);
|
|
|
|
|
+ border-radius: calc(var(--custom-radius) / 2 + 2px) calc(var(--custom-radius) / 2 + 2px) 0 0;
|
|
|
|
|
+
|
|
|
|
|
+ .image-slot {
|
|
|
|
|
+ font-size: 26px;
|
|
|
|
|
+ color: var(--art-gray-400);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- .offset {
|
|
|
|
|
|
|
+ .type {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 2px;
|
|
|
|
|
+ right: 5px;
|
|
|
|
|
+ left: 5px;
|
|
|
|
|
+ padding: 5px 4px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: rgba(#fff, 0.8);
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
- width: calc(100% + 20px);
|
|
|
|
|
-
|
|
|
|
|
- .item {
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
- width: calc(20% - 20px);
|
|
|
|
|
- margin: 0 20px 20px 0;
|
|
|
|
|
- padding: 20px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- border: 1px solid var(--art-border-color);
|
|
|
|
|
- border-radius: calc(var(--custom-radius) / 2 + 2px) !important;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- .el-button {
|
|
|
|
|
- opacity: 1 !important;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .top {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- aspect-ratio: 16/7;
|
|
|
|
|
-
|
|
|
|
|
- .cover {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- object-fit: cover;
|
|
|
|
|
- background: var(--art-gray-200);
|
|
|
|
|
- border-radius: calc(var(--custom-radius) / 2 + 2px) calc(var(--custom-radius) / 2 + 2px) 0 0;
|
|
|
|
|
-
|
|
|
|
|
- .image-slot {
|
|
|
|
|
- font-size: 26px;
|
|
|
|
|
- color: var(--art-gray-400);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- .type {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- top: 5px;
|
|
|
|
|
- right: 5px;
|
|
|
|
|
- left: 5px;
|
|
|
|
|
- padding: 5px 4px;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- color: rgba(#fff, 0.8);
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- .type-tag {
|
|
|
|
|
- padding: 2px 4px;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- background: rgba($color: #000, $alpha: 60%);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .bottom {
|
|
|
|
|
- padding: 5px 10px;
|
|
|
|
|
-
|
|
|
|
|
- h2 {
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- color: #333;
|
|
|
|
|
-
|
|
|
|
|
- @include ellipsis();
|
|
|
|
|
- }
|
|
|
|
|
- .desc {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- color: var(--art-text-gray-600);
|
|
|
|
|
-
|
|
|
|
|
- @include ellipsis();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .info {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 25px;
|
|
|
|
|
- margin-top: 6px;
|
|
|
|
|
- line-height: 25px;
|
|
|
|
|
-
|
|
|
|
|
- .text {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- color: var(--art-text-gray-600);
|
|
|
|
|
-
|
|
|
|
|
- i {
|
|
|
|
|
- margin-right: 5px;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- span {
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- color: var(--art-gray-600);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .line {
|
|
|
|
|
- width: 1px;
|
|
|
|
|
- height: 12px;
|
|
|
|
|
- margin: 0 15px;
|
|
|
|
|
- background-color: var(--art-border-dashed-color);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .el-button {
|
|
|
|
|
- opacity: 1;
|
|
|
|
|
- transition: all 0.3s;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ .type-tag {
|
|
|
|
|
+ padding: 2px 4px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ background: rgba($color: #000, $alpha: 60%);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ .card-bottom {
|
|
|
|
|
+ padding: 5px 10px;
|
|
|
|
|
|
|
|
- @media only screen and (max-width: $device-notebook) {
|
|
|
|
|
- .list {
|
|
|
|
|
- .offset {
|
|
|
|
|
- .item {
|
|
|
|
|
- width: calc(25% - 20px);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ h2 {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+
|
|
|
|
|
+ @include ellipsis();
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
+ .desc {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: var(--art-text-gray-600);
|
|
|
|
|
|
|
|
- @media only screen and (max-width: $device-ipad-pro) {
|
|
|
|
|
- .list {
|
|
|
|
|
- .offset {
|
|
|
|
|
- .item {
|
|
|
|
|
- width: calc(33.333% - 20px);
|
|
|
|
|
|
|
+ @include ellipsis();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .bottom {
|
|
|
|
|
- h2 {
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .info {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 25px;
|
|
|
|
|
+ margin-top: 6px;
|
|
|
|
|
+ line-height: 25px;
|
|
|
|
|
+
|
|
|
|
|
+ .text {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ color: var(--art-text-gray-600);
|
|
|
|
|
+
|
|
|
|
|
+ i {
|
|
|
|
|
+ margin-right: 5px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- @media only screen and (max-width: $device-ipad) {
|
|
|
|
|
- .list {
|
|
|
|
|
- .offset {
|
|
|
|
|
- .item {
|
|
|
|
|
- width: calc(50% - 20px);
|
|
|
|
|
|
|
+ span {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: var(--art-gray-600);
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- @media only screen and (max-width: $device-phone) {
|
|
|
|
|
- .list {
|
|
|
|
|
- .offset {
|
|
|
|
|
- .item {
|
|
|
|
|
- width: calc(100% - 20px);
|
|
|
|
|
|
|
+ .line {
|
|
|
|
|
+ width: 1px;
|
|
|
|
|
+ height: 12px;
|
|
|
|
|
+ margin: 0 15px;
|
|
|
|
|
+ background-color: var(--art-border-dashed-color);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .el-button {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|