2025-12-02 15:24:51 +08:00

1433 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="肝胆视频"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
>
<template #right>
<view class="nav-actions" >
<view class="collect-img" @click="goSearch">
<image class="img-icon" :src="searchImg" mode="aspectFill" />
</view>
<view class="collect-img" @click="goMeet">
<image
class="img-icon"
:src="meetImg"
mode="aspectFill"
/>
</view>
</view>
</template>
</uni-nav-bar>
</view>
<view class="video-page">
<!-- Header -->
<!-- Fixed Banner Swiper -->
<view class="banner-container">
<view class="swipemask" >
<view class="banner-subtitle" v-if="bannerList.length>0">{{bannerList[currentBannerIndex].name}}</view>
<view class="dotbox">
<view class="circle" :class="{active:currentBannerIndex==index} "v-for="(banner, index) in bannerList"
:key="banner.id"></view>
</view>
</view>
<swiper
class="banner-swiper"
:indicator-dots="false"
:autoplay="true"
:interval="5000"
:duration="300"
indicator-color="rgba(255,255,255,0.4)"
indicator-active-color="#fff"
circular
@change="onSwiperChange"
>
<swiper-item
v-for="(banner, index) in bannerList"
:key="banner.id"
@click="playBannerVideo(banner)"
>
<view class="banner-item">
<up-image
class="banner-image"
:src="docUrl+banner.imgpath"
mode="aspectFill"
:loadingIcon="lazyVideoImg"
:errorIcon="lazyVideoImg"
:lazy-load="true"
width="100%"
height="400rpx"
>
<template #error>
<image :src="lazyVideoImg" class="banner-image" mode="aspectFill"></image>
</template>
<template #loading>
<image :src="lazyVideoImg" class="banner-image" mode="aspectFill"></image>
</template>
</up-image>
</view>
</swiper-item>
</swiper>
</view>
<!-- Fixed Filter Tabs -->
<view class="filter-tabs-container">
<view class="filter-tabs">
<view
class="tab-item"
@click="showAllVideoPopup=!showAllVideoPopup"
>
<up-image :src="isAllActive?allOnImg:allImg" width="30rpx" height="30rpx" ></up-image>
<text class="tab-text" :class="{active:isAllActive}">{{typeName}}</text>
<up-image :src="isAllActive?selectOnImg:selectImg" width="30rpx" height="30rpx" ></up-image>
</view>
<view class="right">
<view
class="tab-item"
@click="toggleSort"
>
<text class="tab-text active">{{sort==2?'最新':'最热'}}</text>
<view class="newbox">
<up-image :src="sort==2?upImg:downImg" width="20rpx" height="26rpx" ></up-image>
</view>
</view>
<view class="bar"></view>
<view
class="tab-item"
@click="showFilterPopup"
>
<text class="tab-text " :class="{active:isFilterActive}">筛选</text>
<view class="filterbox">
<up-image :src="isFilterActive ? filterOn : filter" width="30rpx" height="30rpx" ></up-image>
</view>
</view>
</view>
</view>
</view>
<!-- Main Content -->
<scroll-view
class="scroll-view"
scroll-y="true"
refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
lower-threshold="100"
>
<!-- Video List -->
<view class="video-list">
<view
class="video-item"
v-for="(video, index) in videoList"
:key="video.id || video.uuid"
@click="playVideo(video)"
>
<view class="video-thumbnail">
<up-image
:src="docUrl + video.imgpath"
class="poster-image"
mode="aspectFill"
:loadingIcon="lazyImg"
:errorIcon="lazyImg"
:lazy-load="true"
width="100%"
height="220rpx"
>
<template #error>
<image :src="lazyImg" class="poster-image" mode="aspectFill"></image>
</template>
<template #loading>
<image :src="lazyImg" class="poster-image" mode="aspectFill"></image>
</template>
</up-image>
</view>
<view class="video-info">
<view class="video-title">{{video.title || video.name}}</view>
<view class="video-meta">
<text class="author">{{video.public_name}}</text>
<view class="stats">
<uni-icons type="eye" size="14" color="#999"></uni-icons>
<text class="view-count">{{formatNumber(video.readnum)}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- Loading More -->
<uni-load-more
v-if="videoList.length > 0"
:status="loadMoreStatus"
:content-text="{
contentdown: '上拉加载更多',
contentrefresh: '正在加载...',
contentnomore: '没有更多数据了'
}"
></uni-load-more>
<!-- Empty State -->
<view class="empty-state" v-if="videoList.length === 0 && !loading">
<image src="/static/empty-video.png" mode="aspectFit"></image>
<text>暂无视频内容</text>
</view>
</scroll-view>
<view class="btnbox" @click="goPatientVideo">
<up-image :src="videoImg" width="44rpx" height="44rpx" ></up-image>
患教视频
</view>
<!-- 筛选弹窗 -->
<view class="filter-popup" v-if="showFilter" @click="hideFilterPopup">
<view class="filter-content" @click.stop>
<!-- 筛选标签网格 -->
<view class="filter-tags">
<view
class="tag-item"
v-for="(tag, index) in filterTags"
:key="index"
:class="{ active: tag.selected }"
@click="toggleTag(index)"
>
{{ tag.NAME }}
</view>
</view>
<!-- 底部按钮 -->
<view class="filter-buttons">
<view class="btn-reset" @click="resetFilter">重置</view>
<view class="btn-confirm" @click="confirmFilter">确定</view>
</view>
</view>
</view>
<!-- 全部视频弹窗 -->
<view class="all-video-popup" v-if="showAllVideoPopup" @click="closeAllVideoPopup">
<view class="popup-content" @click.stop>
<view class="popup-body">
<view class="category-sidebar">
<view
class="category-item"
:class="{ active: selectedCategory === category.value }"
v-for="category in categoryList"
:key="category.value"
@click="selectCategory(category.value)"
>
<view class="category-label">{{ category.label }}</view>
<view class="category-date" v-if="category.value !== '全部' && category.originalDate">
{{ formatDate(category.originalDate) }}
</view>
</view>
</view>
<view class="content-area">
<view class="content-list">
<view
class="content-item"
v-for="item in filteredContent"
:key="item.uuid"
:class="{active:selectYearContent.uuid==item.uuid}"
@click="selectContent(item)"
>
{{formatName(item.name) }}
</view>
<view v-if="filteredContent.length === 0" class="empty-content">
<text>该分类暂无内容</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, computed,reactive } from 'vue';
import { onShow } from "@dcloudio/uni-app";
import api from '@/api/api.js';
import allImg from "@/static/video_all.png"
import allOnImg from "@/static/video_select.png"
import selectImg from "@/static/all_video.png"
import selectOnImg from "@/static/select_video.png"
import filter from "@/static/cb_screen_no.png"
import filterOn from "@/static/cb_screen_yes.png"
import upImg from "@/static/cb_up.png"
import downImg from "@/static/cb_down.png"
import videoImg from "@/static/patient_video.png"
import docUrl from '@/utils/docUrl';
import navTo from '@/utils/navTo';
import searchImg from "@/static/search.png"
import lazyImg from "@/static/default_video.png"
import lazyVideoImg from "@/static/videoPlaceholder.png";
import meetImg from "@/static/videoIcon.png"
import formatNumber from '@/utils/formatNumber.js';
import navBar from '@/components/navBar/navBar.vue';
const isAllActive=ref(false)
// 响应式数据
const videoList = ref([]);
const bannerVideo = ref(null);
const activeTab = ref(1); // 默认选中"最新"
const loading = ref(false);
const refreshing = ref(false);
const loadMoreStatus = ref('more'); // more, loading, noMore
const currentPage = ref(1);
const pageSize = ref(10);
const hasMoreData = ref(true);
const filteredContent=ref([])
const sort =ref(2);
const keywords=ref('');
const typeUuid=ref('');
const typeName=ref('全部视频')
// Banner相关数据
const bannerList = ref([]);
const currentBannerIndex = ref(0);
const isFilterActive=ref(false)
const showFilter = ref(false);
const yearList=ref([]);
const selectYearContent=reactive({})
const goSearch=()=>{
uni.sendNativeEvent('goHomeSearch', {
msg: 'goHomeSearch'
},ret => {
console.log(ret);
})
}
const goMeet=()=>{
uni.navigateTo({
url: '/pages_app/liveReplay/liveReplay?from=video'
});
}
// 筛选弹窗相关数据
const filterTags = ref([
{ name: '指南解读', selected: false },
{ name: '病例分析', selected: false },
{ name: '学术讲座', selected: false },
{ name: '手术视频', selected: false },
{ name: '研究进展', selected: false },
{ name: '肝病治疗', selected: false },
{ name: '肝癌诊断', selected: false },
{ name: '肝移植', selected: false },
{ name: '微创手术', selected: false },
{ name: '免疫治疗', selected: false },
{ name: '靶向治疗', selected: false },
{ name: '化疗方案', selected: false }
]);
// 全部视频弹窗相关数据
const showAllVideoPopup = ref(false);
const selectedCategory = ref('全部');
const categoryList = ref([
{ value: 'all', label: '全部' }
]);
// 分类内容数据 - 将根据API返回的类型动态生成
const contentByCategory = ref({
all: []
});
// 存储API返回的原始类型数据
const videoTypesData = ref([]);
// 页面加载
onMounted(() => {
});
const formatName=(name)=>{
let index=name.lastIndexOf('-');
if(index>-1){
return name.substring(index+1,name.length);
}
return name;
}
onShow(() => {
// 调用真实API获取数据
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
loadBannerData();
loadVideoTags();
loadVideoTypes();
// 延迟加载"全部"分类的视频数据,确保类型数据先加载完成
setTimeout(() => {
loadCategoryVideos('all');
}, 500);
});
// 加载轮播数据
const loadBannerData = async () => {
try {
const response = await api.videoRoll({});
console.log('轮播API原始响应:', response);
bannerList.value=response.data;
} catch (error) {
console.error('加载轮播数据失败:', error);
// 使用模拟数据作为备用
}
};
// 加载视频标签
const loadVideoTags = async () => {
try {
const response = await api.videoTagList({
type:2
});
console.log('标签API原始响应:', response);
if(response.code==200){
filterTags.value=response.data;
console.log(22222)
console.log(filterTags.value)
}
} catch (error) {
console.error('加载视频标签失败:', error);
}
};
// 加载视频类型
const loadVideoTypes = async () => {
try {
const response = await api.expertVideoTypeList({
});
console.log('类型API原始响应:', response);
if(response.code==200){
yearList.value=response.data;
let all=[{uuid:'',name:'全部视频'}];
filteredContent.value=all.concat((response.data[0]).list);
console.log(33)
let arr=response.data.map(item=>{
return {
value:item.name,
label:item.name
}
})
categoryList.value=arr
}
console.log(2222)
console.log(categoryList.value)
} catch (error) {
console.error('加载视频类型失败:', error);
}
};
// 加载分类视频数据
const loadCategoryVideos = async (categoryValue) => {
try {
console.log('加载分类视频:', categoryValue);
let response;
if (categoryValue === '全部') {
// 加载所有视频
response = await api.videoByKeyWordsNew({
page: 1,
pageSize: 20,
keywords: '',
sort: 0,
typeUuid: '',
tags: ''
});
} else {
// 根据日期分类加载对应的视频类型数据
// 找到该日期对应的视频类型
const typesForDate = videoTypesData.value.filter(type => type.create_date === categoryValue);
console.log(`日期 ${categoryValue} 对应的视频类型:`, typesForDate);
if (typesForDate.length > 0) {
// 获取这些类型的UUID列表
const typeUuids = typesForDate.map(type => type.id || type.uuid || type.value).join(',');
console.log('类型UUID列表:', typeUuids);
// 加载特定类型的视频
response = await api.videoByKeyWordsNew({
page: 1,
pageSize: 20,
keywords: '',
sort: 0,
typeUuid: typeUuids,
tags: ''
});
} else {
console.warn(`未找到日期 ${categoryValue} 对应的视频类型`);
contentByCategory.value[categoryValue] = [];
return;
}
}
console.log('分类视频API响应:', response);
// 检查响应结构
let videoData = {};
if (response && response.data) {
if (response.data.code === 200) {
videoData = response.data.data || {};
} else if (response.code === 200) {
videoData = response.data || {};
}
} else if (response && response.code === 200) {
videoData = response.data || {};
}
console.log('解析后的分类视频数据:', videoData);
if (videoData && videoData.list) {
const { list } = videoData;
// 更新对应分类的内容
contentByCategory.value[categoryValue] = list.map(video => ({
id: video.id || video.uuid,
title: video.title || video.name,
name: video.name || video.title
}));
console.log(`分类 ${categoryValue} 的内容:`, contentByCategory.value[categoryValue]);
}
} catch (error) {
console.error('加载分类视频失败:', error);
}
};
// 加载视频数据
const loadVideoData = async (isRefresh = false) => {
if (loading.value && !isRefresh) return;
loading.value = true;
try {
// 调用真实API
//const selectedTags = filterTags.value.filter(tag => tag.selected).map(tag => tag.id).join(',');
console.log('请求参数:', {
page: currentPage.value,
pageSize: pageSize.value,
keywords: keywords.value,
sort: sort.value,
typeUuid:typeUuid.value,
tags: keywords.value
});
const response = await api.videoByKeyWordsNew({
page: currentPage.value,
pageSize: pageSize.value,
keywords: keywords.value,
sort: sort.value,
typeUuid:typeUuid.value,
});
console.log('视频列表API原始响应:', response);
// 检查响应结构
let videoData = {};
if (response && response.data) {
if (response.data.code === 200) {
videoData = response.data.data || {};
} else if (response.code === 200) {
videoData = response.data || {};
}
} else if (response && response.code === 200) {
videoData = response.data || {};
}
console.log('解析后的视频数据:', videoData);
if (videoData && videoData.list) {
const { list, pageNumber,totalPage } = videoData;
console.log('视频列表数据:', list);
if (isRefresh) {
videoList.value = list || [];
currentPage.value = 1;
} else {
videoList.value = [...videoList.value, ...(list || [])];
}
//const { list, pageNumber,totalPage } = videoData;
hasMoreData.value = totalPage>pageNumber;
loadMoreStatus.value = hasMoreData.value ? 'more' : 'noMore';
} else {
throw new Error(response?.message || '获取数据失败');
}
} catch (error) {
console.error('加载视频失败:', error);
} finally {
loading.value = false;
refreshing.value = false;
}
};
// 使用模拟数据加载视频列表
const loadMockVideoData = async (isRefresh = false) => {
if (loading.value && !isRefresh) return;
loading.value = true;
try {
const mockData = await getMockVideoData(currentPage.value, pageSize.value);
if (isRefresh) {
videoList.value = mockData.list;
currentPage.value = 1;
} else {
videoList.value = [...videoList.value, ...mockData.list];
}
hasMoreData.value = mockData.hasMore;
loadMoreStatus.value = hasMoreData.value ? 'more' : 'noMore';
} finally {
loading.value = false;
refreshing.value = false;
}
};
// 下拉刷新
const onRefresh = () => {
refreshing.value = true;
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
// 上拉加载更多
const onLoadMore = () => {
if (!hasMoreData.value || loading.value) return;
loadMoreStatus.value = 'loading';
currentPage.value++;
loadVideoData();
};
// 切换标签
const switchTab = (tabIndex) => {
activeTab.value = tabIndex;
// 可以根据标签加载不同的数据
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
// 播放视频
const playVideo = (video) => {
const videoId = video.id || video.uuid;
navTo({
url: `/pages_app/videoDetail/videoDetail?id=${videoId}`
});
};
// 搜索
// const goSearch = () => {
// uni.navigateTo({
// url: '/pages_app/search/search'
// });
// };
// 跳转到患教视频列表
const goPatientVideo = () => {
navTo({
url: '/pages_app/patientVideo/patientVideo'
});
};
// 搜索视频
const searchVideos = async (keywords) => {
if (!keywords || keywords.trim() === '') {
loadVideoData(true);
return;
}
try {
console.log('搜索关键词:', keywords.trim());
const response = await api.videoByKeyWordsNew({
keywords: keywords.trim(),
page: 1,
pageSize: pageSize.value
});
console.log('搜索API原始响应:', response);
// 检查响应结构
let searchData = {};
if (response && response.data) {
if (response.data.code === 200) {
searchData = response.data.data || {};
} else if (response.code === 200) {
searchData = response.data || {};
}
} else if (response && response.code === 200) {
searchData = response.data || {};
}
console.log('解析后的搜索数据:', searchData);
if (searchData && searchData.list) {
const { list,totalPage,pageNumber} = searchData;
videoList.value = list || [];
hasMoreData.value = totalPage>pageNumber;
console.log('hasMoreData:'+hasMoreData.value)
loadMoreStatus.value = hasMoreData.value ? 'more' : 'noMore';
currentPage.value = 1;
}
} catch (error) {
console.error('搜索视频失败:', error);
uni.showToast({
title: '搜索失败,请重试',
icon: 'none'
});
}
};
const onSwiperChange = (e) => {
currentBannerIndex.value = e.detail.current;
};
const playBannerVideo = (banner) => {
const videoId = banner.id || banner.uuid;
navTo({
url: `/pages_app/videoDetail/videoDetail?id=${videoId}`
});
};
// 全部视频弹窗相关方法
const openAllVideoPopup = () => {
showAllVideoPopup.value = true;
};
const closeAllVideoPopup = () => {
showAllVideoPopup.value = false;
};
const selectCategory = async (categoryValue) => {
selectedCategory.value = categoryValue;
console.log(yearList.value)
console.log('选择分类:', categoryValue);
let all=[{uuid:'',name:'全部视频'}];
for (var i = 0; i < yearList.value.length; i++) {
if(categoryValue==yearList.value[i].name){
console.log(yearList.value[i]);
filteredContent.value=categoryValue=='全部'?all.concat(yearList.value[i].list):yearList.value[i].list;
console.log(filteredContent.value)
break;
}
}
};
const selectContent = (content) => {
// 这里可以根据选中的内容进行相应的操作
console.log('选中内容:', content);
closeAllVideoPopup();
Object.assign(selectYearContent,content);
typeUuid.value=content.uuid;
typeName.value=content.name;
isAllActive.value=true;
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
// 筛选弹窗相关方法
const showFilterPopup = () => {
showFilter.value = true;
};
const hideFilterPopup = () => {
showFilter.value = false;
};
const toggleTag = (index) => {
filterTags.value[index].selected = !filterTags.value[index].selected;
isFilterActive.value = filterTags.value.some(tag => tag.selected);
};
const resetFilter = () => {
// 清空所有已选标签
filterTags.value.forEach(tag => tag.selected = false);
// 关闭筛选激活态
isFilterActive.value = false;
// 清空关键字
keywords.value = '';
// 刷新列表数据(可按需保留或移除)
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
const confirmFilter = () => {
let str='';
const selectedTags = filterTags.value.filter(tag => tag.selected);
console.log('选中的筛选标签:', selectedTags);
for (var i = 0; i < selectedTags.length; i++) {
if(str){
str+=","+selectedTags[i].NAME
}else{
str=selectedTags[i].NAME
}
}
keywords.value=str;
console.log('keywords:'+keywords.value)
isFilterActive.value=true;
hideFilterPopup();
// 根据选中的标签重新加载数据
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
// 模拟数据 - 实际开发时替换为真实API
const getMockVideoData = (page, size) => {
return new Promise((resolve) => {
setTimeout(() => {
const mockList = [];
const startIndex = (page - 1) * size;
for (let i = 0; i < size; i++) {
const index = startIndex + i;
mockList.push({
id: `video_${index}`,
title: index === 0 ? '《2025年版慢加急性肝衰竭指南》解读' :
index % 4 === 1 ? '自身免疫性肝病专栏免疫治疗的双刃剑——1例自…' :
index % 4 === 2 ? '徐医感染:硬化出血发热路,关关难过关关过' :
index % 4 === 3 ? '徐医感染:一场呼吸的迷局' :
'南京市第二医院疑难肝病病理读片会暨疑难肝病MDT',
thumbnail: '/static/video-thumb-' + (index % 4 + 1) + '.png',
author: index % 3 === 0 ? '首都医…' :
index % 3 === 1 ? '徐州医…' : '南京市…',
viewCount: Math.floor(Math.random() * 2000) + 100,
duration: '15:30'
});
}
resolve({
list: mockList,
hasMore: page <2 // 模拟5页数据
});
}, 1000);
});
};
// 返回
const goBack = () => {
uni.navigateBack({
fail() {
uni.redirectTo({
url: '/pages/index/index'
});
}
});
}
const toggleSort = () => {
sort.value = sort.value === 1 ? 2 : 1; // 1=最新, 2=最热(与后端约定)
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
</script>
<style scoped lang="scss">
$primary-color: #ff6b6b;
$theme-color: #8B2316;
$white: #fff;
$gray-bg: #f5f5f5;
$gray-light: #eee;
$gray-medium: #999;
$gray-dark: #666;
$text-color: #333;
// 尺寸变量
$border-radius: 8px;
$border-radius-small: 6px;
$padding: 15px;
$padding-small: 10px;
.img-icon{
width: 42rpx;
height: 42rpx;
}
.nav-actions{
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx;
}
.video-page {
background-color: #f5f5f5;
height: 100vh;
display: flex;
overflow: hidden;
flex-direction: column;
.btnbox{
width:100%;
position: fixed;
bottom:0;
height: 100rpx;
background-color: #00cbc0;
display: flex;
justify-content: center;
align-items: center;
color:#fff;
font-size: 30rpx;
}
}
/* Header Styles */
.header {
background-color: #fff;
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
position: relative;
z-index: 100;
}
.header-left, .header-right {
display: flex;
align-items: center;
}
.header-title {
font-size: 36rpx;
font-weight: 600;
color: #8B4513;
}
/* Scroll View */
.scroll-view {
position: fixed;
bottom:0rpx;
height: calc(100vh - var(--status-bar-height) - 54px - 430rpx); /* 固定高度减去固定的banner和Filter Tabs空间 */
background-color: #f5f5f5;
margin-bottom: 200rpx;
top: calc(var(--status-bar-height) + 54px + 400rpx); /* 为固定的banner和Filter Tabs留出空间 */
}
/* Banner Styles */
.banner-section {
position: relative;
height: 400rpx;
margin-bottom: 20rpx;
}
.bar{
width:2rpx;
margin:0 26rpx 0 30rpx;
height: 32rpx;
background-color: #666;
}
.banner-image {
width: 100%;
height: 100%;
}
.banner-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.7));
padding: 60rpx 30rpx 30rpx;
}
.banner-title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
line-height: 1.4;
}
/* Filter Tabs */
.filter-tabs {
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 30rpx 30rpx;
}
.tab-item {
display: flex;
align-items: center;
padding: 10rpx 0;
}
.tab-item:last-child{
margin-right: 0;
}
.right{
display: flex;
align-items: center;
justify-content: space-between;
}
.tab-item.active .tab-text {
color: #E74C3C;
font-weight: 600;
}
.tab-text {
margin: 0 8rpx;
font-size: 28rpx;
color: #666;
}
.tab-item:first-child .tab-text{
max-width:295rpx;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tab-item .tab-text.active {
color: #8B2316;
}
/* Fixed Banner Container */
.banner-container {
position: fixed;
top: calc(var(--status-bar-height) + 44px); /* uni-nav-bar height */
left: 0;
right: 0;
z-index: 50;
background-color: #fff;
.swipemask{
width:100%;
z-index:3;
box-sizing:border-box;
position: absolute;
bottom:0;
padding:0 20rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: space-between;
background-color: rgba(0,0,0,0.7);
.dotbox{
display:flex;
align-items: center;
.circle{
margin:0 4rpx;
width:16rpx;
height:16rpx;
background-color: #eee;
border-radius: 50%;
}
.circle.active{
background-color: #8B2316;
}
}
}
}
/* Fixed Filter Tabs Container */
.filter-tabs-container {
position: fixed;
top: calc(var(--status-bar-height) + 44px + 400rpx); /* banner-container top + banner height */
left: 0;
right: 0;
z-index: 40;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
}
.banner-swiper {
height: 400rpx;
}
.banner-item {
position: relative;
width: 100%;
height: 400rpx;
}
.banner-image {
width: 100%;
height: 100%;
}
.banner-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 40rpx 30rpx;
background: linear-gradient(transparent, rgba(0,0,0,0.7));
display: flex;
align-items: flex-end;
}
.doctor-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
overflow: hidden;
}
.doctor-avatar image {
width: 100%;
height: 100%;
}
.banner-info {
flex: 1;
}
.banner-title {
font-size: 32rpx;
font-weight: 600;
color: #fff;
margin-bottom: 8rpx;
line-height: 1.3;
}
.banner-subtitle {
font-size: 24rpx;
color: rgba(255,255,255,0.8);
white-space: nowrap; /* 单行显示 */
overflow: hidden; /* 隐藏溢出 */
text-overflow: ellipsis; /* 超出显示省略号 */
}
/* Video List */
.video-list {
padding: 0 20rpx;
margin-top: 100rpx; /* 为固定的Filter Tabs留出空间 */
display: flex;
flex-wrap: wrap;
/* 卡片间距 */
}
.video-list .video-item:nth-child(2n+2) {
margin-left: 16rpx;
}
.video-item {
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 0;
margin-top: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
width: calc(50% - 8rpx); /* 两列布局减半gap */
min-width: 300rpx; /* 确保最小宽度 */
flex-shrink: 0; /* 防止收缩 */
}
.video-thumbnail {
position: relative;
height: 200rpx;
}
.video-thumbnail image {
width: 100%;
height: 100%;
}
.video-duration {
margin-top: 8rpx;
}
.video-duration text {
font-size: 22rpx;
color: #999;
}
.video-info {
padding: 24rpx;
}
.video-title {
font-size: 28rpx;
font-weight: 400;
color: #333;
line-height: 1.4;
margin-bottom: 16rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
height: 2.8em; /* 固定2行高度1.4 * 2 = 2.8em */
}
.video-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.author {
font-size: 24rpx;
color: #999;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 20rpx;
}
.stats {
display: flex;
align-items: center;
}
.view-count {
font-size: 24rpx;
color: #999;
margin-left: 8rpx;
}
/* Empty State */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.empty-state image {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.5;
}
.empty-state text {
color: #999;
font-size: 28rpx;
}
.newbox{
margin-top: -12rpx;
}
/* 全部视频弹窗样式 */
.all-video-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
}
.popup-content {
width: 100%;
margin-top: calc(var(--status-bar-height) + 44px + 400rpx + 117rpx);
height: calc(100vh - var(--status-bar-height) - 44px - 400rpx - 117rpx);
background-color: #fff;
overflow: scroll;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
background-color: #f8f8f8;
}
.header-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.header-right {
padding: 10rpx;
cursor: pointer;
}
.popup-body {
display: flex;
height: 100%;
}
.category-sidebar {
width: 200rpx;
background-color: #f8f8f8;
border-right: 1rpx solid #f0f0f0;
overflow-y: auto;
}
.category-item {
padding: 30rpx 20rpx;
font-size: 28rpx;
color: #666;
border-bottom: 1rpx solid #f0f0f0;
cursor: pointer;
transition: all 0.3s ease;
}
.category-item:hover {
background-color: #e8f4fd;
}
.category-item.active {
background-color: #fff;
color: #8B2316;
}
.category-label {
font-size: 28rpx;
font-weight: 500;
}
.category-date {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
.content-area {
flex: 1;
padding: 20rpx;
overflow-y: auto;
}
.content-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.content-item {
padding: 20rpx;
background-color: #f8f8f8;
border-radius: 10rpx;
font-size: 26rpx;
color: #333;
cursor: pointer;
transition: all 0.3s ease;
border: 2rpx solid transparent;
}
.content-item.active {
color: #8B2316;
}
.empty-content {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
color: #999;
font-size: 28rpx;
}
// 筛选弹窗样式
.filter-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
// background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
z-index: 9999;
.filter-content {
margin-top:calc(var(--status-bar-height) + 44px + 400rpx + 117rpx);
overflow-y:scroll;
background-color: $white;
// border-radius: 20rpx 20rpx 0 0;
padding: 20rpx 30rpx 60rpx;
width: 100%;
max-height: 80vh;
.filter-tags {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-bottom: 60rpx;
max-height: 65vh;
overflow-y: auto;
.tag-item {
background-color: #f8f8f8;
color: $gray-dark;
padding: 8rpx 0; /* 去掉左右padding仅保留上下 */
border-radius: 30rpx;
font-size: 26rpx;
border: 2rpx solid #efefef;
transition: all 0.3s ease;
width: 152rpx; /* 固定宽度 */
text-align: center; /* 文本居中 */
&.active {
background-color: #fff;
color: $theme-color;
border-color: $theme-color;
}
}
}
.filter-buttons {
display: flex;
gap: 20rpx;
position: fixed;
bottom: 30rpx;
left: 30rpx;
right: 30rpx;
background-color: #fff; /* 背景色为白色 */
.btn-reset,
.btn-confirm {
flex: 1;
height: 70rpx;
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
}
.btn-reset {
background-color: $white;
color: $theme-color;
border: 2rpx solid $theme-color;
}
.btn-confirm {
border: 2rpx solid $theme-color;
background-color: $theme-color;
color: $white;
}
}
}
}
</style>