|
@@ -0,0 +1,395 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <!-- 父母课堂-手机端页面 -->
|
|
|
|
|
+ <div class="parent-mobile-course-container">
|
|
|
|
|
+ <!-- 页面标题和返回按钮 -->
|
|
|
|
|
+ <div class="page-header">
|
|
|
|
|
+ <h1 class="page-title">父母课堂</h1>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 搜索框 -->
|
|
|
|
|
+ <div class="search-section">
|
|
|
|
|
+ <el-autocomplete
|
|
|
|
|
+ v-model="SearchInput"
|
|
|
|
|
+ :fetch-suggestions="querySearch"
|
|
|
|
|
+ placeholder="搜索课程"
|
|
|
|
|
+ @select="handleSearchSelect"
|
|
|
|
|
+ class="search-input"
|
|
|
|
|
+ value-key="ctType"
|
|
|
|
|
+ :trigger-on-focus="false"
|
|
|
|
|
+ :key="searchKey"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #prefix>
|
|
|
|
|
+ <el-icon class="el-input__icon"><Search /></el-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 下拉项模板 -->
|
|
|
|
|
+ <template #default="{ item }">
|
|
|
|
|
+ <div class="scrollbar">
|
|
|
|
|
+ <!-- 序号和标题 -->
|
|
|
|
|
+ {{ item.ctTypeSort }} {{ item.ctType }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-autocomplete>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 课程列表 -->
|
|
|
|
|
+ <div class="course-list">
|
|
|
|
|
+ <!-- 电脑端样式:与原课程相同的布局结构 -->
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="small-box parent-mobile-course-item"
|
|
|
|
|
+ v-for="(course, index) in parentMobileCourseData"
|
|
|
|
|
+ :key="course.id"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="nested-box"
|
|
|
|
|
+ :style="{
|
|
|
|
|
+ backgroundImage: `url(${course.ctTypeImage})`,
|
|
|
|
|
+ backgroundSize: 'cover'
|
|
|
|
|
+ }"
|
|
|
|
|
+ @click="goToParentMobileCourse(course)"
|
|
|
|
|
+ ></div>
|
|
|
|
|
+ <div class="additional-text">
|
|
|
|
|
+ {{ course.ctTypeSort }} {{ course.ctType }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 移动端样式 -->
|
|
|
|
|
+ <div class="parent-mobile-container">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="parent-mobile-item"
|
|
|
|
|
+ v-for="(course, index) in parentMobileCourseData"
|
|
|
|
|
+ :key="course.id"
|
|
|
|
|
+ @click="goToParentMobileCourse(course)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <!-- 左侧图片 -->
|
|
|
|
|
+ <div class="parent-mobile-image">
|
|
|
|
|
+ <img :src="course.ctTypeImage" :alt="course.title" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 右侧内容 -->
|
|
|
|
|
+ <div class="parent-mobile-content">
|
|
|
|
|
+ <div class="parent-mobile-title">
|
|
|
|
|
+ <span class="parent-mobile-index">{{ course.ctTypeSort }}</span>
|
|
|
|
|
+ {{ course.ctType }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="parent-mobile-desc">{{ course.ctTypeDescribe || '' }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 空状态 -->
|
|
|
|
|
+ <div v-if="parentMobileCourseData.length === 0" class="empty-state" style="width: 100%; text-align: center; padding: 40px;">
|
|
|
|
|
+ <p>暂无课程</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
|
|
+import { ClassList, ClassOutline } from '@/api/class.js'
|
|
|
|
|
+import { Search, ArrowLeftBold } from '@element-plus/icons-vue'
|
|
|
|
|
+import { Message } from '@/utils/message/Message.js'
|
|
|
|
|
+import { CONFIG } from '@/utils/roleUtils.js'
|
|
|
|
|
+import '@/styles/parentMobileCourse.css'
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+
|
|
|
|
|
+// 搜索框
|
|
|
|
|
+const SearchInput = ref('')
|
|
|
|
|
+// 用于强制重新渲染搜索组件的key
|
|
|
|
|
+const searchKey = ref(Date.now())
|
|
|
|
|
+
|
|
|
|
|
+// 父母课堂-手机端数据
|
|
|
|
|
+const parentMobileCourseData = ref([])
|
|
|
|
|
+
|
|
|
|
|
+// 检测是否为移动设备
|
|
|
|
|
+const isMobile = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const checkIsMobile = () => {
|
|
|
|
|
+ if (typeof window !== 'undefined') {
|
|
|
|
|
+ // 使用多种方式检测移动设备
|
|
|
|
|
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
|
|
|
|
|
+ window.innerWidth <= 768; // 或者使用屏幕宽度作为判断标准
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取父母课堂-手机端数据
|
|
|
|
|
+const fetchParentMobileCourseData = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 获取年级数据
|
|
|
|
|
+ const classResponse = await ClassList()
|
|
|
|
|
+ if (classResponse.code === 0) {
|
|
|
|
|
+ const classData = classResponse.data
|
|
|
|
|
+
|
|
|
|
|
+ // 获取到数据,优先从localStorage读取选中值
|
|
|
|
|
+ const savedGrade = localStorage.getItem('selectedGrade')
|
|
|
|
|
+ const selectedGrade = savedGrade || (classData.length > 0 ? classData[0].ctType : '')
|
|
|
|
|
+
|
|
|
|
|
+ // 查找对应的年级ID
|
|
|
|
|
+ const selectedItem = classData.find(item => item.ctType === selectedGrade) || classData[0]
|
|
|
|
|
+ if (selectedItem) {
|
|
|
|
|
+ // 获取该年级下的课程大纲数据(父母课堂-手机端类型,根据原页面配置)
|
|
|
|
|
+ const outlineRes = await ClassOutline(selectedItem.id, '5')
|
|
|
|
|
+ if (outlineRes.code === 0) {
|
|
|
|
|
+ // 处理课程数据,添加序号
|
|
|
|
|
+ parentMobileCourseData.value = outlineRes.data.map((item, index) => {
|
|
|
|
|
+ item.ctTypeSort = index + 1
|
|
|
|
|
+ item.ctTypeSort = item.ctTypeSort > 9 ? item.ctTypeSort : "0" + item.ctTypeSort
|
|
|
|
|
+ return item
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取父母课堂-手机端数据失败:', error)
|
|
|
|
|
+ Message().notifyWarning('获取课程数据失败', true)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 搜索建议查询方法
|
|
|
|
|
+const querySearch = (queryString, cb) => {
|
|
|
|
|
+ const results = queryString
|
|
|
|
|
+ ? parentMobileCourseData.value.filter(item => {
|
|
|
|
|
+ // 课程标题和序号查询
|
|
|
|
|
+ return item.ctType.toLowerCase().includes(queryString.toLowerCase()) ||
|
|
|
|
|
+ // 类型检查,确保ctTypeSort是字符串类型
|
|
|
|
|
+ (item.ctTypeSort && item.ctTypeSort.toString().includes(queryString))
|
|
|
|
|
+ })
|
|
|
|
|
+ : parentMobileCourseData.value
|
|
|
|
|
+ cb(results)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 搜索选择处理方法
|
|
|
|
|
+const handleSearchSelect = item => {
|
|
|
|
|
+ goToParentMobileCourse(item)
|
|
|
|
|
+ // 清空输入框
|
|
|
|
|
+ SearchInput.value = '';
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 返回首页
|
|
|
|
|
+const goBack = () => {
|
|
|
|
|
+ router.push('/home')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 跳转到父母课堂-手机端详情页
|
|
|
|
|
+const goToParentMobileCourse = (outlineData) => {
|
|
|
|
|
+ router.push({
|
|
|
|
|
+ path: '/parent-mobile-course-detail',
|
|
|
|
|
+ state: { typeId: outlineData.id, typeName: outlineData.ctType, typeSort: outlineData.ctTypeSort }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ // 检测是否为移动设备
|
|
|
|
|
+ isMobile.value = checkIsMobile();
|
|
|
|
|
+
|
|
|
|
|
+ // 获取父母课堂-手机端数据
|
|
|
|
|
+ fetchParentMobileCourseData()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+@use 'sass:math';
|
|
|
|
|
+
|
|
|
|
|
+// 定义rpx转换函数
|
|
|
|
|
+@function rpx($px) {
|
|
|
|
|
+ @return math.div($px, 750) * 100vw;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.parent-mobile-course-container {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ background: linear-gradient(
|
|
|
|
|
+ to bottom,
|
|
|
|
|
+ #e2ddfc,
|
|
|
|
|
+ #f1effd
|
|
|
|
|
+ );
|
|
|
|
|
+ padding: rpx(20);
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-header {
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: rpx(30);
|
|
|
|
|
+ padding-bottom: rpx(20);
|
|
|
|
|
+ border-bottom: 1px solid #dcdcff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-title {
|
|
|
|
|
+ font-size: rpx(36);
|
|
|
|
|
+ color: #44449c;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-section {
|
|
|
|
|
+ margin-bottom: rpx(30);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-input {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ max-width: rpx(600);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.course-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: rpx(20);
|
|
|
|
|
+ justify-content: flex-start;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.small-box {
|
|
|
|
|
+ width: calc(25% - rpx(15)); /* 每行4个 */
|
|
|
|
|
+ aspect-ratio: 1 / 1;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ border-radius: rpx(10);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ transform: translateY(rpx(-5));
|
|
|
|
|
+ box-shadow: 0 rpx(10) rpx(20) rgba(0, 0, 0, 0.1);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.nested-box {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ transform: scale(1.05);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.additional-text {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ padding: rpx(10);
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: rpx(24);
|
|
|
|
|
+ backdrop-filter: blur(rpx(5));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 移动端样式 */
|
|
|
|
|
+.parent-mobile-container {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: rpx(20);
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.parent-mobile-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border-radius: rpx(15);
|
|
|
|
|
+ padding: rpx(20);
|
|
|
|
|
+ box-shadow: 0 rpx(5) rpx(15) rgba(0, 0, 0, 0.05);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ transform: translateY(rpx(-2));
|
|
|
|
|
+ box-shadow: 0 rpx(8) rpx(20) rgba(0, 0, 0, 0.1);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.parent-mobile-image {
|
|
|
|
|
+ width: rpx(120);
|
|
|
|
|
+ height: rpx(120);
|
|
|
|
|
+ border-radius: rpx(10);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ margin-right: rpx(20);
|
|
|
|
|
+
|
|
|
|
|
+ img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.parent-mobile-content {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.parent-mobile-title {
|
|
|
|
|
+ font-size: rpx(28);
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ margin-bottom: rpx(10);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: rpx(10);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.parent-mobile-index {
|
|
|
|
|
+ background: #44449c;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ width: rpx(30);
|
|
|
|
|
+ height: rpx(30);
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-size: rpx(20);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.parent-mobile-desc {
|
|
|
|
|
+ font-size: rpx(24);
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ line-height: rpx(32);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 响应式设计 */
|
|
|
|
|
+@media (max-width: 768px) {
|
|
|
|
|
+ .parent-mobile-course-container {
|
|
|
|
|
+ padding: rpx(15);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .page-header {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: rpx(15);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .small-box {
|
|
|
|
|
+ width: calc(50% - rpx(10)); /* 移动端每行2个 */
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .parent-mobile-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .course-list {
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 480px) {
|
|
|
|
|
+ .small-box {
|
|
|
|
|
+ width: 100%; /* 超小屏幕上每行1个 */
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.empty-state {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: rpx(40);
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ font-size: rpx(28);
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|