Browse Source

活动板块

master
xuhuajiao 1 month ago
parent
commit
5414d7890d
  1. 44
      api/activity.js
  2. 14
      pages.json
  3. 2
      pages/home/home.vue
  4. 707
      pages/home/home2.vue
  5. 13
      pages/user/user.vue
  6. BIN
      static/images/menu-hd.png
  7. 513
      subpkg/pages/activity-detail/activity-detail.vue
  8. 398
      subpkg/pages/activity-list/activity-list.vue
  9. 484
      subpkg/pages/activity-record/activity-record.vue
  10. 30
      subpkg/pages/booking-record/booking-record.vue
  11. 241
      subpkg/pages/seat-booking/seat-booking copy.vue
  12. 451
      subpkg/pages/seat-booking/seat-booking.vue
  13. 4
      utils/config.js
  14. 35
      utils/request.js

44
api/activity.js

@ -0,0 +1,44 @@
import request from '../utils/request';
// 活动列表
export function FetchActivityInfo(data) {
return request({
url: '/api/weixin/initActivityInfo',
data
})
}
// 报名活动
export function FetchSignActivity(data) {
return request({
url: '/api/weixin/signActivity',
method: 'POST',
data
})
}
// 我的报名活动列表
export function FetchMySignActivityInfo(data) {
return request({
url: '/api/weixin/mySignActivityInfo',
data
})
}
// 取消报名
export function FetchCancelSignActivity(data) {
return request({
url: '/api/weixin/cancelSignActivity',
method: 'POST',
data
})
}
// 删除自己报名的活动
export function FetchDeleteSignActivity(data) {
return request({
url: '/api/weixin/deleteSignActivity',
method: 'POST',
data
})
}

14
pages.json

