This commit is contained in:
Net 2025-06-14 13:09:26 +08:00
parent 3ee472f690
commit 9fe8dd2880
5 changed files with 1800 additions and 365 deletions

View File

@ -139,6 +139,13 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiaoXueZiYuan/indexList",
"style": {
"navigationBarTitleText": "资源类型",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/HuoDongZiYuan/index",
"style": {
@ -420,13 +427,13 @@
"pagePath": "pages/base/service/index",
"iconPath": "static/tabBar/x2.png",
"selectedIconPath": "static/tabBar/2.png"
},
{
"text": "我的",
"pagePath": "pages/base/mine/index",
"iconPath": "static/tabBar/x3.png",
"selectedIconPath": "static/tabBar/3.png"
}
// {
// "text": "我的",
// "pagePath": "pages/base/mine/index",
// "iconPath": "static/tabBar/x3.png",
// "selectedIconPath": "static/tabBar/3.png"
// }
]
}
}
}

View File

@ -1,14 +1,977 @@
<template>
<view class="mine-page">
<!-- 1. 顶部 Header - 复制自service页面 -->
<view class="header-section">
<view class="header-gradient"></view>
<!-- 退出按钮 -->
<view class="logout-btn" @click="handleLogout">
<text class="logout-text">退出</text>
</view>
<!-- 老师信息 -->
<view class="teacher-info">
<view class="teacher-avatar">
<image
class="avatar-image"
:src="teacherData.avatar || '/static/base/default-avatar.png'"
mode="aspectFill"
></image>
</view>
<view class="teacher-details">
<view class="teacher-name">{{ teacherData.name }}</view>
<view class="teacher-position">{{ teacherData.position }}</view>
<view class="teacher-class">{{ teacherData.className }}</view>
</view>
</view>
<!-- 统计信息 -->
<view class="stats-info">
<view class="stat-item">
<text class="stat-label">积分</text>
<text class="stat-value">{{ teacherData.score }}</text>
</view>
<view class="stat-divider">|</view>
<view class="stat-item">
<text class="stat-label">工作量</text>
<text class="stat-value">{{ teacherData.workload }}课时</text>
</view>
</view>
<!-- 介绍文字 -->
<view class="teacher-intro">
{{ teacherData.introduction }}
</view>
</view>
<!-- 2. 日程管理区域 -->
<view class="main-content">
<!-- 近期日程标题 -->
<view class="schedule-header">
<text class="schedule-title">近期日程</text>
<view class="schedule-link" @click="goToSchedule">
<text class="link-text">查看日程</text>
<text class="link-arrow">></text>
</view>
</view>
<!-- 日期选择器 -->
<view class="date-selector">
<view class="date-container">
<view
class="date-item"
v-for="(date, index) in weekDates"
:key="index"
:class="{ active: selectedDateIndex === index }"
@click="selectDate(index)"
>
<text class="date-day">{{ date.day }}</text>
<text class="date-number">{{ date.number }}</text>
<view v-if="date.hasEvent" class="date-dot"></view>
</view>
</view>
</view>
<!-- 日程列表 -->
<scroll-view class="schedule-list" scroll-y="true" show-scrollbar="false" enhanced="true">
<view class="schedule-content">
<view
class="schedule-item"
v-for="(item, index) in getCurrentSchedule"
:key="index"
>
<view class="time-column">
<text class="start-time">{{ item.startTime }}</text>
<text class="end-time" v-if="item.endTime">{{ item.endTime }}</text>
</view>
<view class="content-column">
<view class="event-header">
<view
class="event-tag"
:class="item.type"
v-if="item.tag"
>
<text class="tag-text">{{ item.tag }}</text>
</view>
<text class="event-title">{{ item.title }}</text>
</view>
<view class="event-details" v-if="item.details">
<view class="event-location" v-if="item.location">
<text class="location-icon">📍</text>
<text class="location-text">{{ item.location }}</text>
</view>
<view class="event-description" v-if="item.description">
<text class="description-icon">📝</text>
<text class="description-text">{{ item.description }}</text>
</view>
</view>
</view>
</view>
<!-- 无日程时的提示 -->
<view class="no-schedule" v-if="getCurrentSchedule.length === 0">
<text class="no-schedule-text">暂无日程安排</text>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script lang="ts" setup>
import { useUserStore } from "@/store/modules/user";
import { imagUrl } from "@/utils";
import { reactive, ref, computed, onMounted } from "vue";
//
interface TeacherData {
name: string;
position: string;
className: string;
score: number;
workload: number;
introduction: string;
avatar?: string;
}
//
interface ScheduleItem {
startTime: string;
endTime?: string;
title: string;
type: string;
tag?: string;
location?: string;
description?: string;
details?: boolean;
}
//
interface DateItem {
day: string;
number: string;
hasEvent: boolean;
date: string;
}
const { logout, getUser } = useUserStore();
//
const teacherData = reactive<TeacherData>({
name: getUser.loginName,
position: "教研组长",
className: "2014级1班班主任",
score: 48,
workload: 20,
introduction: "北冥有鱼,其名为鲲。鲲之大,不知其几千里也。",
avatar: imagUrl(getUser.profilePhoto),
});
// 退
const handleLogout = () => {
uni.showModal({
title: "确认退出",
content: "确定要退出登录吗?",
success: (res) => {
if (res.confirm) {
logout();
uni.reLaunch({
url: "/pages/system/login/login",
});
}
},
});
};
//
const selectedDateIndex = ref(0); //
//
const weekDates = ref<DateItem[]>([]);
//
const generateScheduleData = () => {
const today = new Date();
const currentDay = today.getDay();
const mondayOffset = currentDay === 0 ? -6 : 1 - currentDay;
const startOfWeek = new Date(today);
startOfWeek.setDate(today.getDate() + mondayOffset);
const data: Record<string, ScheduleItem[]> = {};
for (let i = 0; i < 7; i++) {
const date = new Date(startOfWeek);
date.setDate(startOfWeek.getDate() + i);
const dateStr = date.getDate().toString();
//
switch (i) {
case 0: //
data[dateStr] = [
{
startTime: '08:30',
endTime: '10:00',
title: '语文课',
type: 'class',
tag: '课程',
location: '教学楼A201',
description: '高一(3)班语文课',
details: true
},
{
startTime: '14:00',
endTime: '15:30',
title: '班主任会议',
type: 'meeting',
tag: '会议',
location: '会议室B302',
description: '讨论期中考试安排',
details: true
}
];
break;
case 1: //
data[dateStr] = [
{
startTime: '09:00',
endTime: '10:30',
title: '教研活动',
type: 'meeting',
tag: '教研',
location: '教研室',
description: '语文组教研活动',
details: true
},
{
startTime: '15:00',
endTime: '16:30',
title: '家长会准备',
type: 'preparation',
tag: '准备',
location: '办公室',
description: '准备家长会材料',
details: true
}
];
break;
case 2: //
data[dateStr] = [
{
startTime: '08:00',
endTime: '09:30',
title: '早读辅导',
type: 'tutoring',
tag: '辅导',
location: '教学楼A201',
description: '高一(3)班早读辅导',
details: true
},
{
startTime: '10:00',
endTime: '12:00',
title: '教研英语无痕计划OMO',
type: 'meeting',
tag: '会议',
location: 'POHCSOO007',
description: '智慧校园211楼会议室',
details: true
},
{
startTime: '14:00',
endTime: '16:00',
title: '德法内科学习课',
type: 'class',
tag: '课程',
location: 'POHCSOO007',
description: '智慧校园211楼会议室',
details: true
} , {
startTime: '08:00',
endTime: '09:30',
title: '早读辅导',
type: 'tutoring',
tag: '辅导',
location: '教学楼A201',
description: '高一(3)班早读辅导',
details: true
},
{
startTime: '10:00',
endTime: '12:00',
title: '教研英语无痕计划OMO',
type: 'meeting',
tag: '会议',
location: 'POHCSOO007',
description: '智慧校园211楼会议室',
details: true
},
{
startTime: '14:00',
endTime: '16:00',
title: '德法内科学习课',
type: 'class',
tag: '课程',
location: 'POHCSOO007',
description: '智慧校园211楼会议室',
details: true
}, {
startTime: '08:00',
endTime: '09:30',
title: '早读辅导',
type: 'tutoring',
tag: '辅导',
location: '教学楼A201',
description: '高一(3)班早读辅导',
details: true
},
{
startTime: '10:00',
endTime: '12:00',
title: '教研英语无痕计划OMO',
type: 'meeting',
tag: '会议',
location: 'POHCSOO007',
description: '智慧校园211楼会议室',
details: true
},
{
startTime: '14:00',
endTime: '16:00',
title: '德法内科学习课',
type: 'class',
tag: '课程',
location: 'POHCSOO007',
description: '智慧校园211楼会议室',
details: true
}
];
break;
case 3: //
data[dateStr] = [
{
startTime: '08:30',
endTime: '10:00',
title: '语文课',
type: 'class',
tag: '课程',
location: '教学楼A201',
description: '高一(3)班语文课',
details: true
},
{
startTime: '10:30',
endTime: '12:00',
title: '作文批改',
type: 'preparation',
tag: '批改',
location: '办公室',
description: '批改学生作文',
details: true
},
{
startTime: '14:00',
endTime: '15:30',
title: '课程设计',
type: 'preparation',
tag: '备课',
location: '办公室',
description: '准备下周课程内容',
details: true
},
{
startTime: '16:00',
endTime: '17:00',
title: '学生谈话',
type: 'counseling',
tag: '谈话',
location: '办公室',
description: '与学生张三谈话',
details: true
}
];
break;
case 4: //
data[dateStr] = [
{
startTime: '08:00',
endTime: '09:30',
title: '晨会',
type: 'meeting',
tag: '晨会',
location: '办公室',
description: '教师晨会',
details: true
},
{
startTime: '09:00',
endTime: '10:30',
title: '公开课',
type: 'class',
tag: '公开课',
location: '教学楼A201',
description: '语文公开课展示',
details: true
},
{
startTime: '11:00',
endTime: '12:00',
title: '课后辅导',
type: 'tutoring',
tag: '辅导',
location: '教学楼A201',
description: '学困生辅导',
details: true
},
{
startTime: '14:30',
endTime: '16:00',
title: '周总结会议',
type: 'meeting',
tag: '会议',
location: '会议室B302',
description: '本周工作总结',
details: true
},
{
startTime: '16:30',
endTime: '17:30',
title: '家长沟通',
type: 'counseling',
tag: '沟通',
location: '办公室',
description: '与家长电话沟通',
details: true
}
];
break;
case 5: //
data[dateStr] = [
{
startTime: '09:00',
endTime: '11:00',
title: '补课',
type: 'class',
tag: '补课',
location: '教学楼A201',
description: '高一(3)班语文补课',
details: true
}
];
break;
case 6: //
data[dateStr] = []; //
break;
}
}
return data;
};
//
const scheduleData = reactive<Record<string, ScheduleItem[]>>(generateScheduleData());
//
const getCurrentSchedule = computed(() => {
const currentDate = weekDates.value[selectedDateIndex.value];
if (!currentDate) return [];
return scheduleData[currentDate.number] || [];
});
//
const initDates = () => {
const days = ['一', '二', '三', '四', '五', '六', '日'];
const today = new Date();
const currentDay = today.getDay(); // 0-60
//
const startOfWeek = new Date(today);
const mondayOffset = currentDay === 0 ? -6 : 1 - currentDay; // 6
startOfWeek.setDate(today.getDate() + mondayOffset);
weekDates.value = [];
for (let i = 0; i < 7; i++) {
const date = new Date(startOfWeek);
date.setDate(startOfWeek.getDate() + i);
const dateStr = date.getDate().toString();
const hasEvent = scheduleData[dateStr] && scheduleData[dateStr].length > 0;
weekDates.value.push({
day: days[i],
number: dateStr,
hasEvent,
date: date.toISOString().split('T')[0]
});
}
};
//
const selectDate = (index: number) => {
selectedDateIndex.value = index;
};
//
const goToSchedule = () => {
uni.navigateTo({
url: '/pages/view/schedule/index'
});
};
onMounted(() => {
initDates();
//
const today = new Date();
const currentDay = today.getDay();
const todayIndex = currentDay === 0 ? 6 : currentDay - 1; // 6day-1
selectedDateIndex.value = todayIndex;
});
</script>
<style scoped lang="scss">
.mine-page {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
position: relative;
}
// Header - service
.header-section {
position: relative;
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #ec4899 100%);
color: #ffffff;
overflow: hidden;
padding: 40rpx 30rpx 30rpx;
height: auto;
min-height: 320rpx;
.header-gradient {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
45deg,
rgba(255, 255, 255, 0.1) 0%,
rgba(255, 255, 255, 0.05) 100%
);
z-index: 1;
}
.logout-btn {
position: absolute;
top: 40rpx;
right: 30rpx;
z-index: 3;
background: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
padding: 10rpx 20rpx;
border: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
.logout-text {
color: #ffffff;
font-size: 14px;
font-weight: 500;
}
}
.teacher-info {
display: flex;
align-items: center;
margin-top: 20rpx;
z-index: 2;
position: relative;
.teacher-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
overflow: hidden;
border: 3px solid rgba(255, 255, 255, 0.3);
margin-right: 20rpx;
.avatar-image {
width: 100%;
height: 100%;
}
}
.teacher-details {
flex: 1;
.teacher-name {
font-size: 20px;
font-weight: bold;
color: #ffffff;
margin-bottom: 5rpx;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.teacher-position {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 5rpx;
}
.teacher-class {
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
}
}
}
.stats-info {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20rpx;
z-index: 2;
position: relative;
.stat-item {
display: flex;
align-items: center;
.stat-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
}
.stat-value {
font-size: 14px;
color: #ffffff;
font-weight: 600;
}
}
.stat-divider {
margin: 0 30rpx;
color: rgba(255, 255, 255, 0.6);
font-size: 14px;
}
}
.teacher-intro {
text-align: center;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
margin-top: 20rpx;
line-height: 1.4;
z-index: 2;
position: relative;
}
}
//
.main-content {
box-sizing: border-box;
padding: 20px 12px 0px 12px; // padding
position: relative;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.95) 0%,
rgba(248, 250, 252, 0.98) 100%
);
border-radius: 20px 20px 0 0;
margin-top: -20px;
z-index: 3;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
height: calc(100vh - 340rpx); //
display: flex;
flex-direction: column;
overflow: hidden; //
}
//
.schedule-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-shrink: 0; //
.schedule-title {
font-size: 17px;
font-weight: 600;
color: #1f2937;
}
.schedule-link {
display: flex;
align-items: center;
cursor: pointer;
.link-text {
font-size: 13px;
color: #6b7280;
margin-right: 4px;
}
.link-arrow {
font-size: 13px;
color: #6b7280;
}
}
}
//
.date-selector {
margin-bottom: 15px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-shrink: 0; //
margin-left: 0;
margin-right: 0;
.date-container {
display: flex;
padding: 8px 4px;
justify-content: space-around; // 使space-around
width: 100%; //
box-sizing: border-box;
}
.date-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 6px 2px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
flex: 1; //
max-width: calc((100% - 32px) / 7); // padding
min-width: 0; //
.date-day {
font-size: 10px;
color: #6b7280;
margin-bottom: 2px;
white-space: nowrap; //
text-align: center;
}
.date-number {
font-size: 14px;
font-weight: 600;
color: #374151;
white-space: nowrap; //
text-align: center;
}
.date-dot {
position: absolute;
bottom: 1px;
width: 3px;
height: 3px;
background: #10b981;
border-radius: 50%;
}
&.active {
background: #10b981;
color: #ffffff;
transform: scale(1.02); //
z-index: 1; //
margin: 0 -1px; //
.date-day,
.date-number {
color: #ffffff;
}
.date-dot {
background: #ffffff;
}
}
}
}
//
.schedule-list {
flex: 1; //
height: 0; // flex使
padding-bottom: 20px;
.schedule-content {
padding-bottom: 35px; //
}
.schedule-item {
display: flex;
padding: 14px 0;
border-bottom: 1px solid #f3f4f6;
&:last-child {
border-bottom: none;
}
.time-column {
width: 60px;
flex-shrink: 0;
margin-right: 15px;
.start-time {
display: block;
font-size: 15px;
font-weight: 600;
color: #374151;
line-height: 1.2;
}
.end-time {
display: block;
font-size: 13px;
color: #6b7280;
margin-top: 2px;
line-height: 1.2;
}
}
.content-column {
flex: 1;
min-width: 0; //
.event-header {
display: flex;
align-items: center;
margin-bottom: 8px;
.event-tag {
display: inline-flex;
align-items: center;
padding: 3px 8px;
border-radius: 12px;
margin-right: 8px;
flex-shrink: 0; //
&.meeting {
background: #10b981;
}
&.class {
background: #3b82f6;
}
&.tutoring {
background: #f59e0b;
}
&.preparation {
background: #8b5cf6;
}
&.counseling {
background: #ef4444;
}
.tag-text {
font-size: 11px;
color: #ffffff;
font-weight: 500;
white-space: nowrap;
}
}
.event-title {
font-size: 15px;
font-weight: 500;
color: #1f2937;
flex: 1;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; //
}
}
.event-details {
.event-location,
.event-description {
display: flex;
align-items: center;
margin-bottom: 4px;
.location-icon,
.description-icon {
font-size: 13px;
margin-right: 4px;
flex-shrink: 0;
}
.location-text,
.description-text {
font-size: 13px;
color: #6b7280;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; //
}
}
}
}
}
.no-schedule {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 200px;
.no-schedule-text {
font-size: 15px;
color: #9ca3af;
}
}
}
//
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
//
@media (max-width: 375px) {
.main-content {
padding: 20px 8px 0px 8px; // padding
}
.date-selector {
.date-container {
padding: 8px 2px; //
}
.date-item {
padding: 5px 1px; //
.date-day {
font-size: 9px; //
}
.date-number {
font-size: 13px; //
}
&.active {
transform: scale(1.01); //
margin: 0; //
}
}
}
}
</style>

