396 lines
11 KiB
Vue
Raw Normal View History

2025-04-22 10:22:33 +08:00
<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>
<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>
</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"
: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;
}
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: '单元测试' },
]);
// --- 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
},
});
// --- 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">
.filter-section {
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;
.filter-item {
display: flex;
align-items: center;
font-size: 28rpx;
color: #666;
padding: 10rpx; // Add padding for better click area
cursor: pointer;
text {
margin-right: 8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 150rpx; // Limit text width
}
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;
}
}
}
}
}
// 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>