图书馆小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

526 lines
13 KiB

<template>
<view class="activity-list">
<!-- tab栏 -->
<view class="tab-box">
<view
class="tab-item"
v-for="(tab,idx) in tabList"
:key="idx"
:class="{active:activeTab === idx}"
@click="switchTab(idx)"
>
{{tab.label}}
</view>
</view>
<scroll-view
scroll-y
@scrolltolower="onScrollLower"
lower-threshold="150"
class="scroll-view"
>
<view v-if="loading" class="loading-box">
<text class="loading-text">加载中...</text>
</view>
<!-- 左滑组件 uni-swipe-action -->
<!-- :disabled="!(item.status === '未开始' && !item.isCancel)" -->
<uni-swipe-action>
<uni-swipe-action-item
v-for="item in activityList"
:key="item.signId"
>
<!-- 左侧主体内容 -->
<view class="activity-item" @click="toActivityDetail(item)">
<view class="activity-info">
<view class="title-row">
<text class="title">{{ item.title }}</text>
<text class="status-tag" :class="getStatusClass(item.status)">
{{ item.status }}
</text>
</view>
<view class="item-info">
<text class="label">开始时间</text>
<text class="value">{{ formatShowDateTime(item.start_time) }}</text>
</view>
<view class="item-info">
<text class="label">结束时间</text>
<text class="value">{{ formatShowDateTime(item.end_time) }}</text>
</view>
<view class="item-info">
<text class="label">预约人</text>
<text class="value">{{ item.signName }}</text>
</view>
<view class="item-info">
<text class="label">参加人数</text>
<text class="value">{{ item.signNum }}</text>
</view>
<!-- 未开始 && 未取消预约 才显示取消预约按钮 -->
<view class="btn-box" v-if="item.status === '未开始' && !item.isCancel">
<button class="activity-btn" type="primary" @click.stop="cancelAppoint(item)"> 取消预约 </button>
</view>
</view>
</view>
<!-- 右滑出来的按钮插槽 -->
<!-- v-if="!(item.status === '未开始' && !item.isCancel)" -->
<!-- v-if="item.status !== '进行中'" -->
<template #right >
<view class="swipe-right-wrap">
<view class="swipe-btn swipe-btn-delete" @click.stop="deleteSignActivity(item)">
<uni-icons type="trash" size="24"></uni-icons>
</view>
</view>
</template>
</uni-swipe-action-item>
</uni-swipe-action>
<view v-if="!loading && noMore && activityList.length > 0" class="nomore-box">
<text class="nomore-text">没有更多数据了</text>
</view>
<view class="empty-box" v-if="!loading && activityList.length === 0">
<uni-icons type="empty" size="80" color="#ccc"></uni-icons>
<text class="empty-text">暂无相关活动~</text>
</view>
</scroll-view>
</view>
</template>
<script>
import config from '@/utils/config';
import { FetchSessionKey } from '@/api/user.js';
import { FetchMySignActivityInfo, FetchCancelSignActivity,FetchDeleteSignActivity } from '@/api/activity.js'
export default {
data() {
return {
tabList:[
{label:'全部',value:null},
{label:'预约成功',value:false},
{label:'已取消',value:true}
],
activeTab:0,
activityList: [],
baseUrl: config.baseUrl,
refreshing: false,
loading: false,
noMore: false,
page: 0,
pageSize: 20,
sessionKey: '',
openid: ''
};
},
async onLoad() {
await this.getSessionKey();
this.getActivityList(true);
uni.$on('refreshMySignList',()=>{
this.getActivityList(true)
})
},
onUnload(){
uni.$off('refreshMySignList')
},
onPullDownRefresh() {
if(this.loading){
uni.stopPullDownRefresh()
return
}
this.getActivityList(true).finally(()=>{
uni.stopPullDownRefresh()
})
},
methods: {
switchTab(index){
this.activeTab = index
this.page = 0
this.noMore = false
this.activityList = []
this.getActivityList(true)
},
// 新增:时间戳 → 今天/昨天/明天 HH:mm
formatShowDateTime(timestamp) {
if (!timestamp) return '';
const d = new Date(timestamp);
const targetY = d.getFullYear();
const targetM = d.getMonth();
const targetD = d.getDate();
const hh = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
const timeStr = `${hh}:${mm}`;
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const targetDayTs = new Date(targetY, targetM, targetD).getTime();
const oneDay = 24 * 60 * 60 * 1000;
const diff = Math.round((targetDayTs - today.getTime()) / oneDay);
let dateText = '';
if (diff === 0) {
dateText = '今天';
} else if (diff === -1) {
dateText = '昨天';
} else if (diff === 1) {
dateText = '明天';
} else {
const m = String(targetM + 1).padStart(2, '0');
const day = String(targetD).padStart(2, '0');
dateText = `${targetY}-${m}-${day}`;
}
return `${dateText} ${timeStr}`;
},
formatTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const min = String(date.getMinutes()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}`;
},
getStatusClass(status) {
if (status === '未开始') return 'status-0';
if (status === '进行中') return 'status-1';
if (status === '已结束') return 'status-2';
return '';
},
async getSessionKey() {
return new Promise((resolve) => {
uni.login({
success: async (loginRes) => {
console.log('loginRes', loginRes)
if (loginRes.code) {
const res = await FetchSessionKey({
libcode: config.LIB_CODE,
code: loginRes.code
});
if (res.code === 200) {
this.sessionKey = res.data.session_key;
this.openid = res.data.openid;
}
}
resolve()
},
fail: () => {
resolve()
}
});
})
},
async getActivityList(isRefresh = false) {
if (!this.openid) {
uni.showToast({ title: '获取用户信息失败', icon: 'none' })
return
}
if (this.loading) return;
if (isRefresh) {
this.page = 0;
this.noMore = false;
this.refreshing = true;
} else {
if (this.noMore) return;
}
this.loading = true;
const currentTabItem = this.tabList[this.activeTab]
const params = {
openId: this.openid,
page: this.page,
pageSize: this.pageSize
}
if(currentTabItem.value !== null){
params.isCancel = currentTabItem.value
}
try {
const res = await FetchMySignActivityInfo(params);
console.log('FetchMySignActivityInfo返回结果', res)
if (res.code === 200) {
const pageData = res.data;
const list = pageData.content || [];
// ==========新增:统一处理图片地址,和第一个接口逻辑保持一致==========
list.forEach(item => {
if(item.cover_image){
const imgId = encodeURIComponent(item.cover_image);
item.cover_image = `${this.baseUrl}/files/${imgId}`;
}else{
item.cover_image = ''
}
})
const currentPage = pageData.number ?? 0;
const totalPage = pageData.totalPages ?? 0;
this.noMore = currentPage >= totalPage - 1;
if (isRefresh) {
this.activityList = list;
} else {
this.activityList = [...this.activityList, ...list];
}
} else {
if (isRefresh) this.activityList = [];
}
} catch (err) {
console.error('请求活动接口异常', err)
uni.showToast({ title: '网络请求失败', icon: 'none' })
} finally {
this.refreshing = false;
this.loading = false;
}
},
onRefresh() {
if(this.loading) return
this.getActivityList(true);
},
onScrollLower() {
if(this.loading || this.noMore) return;
this.page += 1;
this.getActivityList(false);
},
cancelAppoint(item) {
uni.showModal({
title: '提示',
content: `确定要取消活动【${item.title}】的预约吗?`,
success: async (res) => {
if (res.confirm) {
try {
const apiRes = await FetchCancelSignActivity({
id: item.signId
})
if(apiRes.code === 200){
uni.showToast({ title: '取消成功', icon: 'success' })
this.getActivityList(true)
}else{
uni.showToast({ title: apiRes.message || '取消失败', icon: 'none' })
}
}catch(err){
console.error('取消预约失败', err)
uni.showToast({ title: '网络异常,取消失败', icon: 'none' })
}
}
}
})
},
deleteSignActivity(item){
uni.showModal({
title: '提示',
content: `确定要删除活动【${item.title}】的预约吗?`,
success: async (res) => {
if (res.confirm) {
try {
const apiRes = await FetchDeleteSignActivity({
id: item.signId
})
if(apiRes.code === 200){
uni.showToast({ title: '删除成功', icon: 'success' })
this.getActivityList(true)
}else{
uni.showToast({ title: apiRes.message || '删除失败', icon: 'none' })
}
}catch(err){
console.error('删除预约失败', err)
uni.showToast({ title: '网络异常,删除失败', icon: 'none' })
}
}
}
})
},
toActivityDetail(item) {
console.log('toActivityDetail',item);
uni.navigateTo({
url: "/subpkg/pages/activity-detail/activity-detail?item=" + encodeURIComponent(JSON.stringify(item))
});
}
},
};
</script>
<style lang="scss" scoped>
.activity-list {
padding: 0;
height: 100vh;
box-sizing: border-box;
background-color: #f5f5f5;
display:flex;
flex-direction:column;
}
.tab-box {
display: flex;
background-color: #fff;
margin-bottom: 10px;
position: sticky;
top: 0;
z-index: 99;
}
.tab-item{
flex:1;
text-align:center;
padding:12px 0;
font-size:14px;
color:#666;
position:relative;
}
.tab-item.active{
color:#01a4fe;
font-weight:bold;
}
.tab-item.active::after{
content:"";
position:absolute;
bottom:0;
left:50%;
transform:translateX(-50%);
width:28px;
height:2px;
background:#01a4fe;
}
.scroll-view {
flex:1;
height: 0;
padding: 0 12px;
box-sizing: border-box;
}
.loading-box{
padding:20px 0;
text-align:center;
}
.loading-text{
font-size:14px;
color:#999;
}
.nomore-box{
padding:15px 0;
text-align:center;
}
.nomore-text{
font-size:13px;
color:#999;
}
.empty-box {
height: calc(100vh - 180px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-text {
margin-top: 16px;
font-size: 14px;
color: #999;
}
.activity-item {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
.activity-info {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 16px;
.title-row{
width:100%;
display:flex;
justify-content: space-between;
align-items:flex-start;
margin-bottom:12px;
}
.title {
flex: 1;
font-size: 16px;
font-weight: bold;
color: #222;
}
.item-info {
display: flex;
justify-content: space-between;
padding: 4px 0;
font-size: 14px;
width:100%;
.label {
color: #666;
width: 72px;
}
.value {
color: #333;
flex: 1;
text-align: right;
}
}
}
}
.btn-box {
width: 100%;
display: flex;
justify-content: flex-end;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #e5e5e5;
}
.status-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-tag.status-0 {
color: #01a4fe;
background: #e6f7ff;
}
.status-tag.status-1 {
color: #00b42a;
background: #e6ffed;
}
.status-tag.status-2 {
color: #999;
background: #f5f5f5;
}
/* 👉 插槽外层容器:占满高度,垂直水平居中 */
.swipe-right-wrap {
width: 80px;
height: calc(100% - 20px);
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /*水平居中*/
// background-color: #f5222d;
}
/* 圆形按钮,不再写height:100%,固定大小做圆圈 */
.swipe-btn {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff !important;
}
.swipe-btn-delete {
background-color: #f5222d;
}
/* 深度修改uni-icons图标颜色,scoped必须加 ::v‑deep */
::v-deep .swipe-btn .uni-icons {
color: #ffffff !important;
}
</style>