uniapp-app/pages/live/live.vue
2025-09-02 19:03:45 +08:00

961 lines
23 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 title="肝胆会议" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#eeeeee"></uni-nav-bar>
<view class="page">
<!-- 筛选标签栏 -->
<view class="filter-bar">
<view class="filter-item active" @click="showTimePopup">
<text>会议时间</text>
<up-image :src="select" width="26rpx" height="26rpx" ></up-image>
</view>
<view class="filter-divider"></view>
<view class="filter-item" @click="showLocationPopup">
<text>会议地点</text>
<up-image :src="select" width="26rpx" height="26rpx" ></up-image>
</view>
<view class="filter-divider"></view>
<view class="filter-item">
<text>会议回放</text>
</view>
</view>
<!-- 可滚动内容区域 -->
<scroll-view
ref="scrollView"
class="scroll-content"
scroll-y="true"
refresher-enabled="true"
:refresher-triggered="isRefreshing"
:refresher-threshold="100"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
:lower-threshold="100"
:scroll-top="scrollTop"
:enable-back-to-top="true"
>
<!-- 时间标题 -->
<view class="time-header">2025年08月</view>
<!-- 会议列表 -->
<view class="meeting-list" v-if="meetingList.length > 0">
<view class="meeting-item" v-for="(item, index) in meetingList" :key="item.id || index">
<!-- 左侧日期标识 -->
<view class="date-tag" :style="{backgroundColor: item.tagColor}">
<text class="date-text">{{ item.date }}</text>
</view>
<!-- 会议内容 -->
<view class="meeting-content">
<view class="meeting-title">{{ item.title }}</view>
<view class="meeting-poster" @click="playVideo(item)">
<image
:src="docUrl+item.liveimg"
class="poster-image"
mode="aspectFill"
@error="onImageError"
@load="onImageLoad"
:data-item-id="item.id"
></image>
<view class="play-btn">
<up-image :src="playImg" width="108rpx" height="108rpx" ></up-image>
</view>
<view class="preview-tag" v-if="item.status === 'upcoming'">预告</view>
<view class="live-tag" v-else-if="item.status === 'live'">直播中</view>
<view class="replay-tag" v-else-if="item.status === 'replay'">回放</view>
</view>
<view class="meeting-info">
<view class="info-item">
<view class="timebox">
<up-image :src="timeImg" width="24rpx" height="24rpx" ></up-image>
</view>
<text class="info-text">{{ item.time }}</text>
</view>
<view class="info-item">
<uni-icons type="location" size="14" color="#999"></uni-icons>
<text class="info-text">{{ item.location }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else-if="!isRefreshing">
<view class="empty-icon">
<uni-icons type="calendar" size="80" color="#ccc"></uni-icons>
</view>
<text class="empty-text">暂无会议数据</text>
<text class="empty-subtext">请稍后再试或调整筛选条件</text>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="showLoadMore">
<view class="load-more-content" v-if="isLoadingMore">
<uni-icons type="spinner-cycle" size="20" color="#999"></uni-icons>
<text class="load-more-text">加载中...</text>
</view>
<view class="load-more-content" v-else-if="hasMoreData">
<text class="load-more-text">上拉加载更多</text>
</view>
<view class="load-more-content" v-else>
<text class="load-more-text">没有更多数据了</text>
</view>
</view>
</scroll-view>
<!-- 时间选择弹窗 -->
<view class="time-popup" v-if="isTimePopupShow" @click="hideTimePopup">
<view class="popup-mask"></view>
<view class="time-popup-content" @click.stop>
<view class="time-list">
<view
class="time-item"
v-for="(month, index) in monthList"
:key="index"
:class="{ active: selectedMonth === month.value }"
@click="selectMonth(month)"
>
<text>{{ month.label }}</text>
</view>
</view>
</view>
</view>
<!-- 地区选择弹窗 -->
<view class="location-popup" v-if="isLocationPopupShow" @click="hideLocationPopup">
<view class="popup-mask"></view>
<view class="popup-content" @click.stop>
<view class="location-grid">
<view
class="location-item"
v-for="(province, index) in provinceList"
:key="index"
:class="{ active: selectedProvince === province.code }"
@click="selectProvince(province)"
>
<text>{{ province.name }}</text>
</view>
</view>
</view>
</view>
<!-- 过往会议提示 -->
<view class="history-tip">
<view class="tip-icon">
<up-icon name="clock" color="#00cbc0" size="28"></up-icon>
</view>
<text class="tip-text">过往会议</text>
</view>
<!-- 底部导航栏 -->
<CustomTabbar></CustomTabbar>
</view>
</template>
<script setup>
import { ref,nextTick} from 'vue';
import { onShow } from "@dcloudio/uni-app";
import CustomTabbar from '@/components/tabBar/tabBar.vue';
import api from '@/api/api.js';
import select from "@/static/triangle_normal.png"
import selectOn from "@/static/triangle_normal.png"
import playImg from "@/static/bofang.png"
import timeImg from "@/static/play_long.png"
import docUrl from "@/utils/docUrl"
// 弹窗状态
const isTimePopupShow = ref(false);
const isLocationPopupShow = ref(false);
const selectedMonth = ref('all');
const selectedProvince = ref('');
// 下拉刷新和上拉加载状态
const isRefreshing = ref(false);
const isLoadingMore = ref(false);
const hasMoreData = ref(true);
const showLoadMore = ref(true);
const currentPage = ref(1);
const pageSize = ref(10);
const scrollTop = ref(0);
// 月份数据
const monthList = ref([
{ value: 'all', label: '所有' },
{ value: '8', label: '8月' },
{ value: '9', label: '9月' },
{ value: '10', label: '10月' },
{ value: '11', label: '11月' },
{ value: '12', label: '12月' },
{ value: '1', label: '1月' },
{ value: '2', label: '2月' },
{ value: '3', label: '3月' },
{ value: '4', label: '4月' },
{ value: '5', label: '5月' },
{ value: '6', label: '6月' },
{ value: '7', label: '7月' }
]);
// 省份数据
const provinceList = ref([
{ code: 'all', name: '全国' },
{ code: 'beijing', name: '北京市' },
{ code: 'tianjin', name: '天津市' },
{ code: 'hebei', name: '河北省' },
{ code: 'shanxi', name: '山西省' },
{ code: 'neimenggu', name: '内蒙古...' },
{ code: 'liaoning', name: '辽宁省' },
{ code: 'jilin', name: '吉林省' },
{ code: 'heilongjiang', name: '黑龙江省' },
{ code: 'shanghai', name: '上海市' },
{ code: 'jiangsu', name: '江苏省' },
{ code: 'zhejiang', name: '浙江省' },
{ code: 'anhui', name: '安徽省' },
{ code: 'fujian', name: '福建省' },
{ code: 'jiangxi', name: '江西省' },
{ code: 'shandong', name: '山东省' },
{ code: 'henan', name: '河南省' },
{ code: 'hubei', name: '湖北省' },
{ code: 'hunan', name: '湖南省' },
{ code: 'guangdong', name: '广东省' },
{ code: 'guangxi', name: '广西壮...' },
{ code: 'hainan', name: '海南省' },
{ code: 'chongqing', name: '重庆市' },
{ code: 'sichuan', name: '四川省' },
{ code: 'guizhou', name: '贵州省' },
{ code: 'yunnan', name: '云南省' },
{ code: 'xizang', name: '西藏自...' },
{ code: 'shaanxi', name: '陕西省' },
{ code: 'gansu', name: '甘肃省' },
{ code: 'qinghai', name: '青海省' },
{ code: 'ningxia', name: '宁夏回...' },
{ code: 'xinjiang', name: '新疆维...' },
{ code: 'taiwan', name: '台湾省' },
{ code: 'hongkong', name: '香港特...' },
{ code: 'macao', name: '澳门特...' }
]);
// 会议列表数据
const meetingList = ref([]);
// 页面显示时获取会议列表数据
onShow(() => {
getMeetingList(true);
});
// 获取会议列表数据的函数
const getMeetingList = async (isRefresh = false) => {
if (isRefresh) {
currentPage.value = 1;
hasMoreData.value = true;
}
const params = {
page: currentPage.value,
pageSize: pageSize.value,
month: selectedMonth.value !== 'all' ? selectedMonth.value : '',
province: selectedProvince.value !== 'all' ? selectedProvince.value : ''
};
try {
console.log('获取会议列表参数:', params);
const response = await api.meetingListV2U(params);
console.log('会议列表API响应:', response);
if (response && response.code === 200 && response.data) {
let newItems = [];
let totalCount = 0;
// 处理不同的数据结构
if (response.data.list && Array.isArray(response.data.list)) {
newItems = response.data.list;
totalCount = response.data.total || response.data.totalRow || 0;
console.log('使用 res.data.list 结构');
} else if (response.data && Array.isArray(response.data)) {
newItems = response.data;
totalCount = response.total || response.totalRow || newItems.length;
console.log('使用 res.data 结构');
} else if (Array.isArray(response)) {
newItems = response;
totalCount = newItems.length;
console.log('使用 res 数组结构');
}
console.log('解析后的数据:', { newItems, totalCount });
console.log('图片字段映射示例:', newItems.slice(0, 2).map(item => ({
liveimg: item.liveimg || item.live_image || item.live_img,
poster: item.poster || item.cover_image || item.image
})));
if (Array.isArray(newItems) && newItems.length > 0) {
// 处理会议数据,添加必要的字段
const processedItems = newItems.map(item => ({
id: item.id || item.meeting_id || Math.random().toString(36).substr(2, 9),
date: item.date || item.meeting_date || item.start_time || '13',
tagColor: getTagColor(item.status || item.meeting_status || 'upcoming'),
title: item.title || item.meeting_title || item.name || '会议标题',
liveimg: item.liveimg || item.live_image || item.live_img || '',
poster: item.poster || item.cover_image || item.image || '/static/meeting-poster-1.jpg',
time: formatMeetingTime(item.start_time || item.meeting_time || item.time),
location: item.location || item.address || item.venue || '线上',
status: item.status || item.meeting_status || 'upcoming',
description: item.description || item.content || '',
organizer: item.organizer || item.host || '',
speakers: item.speakers || item.experts || []
}));
if (isRefresh) {
meetingList.value = processedItems;
} else {
meetingList.value.push(...processedItems);
}
// 检查是否还有更多数据
if (meetingList.value.length >= totalCount) {
hasMoreData.value = false;
}
console.log('会议列表更新成功,当前总数:', meetingList.value.length);
console.log('图片字段详情:', processedItems.slice(0, 2).map(item => ({
id: item.id,
liveimg: item.liveimg,
poster: item.poster,
finalImage: item.liveimg || item.poster
})));
} else {
console.log('API返回的数据为空');
if (isRefresh) {
meetingList.value = [];
}
}
} else {
console.log('API响应格式不正确:', response);
if (isRefresh) {
meetingList.value = [];
}
}
} catch (error) {
console.error('获取会议列表失败:', error);
if (isRefresh) {
meetingList.value = [];
}
uni.showToast({
title: '获取会议列表失败',
icon: 'error',
duration: 2000
});
}
};
// 根据会议状态获取标签颜色
const getTagColor = (status) => {
const colorMap = {
'upcoming': '#FF4444', // 预告
'live': '#00BCD4', // 直播中
'replay': '#9C27B0', // 回放
'finished': '#4CAF50', // 已结束
'cancelled': '#FF9800' // 已取消
};
return colorMap[status] || '#FF4444';
};
// 格式化会议时间
const formatMeetingTime = (timeStr) => {
if (!timeStr) return '2025.08.13';
try {
// 如果是时间戳
if (typeof timeStr === 'number') {
const date = new Date(timeStr);
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
}
// 如果是字符串,尝试解析
const date = new Date(timeStr);
if (!isNaN(date.getTime())) {
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
}
// 如果解析失败,返回原字符串
return timeStr;
} catch (error) {
console.error('时间格式化失败:', error);
return timeStr;
}
};
// 显示时间选择弹窗
const showTimePopup = () => {
isTimePopupShow.value = !isTimePopupShow.value;
};
// 隐藏时间选择弹窗
const hideTimePopup = () => {
isTimePopupShow.value = false;
};
// 选择月份
const selectMonth = (month) => {
selectedMonth.value = month.value;
console.log('选择月份:', month.label);
// 选择月份后重新加载数据
getMeetingList(true);
hideTimePopup();
};
// 显示地区选择弹窗
const showLocationPopup = () => {
isLocationPopupShow.value = !isLocationPopupShow.value;
};
// 隐藏地区选择弹窗
const hideLocationPopup = () => {
isLocationPopupShow.value = false;
};
// 选择省份
const selectProvince = (province) => {
selectedProvince.value = province.code;
console.log('选择省份:', province.name);
// 选择省份后重新加载数据
getMeetingList(true);
hideLocationPopup();
};
// 播放视频
const playVideo = (item) => {
console.log('播放视频:', item.title);
// 这里可以实现视频播放逻辑
};
// 图片加载失败处理
const onImageError = (e) => {
const itemId = e.currentTarget.dataset.itemId;
console.log('图片加载失败项目ID:', itemId);
// 找到对应的项目并设置默认图片
const itemIndex = meetingList.value.findIndex(item => item.id === itemId);
if (itemIndex !== -1) {
// 如果liveimg加载失败尝试使用poster
if (meetingList.value[itemIndex].liveimg && meetingList.value[itemIndex].liveimg !== meetingList.value[itemIndex].poster) {
console.log('liveimg加载失败切换到poster');
meetingList.value[itemIndex].liveimg = meetingList.value[itemIndex].poster;
} else {
// 如果poster也失败使用默认图片
console.log('设置默认图片');
meetingList.value[itemIndex].poster = '/static/meeting-poster-1.jpg';
}
}
};
// 图片加载成功处理
const onImageLoad = (e) => {
const itemId = e.currentTarget.dataset.itemId;
console.log('图片加载成功项目ID:', itemId, '图片地址:', e.currentTarget.src);
};
// 下拉刷新
const onRefresh = () => {
isRefreshing.value = true;
currentPage.value = 1;
hasMoreData.value = true;
// 调用获取会议列表函数
getMeetingList(true).finally(() => {
// 延迟关闭刷新状态,给用户更好的体验
setTimeout(() => {
isRefreshing.value = false;
uni.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500
});
}, 500);
});
};
// 上拉加载更多
const onLoadMore = () => {
console.log('上拉加载更多');
if (isLoadingMore.value || !hasMoreData.value) return;
isLoadingMore.value = true;
currentPage.value++;
// 调用API获取更多数据
getMeetingList(false).finally(() => {
isLoadingMore.value = false;
});
};
</script>
<style lang="scss" scoped>
// 颜色变量
$primary-color: #D32F2F;
$secondary-color: #8B2316;
$background-color: #f5f5f5;
$white: #ffffff;
$gray-light: #f0f0f0;
$gray: #666;
$gray-dark: #333;
$gray-text: #999;
$border-color: #e0e0e0;
$green-accent: #00D4AA;
$shadow: 0 2px 8px rgba(0,0,0,0.1);
.page {
background-color: $background-color;
min-height: 100vh;
padding-bottom: 120rpx;
}
// 可滚动内容区域
.scroll-content {
position: fixed;
top: 228rpx; // 导航栏140rpx + 筛选栏88rpx
left: 0;
right: 0;
bottom: 120rpx; // 底部导航栏高度
width: 100%;
}
// 筛选标签栏
.filter-bar {
position:fixed ;
top:140rpx;
width:100%;
z-index:2;
background-color: $white;
height: 88rpx;
display: flex;
align-items: center;
padding: 0 30rpx;
border-bottom: 2rpx solid $gray-light;
.filter-item {
flex:1;
display: flex;
align-items: center;
padding: 0 20rpx;
font-size: 28rpx;
color: $gray;
text{
margin-right: 10rpx;
}
&.active {
color: $gray-dark;
}
}
.filter-divider {
width: 2rpx;
height: 32rpx;
background-color: $border-color;
margin: 0 10rpx;
}
}
// 时间选择弹窗
.time-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.time-popup-content {
margin-top: 231rpx;
position: relative;
background-color: $white;
width: 100%;
height: calc(100vh - 348rpx);
.time-list {
padding: 0;
height: 100%;
overflow-y: auto;
.time-item {
padding: 25rpx 60rpx;
font-size: 32rpx;
color: #666;
border-bottom: 2rpx solid #f0f0f0;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
&:hover {
background-color: #f8f8f8;
}
&.active {
background-color: #f8f8f8;
color: $primary-color;
text {
font-weight: 500;
}
}
&:last-child {
border-bottom: none;
}
text {
display: block;
width: 100%;
}
}
}
}
}
// 地区选择弹窗
.location-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.popup-content {
margin-top: 231rpx;
position: relative;
background-color: $white;
width: 100%;
height: calc(100vh - 433rpx);
padding:40rpx;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.popup-title {
font-size: 18px;
font-weight: 500;
color: $gray-dark;
}
.close-btn {
width: 30px;
height: 30px;
background-color: $gray-light;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: $gray;
cursor: pointer;
}
}
.location-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 30rpx;
height: 100%;
overflow-y: auto;
.location-item {
padding: 16rpx;
background-color: $background-color;
border-radius: 16rpx;
text-align: center;
font-size: 28rpx;
color: #999;
border: 2rpx solid #999;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background-color: lighten($primary-color, 45%);
}
&.active {
background-color: lighten($primary-color, 40%);
border-color: $primary-color;
color: $primary-color;
}
text {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
}
}
}
}
// 时间标题
.time-header {
text-align: center;
padding: 30rpx 0;
font-size: 32rpx;
color: $gray;
background-color: $background-color;
}
// 会议列表
.meeting-list {
padding: 0 30rpx;
.meeting-item {
display: flex;
margin-bottom: 30rpx;
background-color: $white;
border-radius: 16rpx;
overflow: hidden;
box-shadow: $shadow;
// 日期标识
.date-tag {
width: 50rpx;
display: flex;
align-items: center;
justify-content: center;
position:relative;
.date-text {
background: $white;
position:absolute;
top:50%;
color:red;
transform:translateY(-50%);
font-size: 32rpx;
width:40rpx;
display: flex;
justify-content: center;
align-items: center;
left: 35rpx;
height: 40rpx;
border-radius:50%;
}
}
// 会议内容
.meeting-content {
flex: 1;
padding: 30rpx;
.meeting-title {
font-size: 32rpx;
font-weight: 500;
color: $gray-dark;
margin-bottom: 20rpx;
line-height: 1.4;
}
.meeting-poster {
position: relative;
height: 240rpx;
border-radius: 12rpx;
overflow: hidden;
margin-bottom: 20rpx;
.poster-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width:210rpx;
height:210rpx;
opacity: 0.3;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.preview-tag {
position: absolute;
top: 16rpx;
right: 16rpx;
background-color: #FF4444;
color: $white;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
.live-tag {
position: absolute;
top: 16rpx;
right: 16rpx;
background-color: #00BCD4;
color: $white;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
.replay-tag {
position: absolute;
top: 16rpx;
right: 16rpx;
background-color: #9C27B0;
color: $white;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
}
.meeting-info {
display: flex;
justify-content: space-between;
.info-item {
display: flex;
align-items: center;
.timebox{
margin-top: -19rpx;
}
.info-text {
font-size: 24rpx;
color: $gray-text;
margin-left: 8rpx;
}
}
}
}
}
}
// 空状态
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 30rpx;
.empty-icon {
margin-bottom: 30rpx;
}
.empty-text {
font-size: 32rpx;
color: $gray;
margin-bottom: 16rpx;
}
.empty-subtext {
font-size: 26rpx;
color: $gray-text;
text-align: center;
}
}
// 加载更多提示
.load-more {
padding: 30rpx;
.load-more-content {
display: flex;
align-items: center;
justify-content: center;
.load-more-text {
font-size: 28rpx;
color: $gray-text;
margin-left: 10rpx;
}
}
}
// 过往会议提示
.history-tip {
position: fixed;
bottom:200rpx;
z-index:9;
right:0;
border-radius: 50rpx 0 0 50rpx;
background:#00cbc0;
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx 10rpx;
.tip-icon {
padding:10rpx;
background-color: #fff;
margin-right: 10rpx;
border-radius: 50%;
}
.tip-text {
width: 60rpx;
font-size: 26rpx;
color:#fff;
}
}
// 底部导航栏
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background-color: $white;
display: flex;
border-top: 1px solid $gray-light;
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 5px 0;
.nav-text {
font-size: 10px;
color: $gray-text;
margin-top: 2px;
&.active {
color: $primary-color;
}
}
}
}
</style>