View File

@ -153,13 +153,13 @@ const sections = reactive<Section[]>([
id: "routine",
title: "常规",
items: [
{
id: 10,
text: "一师一策",
icon: "clipboardfill",
path: "/pages/view/routine/yishiyice/index",
show: true,
},
// {
// id: 10,
// text: "",
// icon: "clipboardfill",
// path: "/pages/view/routine/yishiyice/index",
// show: true,
// },
{
id: "r2",
icon: "stack-fill",

View File

@ -1,396 +1,393 @@
<template>
<BasicListLayout
:show-nav-bar="true"
:nav-bar-props="{ title: '教学资源' }"
@register="register"
>
<template #top>
<view class="filter-section">
<!-- Directory Filter -->
<view class="filter-item" @click="openFilterPopup('directory')">
<text>{{ selectedDirectory?.name || '目录' }}</text>
<uni-icons type="bottom" size="14"></uni-icons>
</view>
<!-- Type Filter 1 -->
<view class="filter-item" @click="openFilterPopup('type1')">
<text>{{ selectedType1?.name || '类型' }}</text>
<uni-icons type="bottom" size="14"></uni-icons>
</view>
<!-- Type Filter 2 -->
<view class="filter-item" @click="openFilterPopup('type2')">
<text>{{ selectedType2?.name || '类型' }}</text>
<uni-icons type="bottom" size="14"></uni-icons>
</view>
</view>
</template>
<template #default="{ list }">
<view class="list-container">
<!-- Add @click handler to navigate -->
<view class="resource-item" v-for="item in list" :key="item.id" @click="goToDetail(item.id)">
<view class="item-icon-container">
<view class="item-icon">{{ item.iconLetter }}</view>
<text class="item-pages">-{{ item.pages }}-</text>
<view class="teaching-resource-page">
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 左侧课程列表 -->
<view class="left-panel">
<scroll-view class="subject-list" scroll-y="true" show-scrollbar="false">
<view
class="subject-item"
v-for="(subject, index) in subjectList"
:key="subject.id"
:class="{ active: selectedSubjectIndex === index }"
@click="selectSubject(index)"
>
<text class="subject-name">{{ subject.name }}</text>
</view>
<view class="item-details">
<text class="item-title">{{ item.title }}</text>
<view class="item-meta">
<text class="meta-text">{{ item.publishDate }}</text>
<text class="meta-text">浏览量: {{ item.views }}</text>
<text class="meta-text">下载量: {{ item.downloads }}</text>
</scroll-view>
</view>
<!-- 右侧内容区域 -->
<view class="right-panel">
<!-- 资源类型 -->
<view class="resource-section" v-if="currentSubject.resourceTypes && currentSubject.resourceTypes.length > 0">
<view class="section-title">资源类型</view>
<view class="type-grid">
<view
class="type-item"
v-for="(type, index) in currentSubject.resourceTypes"
:key="index"
@click="goToResourceList('type', type)"
>
<text class="type-text">{{ type }}</text>
</view>
</view>
</view>
<!-- 年级选择 -->
<view class="grade-section" v-if="currentSubject.grades && currentSubject.grades.length > 0">
<view class="section-title">年级</view>
<view class="grade-grid">
<view
class="grade-item"
v-for="(grade, index) in currentSubject.grades"
:key="index"
@click="goToResourceList('grade', grade)"
>
<text class="grade-text">{{ grade }}</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="!currentSubject.name">
<text class="empty-text">请从左侧选择学科</text>
</view>
</view>
</template>
</BasicListLayout>
<!-- Filter Popup -->
<uni-popup ref="filterPopupRef" type="bottom" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text>{{ currentFilterTitle }}</text>
<uni-icons type="closeempty" size="20" @click="closeFilterPopup"></uni-icons>
</view>
<scroll-view scroll-y class="popup-options">
<view
class="option-item"
v-for="option in currentFilterOptions"
:key="option.id"
:class="{ active: isOptionSelected(option) }"
@click="selectFilterOption(option)"
>
{{ option.name }}
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { reactive, ref, computed, onMounted } from "vue";
interface Resource {
id: number;
iconLetter: string;
title: string;
pages: number;
publishDate: string;
views: number;
downloads: number;
}
interface FilterOption {
id: string | number | null;
//
interface Subject {
id: string;
name: string;
description: string;
resourceTypes?: string[];
grades?: string[];
}
// --- Filter Data (Mock) ---
const directories = ref<FilterOption[]>([
{ id: null, name: '全部目录' },
{ id: 'd1', name: '数学教案' },
{ id: 'd2', name: '语文课件' },
{ id: 'd3', name: '英语练习' },
{ id: 'd4', name: '科学实验' },
]);
//
const selectedSubjectIndex = ref(1); //
const types1 = ref<FilterOption[]>([
{ id: null, name: '全部类型1' },
{ id: 't1-1', name: '课件PPT' },
{ id: 't1-2', name: '教学视频' },
{ id: 't1-3', name: 'Word文档' },
]);
const types2 = ref<FilterOption[]>([
{ id: null, name: '全部类型2' },
{ id: 't2-1', name: '期中' },
{ id: 't2-2', name: '期末' },
{ id: 't2-3', name: '单元测试' },
]);
// --- Filter State ---
const selectedDirectory = ref<FilterOption | null>(directories.value[0]);
const selectedType1 = ref<FilterOption | null>(types1.value[0]);
const selectedType2 = ref<FilterOption | null>(types2.value[0]);
const filterPopupRef = ref<any>(null);
const currentFilterType = ref<'directory' | 'type1' | 'type2' | null>(null);
// --- Computed Properties for Popup ---
const currentFilterOptions = computed(() => {
switch (currentFilterType.value) {
case 'directory': return directories.value;
case 'type1': return types1.value;
case 'type2': return types2.value;
default: return [];
}
});
const currentFilterTitle = computed(() => {
switch (currentFilterType.value) {
case 'directory': return '选择目录';
case 'type1': return '选择类型1';
case 'type2': return '选择类型2';
default: return '选择选项';
}
});
const isOptionSelected = (option: FilterOption) => {
switch (currentFilterType.value) {
case 'directory': return selectedDirectory.value?.id === option.id;
case 'type1': return selectedType1.value?.id === option.id;
case 'type2': return selectedType2.value?.id === option.id;
default: return false;
}
};
// --- Mock API (Changed return type to Promise<any>) ---
const testList = async (params: any): Promise<any> => {
console.log('API called with params:', params);
const page = params.page || 1;
const pageSize = params.pageSize || 10;
const allItems: Resource[] = Array.from({ length: 50 }).map((_, index) => {
const id = index + 1;
const directoryFilter = params.directoryId ? `[目录${params.directoryId}]` : '';
const type1Filter = params.type1Id ? `[类型1-${params.type1Id}]` : '';
const type2Filter = params.type2Id ? `[类型2-${params.type2Id}]` : '';
return {
id: id,
iconLetter: 'W',
title: `专题${String(id).padStart(2, '0')}${directoryFilter}${type1Filter}${type2Filter} (突破)`,
pages: 30 + (id % 15),
publishDate: `0${1 + (id % 9)}${10 + (id % 20)}日发布`,
views: 1500 + (id * 17 % 500),
downloads: 50 + (id * 7 % 40),
};
});
let filteredItems = allItems;
if (params.directoryId) {
filteredItems = filteredItems.filter(item => item.title.includes(`[目录${params.directoryId}]`));
}
if (params.type1Id) {
filteredItems = filteredItems.filter(item => item.title.includes(`[类型1-${params.type1Id}]`));
}
if (params.type2Id) {
filteredItems = filteredItems.filter(item => item.title.includes(`[类型2-${params.type2Id}]`));
}
const totalFilteredItems = filteredItems.length;
const paginatedItems = filteredItems.slice((page - 1) * pageSize, page * pageSize);
return new Promise((resolve) => {
setTimeout(() => {
// Return structure expected by useLayout
resolve({ message: "成功", resultCode: 200, rows: paginatedItems, total: totalFilteredItems });
}, 300);
});
};
// --- Layout Hook (Removed 'immediate' from componentProps) ---
const [register, { reload, setParam }] = useLayout({
api: testList,
componentProps: {
// No problematic props
//
const subjectList = reactive<Subject[]>([
{
id: 'chinese',
name: '语文',
description: '语言文字运用与文学鉴赏',
resourceTypes: ['课件', '教案', '学案', '作业', '试卷'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'math',
name: '数学',
description: '数学思维与逻辑推理',
resourceTypes: ['题集', '素材', '备课综合'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'english',
name: '英语',
description: '英语听说读写综合能力',
resourceTypes: ['课件', '音频', '视频', '练习'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'moral',
name: '道德与法治',
description: '品德修养与法律意识',
resourceTypes: [],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'science',
name: '科学',
description: '科学探究与实验操作',
resourceTypes: ['实验', '课件', '视频'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'it',
name: '信息科技',
description: '信息技术与编程思维',
resourceTypes: ['软件', '教程', '项目'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'music',
name: '音乐',
description: '音乐欣赏与艺术表现',
resourceTypes: ['音频', '乐谱', '视频'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'art',
name: '美术',
description: '美术创作与艺术鉴赏',
resourceTypes: ['图片', '视频', '作品'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'pe',
name: '体育',
description: '体育运动与健康生活',
resourceTypes: ['视频', '规则', '训练'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'labor',
name: '劳动技术',
description: '劳动实践与技能培养',
resourceTypes: ['教程', '工具', '项目'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'expand',
name: '拓展',
description: '综合素质拓展活动',
resourceTypes: ['活动', '方案', '素材'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'psychology',
name: '心理健康',
description: '心理健康与情感教育',
resourceTypes: ['课件', '测试', '案例'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'practice',
name: '综合实践',
description: '综合实践活动课程',
resourceTypes: ['方案', '案例', '工具'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
},
{
id: 'calligraphy',
name: '书法',
description: '书法艺术与汉字文化',
resourceTypes: ['字帖', '视频', '作品'],
grades: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']
}
]);
//
const currentSubject = computed(() => {
return subjectList[selectedSubjectIndex.value] || {};
});
// --- Navigation ---
const goToDetail = (id: number) => {
//
const selectSubject = (index: number) => {
selectedSubjectIndex.value = index;
};
//
const goToResourceList = (filterType: string, filterValue: string) => {
const subject = currentSubject.value;
const params = {
subjectId: subject.id,
subjectName: subject.name,
filterType: filterType,
filterValue: filterValue
};
const queryString = Object.entries(params).map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join('&');
uni.navigateTo({
url: `./detail?id=${id}` // Navigate to detail page in the same directory
url: `/pages/view/routine/JiaoXueZiYuan/indexList?${queryString}`
});
};
// --- Filter Popup Methods ---
const openFilterPopup = (type: 'directory' | 'type1' | 'type2') => {
currentFilterType.value = type;
filterPopupRef.value?.open();
};
const closeFilterPopup = () => {
filterPopupRef.value?.close();
};
const selectFilterOption = (option: FilterOption) => {
switch (currentFilterType.value) {
case 'directory':
selectedDirectory.value = option;
setParam({ directoryId: option.id });
break;
case 'type1':
selectedType1.value = option;
setParam({ type1Id: option.id });
break;
case 'type2':
selectedType2.value = option;
setParam({ type2Id: option.id });
break;
}
closeFilterPopup();
};
onMounted(() => {
//
console.log('教学资源页面加载完成');
});
</script>
<style scoped lang="scss">
.filter-section {
.teaching-resource-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
justify-content: space-around;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #e0e0e0;
position: sticky;
// Adjust top based on your actual NavBar height if needed
// Consider using var(--status-bar-height) + 44px (uni-app standard NavBar)
top: 0;
z-index: 10;
flex-direction: column;
}
.filter-item {
display: flex;
align-items: center;
font-size: 28rpx;
color: #666;
padding: 10rpx; // Add padding for better click area
//
.main-content {
flex: 1;
display: flex;
height: 100vh;
}
//
.left-panel {
width: 280rpx;
background: #ffffff;
border-right: 1px solid #e5e5e5;
.subject-list {
height: 100%;
}
.subject-item {
padding: 30rpx 20rpx;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
text {
margin-right: 8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 150rpx; // Limit text width
transition: all 0.3s ease;
text-align: center;
.subject-name {
font-size: 28rpx;
color: #666666;
display: block;
}
uni-icons {
color: #999;
}
}
}
.list-container {
background-color: #f4f5f7;
/* Add padding-top to prevent content from hiding behind sticky filter */
/* Adjust value based on filter-section height */
}
.resource-item {
display: flex;
align-items: center;
background-color: #ffffff;
border-radius: 16rpx;
padding: 25rpx 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
cursor: pointer; // Add cursor pointer to indicate clickability
.item-icon-container {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 25rpx;
flex-shrink: 0;
.item-icon {
width: 80rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 12rpx;
background-color: #409eff;
color: #ffffff;
font-size: 40rpx;
font-weight: bold;
text-align: center;
margin-bottom: 8rpx;
}
.item-pages {
font-size: 22rpx;
color: #999;
}
}
.item-details {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.item-title {
font-size: 30rpx;
color: #333;
font-weight: bold;
margin-bottom: 15rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 24rpx;
color: #999;
flex-wrap: wrap;
.meta-text {
margin-right: 15rpx;
white-space: nowrap;
&:last-child {
margin-right: 0;
}
&.active {
background: #fff3e0;
.subject-name {
color: #ff9800;
font-weight: 600;
}
}
&:hover:not(.active) {
background: #f8f9fa;
}
}
}
// Popup Styles
.popup-content {
background-color: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
padding: 20rpx;
padding-bottom: 40rpx; // Add space at the bottom
}
.popup-header {
//
.right-panel {
flex: 1;
background: #ffffff;
padding: 40rpx;
overflow: hidden;
display: flex;
justify-content: space-between;
align-items: center;
padding: 15rpx 10rpx;
font-size: 32rpx;
font-weight: bold;
border-bottom: 1rpx solid #eee;
margin-bottom: 10rpx;
flex-direction: column;
}
.popup-options {
max-height: 60vh; // Limit popup height
//
.resource-section,
.grade-section {
margin-bottom: 40rpx;
flex-shrink: 0;
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
margin-bottom: 30rpx;
}
}
.option-item {
padding: 25rpx 20rpx;
font-size: 28rpx;
color: #333;
border-bottom: 1rpx solid #f5f5f5;
//
.type-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-bottom: 20rpx;
}
.type-item {
padding: 20rpx 30rpx;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8rpx;
cursor: pointer;
&:last-child {
border-bottom: none;
transition: all 0.3s ease;
.type-text {
font-size: 28rpx;
color: #666666;
}
&.active {
color: #409eff; // Highlight selected item
font-weight: bold;
}
&:hover {
background-color: #f9f9f9; // Subtle hover effect
border-color: #ff9800;
background: #fff3e0;
.type-text {
color: #ff9800;
}
}
}
//
.grade-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
}
.grade-item {
padding: 25rpx 20rpx;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8rpx;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
.grade-text {
font-size: 28rpx;
color: #666666;
}
&:hover {
border-color: #ff9800;
background: #fff3e0;
.grade-text {
color: #ff9800;
}
}
}
//
.empty-state {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
//
@media (max-width: 768px) {
.left-panel {
width: 150rpx;
}
.subject-item {
padding: 20rpx 10rpx;
.subject-name {
font-size: 12px;
}
}
.right-panel {
padding: 20rpx;
}
.tag-item {
padding: 10rpx 18rpx;
.tag-text {
font-size: 12px;
}
}
}
</style>

View File

@ -0,0 +1,468 @@
<template>
<BasicListLayout
:show-nav-bar="true"
:nav-bar-props="{ title: '教学资源' }"
@register="register"
>
<template #top>
<view class="search-section">
<view class="search-box">
<uni-icons type="search" size="18" color="#999"></uni-icons>
<input
class="search-input"
type="text"
placeholder="搜索教学资源..."
v-model="searchKeyword"
@input="onSearchInput"
@confirm="onSearchConfirm"
/>
<view class="search-clear" v-if="searchKeyword" @click="clearSearch">
<uni-icons type="clear" size="16" color="#999"></uni-icons>
</view>
</view>
</view>
</template>
<template #default="{ list }">
<view class="list-container">
<!-- Add @click handler to navigate -->
<view class="resource-item" v-for="item in list" :key="item.id" @click="goToDetail(item.id)">
<view class="item-icon-container">
<view class="item-icon">{{ item.iconLetter }}</view>
<text class="item-pages">-{{ item.pages }}-</text>
</view>
<view class="item-details">
<text class="item-title">{{ item.title }}</text>
<!-- 标签区域 -->
<view class="tags-container" v-if="item.tags && item.tags.length > 0">
<view class="tag-item" v-for="tag in item.tags" :key="tag">{{ tag }}</view>
</view>
<view class="item-meta">
<text class="meta-text">{{ item.publishDate }}</text>
<text class="meta-text">浏览量: {{ item.views }}</text>
<text class="meta-text">下载量: {{ item.downloads }}</text>
</view>
</view>
</view>
</view>
</template>
</BasicListLayout>
<!-- Filter Popup -->
<uni-popup ref="filterPopupRef" type="bottom" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text>{{ currentFilterTitle }}</text>
<uni-icons type="closeempty" size="20" @click="closeFilterPopup"></uni-icons>
</view>
<scroll-view scroll-y class="popup-options">
<view
class="option-item"
v-for="option in currentFilterOptions"
:key="option.id ?? 'null'"
:class="{ active: isOptionSelected(option) }"
@click="selectFilterOption(option)"
>
{{ option.name }}
</view>
</scroll-view>
</view>
</uni-popup>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
interface Resource {
id: number;
iconLetter: string;
title: string;
pages: number;
publishDate: string;
views: number;
downloads: number;
tags: string[];
}
interface FilterOption {
id: string | number | null;
name: string;
}
// --- Filter Data (Mock) ---
const directories = ref<FilterOption[]>([
{ id: null, name: '全部目录' },
{ id: 'd1', name: '数学教案' },
{ id: 'd2', name: '语文课件' },
{ id: 'd3', name: '英语练习' },
{ id: 'd4', name: '科学实验' },
]);
const types1 = ref<FilterOption[]>([
{ id: null, name: '全部类型1' },
{ id: 't1-1', name: '课件PPT' },
{ id: 't1-2', name: '教学视频' },
{ id: 't1-3', name: 'Word文档' },
]);
const types2 = ref<FilterOption[]>([
{ id: null, name: '全部类型2' },
{ id: 't2-1', name: '期中' },
{ id: 't2-2', name: '期末' },
{ id: 't2-3', name: '单元测试' },
]);
// --- Search State ---
const searchKeyword = ref<string>('');
// --- Filter State ---
const selectedDirectory = ref<FilterOption | null>(directories.value[0]);
const selectedType1 = ref<FilterOption | null>(types1.value[0]);
const selectedType2 = ref<FilterOption | null>(types2.value[0]);
const filterPopupRef = ref<any>(null);
const currentFilterType = ref<'directory' | 'type1' | 'type2' | null>(null);
// --- Computed Properties for Popup ---
const currentFilterOptions = computed(() => {
switch (currentFilterType.value) {
case 'directory': return directories.value;
case 'type1': return types1.value;
case 'type2': return types2.value;
default: return [];
}
});
const currentFilterTitle = computed(() => {
switch (currentFilterType.value) {
case 'directory': return '选择目录';
case 'type1': return '选择类型1';
case 'type2': return '选择类型2';
default: return '选择选项';
}
});
const isOptionSelected = (option: FilterOption) => {
switch (currentFilterType.value) {
case 'directory': return selectedDirectory.value?.id === option.id;
case 'type1': return selectedType1.value?.id === option.id;
case 'type2': return selectedType2.value?.id === option.id;
default: return false;
}
};
// --- Mock API (Changed return type to Promise<any>) ---
const testList = async (params: any): Promise<any> => {
console.log('API called with params:', params);
const page = params.page || 1;
const pageSize = params.pageSize || 10;
const allItems: Resource[] = Array.from({ length: 50 }).map((_, index) => {
const id = index + 1;
const directoryFilter = params.directoryId ? `[目录${params.directoryId}]` : '';
const type1Filter = params.type1Id ? `[类型1-${params.type1Id}]` : '';
const type2Filter = params.type2Id ? `[类型2-${params.type2Id}]` : '';
//
const subjectTags = ['语文', '数学', '英语', '科学', '历史'];
const gradeTags = ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级'];
const termTags = ['上册', '下册'];
const typeTags = ['练习题', '课件', '教案', '测试卷'];
const tags = [
subjectTags[id % subjectTags.length],
gradeTags[id % gradeTags.length],
termTags[id % termTags.length],
...(id % 3 === 0 ? [typeTags[id % typeTags.length]] : [])
];
return {
id: id,
iconLetter: 'W',
title: `专题${String(id).padStart(2, '0')}${directoryFilter}${type1Filter}${type2Filter} (突破)`,
pages: 30 + (id % 15),
publishDate: `0${1 + (id % 9)}${10 + (id % 20)}日发布`,
views: 1500 + (id * 17 % 500),
downloads: 50 + (id * 7 % 40),
tags: tags,
};
});
let filteredItems = allItems;
//
if (params.keyword) {
filteredItems = filteredItems.filter(item =>
item.title.toLowerCase().includes(params.keyword.toLowerCase())
);
}
if (params.directoryId) {
filteredItems = filteredItems.filter(item => item.title.includes(`[目录${params.directoryId}]`));
}
if (params.type1Id) {
filteredItems = filteredItems.filter(item => item.title.includes(`[类型1-${params.type1Id}]`));
}
if (params.type2Id) {
filteredItems = filteredItems.filter(item => item.title.includes(`[类型2-${params.type2Id}]`));
}
const totalFilteredItems = filteredItems.length;
const paginatedItems = filteredItems.slice((page - 1) * pageSize, page * pageSize);
return new Promise((resolve) => {
setTimeout(() => {
// Return structure expected by useLayout
resolve({ message: "成功", resultCode: 200, rows: paginatedItems, total: totalFilteredItems });
}, 300);
});
};
// --- Layout Hook (Removed 'immediate' from componentProps) ---
const [register, { reload, setParam }] = useLayout({
api: testList,
componentProps: {
// No problematic props
},
});
// --- Search Methods ---
const onSearchInput = () => {
setParam({ keyword: searchKeyword.value });
};
const onSearchConfirm = () => {
setParam({ keyword: searchKeyword.value });
};
const clearSearch = () => {
searchKeyword.value = '';
setParam({ keyword: '' });
};
// --- Navigation ---
const goToDetail = (id: number) => {
uni.navigateTo({
url: `./detail?id=${id}` // Navigate to detail page in the same directory
});
};
// --- Filter Popup Methods ---
const openFilterPopup = (type: 'directory' | 'type1' | 'type2') => {
currentFilterType.value = type;
filterPopupRef.value?.open();
};
const closeFilterPopup = () => {
filterPopupRef.value?.close();
};
const selectFilterOption = (option: FilterOption) => {
switch (currentFilterType.value) {
case 'directory':
selectedDirectory.value = option;
setParam({ directoryId: option.id });
break;
case 'type1':
selectedType1.value = option;
setParam({ type1Id: option.id });
break;
case 'type2':
selectedType2.value = option;
setParam({ type2Id: option.id });
break;
}
closeFilterPopup();
};
</script>
<style scoped lang="scss">
.search-section {
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #e0e0e0;
position: sticky;
top: 0;
z-index: 10;
.search-box {
display: flex;
align-items: center;
background-color: #f5f5f5;
border-radius: 50rpx;
padding: 0 20rpx;
height: 70rpx;
uni-icons {
margin-right: 15rpx;
}
.search-input {
flex: 1;
height: 100%;
border: none;
background: transparent;
font-size: 28rpx;
color: #333;
&::placeholder {
color: #999;
}
}
.search-clear {
margin-left: 15rpx;
cursor: pointer;
}
}
}
.list-container {
background-color: #f4f5f7;
/* Add padding-top to prevent content from hiding behind sticky filter */
/* Adjust value based on filter-section height */
}
.resource-item {
display: flex;
align-items: center;
background-color: #ffffff;
border-radius: 16rpx;
padding: 25rpx 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
cursor: pointer; // Add cursor pointer to indicate clickability
.item-icon-container {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 25rpx;
flex-shrink: 0;
.item-icon {
width: 80rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 12rpx;
background-color: #409eff;
color: #ffffff;
font-size: 40rpx;
font-weight: bold;
text-align: center;
margin-bottom: 8rpx;
}
.item-pages {
font-size: 22rpx;
color: #999;
}
}
.item-details {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.item-title {
font-size: 30rpx;
color: #333;
font-weight: bold;
margin-bottom: 15rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-bottom: 15rpx;
.tag-item {
background-color: #f0f2f5;
color: #666;
font-size: 22rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
white-space: nowrap;
border: 1rpx solid #e0e0e0;
&:first-child {
background-color: #e7f4ff;
color: #1890ff;
border-color: #91d5ff;
}
}
}
.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 24rpx;
color: #999;
flex-wrap: wrap;
.meta-text {
margin-right: 15rpx;
white-space: nowrap;
&:last-child {
margin-right: 0;
}
}
}
}
}
// Popup Styles
.popup-content {
background-color: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
padding: 20rpx;
padding-bottom: 40rpx; // Add space at the bottom
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15rpx 10rpx;
font-size: 32rpx;
font-weight: bold;
border-bottom: 1rpx solid #eee;
margin-bottom: 10rpx;
}
.popup-options {
max-height: 60vh; // Limit popup height
}
.option-item {
padding: 25rpx 20rpx;
font-size: 28rpx;
color: #333;
border-bottom: 1rpx solid #f5f5f5;
cursor: pointer;
&:last-child {
border-bottom: none;
}
&.active {
color: #409eff; // Highlight selected item
font-weight: bold;
}
&:hover {
background-color: #f9f9f9; // Subtle hover effect
}
}
</style>