This commit is contained in:
zoujiandong
2025-09-01 16:25:46 +08:00
parent 7270482bd9
commit 82e0a5c10d
14 changed files with 3089 additions and 464 deletions
+397 -151
View File
@@ -6,8 +6,9 @@
<!-- Fixed Banner Swiper -->
<view class="banner-container">
<view class="swipemask">
<view class="banner-subtitle">111111111</view>
<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>
@@ -33,18 +34,10 @@
<view class="banner-item">
<image
class="banner-image"
:src="banner.thumbnail || '/static/big_background_my.png'"
:src="docUrl+banner.imgpath"
mode="aspectFill"
></image>
<view class="banner-content">
<view class="doctor-avatar" v-if="banner.doctorAvatar">
<image :src="banner.doctorAvatar" mode="aspectFill"></image>
</view>
<view class="banner-info">
<view class="banner-title">{{banner.title}}</view>
<view class="banner-subtitle">{{banner.subtitle || '专业医学视频教育平台'}}</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
@@ -57,25 +50,25 @@
class="tab-item"
@click="showAllVideoPopup=!showAllVideoPopup"
>
<up-image :src="allImg" width="30rpx" height="30rpx" ></up-image>
<text class="tab-text">全部视频</text>
<up-image :src="selectImg" width="30rpx" height="30rpx" ></up-image>
<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">最新</text>
<text class="tab-text active">{{sort==2?'最新':'最热'}}</text>
<view class="newbox">
<up-image :src="upImg" width="20rpx" height="26rpx" ></up-image>
<up-image :src="sort==2?upImg:downImg" width="20rpx" height="26rpx" ></up-image>
</view>
</view>
<view
class="tab-item"
@click="showFilterPopup"
>
<text class="tab-text">筛选</text>
<text class="tab-text " :class="{active:isFilterActive}">筛选</text>
<view class="filterbox">
<up-image :src="isFilterActive ? filterOn : filter" width="30rpx" height="30rpx" ></up-image>
</view>
@@ -100,22 +93,22 @@
<view
class="video-item"
v-for="(video, index) in videoList"
:key="video.id"
:key="video.id || video.uuid"
@click="playVideo(video)"
>
<view class="video-thumbnail">
<image :src="video.thumbnail" mode="aspectFill"></image>
<image :src="docUrl + video.imgpath" mode="aspectFill"></image>
</view>
<view class="video-info">
<view class="video-title">{{video.title}}</view>
<view class="video-title">{{video.title || video.name}}</view>
<view class="video-meta">
<text class="author">{{video.author}}</text>
<text class="author">{{video.public_name}}</text>
<view class="stats">
<uni-icons type="eye" size="14" color="#999"></uni-icons>
<text class="view-count">{{video.viewCount}}</text>
<text class="view-count">{{video.readnum}}</text>
</view>
</view>
</view>
</view>
</view>
@@ -137,7 +130,7 @@
<text>暂无视频内容</text>
</view>
</scroll-view>
<view class="btnbox">
<view class="btnbox" @click="goPatientVideo">
<up-image :src="videoImg" width="44rpx" height="44rpx" ></up-image>
患教视频
@@ -154,7 +147,7 @@
:class="{ active: tag.selected }"
@click="toggleTag(index)"
>
{{ tag.name }}
{{ tag.DES }}
</view>
</view>
@@ -180,18 +173,28 @@
:key="category.value"
@click="selectCategory(category.value)"
>
{{ category.label }}
<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.id"
:key="item.uuid"
:class="{active:selectYearContent.uuid==item.uuid}"
@click="selectContent(item)"
>
{{ item.title }}
{{item.name }}
</view>
<view v-if="filteredContent.length === 0" class="empty-content">
<text>该分类暂无内容</text>
</view>
</view>
</view>
@@ -202,7 +205,7 @@
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
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"
@@ -212,8 +215,11 @@
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_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';
const isAllActive=ref(false)
// 响应式数据
const videoList = ref([]);
const bannerVideo = ref(null);
@@ -224,13 +230,18 @@
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 filterTags = ref([
{ name: '指南解读', selected: false },
@@ -249,65 +260,171 @@
// 全部视频弹窗相关数据
const showAllVideoPopup = ref(false);
const selectedCategory = ref('all');
const selectedCategory = ref('全部');
const categoryList = ref([
{ value: 'all', label: '全部' },
{ value: 'guide', label: '指南解读' },
{ value: 'case', label: '病例分析' },
{ value: 'lecture', label: '学术讲座' },
{ value: 'surgery', label: '手术视频' },
{ value: 'research', label: '研究进展' }
{ value: 'all', label: '全部' }
]);
// 分类内容数据
// 分类内容数据 - 将根据API返回的类型动态生成
const contentByCategory = ref({
all: [
{ id: 1, title: '《2025年版慢加急性肝衰竭指南》解读' },
{ id: 2, title: '自身免疫性肝病专栏|免疫治疗的双刃剑' },
{ id: 3, title: '徐医感染:硬化出血发热路,关关难过关关过' },
{ id: 4, title: '徐医感染:一场呼吸的迷局' },
{ id: 5, title: '南京市第二医院疑难肝病病理读片会' }
],
guide: [
{ id: 6, title: '《2025年版慢加急性肝衰竭指南》解读' },
{ id: 7, title: '《原发性肝癌诊疗指南》更新要点' },
{ id: 8, title: '《慢性乙型肝炎防治指南》解读' }
],
case: [
{ id: 9, title: '自身免疫性肝病专栏|免疫治疗的双刃剑' },
{ id: 10, title: '徐医感染:硬化出血发热路,关关难过关关过' },
{ id: 11, title: '徐医感染:一场呼吸的迷局' }
],
lecture: [
{ id: 12, title: '南京市第二医院疑难肝病病理读片会' },
{ id: 13, title: '肝胆胰外科手术技巧分享' },
{ id: 14, title: '肝移植围手术期管理' }
],
surgery: [
{ id: 15, title: '腹腔镜肝切除术操作要点' },
{ id: 16, title: '机器人辅助肝切除术' },
{ id: 17, title: '肝移植手术技术进展' }
],
research: [
{ id: 18, title: '肝癌免疫治疗最新研究进展' },
{ id: 19, title: '肝纤维化分子机制研究' },
{ id: 20, title: '肝细胞再生与修复研究' }
]
all: []
});
// 存储API返回的原始类型数据
const videoTypesData = ref([]);
// 页面加载
onMounted(() => {
});
onShow(() => {
// 使用模拟数据初始化列表
// 调用真实API获取数据
currentPage.value = 1;
hasMoreData.value = true;
loadMockVideoData(true);
// 初始化banner数据
bannerList.value = getMockBannerData();
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);
// 使用模拟数据作为备用
bannerList.value = getMockBannerData();
}
};
// 加载视频标签
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;
filteredContent.value=(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;
@@ -316,58 +433,60 @@
try {
// 调用真实API
const response = await api.getVideoList({
const selectedTags = filterTags.value.filter(tag => tag.selected).map(tag => tag.id).join(',');
console.log('请求参数:', {
page: currentPage.value,
pageSize: pageSize.value,
category: activeTab.value // 根据选中的标签获取不同类别的视频
keywords: keywords.value,
sort: sort.value,
typeUuid:typeUuid.value,
tags: selectedTags
});
if (response.data.code === 200) {
const { list, hasMore } = response.data.data;
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, hasMore, total } = videoData;
console.log('视频列表数据:', list);
if (isRefresh) {
videoList.value = list;
videoList.value = list || [];
currentPage.value = 1;
} else {
videoList.value = [...videoList.value, ...list];
videoList.value = [...videoList.value, ...(list || [])];
}
// 设置banner视频(第一个视频)
if (isRefresh || currentPage.value === 1) {
bannerVideo.value = list[0];
}
hasMoreData.value = hasMore;
hasMoreData.value = hasMore !== false;
loadMoreStatus.value = hasMoreData.value ? 'more' : 'noMore';
} else {
throw new Error(response.data.message || '获取数据失败');
throw new Error(response?.message || '获取数据失败');
}
} catch (error) {
console.error('加载视频失败:', error);
// 如果API失败,使用模拟数据作为备用方案
const mockData = await getMockVideoData(currentPage.value, pageSize.value);
if (isRefresh) {
videoList.value = mockData.list;
currentPage.value = 1;
} else {
videoList.value = [...videoList.value, ...mockData.list];
}
// 设置banner视频(第一个视频)
if (isRefresh || currentPage.value === 1) {
bannerVideo.value = mockData.list[0];
}
hasMoreData.value = mockData.hasMore;
loadMoreStatus.value = hasMoreData.value ? 'more' : 'noMore';
uni.showToast({
title: '网络连接异常,显示离线数据',
icon: 'none'
});
} finally {
loading.value = false;
refreshing.value = false;
@@ -399,7 +518,7 @@
refreshing.value = true;
currentPage.value = 1;
hasMoreData.value = true;
loadMockVideoData(true);
loadVideoData(true);
};
// 上拉加载更多
@@ -408,7 +527,7 @@
loadMoreStatus.value = 'loading';
currentPage.value++;
loadMockVideoData();
loadVideoData();
};
// 切换标签
@@ -417,13 +536,15 @@
// 可以根据标签加载不同的数据
currentPage.value = 1;
hasMoreData.value = true;
loadMockVideoData(true);
loadVideoData(true);
};
// 播放视频
const playVideo = (video) => {
uni.navigateTo({
url: `/pages/videoDetail/videoDetail?id=${video.id}`
const videoId = video.id || video.uuid;
navTo({
url: `/pages_app/videoDetail/videoDetail?id=${videoId}`
});
};
@@ -436,6 +557,61 @@
});
};
// 跳转到患教视频列表
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, hasMore } = searchData;
console.log('搜索结果:', list);
videoList.value = list || [];
hasMoreData.value = hasMore !== false;
loadMoreStatus.value = hasMoreData.value ? 'more' : 'noMore';
currentPage.value = 1;
}
} catch (error) {
console.error('搜索视频失败:', error);
uni.showToast({
title: '搜索失败,请重试',
icon: 'none'
});
}
};
// Banner相关方法
const getMockBannerData = () => {
return [
@@ -468,8 +644,10 @@
};
const playBannerVideo = (banner) => {
uni.navigateTo({
url: `/pages/videoDetail/videoDetail?id=${banner.id}`
const videoId = banner.id || banner.uuid;
navTo({
url: `/pages_app/videoDetail/videoDetail?id=${videoId}`
});
};
@@ -481,23 +659,35 @@
const closeAllVideoPopup = () => {
showAllVideoPopup.value = false;
};
const selectCategory = (categoryValue) => {
const selectCategory = async (categoryValue) => {
selectedCategory.value = categoryValue;
console.log(yearList.value)
console.log('选择分类:', categoryValue);
for (var i = 0; i < yearList.value.length; i++) {
if(categoryValue==yearList.value[i].name){
console.log(yearList.value[i]);
filteredContent.value=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;
};
// 计算属性:根据选中的分类过滤内容
const filteredContent = computed(() => {
return contentByCategory.value[selectedCategory.value] || [];
});
// 筛选弹窗相关方法
const showFilterPopup = () => {
showFilter.value = true;
@@ -513,15 +703,37 @@
};
const resetFilter = () => {
// 清空所有已选标签
filterTags.value.forEach(tag => tag.selected = false);
// 关闭筛选激活态
isFilterActive.value = false;
// 清空关键字
keywords.value = '';
// 刷新列表数据(可按需保留或移除)
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
const confirmFilter = () => {
const selectedTags = filterTags.value.filter(tag => tag.selected);
console.log('选中的筛选标签:', selectedTags);
for (var i = 0; i < selectedTags.length; i++) {
if(keywords.value){
keywords.value+=","+selectedTags[i].DES
}else{
keywords.value=selectedTags[i].DES
}
}
isFilterActive.value=true;
hideFilterPopup();
// 这里可以根据选中的标签进行数据筛选
// 根据选中的标签重新加载数据
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
// 模拟数据 - 实际开发时替换为真实API
@@ -565,6 +777,13 @@
}
});
}
const toggleSort = () => {
sort.value = sort.value === 1 ? 2 : 1; // 1=最新, 2=最热(与后端约定)
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
</script>
<style scoped lang="scss">
@@ -697,11 +916,16 @@
color: #666;
}
.tab-item:first-child .tab-text{
max-width:600rpx;
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;
@@ -805,6 +1029,9 @@
.banner-subtitle {
font-size: 24rpx;
color: rgba(255,255,255,0.8);
white-space: nowrap; /* 单行显示 */
overflow: hidden; /* 隐藏溢出 */
text-overflow: ellipsis; /* 超出显示省略号 */
}
/* Video List */
@@ -837,18 +1064,15 @@
height: 100%;
}
.play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0,0,0,0.6);
border-radius: 50%;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
.video-duration {
margin-top: 8rpx;
}
.video-duration text {
font-size: 22rpx;
color: #999;
}
.video-info {
@@ -857,7 +1081,7 @@
.video-title {
font-size: 28rpx;
font-weight: 600;
font-weight: 400;
color: #333;
line-height: 1.4;
margin-bottom: 16rpx;
@@ -866,6 +1090,7 @@
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
height: 2.8em; /* 固定2行高度:1.4 * 2 = 2.8em */
}
.video-meta {
@@ -988,6 +1213,17 @@
}
.category-label {
font-size: 28rpx;
font-weight: 500;
}
.category-date {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
.content-area {
flex: 1;
padding: 20rpx;
@@ -1011,13 +1247,20 @@
border: 2rpx solid transparent;
}
.content-item:hover {
background-color: #e8f4fd;
border-color: #4A90E2;
.content-item.active {
color: #8B2316;
}
.content-item:active {
transform: scale(0.98);
.empty-content {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
color: #999;
font-size: 28rpx;
}
// 筛选弹窗样式
.filter-popup {
@@ -1033,8 +1276,8 @@
z-index: 9999;
.filter-content {
margin-top:329rpx;
margin-top:660rpx;
overflow-y:scroll;
background-color: $white;
// border-radius: 20rpx 20rpx 0 0;
padding: 20rpx 30rpx 60rpx;
@@ -1052,11 +1295,13 @@
.tag-item {
background-color: #f8f8f8;
color: $gray-dark;
padding: 8rpx 24rpx;
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;
@@ -1073,6 +1318,7 @@
bottom: 30rpx;
left: 30rpx;
right: 30rpx;
background-color: #fff; /* 背景色为白色 */
.btn-reset,
.btn-confirm {