2025-10-14 17:46:23 +08:00

861 lines
18 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>
<uni-nav-bar left-icon="left"
@clickLeft="goBack" title="患教视频" fixed color="#8B2316" height="180rpx" :border="false" backgroundColor="#eeeeee"></uni-nav-bar>
<view class="video-page">
<!-- Header -->
<!-- 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="divider"></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="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">
<image :src="docUrl + video.imgpath" mode="aspectFill"></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">{{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">
<empty></empty>
</view>
</scroll-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-item"
:class="{ active: typeUuid === item.uuid }"
v-for="item in videoTypeList"
:key="item.uuid"
@click="selectCategory(item.uuid)"
>
{{ item.name }}
</view>
<view v-if="videoTypeList.length === 0" class="empty-content">
<text>暂无数据</text>
</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 docUrl from '@/utils/docUrl';
import navTo from '@/utils/navTo';
import empty from '@/components/empty/empty.vue';
const isAllActive=ref(false)
// 响应式数据
const videoList = ref([]);
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('0e5fa3d76b8047528fdd3c452b77e9dd');
const typeName=ref('乙肝')
const isFilterActive=ref(false)
const showFilter = ref(false);
const yearList=ref([]);
const selectYearContent=reactive({})
// 筛选弹窗相关数据
const filterTags = ref([]);
// 全部视频弹窗相关数据
const showAllVideoPopup = ref(false);
const selectedCategory = ref('全部');
const selectCategory=(uuid)=>{
console.log(222)
console.log(uuid)
typeUuid.value=uuid;
typeName.value=videoTypeList.value.find(item=>item.uuid==uuid).name;
isAllActive.value=true;
closeAllVideoPopup();
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData();
}
const videoTypeList=ref([]);
const patientVideoNew=async ()=>{
const res=await api.patientVideoNew();
if(res.code==200){
videoTypeList.value=res.data;
}
}
// 页面加载
onMounted(() => {
});
onShow(() => {
// 调用真实API获取数据
currentPage.value = 1;
hasMoreData.value = true;
loadGuideTags();
loadVideoData();
patientVideoNew();
});
// 加载轮播数据
// 加载视频标签
const loadGuideTags = async () => {
try {
const res = await api.guideTag({
type:5
});
console.log('指南标签API响应:', res);
if(res && res.code === 200 && res.data) {
// 将API返回的标签数据转换为筛选标签格式
filterTags.value = res.data.map(tag => ({
...tag,
selected: false
}));
console.log('指南标签加载成功:', filterTags.value);
}
} catch (e) {
console.error('加载指南标签失败:', e);
}
};
// 加载视频数据
const loadVideoData = async (isRefresh = false) => {
if (loading.value && !isRefresh) return;
loading.value = true;
try {
const response = await api.patientVideoByKeyWordsNew({
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, totalPage,pageNumber } = videoData;
console.log('视频列表数据:', list);
if (isRefresh) {
videoList.value = list || [];
currentPage.value = 1;
} else {
videoList.value = [...videoList.value, ...(list || [])];
}
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 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();
};
// 播放视频
const playVideo = (video) => {
uni.$emit('videoItem', video);
uni.navigateBack();
};
// 搜索
const goSearch = () => {
uni.navigateTo({
url: '/pages_app/search/search'
});
};
// 跳转到患教视频列表
// 搜索视频
const searchVideos = async (keywords) => {
if (!keywords || keywords.trim() === '') {
loadVideoData();
return;
}
try {
console.log('搜索关键词:', keywords.trim());
const response = await api.patientVideoByKeyWordsNew({
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'
});
}
};
// 全部视频弹窗相关方法
const openAllVideoPopup = () => {
showAllVideoPopup.value = true;
};
const closeAllVideoPopup = () => {
showAllVideoPopup.value = false;
};
// 筛选弹窗相关方法
const showFilterPopup = () => {
showFilter.value = true;
};
const hideFilterPopup = () => {
showFilter.value = false;
};
const toggleTag = (index) => {
filterTags.value[index].selected = !filterTags.value[index].selected;
};
const resetFilter = () => {
// 清空所有已选标签
filterTags.value.forEach(tag => tag.selected = 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].NAME
}else{
keywords.value=selectedTags[i].NAME
}
}
isFilterActive.value=true;
hideFilterPopup();
// 根据选中的标签重新加载数据
currentPage.value = 1;
hasMoreData.value = true;
loadVideoData(true);
};
// 返回
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();
};
</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;
.video-page {
background-color: #f5f5f5;
height: 100vh;
display: flex;
overflow: hidden;
flex-direction: column;
}
.divider {
width: 1px;
height: 16px;
background-color: $gray-medium;
margin: 0 $padding;
}
/* 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 - 180rpx); /* 固定高度,减去导航栏高度 */
background-color: #f5f5f5;
top: 172rpx; /* 为导航栏留出空间 */
}
/* Filter Tabs */
.filter-tabs {
background-color: #fff;
display: flex;
align-items: center;
justify-content:center;
padding: 30rpx 30rpx;
}
.tab-item {
display: flex;
align-items: center;
justify-content: center;
flex:1;
padding: 10rpx 0;
}
.tab-item:last-child{
margin-right: 0;
}
.right{
flex:1;
display: flex;
}
.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:155rpx;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tab-item .tab-text.active {
color: #8B2316;
}
/* Fixed Filter Tabs Container */
.filter-tabs-container {
position: fixed;
top: 180rpx; /* uni-nav-bar height */
left: 0;
right: 0;
z-index: 40;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
}
.doctor-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
overflow: hidden;
}
.doctor-avatar image {
width: 100%;
height: 100%;
}
/* Video List */
.video-list {
padding: 0 20rpx;
margin-top: 100rpx; /* 为固定的Filter Tabs留出空间 */
display: flex;
flex-wrap: wrap;
gap: 16rpx; /* 卡片间距 */
}
.video-item {
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 0;
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;
}
.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;
align-items: flex-end;
}
.popup-content {
width: 100%;
background-color: #fff;
height:calc(100vh - 272rpx);
overflow-y: scroll;
}
.popup-header {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
background-color: #fff;
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.popup-body {
height:100%;
}
.category-item {
padding: 30rpx;
font-size: 28rpx;
color: #333;
border-bottom: 1rpx solid #f0f0f0;
background-color: #fff;
transition: background-color 0.2s ease;
}
.category-item:last-child {
border-bottom: none;
}
.category-item.active {
background-color: #f8f8f8;
color: #8B2316;
}
.category-item:active {
background-color: #e8f4fd;
}
.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:272rpx;
overflow-y:scroll;
background-color: $white;
// border-radius: 20rpx 20rpx 0 0;
padding: 20rpx 30rpx 60rpx;
width: 100%;
height: calc(100vh - 272rpx);
.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; /* 文本居中 */
white-space: nowrap; /* 单行显示 */
overflow: hidden; /* 隐藏溢出 */
text-overflow: ellipsis; /* 超出显示省略号 */
&.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>