@ -96,7 +96,8 @@
{
"path": "pages/activity-list/activity-list",
"style": {
"navigationBarTitleText": "活动列表"
"navigationBarTitleText": "活动列表",
"enablePullDownRefresh": true
}
},
{
@ -154,7 +155,7 @@
{
"path": "pages/seat-booking/seat-booking",
"style": {
"navigationBarTitleText": "座位预约"
"navigationBarTitleText": "预约登记"
}
},
{
@ -166,7 +167,14 @@
{
"path": "pages/booking-record/booking-record",
"style": {
"navigationBarTitleText": "预约座位记录"
"navigationBarTitleText": "预约入馆记录"
}
},
{
"path": "pages/activity-record/activity-record",
"style": {
"navigationBarTitleText": "活动预约记录",
"enablePullDownRefresh": true
}
},
{

2
pages/home/home.vue

@ -329,7 +329,7 @@ export default {
if (res.code === 200 && res.data.length > 0) {
this.isBindLibraryCard = true;
// 👉
//
const defaultCard = res.data.find(item => item.bindDefault === true);
const otherCards = res.data.filter(item => !item.bindDefault);
//

707
pages/home/home2.vue

@ -0,0 +1,707 @@
<template>
<view style="padding-bottom: 20px;">
<view class="top-bar">
<uni-icons class="erweima-top" custom-prefix="iconfont" type="icon-erweima" size="22" color="#fff" @click="showTopImgPopup"></uni-icons>
<image class="top-bar-bg" src="@/static/images/mingqi-beij@2x.png" mode="aspectFill"></image>
<view class="library-info">
<image class="avatar" src="@/static/images/logo.jpg" mode="aspectFill"></image>
<view class="library-name">葛店经济技术开发区图书馆</view>
</view>
<uni-icons class="search-icon" type="search" size="24" @click="onToSearch"></uni-icons>
</view>
<view class="status-cards">
<view class="status-card" @click="toBorrowPage(0)">
<view class="status-icon icon-color1" >
<uni-icons custom-prefix="iconfont" type="icon-daiban02" size="22"></uni-icons>
</view>
<view class="status-label">在借中</view>
</view>
<view class="status-card" @click="toBorrowPage(1)">
<view class="status-icon icon-color4" >
<uni-icons custom-prefix="iconfont" type="icon-a-zhidu4" size="22"></uni-icons>
</view>
<view class="status-label">历史借阅</view>
</view>
</view>
<swiper class="banner-swiper" interval="3000" circular :vertical="false" :indicator-dots="true" :autoplay="true">
<swiper-item>
<image data-post-id="3" src="https://mmbiz.qpic.cn/mmbiz_jpg/bVyA57fUOMicbQqItawJwQlR2oCoYql1icEbuH45ibSAzZsk2T3aDbUOIdMyOOc2CzkYouok0El5u5U3LONACaeNw/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=1"></image>
</swiper-item>
<swiper-item>
<image data-post-id="4" src="https://mmbiz.qpic.cn/sz_mmbiz_gif/tuyGibvSXrsqqiaNpAFJ7lbfGL5vibHtUdV5SUk1RWnlq3QqDe9liaWhdS91L0orEsFxZMiaHOFxDOzINmkiby24fYmmWstiayUgywfVia1pwMcNpBA/640?wx_fmt=gif&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=3"></image>
</swiper-item>
</swiper>
<swiper class="menu-swiper" interval="6000" :vertical="false" :autoplay="false">
<swiper-item>
<view class="menu-grid">
<view class="menu-item" @click="onToSearch">
<view class="menu-icon">
<uni-icons custom-prefix="iconfont" type="icon-ziyuan" size="22"></uni-icons>
</view>
<view class="menu-label">书目检索</view>
</view>
<view class="menu-item" @click="onToLendCar">
<view class="menu-icon">
<uni-icons custom-prefix="iconfont" type="icon-z_renew_normal" size="24"></uni-icons>
</view>
<view class="menu-label">图书续借</view>
</view>
<view class="menu-item" @click="toLibraryCard">
<view class="menu-icon">
<uni-icons custom-prefix="iconfont" type="icon-duzhezheng" size="26"></uni-icons>
</view>
<view class="menu-label">电子证</view>
</view>
<view class="menu-item" @click="toRanking">
<view class="menu-icon">
<uni-icons custom-prefix="iconfont" type="icon-paihangbang" size="22"></uni-icons>
</view>
<view class="menu-label">借阅排行</view>
</view>
<view class="menu-item" @click="toActivityList">
<view class="menu-icon">
<uni-icons custom-prefix="iconfont" type="icon-remenhuodong" size="26"></uni-icons>
</view>
<view class="menu-label">活动报名</view>
</view>
<view class="menu-item" @click="toFeedback">
<view class="menu-icon">
<uni-icons custom-prefix="iconfont" type="icon-ruguanyuyue" size="26"></uni-icons>
</view>
<view class="menu-label">预约入馆</view>
</view>
<view class="menu-item" @click="onToUser">
<view class="menu-icon">
<uni-icons custom-prefix="iconfont" type="icon-yonghuxinxi-gerenxinxi" size="20"></uni-icons>
</view>
<view class="menu-label">个人中心</view>
</view>
<view class="menu-item" @click="goRegister">
<view class="menu-icon">
<uni-icons custom-prefix="iconfont" type="icon-daohang-banzhengxuzhi" size="25"></uni-icons>
</view>
<view class="menu-label">在线办证</view>
</view>
</view>
</swiper-item>
</swiper>
<view v-if="isBindLibraryCard" class="library-card-has">
<view class="section-title">我的借阅证</view>
<swiper
class="card-swiper"
:circular="false"
>
<swiper-item v-for="item in readerCardList" :key="item.id">
<view class="card-item" @click.stop="showQrcode(item.bindValue)">
<image class="card-left-img" src="@/static/images/card-img2.png" mode="widthFix" />
<view class="card-right-info">
<text class="info-title">读者证号</text>
<text class="info-num">{{ formatCardNo(item.bindValue) }}</text>
</view>
<view class="default-tag" v-if="item.bindDefault">默认证</view>
<uni-icons class="erweima-style" custom-prefix="iconfont" type="icon-erweima" size="28"></uni-icons>
</view>
</swiper-item>
</swiper>
</view>
<view class="library-card-section" @click="toCheckLogin" v-else>
<view class="section-title">借阅证</view>
<image class="card-icon" src="@/static/images/card-img2.png"></image>
<view class="card-tip">您还没有绑定读者证哦</view>
<button class="bind-btn">立即绑定</button>
</view>
<root-portal>
<uni-popup
ref="popup"
type="center"
:mask-click="true"
@maskClick="closeAllPopup"
>
<view class="qrcode-popup">
<view class="qrcode-title">读者证二维码</view>
<w-qrcode
v-if="qrcodeText"
:value="qrcodeText"
:size="160"
foreground="#333"
background="#ffffff"
/>
<view class="qrcode-num" v-if="qrcodeText">{{ qrcodeText }}</view>
</view>
</uni-popup>
<uni-popup
ref="erweimapopup"
type="center"
:mask-click="true"
@maskClick="closeErweiPopup"
:custom-style="popupFixedStyle"
>
<view class="qrcode-popup" style="width: auto !important;">
<image
v-if="wecharQRCode"
:src="wecharQRCode"
style="width:200px;height:200px;"
mode="aspectFit"
:show-menu-by-longpress="true"
@longpress="onQRCodeLongPress"
></image>
</view>
</uni-popup>
</root-portal>
<view class="recommendation-section">
<view class="section-title">
<text class="left-txt">图书推荐</text>
<view class="more-link" @click="getMoreBoook" v-if="!(recommendedBooks.length === 0 && !isLoading)">
<text style="margin-right: 5px;">查看更多</text>
<uni-icons custom-prefix="iconfont" type="icon-gengduo" size="12"></uni-icons>
</view>
</view>
<view class="recommendation-list">
<uni-load-more status="loading" v-if="isLoading"></uni-load-more>
<view v-else class="book-item" v-for="(item,index) in recommendedBooks.slice(0,3)" @click="goToBookDetail(item)" :key="index" >
<image
class="book-cover"
:src="item.coverUrl || '/static/images/default-book.png'"
@error="onImgError"
></image>
<view class="book-title">{{item.name}}</view>
</view>
</view>
<view class="empty" v-if="recommendedBooks.length === 0 && !isLoading">
<uni-icons custom-prefix="iconfont" type="icon-kongshuju" size="80"></uni-icons>
<text style="margin-top: 20px;">暂无推荐书籍~</text>
</view>
</view>
</view>
</template>
<script>
import { FetchInitScreenSetting, FetchOpenId, FetchFindAllReaderBindByOpenId} from '@/api/user';
import { FetchInitScreenBookRecommend } from '@/api/book';
import config from '@/utils/config';
import { STORAGE_KEYS } from '@/utils/storage';
export default {
data() {
return {
isLoading: true,
recommendedBooks: [],
userInfo: {},
isBindLibraryCard: false,
readerCardList: [],
baseUrl: config.baseUrl,
qrcodeText: '',
popupFixedStyle: 'position:fixed;top:0;left:0;right:0;bottom:0;',
wecharQRCode: null
};
},
onLoad() {
this.getRecommendBooks();
this.getScreenSetting()
},
onShow() {
this.initUserAndCheckBind();
},
methods:{
async getScreenSetting() {
const res = await FetchInitScreenSetting({ libcode: config.LIB_CODE });
if (res.data.wechar_qr_code && res.data.wechar_qr_code.context) {
// 使urlbase64
this.wecharQRCode = this.baseUrl + '/api/fileRelevant/getImg?imgType=1&imgId=' + res.data.wechar_qr_code.context;
} else {
this.wecharQRCode = null;
}
},
showTopImgPopup() {
if (!this.wecharQRCode) {
uni.showToast({ title: '暂无图片', icon: 'none' })
return
}
this.$refs.erweimapopup.open()
},
formatCardNo(cardNo) {
if (!cardNo) return '';
const str = String(cardNo);
const len = str.length;
if (len > 8) {
const front = str.substring(0, 2);
const end = str.substring(len - 2);
const star = '*'.repeat(len - 4);
return front + star + end;
} else {
return '***' + str.substring(len - 2);
}
},
onImgError(e) {
//
e.target.src = "/static/images/default-book.png";
},
async initUserAndCheckBind() {
try {
let openId = uni.getStorageSync('wx_login_code');
if (!openId) {
const loginRes = await new Promise((resolve, reject) => {
uni.login({
success: resolve,
fail: reject
});
});
if (!loginRes.code) {
uni.showToast({ title: '授权失败', icon: 'none' });
return;
}
const openRes = await FetchOpenId({
libcode: config.LIB_CODE,
code: loginRes.code
});
if (openRes.code !== 200 || !openRes.data) {
return;
}
openId = openRes.data;
uni.setStorageSync('wx_login_code', openId);
}
const res = await FetchFindAllReaderBindByOpenId({
libcode: config.LIB_CODE,
openId: openId
});
if (res.code === 200 && res.data.length > 0) {
this.isBindLibraryCard = true;
const defaultCard = res.data.find(item => item.bindDefault === true);
const otherCards = res.data.filter(item => !item.bindDefault);
this.readerCardList = defaultCard ? [defaultCard, ...otherCards] : res.data;
uni.setStorageSync(STORAGE_KEYS.READER_CARD_LIST, this.readerCardList);
uni.setStorageSync(STORAGE_KEYS.CURRENT_READER_CARD, defaultCard);
} else {
this.isBindLibraryCard = false;
this.readerCardList = [];
uni.setStorageSync(STORAGE_KEYS.READER_CARD_LIST, []);
uni.setStorageSync(STORAGE_KEYS.CURRENT_READER_CARD, null);
}
} catch (err) {
console.error('初始化失败:', err);
const list = uni.getStorageSync(STORAGE_KEYS.READER_CARD_LIST) || [];
const defaultCard = list.find(item => item.bindDefault === true);
const otherCards = list.filter(item => !item.bindDefault);
this.readerCardList = defaultCard ? [defaultCard, ...otherCards] : list;
this.isBindLibraryCard = list.length > 0;
}
},
async getRecommendBooks() {
try {
const res = await FetchInitScreenBookRecommend({ libcode: config.LIB_CODE });
let books = res.data || [];
const limit = 3;
const displayBooks = books.slice(0, limit);
// url
for (let i = 0; i < displayBooks.length; i++) {
let item = displayBooks[i];
if (item.imgPath) {
item.coverUrl = this.baseUrl + '/api/fileRelevant/getImg?imgType=2&imgId=' + item.imgPath;
} else {
item.coverUrl = '';
}
}
this.recommendedBooks = displayBooks;
this.isLoading = false;
} catch (err) {
console.error('获取图书推荐失败:', err);
this.isLoading = false;
}
},
goToBookDetail(item) {
uni.navigateTo({
url: "/subpkg/pages/book-detail/book-detail?bookData=" + encodeURIComponent(JSON.stringify(item)) + "&fromRecommend=true"
})
},
getMoreBoook(){
uni.navigateTo({
url: "/subpkg/pages/book-list/book-list"
})
},
onToSearch() {
uni.switchTab({
url: "/pages/search/search"
});
},
onToLendCar() {
this.checkBindAndNavigate("/pages/lendCar/lendCar", "请您绑定读者证", true);
},
toCheckLogin() {
uni.navigateTo({
url: "/pages/login/login"
});
},
toRanking(){
uni.navigateTo({
url: "/subpkg/pages/ranking/ranking"
})
},
toActivityList(){
this.checkBindAndNavigate("/subpkg/pages/activity-list/activity-list", "请您绑定读者证");
},
toBorrowPage(index) {
if (this.isBindLibraryCard) {
uni.setStorageSync('switch_tab_index', index);
uni.navigateTo({
url: '/subpkg/pages/myLending/myLending'
});
} else {
uni.showModal({
title: '提示',
content: '请您绑定读者证',
confirmText: '去绑定',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: "/pages/login/login" });
}
}
});
}
},
toBorrowCar() {
uni.switchTab({
url: '/pages/lendCar/lendCar'
});
},
toFeedback() {
uni.navigateTo({
url: '/subpkg/pages/seat-booking/seat-booking'
});
},
toLibraryCard() {
if (this.isBindLibraryCard) {
uni.navigateTo({ url: '/subpkg/pages/reader-card/reader-card' });
}
else {
uni.navigateTo({ url: "/pages/login/login" });
}
},
onToUser() {
this.checkBindAndNavigate("/pages/user/user", "请您绑定读者证", true);
},
checkBindAndNavigate(url, message, isSwitchTab = false) {
if (this.isBindLibraryCard) {
if (isSwitchTab) {
uni.switchTab({ url });
} else {
uni.navigateTo({ url });
}
} else {
uni.showModal({
title: '提示',
content: message,
confirmText: '去绑定',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: "/pages/login/login" });
}
}
});
}
},
showQrcode(bindValue) {
this.qrcodeText = bindValue;
this.$refs.popup.open();
},
closeAllPopup() {
this.qrcodeText = '';
this.$refs.popup.close();
},
closeErweiPopup() {
this.$refs.erweimapopup.close()
},
onQRCodeLongPress() {
},
// 使urlToBase64
goRegister() {
uni.navigateTo({
url: '/pages/register/register'
});
}
}
}
</script>
<style lang="scss" scoped>
.top-bar{
.erweima-top{
position: absolute;
left: 16px;
top: 15px;
z-index: 999;
}
.search-icon{
position: absolute;
right: 16px;
top: 15px;
z-index: 22;
::v-deep .uni-icons{
color: #fff !important;
}
}
}
.status-cards {
display: flex;
background-color: #fff;
margin: 10px;
border-radius: 8px;
padding: 20px 10px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
.status-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.status-icon {
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
margin-bottom: 6px;
border-radius: 50%;
}
.icon-color1{
background-color: #e4f2ff;
}
.icon-color2{
background-color: #ffeff0;
}
.icon-color3{
background-color: #e4fff4;
}
.icon-color4{
background-color: #fef8e2;
}
.status-label {
font-size: 12px;
color: #333;
}
}
swiper.banner-swiper{
width: 100%;
height: 130px;
}
swiper.banner-swiper image{
width: 100%;
height: 130px;
}
swiper.menu-swiper{
width:100%;
margin-top: 10px;
background-color: #fff;
}
.menu-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
.menu-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 0;
.menu-icon{
display: flex;
flex-direction: column;
justify-content: center;
height: 30px;
::v-deep .uni-icons{
color: #01a4fe !important;
}
}
.menu-label {
font-size: 12px;
color: #333;
text-align: center;
}
}
}
.library-card-section {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 20px 0;
.section-title {
font-size: 22px;
font-weight: bold;
background: linear-gradient(140deg, #01a4ff, #2ec8fe);
-webkit-background-clip: text;
color: transparent;
letter-spacing: 0.1em;
margin-bottom: 5px;
}
.card-icon {
width: 80px;
height: 80px;
}
.card-tip {
font-size: 12px;
color: #999;
margin: 5px 0;
}
.bind-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border: 1px solid #06a8ff ;
width: 128px !important;
height: 28px;
border-radius: 28px;
background: linear-gradient(140deg, #01a4ff, #2ec8fe);
font-size: 14px;
color: #fff;
line-height: 28px;
}
}
.recommendation-section {
background-color: #fff;
margin: 10px;
border-radius: 8px;
padding: 0 0 10px 0;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
.section-title{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 40px;
padding: 0 10px;
.left-txt{
color: #343434;
font-size: 15px;
font-weight: 700;
}
.more-link{
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: #999;
font-size: 11px;
}
}
}
.library-card-has {
width: 100%;
padding: 20px 10px;
box-sizing: border-box;
.section-title {
font-size: 22px;
font-weight: bold;
background: linear-gradient(140deg, #01a4ff, #2ec8fe);
-webkit-background-clip: text;
color: transparent;
letter-spacing: 0.1em;
margin-bottom: 10px;
padding-left: 5px;
}
.card-list {
width: 100%;
}
}
.card-swiper {
height: 106px;
.card-item{
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border: 2px solid #C6C6E2;
border-radius: 8px;
background: rgba(241,241,249,0.4);
cursor: pointer;
&.active{
border-color: #01a4fe;
background: rgba(1, 164, 254, 0.1);
}
.card-left-img{
width: 80px;
margin-right: 12px;
display: block;
}
.card-right-info{
flex: 1;
display: flex;
flex-direction: column;
.info-title{
font-size: 16px;
color: #333;
font-weight: bold;
padding-bottom: 12px;
}
.info-num{
font-size: 14px;
color: #999;
}
}
.default-tag {
position: absolute;
right: 12px;
top: 12px;
font-size: 11px;
color: #01a4fe;
border: 1px solid #01a4fe;
border-radius: 4px;
padding: 2px 6px;
}
}
}
.erweima-style{
position: absolute;
right: 12px;
bottom: 22px;
::v-deep .icon-erweima{
color: #999 !important;
}
}
</style>

13
pages/user/user.vue

@ -26,6 +26,10 @@
<image class="menu-icon" src="@/static/images/menu-rg.png" mode="scaleToFill" style="width: 30px; height: 30px; margin-bottom: 8px;" />
<text class="menu-txt">入馆</text>
</view>
<view class="menu-item" @click="toActivityRecord">
<image class="menu-icon" src="@/static/images/menu-hd.png" mode="scaleToFill" style="width: 30px; height: 30px; " />
<text class="menu-txt">活动</text>
</view>
</view>
</view>
@ -82,6 +86,12 @@ export default {
});
},
toActivityRecord() {
uni.navigateTo({
url: '/subpkg/pages/activity-record/activity-record'
});
},
async loadUserInfo() {
if (this.loading) return;
this.loading = true;
@ -269,11 +279,12 @@ export default {
border-radius: 6px;
box-shadow: 0px 2px 15px 0px rgba(0, 0, 0, .1);
.menu-item{
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 30px;
// padding: 0 30px;
height: 100px;
.menu-icon{
width: 32px;

BIN
static/images/menu-hd.png

After

Width: 128  |  Height: 128  |  Size: 16 KiB

513
subpkg/pages/activity-detail/activity-detail.vue

@ -1,25 +1,25 @@
<template>
<view class="activity-detail">
<image class="activity-img" :src="detail.imgUrl" mode="aspectFill"></image>
<image class="activity-img" :src="detail.coverImage" mode="aspectFill"></image>
<view class="activity-base">
<text class="title">{{ detail.title }}</text>
<view class="time">
<uni-icons class="detail-icon" custom-prefix="iconfont" type="icon-shijian" size="15"></uni-icons>
<text>{{ detail.time }}</text>
<text>{{ detail.startTime || formatTime(detail.start_time) }} - {{ detail.endTime || formatTime(detail.end_time) }}</text>
</view>
<view class="location">
<uni-icons class="detail-icon" type="location" size="20"></uni-icons>
<text>{{ detail.location }}</text>
<text>{{ detail.activityLocation || detail.activity_location }}</text>
</view>
</view>
<view class="rich-content">
<text class="content-title">活动介绍/回顾</text>
<div v-if="detail.localImg" style="text-align:center; margin:12px 0;">
<div v-if="detail.localImg" style="text-align:center; margin:12px 0;">
<image :src="require(`@/static/${detail.localImg}`)" mode="widthFix" style="width:90%; border-radius:8px;"></image>
</div>
<rich-text :nodes="contentNodes"></rich-text>
<rich-text class="rich-text" :nodes="contentNodes"></rich-text>
</view>
<view class="detail-bottom">
@ -30,63 +30,351 @@
</button>
</view>
<button
class="join-btn"
:class="detail.status === 0 ? 'disabled-btn' : ''"
@click="handleJoin"
>
{{ detail.status === 1 ? '我要参加' : '活动结束' }}
</button>
class="join-btn"
:class="getJoinBtnClass()"
@click="handleJoin"
>
{{ getJoinBtnText() }}
</button>
</view>
<view class="modal-mask" v-if="showJoinModal" @click="closeJoinModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">报名信息</text>
<uni-icons class="close-icon" type="close" size="24" @click="closeJoinModal"></uni-icons>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">姓名</text>
<input class="form-input" v-model="joinForm.name" placeholder="请输入您的姓名" />
</view>
<view class="form-item">
<text class="form-label">联系方式</text>
<view style="display:flex; justify-content: space-between; align-items:center;">
<input class="form-input" v-model="joinForm.phone" placeholder="请输入手机号码" type="number" maxlength="11" />
<button class="get-phone-btn" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
一键获取
</button>
</view>
</view>
<view class="form-item">
<text class="form-label">参加人数</text>
<view class="select-wrapper">
<picker
mode="selector"
:range="rangeText"
:value="joinForm.personCount - 1"
@change="onPickerChange"
class="picker-select"
>
<view class="picker-display">
{{ joinForm.personCountText || '请选择参加人数' }}
</view>
</picker>
</view>
</view>
</view>
<view class="modal-footer">
<button class="cancel-btn" @click="closeJoinModal">取消</button>
<button class="submit-btn" @click="submitJoin">确认报名</button>
</view>
</view>
</view>
</view>
</template>
<script>
import config from '@/utils/config';
// import { getOpenId } from '@/utils/storage';
import { FetchSessionKey, FetchDecryptPhone } from '@/api/user.js';
import { FetchSignActivity, FetchCancelSignActivity } from '@/api/activity.js';
export default {
data() {
return {
detail: {},
activityId: "",
contentNodes: ""
contentNodes: "",
showJoinModal: false,
joinForm: {
name: "",
phone: "",
personCount: 1,
personCountText: ""
},
personOptions: [
{ value: 1, text: "1人" },
{ value: 2, text: "2人" },
{ value: 3, text: "3人" },
{ value: 4, text: "4人" },
{ value: 5, text: "5人" },
],
rangeText: [],
sessionKey: "",
openid: ""
};
},
onLoad(options) {
console.log(options);
const item = JSON.parse(decodeURIComponent(options.item));
this.detail = item;
this.activityId = item.title;
console.log('this.detail',this.detail);
this.activityId = item.id || item.activityId;
this.formatContent();
this.rangeText = this.personOptions.map(item => item.text);
this.joinForm.personCount = this.personOptions[0].value;
this.joinForm.personCountText = this.personOptions[0].text;
this.getSessionKey();
},
methods: {
formatContent() {
let content = this.detail.content || "";
const httpImgReg = /https:\/\/mmbiz\.qpic\.cn[^ \n\r'"]+/g;
content = content.replace(httpImgReg, url => {
return `
<div style="text-align:center; margin:12px 0;">
<img src="${url}" style="width:90%; border-radius:8px;">
</div>
`;
let content = this.detail.activityContent || this.detail.activity_content || "";
if (!content) {
this.contentNodes = "";
return;
}
content = content.replace(/&amp;/g, '&');
// ========== ==========
const imgReg = /<img\s+[^>]*src\s*=\s*["']?([^"'> ]+)["']?[^>]*>/gi;
content = content.replace(imgReg, (match, src) => {
return `<div><img src="${src}" style="width:100%;max-width:750px;display:block;margin:0 auto;"></div>`;
});
// ========== pline-heightmargin ==========
const pHasStyleReg = /<p(\s+[^>]*?)style\s*=\s*["']([^"']+)["']([^>]*)>/gi;
content = content.replace(pHasStyleReg, (match, before, oldStyleStr, after) => {
const hasLineHeight = /line-height\s*:/.test(oldStyleStr);
let append = 'margin:15px 0;';
if (!hasLineHeight) {
append = 'line-height:1.5;margin:15px 0;';
}
const newStyle = oldStyleStr + ';' + append;
return `<p${before}style="${newStyle}"${after}>`;
});
const pNoStyleReg = /<p(?!\s+style\s*=)([^>]*)>/gi;
content = content.replace(pNoStyleReg, '<p$1 style="line-height:1.5;margin:15px 0;">');
// ========== blockquote ==========
content = content.replace(/<blockquote([^>]*)>/gi,
`<blockquote$1 style="background:#f5f7fa;border-left:8px solid #01a4fe;display:block;font-size:100%;line-height:1.5;margin:10px 0;padding:10px;">`);
// ========== ==========
content = content.replace(/<table([^>]*)>/gi,
`<table$1 style="width:100% !important;border-collapse:collapse;margin:10px 0;">`);
content = content.replace(/<th([^>]*)>/gi,
`<th$1 style="border:1px solid #dcdfe6;padding:8px 12px;text-align:center;font-size:14px;">`);
content = content.replace(/<td([^>]*)>/gi,
`<td$1 style="border:1px solid #dcdfe6;padding:8px 12px;text-align:center;font-size:14px;">`);
this.contentNodes = content;
},
onPickerChange(e) {
const index = e.detail.value;
this.joinForm.personCount = this.personOptions[index].value;
this.joinForm.personCountText = this.personOptions[index].text;
},
async getSessionKey() {
try {
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;
}
}
}
});
} catch (error) {
console.error('获取sessionKey失败:', error);
}
},
getPhoneNumber(e) {
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
uni.showToast({ title: '取消授权', icon: 'none' });
return;
}
if (!this.sessionKey) {
uni.showToast({ title: '请稍后再试', icon: 'none' });
return;
}
FetchDecryptPhone({
sessionKey: this.sessionKey,
encryptedData: e.detail.encryptedData,
iv: e.detail.iv
}).then(res => {
if (res.code === 200) {
this.joinForm.phone = res.data.phoneNumber || '';
uni.showToast({ title: '获取成功', icon: 'success' });
} else {
uni.showToast({ title: res.message || '获取失败', icon: 'none' });
}
}).catch(() => {
uni.showToast({ title: '获取手机号失败', icon: 'none' });
});
},
getJoinBtnClass() {
const d = this.detail;
// /
if (d.status === '已结束' || d.status === '已取消') {
return 'disabled-btn'
}
return ''
},
//
getJoinBtnText() {
const d = this.detail;
if(d.status === '已结束') return '活动结束'
if(d.status === '已取消') return '活动取消'
//
if(d.status === '未开始' && d.isCancel === false && !!d.signId){
return '取消预约'
}
return '我要参加'
},
handleJoin() {
if (this.detail.status === 0) {
const d = this.detail
//
if (['已结束', '已取消'].includes(d.status)) {
return;
}
//
if(d.status === '未开始' && d.isCancel === false && !!d.signId){
uni.showModal({
title: '提示',
content: `确定要取消活动【${d.title}】的预约吗?`,
success: async (res) => {
if(res.confirm){
try {
const apiRes = await FetchCancelSignActivity({
id: d.signId
})
if(apiRes.code === 200){
uni.showToast({ title: '取消成功', icon: 'success' })
//
uni.$emit('refreshMySignList')
setTimeout(() => {
uni.navigateBack()
}, 700)
}else{
uni.showToast({ title: apiRes.message || '取消失败', icon: 'none' })
}
} catch (err) {
console.error('取消报名异常', err)
uni.showToast({ title: '网络异常,取消失败', icon: 'none' })
}
}
}
})
return
}
//
this.openJoinModal();
},
closeJoinModal() {
this.showJoinModal = false;
},
onPersonChange(e) {
this.joinForm.personCount = e.detail.value + 1;
},
openJoinModal() {
// picker
this.joinForm = {
name: "",
phone: "",
personCount: 1,
personCountText: "1人"
}
this.showJoinModal = true
},
submitJoin() {
if (!this.joinForm.name) {
uni.showToast({ title: '请输入姓名', icon: 'none' });
return;
}
uni.showToast({
title: '报名成功',
icon: 'success'
});
}
},
onShareAppMessage() {
return {
title: this.detail.title,
path: "/subpkg/pages/activity-detail/activity-detail?item=" + encodeURIComponent(JSON.stringify(this.detail)),
imageUrl: this.detail.imgUrl
};
if (!this.joinForm.phone) {
uni.showToast({ title: '请输入联系方式', icon: 'none' });
return;
}
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(this.joinForm.phone)) {
uni.showToast({ title: '请输入正确的手机号码', icon: 'none' });
return;
}
const params = {
// libcode: config.LIB_CODE,
activityId: this.activityId,
signName: this.joinForm.name,
signPhone: this.joinForm.phone,
signNum: this.joinForm.personCount,
openid: this.openid,
// sessionKey: this.sessionKey
}
console.log('params',params)
FetchSignActivity(params).then(res => {
console.log('FetchSignActivity',res)
if (res.code === 200) {
uni.showToast({
title: '报名成功',
icon: 'success'
});
uni.$emit('refreshMySignList')
this.closeJoinModal();
} else {
console.log('error',res)
uni.showToast({
title: res.message || '报名失败',
icon: 'none'
});
}
}).catch((err) => {
console.log('catch err', err)
if(err){
uni.showToast({
title: err,
icon: 'none'
})
}else{
uni.showToast({
title: '报名失败',
icon: 'none'
});
}
});
},
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}`;
},
onShareAppMessage() {
return {
title: this.detail.title,
path: "/subpkg/pages/activity-detail/activity-detail?item=" + encodeURIComponent(JSON.stringify(this.detail)),
imageUrl: this.detail.coverImage
};
}
}
};
</script>
@ -127,13 +415,14 @@ export default {
.rich-content {
background: #fff;
padding: 10px 0;
padding: 10px;
.content-title{
position: relative;
font-size: 16px;
font-weight: bold;
color: #333;
padding-left: 12px;
margin-bottom: 20px;
&::before{
position: absolute;
left: 0;
@ -145,6 +434,13 @@ export default {
background-color: #01a4fe;
}
}
}
.rich-text{
::v-deep p{
line-height: 1.5;
margin: 15px 0;
}
}
.detail-bottom {
@ -184,4 +480,147 @@ export default {
.disabled-btn {
background: #ccc !important;
}
</style>
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
width: 85%;
background: #fff;
border-radius: 8px;
overflow: hidden;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
border-bottom: 1px solid #f0f0f0;
.modal-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.close-icon {
color: #999;
}
}
.modal-body {
padding: 15px;
}
.form-item {
margin-bottom: 15px;
.form-label {
display: block;
font-size: 14px;
color: #333;
margin-bottom: 8px;
}
.form-input {
width: calc(100% - 25px);
height: 40px;
background: #f8f8f8;
border-radius: 8px;
padding: 0 12px;
font-size: 14px;
}
.select-wrapper {
width: calc(100% - 25px);
height: 40px;
background: #f8f8f8;
border-radius: 8px;
display: flex;
align-items: center;
padding: 0 12px;
font-size: 14px;
::v-deep .picker-select{
width: 100%;
}
.select-value {
font-size: 14px;
color: #333;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
&::after {
content: '';
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #999;
}
}
}
}
.modal-footer {
display: flex;
border-top: 1px solid #f0f0f0;
.cancel-btn,
.submit-btn {
flex: 1;
height: 44px;
line-height: 44px;
text-align: center;
font-size: 15px;
border-radius: 0;
border: none;
margin: 0;
padding: 0;
&::after {
border: none;
border-radius: 0;
}
}
.cancel-btn {
background: #f5f5f5;
color: #666;
// border-right: 1px solid #f0f0f0;
}
.submit-btn {
background: #01a4fe;
color: #fff;
}
}
.get-phone-btn {
font-size: 12px;
color: #fff;
background-color: #01a4fe;
width: 90px;
border-radius: 20px;
padding: 10px;
border: none;
line-height: 1.2;
margin-left: 10px;
&::after {
border: none;
}
}
</style>

398
subpkg/pages/activity-list/activity-list.vue

@ -17,242 +17,189 @@
</view>
</view>
<scroll-view
scroll-y
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
lower-threshold="100"
>
<scroll-view
scroll-y
@scrolltolower="onLoadMore"
lower-threshold="150"
class="scroll-view"
>
<view class="activity-item" v-for="(item, index) in displayList" @click="toActivityDetail(item)" :key="index">
<image class="activity-img" :src="item.imgUrl" mode="aspectFill"></image>
<image class="activity-img" :src="item.coverImage" mode="aspectFill"></image>
<!-- 状态小标 -->
<view class="status-tag"
v-if="item.status === '未开始' || item.status === '进行中' || item.status === '已结束'"
:class="[
item.status === '未开始' ? 'tag-wait' : '',
item.status === '进行中' ? 'tag-doing' : '',
item.status === '已结束' ? 'tag-end' : ''
]"
>
{{item.status}}
</view>
<view class="activity-info">
<view class="activity-info-left">
<text class="title">{{ item.title }}</text>
<view class="title-wrap">
<text class="activity-title">{{ item.title }}</text>
</view>
<view class="time">
<uni-icons class="time-icon" custom-prefix="iconfont" type="icon-shijian" size="15"></uni-icons>
<text>{{ item.time }}</text>
<text>{{ item.startTime }} - {{ item.endTime }}</text>
</view>
</view>
<button
<!-- <button
class="activity-btn"
:class="item.status === 0 ? 'disabled-btn' : ''"
:class="isItemDisabled(item) ? 'disabled-btn' : ''"
type="primary"
:disabled="item.status === 0"
:disabled="isItemDisabled(item)"
>
{{ item.status === 1 ? '立即参加' : '活动结束' }}
</button>
{{ getBtnText(item.status) }}
</button> -->
</view>
</view>
<view class="empty" v-if="displayList.length === 0">
<view class="empty" v-if="displayList.length === 0 && !loading">
<uni-icons custom-prefix="iconfont" type="icon-kongshuju" size="80"></uni-icons>
<text style="margin-top: 20px;">暂无{{ currentTab === 0 ? '进行中的活动' : '往期活动' }}~</text>
</view>
<!-- 底部加载提示 -->
<view class="footer-tip" v-if="displayList.length>0">
<text v-if="loading">加载中...</text>
<text v-else-if="noMore">没有更多数据了</text>
</view>
</scroll-view>
</view>
</template>
<script>
import { FetchActivityInfo } from '@/api/activity';
import config from '@/utils/config';
export default {
data() {
return {
currentTab: 0,
activityList: [],
refreshing: false
baseUrl: config.baseUrl,
page: 0,
size: 10,
loading: false,
noMore: false
};
},
computed: {
displayList() {
return this.currentTab === 0
? this.activityList.filter(item => item.status === 1)
: this.activityList.filter(item => item.status === 0);
return this.activityList
}
},
onLoad() {
this.getActivityList();
this.getNewActivityList(false);
},
/** 页面原生下拉刷新,pages.json对应页面开启enablePullDownRefresh */
async onPullDownRefresh() {
if(this.loading){
uni.stopPullDownRefresh()
return
}
this.page = 0;
this.noMore = false;
try{
await this.getNewActivityList(false);
}finally{
uni.stopPullDownRefresh()
}
},
methods: {
switchTab(index) {
this.currentTab = index;
//tab
this.page = 0;
this.activityList = [];
this.noMore = false;
this.getNewActivityList(false);
},
getActivityList() {
this.refreshing = true;
setTimeout(() => {
this.activityList = [
{
imgUrl: "https://mmbiz.qpic.cn/mmbiz_jpg/tuyGibvSXrso7c068ibUK1dXCGM2oiaBHAe3c82icrPjhLr3aFMLzhQ8n4C9X6qY5n0aDkRibvTNxoFVchrQxnicSJ64LX5KJxKj80TutmMicRKoY0/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=3",
title: "以爱之名赴健康之约,致敬每一位伟大的母亲",
time: "2026-05-10 09:00 ~ 2026-05-15 11:30",
localImg: "images/activity1.png",
content: `<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">2026年5月10日,“以爱之名,守护健康”葛图讲坛35期母亲节健康关爱活动在葛店经开区图书馆温情启幕,汇聚了近40组亲子家庭,将健康科普化作深情告白,把亲子陪伴化作节日惊喜,为每一位母亲送上最真挚的节日祝福与贴心关怀。</p>
https://mmbiz.qpic.cn/mmbiz_jpg/tuyGibvSXrsp5h4G9Ip4Jl10attsaXjlUyRLOcia8zXKfV0yc9cwOIgmvnSqusKkB4iaia8bMFDnBm2ApntEWA06QEmDt7hbwa5t5k3hofWZm3s/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=6
<p style="text-align: center;color: rgb(240, 159, 161); padding-bottom: 40px;">HAPPY MOTHER'S DAY</p>
<p style="text-align: center; line-height:36px;"> 致敬母亲<br/>
不止于告白<br/>
更在于守护她的岁岁安康</p>
/**
* @param {Boolean} isLoadMore true=上拉加载更多追加 false=刷新重置列表
*/
async getNewActivityList(isLoadMore = false){
if(this.loading) return;
this.loading = true;
https://mmbiz.qpic.cn/sz_mmbiz_jpg/tuyGibvSXrspDOvN5sicEDS4tqjTXZzurKrqmQibiazDZldSP5j1bWvKKiahCMvJ2OOpvUcvDPSyodGso32IEEC6ajPznwKm2PL5vyMjh2biahh2g/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=7
<p style="text-align: center; line-height:36px;">
活动特邀湖北省中医院四位专家<br/>
聚焦母亲群体高频健康痛点<br/>
把专业的医学知识<br/>
化作守护母亲的暖心指南<br/>
</p>
<p style="text-align: center; line-height:36px;">
杨露医师重点科普中老年母亲<br/>
高发的睡眠呼吸暂停综合征<br/>
教大家科学识别与干预<br/>
</p>
<p style="text-align: center; line-height:36px;">
王诗蕾护师详解顺时养生与睡眠的关联<br/>
为常见失眠问题提供实用方案<br/>
帮助大家拥抱深度睡眠<br/>
</p>
https://mmbiz.qpic.cn/mmbiz_jpg/tuyGibvSXrsqHcibsoKTrNmhFAOlmA4EibNBoEJibQRd0zoU5IOYPORqIKWAC6xH6qCMYKbKGshU5mkSicMOdhCIP24LpEPkgtyVXN73XXU83vsk/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=12
https://mmbiz.qpic.cn/sz_mmbiz_jpg/tuyGibvSXrsoMcRPkKzwAq229WY37uOaqlzvV1lrPrib1WibQyH8iakKGweuJ2eyYZpRXj79Qmm7zC4IMaibDgn5qXfCuxmF8eibFONhjZOpMZtvQ/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=13
<p style="text-align: center; line-height:36px;">
认真聆听的模样<br/>
是母亲对自己的温柔善待<br/>
专注记录的瞬间<br/>
是我们对母亲最实在的守护<br/>
</p>
https://mmbiz.qpic.cn/sz_mmbiz_jpg/tuyGibvSXrsphvUJMRz60CzqkEd6xuSxdzoSCjdOoQr8CG3aaib2xl6vpcUr35JAqbXfojqib2lQZWh24cNOgf2ewfVmJrRrtrfJadib8aKkkibY/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=17
<p style="text-align: center;color: rgb(240, 159, 161); padding-top: 40px; padding-bottom: 40px;">HAPPY MOTHER'S DAY</p>
<p style="text-align: center; line-height:36px;">
以爱之名守护健康<br/>
以书为媒传递温情<br/>
</p>
<p style="text-align: center; line-height:36px; padding-bottom: 40px;">
这场由葛店经开区图书馆<br/>
湖北省中医院葛店院区<br/>
湖畔社区居委会联合举办的<br/>
葛图讲坛母亲节特别企划<br/>
是我馆人文关怀与文化传播的一次温柔落地<br/>
更是对每一位母亲的深情致敬<br/>
</p>
<p style="text-align: center; line-height:36px;">
愿更多关注投向母亲健康<br/>
愿每一份爱意及时抵达<br/>
让每一位母亲被时光温柔以待<br/>
被健康悉心守护<br/>
</p>`,
status: 1,
location: "葛店经开区图书馆",
},
{
imgUrl: "https://mmbiz.qpic.cn/sz_mmbiz_jpg/tuyGibvSXrsqmHJvND8QQFHPDaFEuGGqaNgVvjEO7tqBziaqlcz5bURSyNd4DoKxcD41LWIDComEbUm25pBA86ibeN45zvoPAzDJzaoxayyicMc/640?wx_fmt=jpeg&from=appmsg#imgIndex=22",
title: "别让肥胖偷走健康,馆藏好书为健康支招",
time: "2026-05-11 09:00 ~ 2026-05-11 17:30",
localImg: "images/activity2.png",
content: `<p style="padding:20px 12px; text-indent: 2em; line-height:28px; font-size: 13px;">5月11日,是专为对抗肥胖问题设立的世界防治肥胖日。当“5·11”谐音成“我要瘦”,这简单的三个字背后,藏着的是全球超20亿超重或肥胖人群的健康焦虑。肥胖早已不是“体态丰满”的小事,而是被世界卫生组织列为十大慢性疾病之一的公共卫生危机。今天,就让我们一起科学认识肥胖,把健康主动权握在自己手中。</p>
<div style="display:flex; justify-content: center; margin:40px auto 20px auto;">
<p style="background-color: rgb(93, 164, 124); padding: 4px 15px; color:#fff; border-radius: 4px; ">科学减重指南书籍推荐</p>
</div>
<p style="text-indent: 2em; line-height:28px; display: inline-block; margin:0 12px; border: 2px solid rgb(93, 164, 124); padding: 10px; background-color: rgb(199, 227, 178); font-size: 13px; text-align: justify; border-radius: 4px;">为什么你拼命运动还是瘦不下来为什么你严格节食却依旧反弹为什么别人轻松减肥你却举步维艰其实减肥不是一场靠意志力硬撑的战争而是一场需要科学指导的持久战想要打破减肥僵局不妨从阅读专业的减肥书籍开始今天葛店经开区图书馆就为你推荐几本靠谱的减肥好书帮你重塑身材拥抱健康生活</p>
https://mmbiz.qpic.cn/sz_mmbiz_jpg/tuyGibvSXrsoXfmjrjPs3sDmOmIkx7LSHA7bMdkNGvbVDdyN7AywSXk8hfL2Z0S0bv9S6NUSxuYngZnyRGXff2gmvuUn89iaxHfPxVQyNLLZg/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=16
<div style="display:flex; justify-content: center; margin:20px auto; flex-direction: column; padding:0 20px; font-size: 13px;">
<p>ISBN:</p>
<p style="text-align: center; line-height:36px;">978-7-5337-9035-6 </p>
<p>馆藏地点</p>
<p style="text-align: center; line-height:36px;">二楼综合阅览一<br/>27架B面03列03层</p>
<p>内容简介:</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">本书分为五个篇章, 包括认知篇饮食营养篇心理篇运动篇, 以及特殊人群篇这五个篇章涵盖了超重和肥胖人群的体重管理, 以及消瘦人群的体重管理, 旨在为读者提供全面科学的体重管理指导</p>
</div>
https://mmbiz.qpic.cn/mmbiz_jpg/tuyGibvSXrso5oZu6lfdRoV4n8qqp55JYqOh44UrIiaicPHbnicIk8C7QVoCGiaNZXZXByXUWo7WqgiaBhGXztdZtYnC0ps8SYHjiaV9PKY8X6iaPqQ/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=17
<div style="display:flex; justify-content: center; margin:20px auto; flex-direction: column; padding:0 20px; font-size: 13px;">
<p>ISBN:</p>
<p style="text-align: center; line-height:36px;">978-7-5001-7513-1 </p>
<p>馆藏地点</p>
<p style="text-align: center; line-height:36px;">二楼综合阅览一<br/>27架B面03列03层</p>
<p>内容简介:</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">本书主要内容: 身体重置计划不需要节食不需要有禁食窗口期, 无关饮食热量限制或其他时髦的花招它包含六大简单的操作技巧, 是为生活在现实世界中的真实成年人设计, 帮助成年人通过饮食实现健康生活, 甚至逆龄生态书中内容非常易于操作和可持续, 不需要很多额外的餐饮动作, 是一套详尽有效丰富的餐饮为主, 配合适当运动的综合性身体调节计划</p>
</div>
https://mmbiz.qpic.cn/sz_mmbiz_jpg/tuyGibvSXrspsqSQqHXGWtMUuLeKD7y1hJ0gDBqpBNJlRCMyPoZwjB8HN1chbl13zcQEIiaVBbrsRdnznThGze0EDQtfQyKen282oFUunVzB0/640?wx_fmt=jpeg&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=18
<div style="display:flex; justify-content: center; margin:20px auto; flex-direction: column; padding:0 20px; font-size: 13px;">
<p>ISBN:</p>
<p style="text-align: center; line-height:36px;">978-7-5532-1045-2</p>
<p>馆藏地点</p>
<p style="text-align: center; line-height:36px;">二楼综合阅览一<br/>27架B面02列02层</p>
<p>内容简介:</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">作者谢佳妤在整理其奶奶李瑞芬遗稿的基础上用年轻化的语言讲述了饮食与健康的关系说明了各种常见的营养素的作用和正确进行食物搭配的方法有助于读者学会科学选择食物理性对待食物养成均衡饮食合理膳食的习惯</p>
</div>
<div style="display:flex; justify-content: center; margin:40px auto 20px auto;">
<p style="background-color: rgb(93, 164, 124); padding: 4px 15px; color:#fff; border-radius: 4px; ">肥胖是疾病的导火索</p>
</div>
<div>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">很多人觉得胖点没关系看着富态但医学研究早已证实肥胖是多种疾病的共同土壤它就像一颗埋在身体里的定时炸弹随时可能引爆健康危机</p>
https://mmbiz.qpic.cn/sz_mmbiz_png/tuyGibvSXrsoGIHpwHdfEr7sM6Ldqd4YjPwaYBBeFc2CRickibceCwIEXIopOCO6HWMIagYQkEwApf42cYxPUbdUqT3d2rFudibJcM53n3wTG0Q/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=20
<p style="text-align: center; line-height:36px;">1.代谢系统紊乱</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">约80%的2型糖尿病患者伴有超重或肥胖内脏脂肪释放的炎症因子会干扰胰岛素信号让身体对胰岛素失灵血糖随之失控此外肥胖还会诱发痛风高脂血症让血液变成浓稠的油脂汤</p>
<p style="text-align: center; line-height:36px;">2.心血管不堪重负</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">肥胖者患高血压的风险比正常人高60%冠心病风险更是高出2-3腹部堆积的脂肪会压迫血管加速动脉粥样硬化心梗中风等急性心血管事件的发生率也随之飙升</p>
https://mmbiz.qpic.cn/sz_mmbiz_jpg/tuyGibvSXrsqmHJvND8QQFHPDaFEuGGqaNgVvjEO7tqBziaqlcz5bURSyNd4DoKxcD41LWIDComEbUm25pBA86ibeN45zvoPAzDJzaoxayyicMc/640?wx_fmt=jpeg&from=appmsg#imgIndex=22
<p style="text-align: center; line-height:36px;">3.癌症风险飙升</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">国际癌症研究机构确认肥胖与至少13种癌症相关其中子宫内膜癌风险增加6倍食管腺癌肝癌肾癌等发病概率也显著上升脂肪组织分泌的异常激素会持续刺激细胞变异为癌症埋下隐患</p>
<p style="text-align: center; line-height:36px;">4.生活质量直线下降</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">体重每增加5公斤膝关节骨关节炎风险上升36%重度肥胖者中阻塞性睡眠呼吸暂停综合征患病率超70%每晚都在缺氧中挣扎更不用提肥胖带来的自卑抑郁等心理问题让很多人在社交中抬不起头 </p>
</div>
<div style="display:flex; justify-content: center; margin:40px auto 20px auto;">
<p style="background-color: rgb(93, 164, 124); padding: 4px 15px; color:#fff; border-radius: 4px; ">科学减重避开误区找对方法</p>
</div>
<div>
https://mmbiz.qpic.cn/mmbiz_png/tuyGibvSXrsroDeiawZ9ApdibzQc2ZXAp8u1JSsViciaibwfIupY4VTpIets2AoEY87dfbKIS8cuDeR7icLqBGNSUkV8nCA3fmyLDw2hH7dtdWK5FE/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=29
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">提到减肥很多人第一反应就是节食吃减肥药但这些极端方法往往伤身又反弹真正的科学减重是建立健康的生活方式让身体慢慢回归平衡</p>
<p style="text-align: center; line-height:36px;">1.不是饿肚子会吃饭</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">控制总能量合理分配先根据身高cm-105算出理想体重再按每天每公斤理想体重15-35kcal计算总能量轻体力劳动者选20-25kcal将一日三餐能量按3:4:3分配早餐吃好午餐吃饱晚餐吃少</p>
https://mmbiz.qpic.cn/mmbiz_png/tuyGibvSXrsobpIdpicy3GeZ9HxuWwQCKqAr85ibIHfZKGFAsgewVSrh6fwI04vlJGAOBgiaRB5eyp4HHo4NJ3wWiberXHvke4LGs6hnKupzqPqk/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=31
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">拒绝高热量陷阱少吃油炸食品含糖糕点肥肉等高能量食物这些食物往往一口就占了半天的能量配额多吃全谷物蔬菜优质蛋白比如燕麦西兰花鸡胸肉它们能带来更强的饱腹感还能保证营养均衡</p>
https://mmbiz.qpic.cn/sz_mmbiz_png/tuyGibvSXrsq1ypaFcwARIlrCXTqibKZcjwozFc9JlKTap25kzbxY7a8Fk7l5ibLxibKN7bTIak0logVmTGR48N9xHz3NF1nL7gxXZ6MncFvGt8/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=32
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">养成健康进食习惯细嚼慢咽每口饭嚼20次以上定时定量进餐不吃夜宵用小盘子盛饭避免眼大肚小吃撑自己</p>
<p style="text-align: center; line-height:36px;">2. 动起来让脂肪起来</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">有氧运动为主抗阻运动为辅每周进行150-300分钟中等强度有氧运动比如快走慢跑游泳每次30分钟以上能有效燃烧脂肪每周再安排2-3次抗阻运动举哑铃做平板支撑都可以增加肌肉量能提高基础代谢让你躺着也能消耗更多能量</p>
https://mmbiz.qpic.cn/mmbiz_png/tuyGibvSXrsp64a3In2BKHfQQHEIkDIUSjCklsbETfrcRkHyd35fW3OUYZeibpJHRE6zicbicSf94DIics1vP9b27o1GPH7X2rQkk0wh8bNNaicoQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=34
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">打破久坐魔咒每坐1小时就起身活动3-5分钟伸伸懒腰倒杯水走两步每天的零碎时间加起来也是一笔不小的运动财富</p>
<p style="text-align: center; line-height:36px;">3.小习惯大改变</p>
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">睡够7小时睡眠不足会导致瘦素分泌减少饥饿素分泌增加让你忍不住想吃高糖高脂食物尽量在晚上11点前入睡给身体足够的修复时间 </p>
https://mmbiz.qpic.cn/mmbiz_png/tuyGibvSXrspLUJPiaZuP1eAZXWjyNyGeKLNgnXibwich4xnqgQJjXcyxCIickVWr8ibsmnEmTUx2JuwARYWxDR3yW2ibIJX2O3RzSic3xT3RNlkwaA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=35
https://mmbiz.qpic.cn/sz_mmbiz_png/tuyGibvSXrsqKnXiak3cs5BPILA59JRapTCkFBycZ0SYdzxBQ1LMnqkXicWZiaNiaYPsTyZ9S1wBOyuv0j24v6pbyjWIO2p8Ue42LWckMYWuaSrs/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=36
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">学会情绪管理压力大时很多人会情绪化进食不妨试试冥想听音乐和朋友聊天用健康的方式释放压力别让食物成为情绪垃圾桶</p>
https://mmbiz.qpic.cn/mmbiz_png/tuyGibvSXrsoGPLDZ7l26cKDDac6BewgQuZiaic4M5Rk052cibY1OYBiaE9G46QAIEeCdSZN58usFVWMIto6dcz2lxg04ksYRaECazibulFu9pjf4/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=37
<p style="padding:20px 12px; text-indent: 2em; line-height:28px; ">定期监测体重每周固定时间称一次体重比如每周一早上空腹称重及时调整饮食和运动计划避免体重悄悄失控</p>
</div>
<p style="text-align: center; line-height:36px; padding-top: 40px;">
每个人<br/>
从自己做起<br/>
主动学习健康知识<br/>
管住嘴<br/>
迈开腿<br/>
用实际行动对抗肥胖<br/>
</p>`,
status: 0,
location: "葛店经开区图书馆",
let statusArr = []
if(this.currentTab === 0){
// 01
statusArr = [0, 1]
}else{
// 2
statusArr = [2]
}
const params = {
page: this.page,
size: this.size,
status: statusArr
}
console.log('params',params)
try {
const res = await FetchActivityInfo(params)
console.log(res)
if (res.code === 200 ) {
const records = res.data.content || []
records.forEach(item => {
if(item.coverImage){
const imgId = encodeURIComponent(item.coverImage);
item.coverImage = `https://wechatapi.aiyxlib.com/files/${imgId}`;
}else{
item.coverImage = ''
}
})
if(isLoadMore){
this.activityList.push(...records)
}else{
this.activityList = records
}
];
this.refreshing = false;
}, 300);
},
//
this.noMore = records.length < this.size
}else{
if(!isLoadMore){
this.activityList = [];
}
}
} catch (e) {
console.error(e)
} finally {
this.loading = false
}
},
onRefresh() {
this.getActivityList();
//
onLoadMore(){
if(this.loading || this.noMore) return
this.page = this.page + 1
this.getNewActivityList(true)
},
toActivityDetail(item) {
if (item.link) {
uni.navigateTo({
url: '/subpkg/pages/webview/webview?url=' + encodeURIComponent(item.link)
});
} else {
uni.navigateTo({
url: "/subpkg/pages/activity-detail/activity-detail?item=" + encodeURIComponent(JSON.stringify(item))
});
getBtnText(status) {
switch(status){
case '已结束':
return '活动结束'
case '已取消':
return '活动取消'
case '未开始':
case '进行中':
return '立即参加'
default:
return '立即参加'
}
},
isItemDisabled(item) {
return ['已结束', '已取消'].includes(item.status)
},
toActivityDetail(item) {
uni.navigateTo({
url: "/subpkg/pages/activity-detail/activity-detail?item=" + encodeURIComponent(JSON.stringify(item))
});
}
},
};
@ -260,13 +207,14 @@ export default {
<style lang="scss" scoped>
.activity-list {
padding: 15px 10px;
padding: 0;
height: 100vh;
box-sizing: border-box;
background-color: #f5f5f5;
display:flex;
flex-direction:column;
}
.tab-box {
display: flex;
background-color: #fff;
@ -299,16 +247,31 @@ export default {
border-radius: 2px;
}
scroll-view {
height: calc(100vh - 120px);
.scroll-view {
flex: 1;
height: 0;
padding: 0 12px;
box-sizing: border-box;
}
.empty {
height: calc(100vh - 200px);
padding: 80rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #999;
}
.footer-tip{
text-align: center;
padding: 10px 0 20px;
color:#999;
font-size:13px;
}
.activity-item {
position: relative;
background: #fff;
border-radius: 12px;
margin-bottom: 15px;
@ -317,10 +280,31 @@ scroll-view {
.activity-img {
width: 100%;
height: 180rpx;
height: 100px;
object-fit: cover;
}
.status-tag{
position: absolute;
top: 0;
left: 0;
padding: 4px 8px;
border-radius: 12px 0 12px 0;
font-size: 12px;
&.tag-wait{
color: #01a4fe;
background: #e6f7ff;
}
&.tag-doing{
color: #00b42a;
background: #e6ffed;
}
&.tag-end{
color:#868686;
background:#f2f2f2;
}
}
.activity-info {
padding: 15px;
display: flex;
@ -329,13 +313,18 @@ scroll-view {
.activity-info-left {
flex: 1;
.title {
.title-wrap{
display:flex;
justify-content: flex-start;
align-items: flex-start;
gap:5px;
margin-bottom:8px;
}
.activity-title {
flex: 1;
font-size: 16px;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 8px;
}
.time {
@ -357,6 +346,9 @@ scroll-view {
padding: 0 15px;
height: 36px;
line-height: 36px;
&::after{
border:none;
}
}
.disabled-btn {
@ -364,4 +356,4 @@ scroll-view {
}
}
}
</style>
</style>

484
subpkg/pages/activity-record/activity-record.vue

@ -0,0 +1,484 @@
<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">{{ formatTime(item.start_time) }}</text>
</view>
<view class="item-info">
<text class="label">结束时间</text>
<text class="value">{{ formatTime(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>
<!-- 非loading有数据是最后一页哪怕仅1条也展示没有更多 -->
<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: [],
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)
},
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 || [];
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>

30
subpkg/pages/booking-record/booking-record.vue

@ -36,26 +36,29 @@
class="activity-item"
v-for="item in displayList"
:key="item.id"
@click="toActivityDetail(item)"
>
<view class="activity-info">
<text class="title">{{ item.area }}</text>
<!-- <text class="title">{{ item.area }}</text> -->
<view class="item-info">
<text class="label">开始时间</text>
<text class="label">入馆日期</text>
<text class="value">{{ item.startTime }}</text>
</view>
<view class="item-info">
<view class="item-info">
<text class="label">入馆时间</text>
<text class="value">{{ item.startTime }}</text>
</view>
<!-- <view class="item-info">
<text class="label">结束时间</text>
<text class="value">{{ item.endTime }}</text>
</view>
<view class="item-info">
</view> -->
<!-- <view class="item-info">
<text class="label">座位号</text>
<text class="value">{{ item.seatNumber }}</text>
</view>
<view class="item-info">
</view> -->
<!-- <view class="item-info">
<text class="label">状态</text>
<text class="value status-tag" :class="item.statusClass">{{ item.statusText }}</text>
</view>
</view> -->
<view class="btn-box" v-if="item.status === 0">
<button class="activity-btn" type="primary" @click.stop="cancelAppoint(item)">
取消预约
@ -135,12 +138,6 @@ export default {
})
},
toActivityDetail(item) {
uni.navigateTo({
url: "/subpkg/pages/activity-detail/activity-detail?item=" + encodeURIComponent(JSON.stringify(item))
});
},
getEmptyText() {
const textMap = {
0: '未开始',
@ -239,7 +236,9 @@ export default {
display: block;
}
.item-info {
width: 100%;
display: flex;
justify-content: space-between;
padding: 4px 0;
font-size: 14px;
.label {
@ -249,6 +248,7 @@ export default {
.value {
color: #333;
flex: 1;
text-align: right;
}
}
}

241
subpkg/pages/seat-booking/seat-booking copy.vue

@ -0,0 +1,241 @@
<template>
<view class="seat-booking">
<!-- 顶部横幅 -->
<view class="banner">
<image class="banner-bg" src="@/static/images/mingqi-beij@2x.png" mode="aspectFill"></image>
<view class="banner-overlay">
<text class="banner-text">葛店经济开发区图书馆预约</text>
</view>
</view>
<view class="content">
<!-- 场馆选择下拉框 -->
<!-- <view class="venue-select">
<picker :value="venueIndex" :range="venues" @change="onVenueChange">
<view class="picker-content">
<text>{{ venues[venueIndex] }}</text>
<uni-icons type="down" size="16" color="#666" />
</view>
</picker>
</view> -->
<!-- 楼层/区域卡片列表 -->
<view class="floor-list">
<view class="floor-item" v-for="floor in floorList" :key="floor.id">
<text class="floor-name">{{ floor.floor }}</text>
<div class="floor-info">
<view class="floor-left">
<text class="floor-title">{{ floor.title }}</text>
<image class="banner-bg" src="@/static/images/lib.png" mode="aspectFill"></image>
</view>
<view class="floor-right">
<text class="floor-desc">{{ floor.desc }}</text>
<view style="display: flex; justify-content: flex-end; width: 100%; text-align: end;">
<button class="booking-btn" @click="bookingFloor(floor)">预约</button>
</view>
</view>
</div>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
venueIndex: 0,
venues: ['所有场馆', '东馆', '西馆', '南馆'],
floorList: [
{
id: 1,
floor: '3楼',
title: '综合阅览一',
desc: '葛店图书馆三楼阅读广场共有464个座位,为读者提供备电源,读者可预约选择心仪的座位。区域采光良好,环境优美,读者可在休息时一览楼下的风景。',
image: '@/static/images/lib.png'
},
{
id: 2,
floor: '3楼',
title: '少儿阅览室',
desc: '少儿阅览室共有464个座位,为读者提供备电源、读者可自助选择位置,各个区采光良好,环境优美,读者可在休息时一览楼下的风景。',
image: '@/static/images/lib.png'
},
{
id: 3,
floor: '3楼',
title: '专题阅览室',
desc: '专题阅览室共有464个座位,为读者提供备电源、读者可自助选择位置,各个区采光良好,环境优美,读者可在休息时一览楼下的风景。',
image: '@/static/images/lib.png'
}
]
};
},
onLoad() {
this.loadSeatInfo();
},
methods: {
loadSeatInfo() {
console.log('加载座位信息');
},
onVenueChange(e) {
this.venueIndex = e.detail.value;
console.log('切换场馆:', this.venues[this.venueIndex]);
},
bookingFloor(floor) {
uni.navigateTo({
url: `/subpkg/pages/booking-detail/booking-detail?floor=${floor.floor}&title=${floor.title}`
});
}
}
};
</script>
<style lang="scss" scoped>
.seat-booking {
min-height: 100vh;
background-color: #f5f7fa;
}
/* 顶部横幅 */
.banner {
position: relative;
height: 140px;
}
.banner-bg {
width: 100%;
height: 100%;
}
.banner-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.banner-text {
color: #fff;
font-size: 20px;
font-weight: 500;
}
.content {
padding: 16px;
// margin-top: -20px;
position: relative;
z-index: 10;
}
/* 场馆选择下拉框 */
.venue-select {
display: flex;
justify-content: flex-end;
margin-bottom: 16px;
}
.picker-content {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 16px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
color: #333;
}
/* 楼层卡片 */
.floor-list {
background-color: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.floor-item {
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #eee;
&:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
}
.floor-name {
display: block;
font-size: 16px;
font-weight: bold;
padding-bottom: 8px;
}
.floor-info{
display: inline-flex;
align-items: flex-start;
justify-content: space-between;
}
.floor-left {
position: relative;
width: 90px;
height: 100px;
border-radius: 8px;
margin-right: 12px;
}
.floor-image {
width: 90px;
height: 100px;
object-fit: cover;
}
.floor-title {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
padding: 4px 0;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
// font-weight: bold;
font-size: 12px;
// font-weight: bold;
}
.floor-right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.floor-desc {
font-size: 13px;
color: #666;
line-height: 1.5;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
.booking-btn {
background-color: #01a4fe;
color: #fff;
font-size: 14px;
padding: 0 20px;
border: none;
margin: 6px 0 0 0;
height: 30px;
line-height: 30px;
}
.booking-btn::after {
border: none;
}
</style>

451
subpkg/pages/seat-booking/seat-booking.vue

@ -1,43 +1,55 @@
<template>
<view class="seat-booking">
<!-- 顶部横幅 -->
<view class="banner">
<image class="banner-bg" src="@/static/images/mingqi-beij@2x.png" mode="aspectFill"></image>
<view class="banner-overlay">
<text class="banner-text">葛店经济开发区图书馆预约</text>
<view class="booking-detail">
<view class="section">
<view class="section-title">日期选择</view>
<view class="calendar">
<view class="calendar-header">
<text class="month">{{ currentYear }}{{ currentMonth }}</text>
</view>
<view class="calendar-weekdays">
<text v-for="day in weekdays" :key="day" class="weekday">{{ day }}</text>
</view>
<view class="calendar-days">
<view
v-for="(day, index) in calendarDays"
:key="index"
class="day-item"
:class="{
'other-month': !day.currentMonth,
'selected': day.date === selectedDate,
'disabled': !day.available,
'today': day.isToday
}"
@click="selectDate(day)"
>
<text class="day-num">{{ day.day }}</text>
<text v-if="day.available && !day.isToday" class="available-tag">开放日</text>
<text v-if="day.isToday" class="today-tag">今天</text>
</view>
</view>
</view>
</view>
<view class="content">
<!-- 场馆选择下拉框 -->
<!-- <view class="venue-select">
<picker :value="venueIndex" :range="venues" @change="onVenueChange">
<view class="picker-content">
<text>{{ venues[venueIndex] }}</text>
<uni-icons type="down" size="16" color="#666" />
</view>
</picker>
</view> -->
<!-- 楼层/区域卡片列表 -->
<view class="floor-list">
<view class="floor-item" v-for="floor in floorList" :key="floor.id">
<text class="floor-name">{{ floor.floor }}</text>
<div class="floor-info">
<view class="floor-left">
<text class="floor-title">{{ floor.title }}</text>
<image class="banner-bg" src="@/static/images/lib.png" mode="aspectFill"></image>
</view>
<view class="floor-right">
<text class="floor-desc">{{ floor.desc }}</text>
<view style="display: flex; justify-content: flex-end; width: 100%; text-align: end;">
<button class="booking-btn" @click="bookingFloor(floor)">预约</button>
</view>
</view>
</div>
<view class="section">
<view class="section-title">时间选择</view>
<view class="time-slots">
<view
v-for="slot in timeSlots"
:key="slot.id"
class="time-slot"
:class="{ 'selected': selectedTime === slot.time }"
@click="selectTime(slot)"
>
<text class="time-text">{{ slot.time }}</text>
<text v-if="slot.available" class="available-badge">可选</text>
<text v-else class="unavailable-badge">已满</text>
</view>
</view>
</view>
<view class="bottom-bar">
<button class="btn-primary" @click="confirmBooking">确认预约</button>
</view>
</view>
</template>
@ -45,197 +57,280 @@
export default {
data() {
return {
venueIndex: 0,
venues: ['所有场馆', '东馆', '西馆', '南馆'],
floorList: [
{
id: 1,
floor: '3楼',
title: '综合阅览一',
desc: '葛店图书馆三楼阅读广场共有464个座位,为读者提供备电源,读者可预约选择心仪的座位。区域采光良好,环境优美,读者可在休息时一览楼下的风景。',
image: '@/static/images/lib.png'
},
{
id: 2,
floor: '3楼',
title: '少儿阅览室',
desc: '少儿阅览室共有464个座位,为读者提供备电源、读者可自助选择位置,各个区采光良好,环境优美,读者可在休息时一览楼下的风景。',
image: '@/static/images/lib.png'
},
{
id: 3,
floor: '3楼',
title: '专题阅览室',
desc: '专题阅览室共有464个座位,为读者提供备电源、读者可自助选择位置,各个区采光良好,环境优美,读者可在休息时一览楼下的风景。',
image: '@/static/images/lib.png'
}
currentYear: new Date().getFullYear(),
currentMonth: new Date().getMonth() + 1,
selectedDate: '',
selectedTime: '',
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
calendarDays: [],
timeSlots: [
{ id: 1, time: '09:15-12:45', available: true },
{ id: 2, time: '13:00-16:30', available: false },
{ id: 3, time: '16:45-20:15', available: true }
]
};
},
onLoad() {
this.loadSeatInfo();
onLoad(options) {
this.generateCalendar();
},
methods: {
loadSeatInfo() {
console.log('加载座位信息');
generateCalendar() {
const year = this.currentYear;
const month = this.currentMonth;
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
const startWeekday = firstDay.getDay();
const today = new Date();
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
const days = [];
const prevMonthLastDay = new Date(year, month - 1, 0).getDate();
for (let i = startWeekday - 1; i >= 0; i--) {
days.push({
day: prevMonthLastDay - i,
date: '',
currentMonth: false,
available: false,
isToday: false
});
}
const todayDate = new Date();
const availableDates = [];
for (let i = 0; i < 7; i++) {
const date = new Date(todayDate);
date.setDate(todayDate.getDate() + i);
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
availableDates.push(dateStr);
}
for (let i = 1; i <= daysInMonth; i++) {
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(i).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const isToday = dateStr === todayStr;
days.push({
day: i,
date: dateStr,
currentMonth: true,
available: isAvailable,
isToday: isToday
});
}
const remainingDays = 42 - days.length;
for (let i = 1; i <= remainingDays; i++) {
days.push({
day: i,
date: '',
currentMonth: false,
available: false,
isToday: false
});
}
this.calendarDays = days;
if (availableDates.length > 0) {
this.selectedDate = availableDates[0];
}
},
onVenueChange(e) {
this.venueIndex = e.detail.value;
console.log('切换场馆:', this.venues[this.venueIndex]);
selectDate(day) {
if (!day.available || !day.currentMonth) return;
this.selectedDate = day.date;
},
bookingFloor(floor) {
uni.navigateTo({
url: `/subpkg/pages/booking-detail/booking-detail?floor=${floor.floor}&title=${floor.title}`
});
selectTime(slot) {
if (!slot.available) return;
this.selectedTime = slot.time;
},
confirmBooking() {
if (!this.selectedDate) {
uni.showToast({ title: '请选择日期', icon: 'none' });
return;
}
if (!this.selectedTime) {
uni.showToast({ title: '请选择时间段', icon: 'none' });
return;
}
uni.showLoading({ title: '预约中...' });
setTimeout(() => {
uni.hideLoading();
uni.showModal({
title: '预约成功',
content: `您已成功预约\n日期:${this.selectedDate}\n时间:${this.selectedTime}`,
showCancel: false,
confirmText: '确定',
success: () => {
uni.navigateBack({ delta: 2 });
}
});
}, 1500);
}
}
};
</script>
<style lang="scss" scoped>
.seat-booking {
.booking-detail {
min-height: 100vh;
background-color: #f5f7fa;
background-color: #f5f5f5;
padding-bottom: 70px;
}
/* 顶部横幅 */
.banner {
position: relative;
height: 140px;
.section {
background-color: #fff;
margin: 10px;
border-radius: 12px;
padding: 16px;
}
.banner-bg {
width: 100%;
height: 100%;
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 16px;
}
.banner-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
/* 日历 */
.calendar-header {
text-align: center;
margin-bottom: 12px;
}
.banner-text {
color: #fff;
font-size: 20px;
font-weight: 500;
.month {
font-size: 16px;
color: #333;
font-weight: bold;
}
.content {
padding: 16px;
// margin-top: -20px;
position: relative;
z-index: 10;
.calendar-weekdays {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.weekday {
width: 14.28%;
text-align: center;
font-size: 13px;
color: #999;
}
/* 场馆选择下拉框 */
.venue-select {
.calendar-days {
display: flex;
justify-content: flex-end;
margin-bottom: 16px;
flex-wrap: wrap;
}
.picker-content {
.day-item {
width: 14.28%;
aspect-ratio: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 8px 16px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
color: #333;
justify-content: center;
margin-bottom: 6px;
border-radius: 6px;
position: relative;
}
/* 楼层卡片 */
.floor-list {
background-color: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
.day-item.other-month .day-num {
color: #ccc;
}
.floor-item {
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #eee;
&:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.day-item.disabled .day-num {
color: #ccc;
}
.floor-name {
display: block;
font-size: 16px;
.day-item.selected {
background-color: #01a4fe;
}
.day-item.selected .day-num {
color: #fff;
font-size: 15px;
font-weight: bold;
padding-bottom: 8px;
}
.floor-info{
display: inline-flex;
align-items: flex-start;
justify-content: space-between;
.day-item.selected .available-tag,
.day-item.selected .today-tag {
color: rgba(255, 255, 255, 0.9);
font-size: 10px;
}
.floor-left {
position: relative;
width: 90px;
height: 100px;
border-radius: 8px;
margin-right: 12px;
.day-item.today:not(.selected) {
background-color: #fff3cd;
}
.day-item.today:not(.selected) .day-num {
color: #856404;
}
.floor-image {
width: 90px;
height: 100px;
object-fit: cover;
.day-num {
font-size: 14px;
color: #333;
}
.floor-title {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
padding: 4px 0;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
// font-weight: bold;
font-size: 12px;
// font-weight: bold;
.available-tag {
font-size: 10px;
color: #01a4fe;
margin-top: 2px;
}
.today-tag {
font-size: 10px;
color: #856404;
margin-top: 2px;
}
.floor-right {
flex: 1;
/* 时间选择 */
.time-slots {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: 10px;
}
.time-slot {
width: calc(50% - 5px);
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
background-color: #f8f8f8;
border-radius: 8px;
border: 1px solid transparent;
}
.time-slot.selected {
background-color: #e8f4fd;
border-color: #01a4fe;
}
.time-text {
font-size: 14px;
color: #333;
}
.available-badge {
font-size: 12px;
color: #01a4fe;
background-color: #e8f4fd;
padding: 2px 8px;
border-radius: 10px;
}
.unavailable-badge {
font-size: 12px;
color: #999;
background-color: #f0f0f0;
padding: 2px 8px;
border-radius: 10px;
}
.floor-desc {
font-size: 13px;
color: #666;
line-height: 1.5;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
.booking-btn {
/* 底部按钮 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 10px;
background-color: #fff;
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
}
.btn-primary {
width: 100%;
height: 44px;
line-height: 44px;
background-color: #01a4fe;
color: #fff;
font-size: 14px;
padding: 0 20px;
border-radius: 22px;
font-size: 15px;
border: none;
margin: 6px 0 0 0;
height: 30px;
line-height: 30px;
}
.booking-btn::after {
.btn-primary::after {
border: none;
}
</style>

4
utils/config.js

@ -1,8 +1,8 @@
const config = {
// 本地
// baseUrl: 'http://192.168.99.63:14000',
baseUrl: 'http://192.168.99.63:14000',
// 线上
baseUrl: 'https://wechatapi.aiyxlib.com',
// baseUrl: 'https://wechatapi.aiyxlib.com',
LIB_CODE: '1201'
};

35
utils/request.js

@ -2,6 +2,24 @@ import store from '../store';
import config from '@/utils/config.js'
const BASE_URL = config.baseUrl
// 把对象转 queryString,数组展开为 key=val&key=val
function buildQuery(params) {
const parts = []
for (const key in params) {
const val = params[key]
if (val === null || val === undefined) continue
if (Array.isArray(val)) {
// 数组:多次追加 key=xxx
val.forEach(item => {
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(item)}`)
})
} else {
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`)
}
}
return parts.join('&')
}
function request({ url, data, method = 'GET' }) {
return new Promise((resolve, reject) => {
// 自动带上 token
@ -10,14 +28,25 @@ function request({ url, data, method = 'GET' }) {
header.Authorization = store.state.user.token;
}
let finalUrl = BASE_URL + url
let finalData = data
// GET 请求:手动拼装query,数组展开多同名参数
if (method.toUpperCase() === 'GET' && data && typeof data === 'object') {
const query = buildQuery(data)
if (query) {
finalUrl += (finalUrl.includes('?') ? '&' : '?') + query
}
finalData = undefined // GET 清空data,防止uni.request重复处理
}
uni.request({
url: BASE_URL + url,
data,
url: finalUrl,
data: finalData,
method: method.toUpperCase(),
header,
success: ({ data }) => {
if (data.code === 200) {
// 直接返回后端的 data 结构
resolve(data);
} else {
uni.showToast({

Loading…
Cancel
Save