feat(activity): 新增活动报名及我的报名功能
11
pages.json
|
|
@ -659,6 +659,17 @@
|
|||
"group": "物业管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "activity/my-registration",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的报名"
|
||||
},
|
||||
"meta": {
|
||||
"sync": true,
|
||||
"title": "我的报名",
|
||||
"group": "物业管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "staff/index",
|
||||
"style": {
|
||||
|
|
|
|||
|
|
@ -77,23 +77,28 @@
|
|||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 活动卡片 -->
|
||||
<view class="activity-card" @tap="goActivityDetail">
|
||||
<image class="activity-cover" src="/static/img/guest.png" mode="aspectFill"></image>
|
||||
<!-- 活动卡片列表 -->
|
||||
<view
|
||||
class="activity-card"
|
||||
v-for="(item, index) in activityList"
|
||||
:key="index"
|
||||
@tap="goActivityDetail(item)"
|
||||
>
|
||||
<image class="activity-cover" :src="item.cover" mode="aspectFill"></image>
|
||||
<view class="activity-info">
|
||||
<view class="activity-location">
|
||||
<view class="location-tag">
|
||||
<image class="location-img" src="/static/img/me-icon7.png" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="location-text">福清市音西街道融侨馨苑1号楼下</text>
|
||||
<text class="location-text">{{ item.location }}</text>
|
||||
</view>
|
||||
<view class="activity-date">
|
||||
<text class="date-label">报名日期:</text>
|
||||
<text class="date-value">2025/06/30-2025/07/01</text>
|
||||
<text class="date-value">{{ item.registerDate }}</text>
|
||||
</view>
|
||||
<view class="activity-date">
|
||||
<text class="date-label">活动日期:</text>
|
||||
<text class="date-value">2025/07/05 12:00-2025/07/05 18:00</text>
|
||||
<text class="date-value">{{ item.dateRange }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -106,6 +111,7 @@ import { ref, onMounted, computed } from 'vue';
|
|||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import MemberHouseApi from '@/sheep/api/community/memberHouse';
|
||||
import NoticeApi from '@/sheep/api/community/notice';
|
||||
import ActivityApi from '@/sheep/api/community/activity';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 状态栏高度
|
||||
|
|
@ -122,7 +128,7 @@ const selectedCommunityName = ref('');
|
|||
|
||||
// 当前小区名称
|
||||
const communityName = computed(() => {
|
||||
return selectedCommunityName.value || userInfo.value.currentCommunityName || '请选择小区';
|
||||
return userInfo.value.currentCommunityName || selectedCommunityName.value || '请选择小区';
|
||||
});
|
||||
|
||||
// 是否已完成房屋认证(根据 currentHouseId 判断)
|
||||
|
|
@ -143,11 +149,16 @@ const houseAddress = computed(() => {
|
|||
// 通知数据
|
||||
const noticeTitle = ref('');
|
||||
|
||||
// 活动列表数据(首页只展示2条)
|
||||
const activityList = ref([]);
|
||||
|
||||
// 查询最新通知
|
||||
const fetchNotice = async () => {
|
||||
const { code, data } = await NoticeApi.getPage({ pageNo: 1, pageSize: 1 });
|
||||
if (code === 0 && data && data.list && data.list.length > 0) {
|
||||
noticeTitle.value = data.list[0].title;
|
||||
}else{
|
||||
noticeTitle.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -165,6 +176,8 @@ const fetchBannerList = async () => {
|
|||
icon: item.picUrl,
|
||||
url: item.url,
|
||||
}));
|
||||
} else {
|
||||
bannerList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -208,6 +221,28 @@ const fetchFunctionList = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
// 查询活动列表(首页只取2条)
|
||||
const fetchActivityList = async () => {
|
||||
const { code, data } = await ActivityApi.getPage({
|
||||
pageNo: 1,
|
||||
pageSize: 2,
|
||||
});
|
||||
if (code === 0 && data) {
|
||||
activityList.value = (data.list || []).map((item) => ({
|
||||
id: item.id,
|
||||
title: item.title || '',
|
||||
cover: item.coverImage || '/static/img/guest.png',
|
||||
location: item.location || '',
|
||||
registerDate: item.registrationStartTime && item.registrationEndTime
|
||||
? `${sheep.$helper.timeFormat(item.registrationStartTime, 'yyyy/mm/dd')} - ${sheep.$helper.timeFormat(item.registrationEndTime, 'yyyy/mm/dd')}`
|
||||
: '',
|
||||
dateRange: item.activityStartTime && item.activityEndTime
|
||||
? `${sheep.$helper.timeFormat(item.activityStartTime, 'yyyy/mm/dd hh:MM')} - ${sheep.$helper.timeFormat(item.activityEndTime, 'yyyy/mm/dd hh:MM')}`
|
||||
: '',
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
onLoad(() => {
|
||||
// 获取状态栏高度
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
|
|
@ -220,6 +255,8 @@ onLoad(() => {
|
|||
fetchBannerList();
|
||||
// 加载功能入口
|
||||
fetchFunctionList();
|
||||
// 加载活动列表(只取2条)
|
||||
fetchActivityList();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
|
@ -227,6 +264,11 @@ onShow(() => {
|
|||
if (userStore.isLogin) {
|
||||
userStore.getInfo();
|
||||
}
|
||||
// 重新加载所有数据(切换房屋后 tenant-id 已变更)
|
||||
fetchNotice();
|
||||
fetchBannerList();
|
||||
fetchFunctionList();
|
||||
fetchActivityList();
|
||||
});
|
||||
|
||||
// 获取小区房屋树
|
||||
|
|
@ -269,8 +311,22 @@ const showCommunityPicker = () => {
|
|||
communityId: selectedCommunity.communityId,
|
||||
});
|
||||
if (switchRes.code === 0) {
|
||||
// 切换成功后刷新用户信息
|
||||
await userStore.getInfo();
|
||||
const resData = switchRes.data || {};
|
||||
// 刷新 token / 用户信息
|
||||
if (resData.needRelogin) {
|
||||
await userStore.setToken(resData.accessToken, resData.refreshToken);
|
||||
} else {
|
||||
await userStore.getInfo();
|
||||
}
|
||||
// 刷新请求头 tenant-id
|
||||
if (resData.communityId) {
|
||||
uni.setStorageSync('tenant-id', String(resData.communityId));
|
||||
}
|
||||
// 重新加载首页数据
|
||||
fetchNotice();
|
||||
fetchBannerList();
|
||||
fetchFunctionList();
|
||||
fetchActivityList();
|
||||
uni.showToast({ title: '切换成功', icon: 'success' });
|
||||
}
|
||||
}
|
||||
|
|
@ -326,8 +382,8 @@ const goActivityList = () => {
|
|||
};
|
||||
|
||||
// 活动详情
|
||||
const goActivityDetail = () => {
|
||||
uni.navigateTo({ url: '/pages/sub/activity/detail?id=1' });
|
||||
const goActivityDetail = (item) => {
|
||||
uni.navigateTo({ url: `/pages/sub/activity/detail?id=${item.id}` });
|
||||
};
|
||||
|
||||
// Banner点击跳转
|
||||
|
|
|
|||
|
|
@ -136,6 +136,11 @@ const selectHouse = async (item) => {
|
|||
await sheep.$store('user').getInfo();
|
||||
}
|
||||
|
||||
// 刷新请求头中的 tenant-id
|
||||
if (resData.communityId) {
|
||||
uni.setStorageSync('tenant-id', String(resData.communityId));
|
||||
}
|
||||
|
||||
uni.showToast({ title: '已切换至 ' + item.name, icon: 'none' });
|
||||
|
||||
// 回到首页重新加载数据
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ const updateUserInfo = () => {
|
|||
avatar: user.avatar || '/static/img/login_img.png',
|
||||
nickname: user.nickname || '未登录',
|
||||
mobile: user.mobile || '',
|
||||
communityAddress: user.communityAddress || '未设置地址'
|
||||
communityAddress: user.currentCommunityName || '未设置地址'
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@
|
|||
|
||||
<!-- 地址 -->
|
||||
<view class="info-row location-row">
|
||||
<text class="sicon-location"></text>
|
||||
<image class="row-icon" src="/static/img/dw.png" mode="aspectFit" />
|
||||
<text class="location-text">{{ activityInfo.location }}</text>
|
||||
<text class="sicon-right navigation-icon" @tap="openNavigation"></text>
|
||||
<image class="row-icon navigation-icon" src="/static/img/dh.png" mode="aspectFit" @tap="openNavigation" />
|
||||
</view>
|
||||
|
||||
<!-- 服务类别 -->
|
||||
|
|
@ -80,14 +80,14 @@
|
|||
|
||||
<!-- 人数上限 -->
|
||||
<view class="info-row text-row">
|
||||
<text class="sicon-person"></text>
|
||||
<image class="row-icon" src="/static/img/people.png" mode="aspectFit" />
|
||||
<text class="label-text">人数上限:</text>
|
||||
<text class="value-text">{{ activityInfo.maxPeople }}人</text>
|
||||
</view>
|
||||
|
||||
<!-- 联系人 -->
|
||||
<view class="info-row contact-row" @tap="callPhone">
|
||||
<text class="sicon-phone"></text>
|
||||
<image class="row-icon" src="/static/img/phone.png" mode="aspectFit" />
|
||||
<text class="contact-name">{{ activityInfo.contactName }}</text>
|
||||
<text class="contact-phone">{{ activityInfo.contactPhone }}</text>
|
||||
</view>
|
||||
|
|
@ -105,14 +105,26 @@
|
|||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部固定按钮栏 -->
|
||||
<!-- 底部按钮栏 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="registered-btn" @tap="showRegisteredList">
|
||||
<text class="registered-text">已有{{ activityInfo.registeredCount }}人报名</text>
|
||||
</view>
|
||||
<view class="back-btn" @tap="goBack">
|
||||
<text class="back-text">返回</text>
|
||||
</view>
|
||||
<!-- 未报名:左侧立即报名,右侧返回 -->
|
||||
<template v-if="!activityInfo.hasRegistered">
|
||||
<view class="register-btn" @tap="handleRegister">
|
||||
<text class="register-text">立即报名</text>
|
||||
</view>
|
||||
<view class="back-btn" @tap="goBack">
|
||||
<text class="back-text">返回</text>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 已报名:左侧显示人数,右侧取消报名 -->
|
||||
<template v-else>
|
||||
<view class="registered-btn" @tap="goBack">
|
||||
<text class="registered-text">已有{{ activityInfo.registeredCount }}人报名</text>
|
||||
</view>
|
||||
<view class="cancel-btn" @tap="handleCancelRegister">
|
||||
<text class="cancel-text">取消报名</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</s-layout>
|
||||
|
|
@ -121,6 +133,8 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import ActivityApi from '@/sheep/api/community/activity';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 当前Tab
|
||||
const currentTab = ref('basic');
|
||||
|
|
@ -139,7 +153,8 @@ const activityInfo = ref({
|
|||
contactName: '',
|
||||
contactPhone: '',
|
||||
registeredCount: 0,
|
||||
introduction: ''
|
||||
hasRegistered: false,
|
||||
introduction: '',
|
||||
});
|
||||
|
||||
// 页面加载
|
||||
|
|
@ -149,50 +164,113 @@ onLoad((options) => {
|
|||
}
|
||||
});
|
||||
|
||||
// 安全解析 JSON 字符串
|
||||
function safeJsonParse(str, defaultVal = []) {
|
||||
if (!str) return defaultVal;
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
// 服务类别映射
|
||||
const serviceCategoryMap = {
|
||||
1: '社区服务',
|
||||
2: '敬老服务',
|
||||
3: '助残服务',
|
||||
4: '关爱儿童',
|
||||
5: '环保宣传',
|
||||
6: '文明礼仪',
|
||||
7: '文化教育',
|
||||
};
|
||||
|
||||
// 服务对象映射
|
||||
const serviceTargetMap = {
|
||||
1: '儿童',
|
||||
2: '孤寡老人',
|
||||
3: '残障人士',
|
||||
4: '优抚对象',
|
||||
5: '其他',
|
||||
};
|
||||
|
||||
// 加载活动详情
|
||||
async function loadActivityDetail(id) {
|
||||
// TODO: 调用API获取活动详情
|
||||
// 模拟数据
|
||||
activityInfo.value = {
|
||||
id: id,
|
||||
title: 'xxxxxxxxxxxxxxxxx活动',
|
||||
banners: [
|
||||
'/static/img/guest.png',
|
||||
'/static/img/login_img.png'
|
||||
],
|
||||
location: '福清市音西街道',
|
||||
serviceTypes: ['社区服务', '敬老服务', '助残服务', '关爱儿童'],
|
||||
targetAudience: ['儿童', '孤寡老人', '残障人士', '优抚对象'],
|
||||
registerDate: '2025/06/30-2025/07/01',
|
||||
activityDate: '2025/07/05 12:00-2025/07/05 18:00',
|
||||
maxPeople: 100,
|
||||
contactName: '王德福',
|
||||
contactPhone: '13322471131',
|
||||
registeredCount: 37,
|
||||
introduction: `<p>本次活动旨在丰富社区居民文化生活,增进邻里关系,共建和谐社区。</p><p>欢迎各位居民踊跃参与!</p>`
|
||||
};
|
||||
const { code, data } = await ActivityApi.getDetail(id);
|
||||
if (code === 0 && data) {
|
||||
activityInfo.value = {
|
||||
id: data.id,
|
||||
title: data.title || '',
|
||||
banners: safeJsonParse(data.bannerImages, [data.coverImage]).filter(Boolean),
|
||||
location: data.location || '',
|
||||
serviceTypes: (data.serviceCategories || []).map((id) => serviceCategoryMap[id] || id),
|
||||
targetAudience: (data.serviceTargets|| []).map((id) => serviceTargetMap[id] || id),
|
||||
registerDate: data.registrationStartTime && data.registrationEndTime
|
||||
? `${sheep.$helper.timeFormat(data.registrationStartTime, 'yyyy/mm/dd')} - ${sheep.$helper.timeFormat(data.registrationEndTime, 'yyyy/mm/dd')}`
|
||||
: '',
|
||||
activityDate: data.activityStartTime && data.activityEndTime
|
||||
? `${sheep.$helper.timeFormat(data.activityStartTime, 'yyyy/mm/dd hh:MM')} - ${sheep.$helper.timeFormat(data.activityEndTime, 'yyyy/mm/dd hh:MM')}`
|
||||
: '',
|
||||
maxPeople: data.maxParticipants || 0,
|
||||
contactName: data.contactPerson || '',
|
||||
contactPhone: data.contactPhone || '',
|
||||
registeredCount: data.currentParticipants || 0,
|
||||
hasRegistered: data.hasRegistered || false,
|
||||
introduction: data.content || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 打开导航
|
||||
function openNavigation() {
|
||||
// TODO: 调用地图导航
|
||||
if (!activityInfo.value.location) return;
|
||||
uni.openLocation({
|
||||
address: activityInfo.value.location,
|
||||
success: () => console.log('打开导航成功'),
|
||||
fail: () => uni.showToast({ title: '无法打开导航', icon: 'none' }),
|
||||
});
|
||||
}
|
||||
|
||||
// 拨打电话
|
||||
function callPhone() {
|
||||
if (activityInfo.value.contactPhone) {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: activityInfo.value.contactPhone
|
||||
phoneNumber: activityInfo.value.contactPhone,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 显示已报名列表
|
||||
function showRegisteredList() {
|
||||
// TODO: 弹出已报名人员列表或跳转页面
|
||||
uni.showToast({
|
||||
title: '查看报名名单',
|
||||
icon: 'none'
|
||||
// 立即报名
|
||||
async function handleRegister() {
|
||||
const { code, data } = await ActivityApi.register({
|
||||
activityId: activityInfo.value.id,
|
||||
});
|
||||
if (code === 0 && data) {
|
||||
uni.showToast({ title: '报名成功', icon: 'success' });
|
||||
activityInfo.value.hasRegistered = true;
|
||||
activityInfo.value.registeredCount++;
|
||||
} else {
|
||||
uni.showToast({ title: msg, icon: 'none' });
|
||||
}
|
||||
}
|
||||
|
||||
// 取消报名
|
||||
async function handleCancelRegister() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定取消报名吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
const { code, data } = await ActivityApi.cancelRegister(activityInfo.value.id);
|
||||
if (code === 0 && data) {
|
||||
uni.showToast({ title: '取消报名成功', icon: 'success' });
|
||||
activityInfo.value.hasRegistered = false;
|
||||
activityInfo.value.registeredCount--;
|
||||
} else {
|
||||
uni.showToast({ title: '取消报名失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -293,17 +371,17 @@ function goBack() {
|
|||
/* 信息行 */
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
padding: 20rpx 32rpx;
|
||||
|
||||
.sicon-location,
|
||||
.sicon-phone,
|
||||
.sicon-person {
|
||||
font-size: 32rpx;
|
||||
color: #666666;
|
||||
.row-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 8rpx;
|
||||
margin-top: 2rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* 地址行 */
|
||||
|
|
@ -397,17 +475,14 @@ function goBack() {
|
|||
padding: 32rpx;
|
||||
}
|
||||
|
||||
/* 底部占位:基础间距 + 安全区域 */
|
||||
/* 底部占位 */
|
||||
.bottom-placeholder {
|
||||
height: calc(140rpx + env(safe-area-inset-bottom));
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
/* 底部固定按钮栏 */
|
||||
/* 底部按钮栏:正常文档流,flex-shrink:0 保证不被压缩 */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
|
|
@ -416,10 +491,11 @@ function goBack() {
|
|||
background-color: #FFFFFF;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
.registered-btn {
|
||||
.registered-btn,
|
||||
.register-btn,
|
||||
.cancel-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border: 2rpx solid #FA7E49;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -428,6 +504,10 @@ function goBack() {
|
|||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.registered-btn {
|
||||
border: 2rpx solid #FA7E49;
|
||||
|
||||
.registered-text {
|
||||
font-size: 30rpx;
|
||||
|
|
@ -436,6 +516,26 @@ function goBack() {
|
|||
}
|
||||
}
|
||||
|
||||
.register-btn {
|
||||
background-color: #FA7E49;
|
||||
|
||||
.register-text {
|
||||
font-size: 30rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
border: 2rpx solid #999999;
|
||||
|
||||
.cancel-text {
|
||||
font-size: 30rpx;
|
||||
color: #999999;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 240rpx;
|
||||
height: 88rpx;
|
||||
|
|
|
|||
|
|
@ -9,12 +9,25 @@
|
|||
<view class="list-header">
|
||||
<!-- 顶部提示条 -->
|
||||
<view class="top-banner">
|
||||
<text class="banner-text">融侨馨苑开展活动啦!!</text>
|
||||
<text class="banner-text">{{ communityName }}开展活动啦!!</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 活动列表 - 独立滚动 -->
|
||||
<scroll-view class="activity-scroll" scroll-y :style="{ height: scrollViewHeight + 'px' }">
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="activityList.length === 0 && !loading">
|
||||
<text class="empty-text">暂无活动</text>
|
||||
</view>
|
||||
|
||||
<!-- 活动列表 - 滚动 -->
|
||||
<scroll-view
|
||||
v-else
|
||||
class="activity-scroll"
|
||||
scroll-y
|
||||
@scrolltolower="onLoadMore"
|
||||
@refresherrefresh="onRefresh"
|
||||
:refresher-triggered="loading"
|
||||
refresher-enabled
|
||||
>
|
||||
<view class="list-inner">
|
||||
<view
|
||||
class="activity-item"
|
||||
|
|
@ -32,19 +45,19 @@
|
|||
<text class="item-location">{{ item.location }}</text>
|
||||
<text class="item-status" :class="'status-' + item.statusType">{{ item.statusText }}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<view class="item-row nowrap">
|
||||
<text class="item-label">报名日期:</text>
|
||||
<text class="item-value">{{ item.registerDate }}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<view class="item-row wrap">
|
||||
<text class="item-date">{{ item.dateRange }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="activityList.length === 0">
|
||||
<text class="empty-text">暂无活动</text>
|
||||
<!-- 加载更多提示 -->
|
||||
<view class="load-more" v-if="activityList.length > 0">
|
||||
<text class="load-text">{{ finished ? '没有更多了' : loading ? '加载中...' : '上拉加载更多' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
|
@ -58,114 +71,105 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import ActivityApi from '@/sheep/api/community/activity';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 列表滚动区域高度(单位:px)
|
||||
const scrollViewHeight = ref(0);
|
||||
// 分页参数
|
||||
const pageNo = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const total = ref(0);
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
|
||||
// 小区名称
|
||||
const communityName = ref('');
|
||||
|
||||
// 活动列表数据
|
||||
const activityList = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: 'xxxxxxxxxxxxx活动',
|
||||
location: '福清市音西街道融侨馨苑',
|
||||
statusText: '进行中',
|
||||
statusType: 'ongoing',
|
||||
registerDate: '2025/06/30-2025/07/01',
|
||||
dateRange: '2025/07/05 12:00-2025/07/05 18:00',
|
||||
cover: '/static/img/guest.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'xxxxxxxxxxxxx活动',
|
||||
location: '福清市音西街道融侨馨苑',
|
||||
statusText: '报名中',
|
||||
statusType: 'registering',
|
||||
registerDate: '2025/06/30-2025/07/01',
|
||||
dateRange: '2025/07/05 12:00-2025/07/05 18:00',
|
||||
cover: '/static/img/guest.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'xxxxxxxxxxxxx活动',
|
||||
location: '福清市音西街道融侨馨苑',
|
||||
statusText: '已结束',
|
||||
statusType: 'ended',
|
||||
registerDate: '2025/06/30-2025/07/01',
|
||||
dateRange: '2025/07/05 12:00-2025/07/05 18:00',
|
||||
cover: '/static/img/guest.png'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'xxxxxxxxxxxxx活动',
|
||||
location: '福清市音西街道融侨馨苑',
|
||||
statusText: '已结束',
|
||||
statusType: 'ended',
|
||||
registerDate: '2025/06/30-2025/07/01',
|
||||
dateRange: '2025/07/05 12:00-2025/07/05 18:00',
|
||||
cover: '/static/img/guest.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'xxxxxxxxxxxxx活动',
|
||||
location: '福清市音西街道融侨馨苑',
|
||||
statusText: '已结束',
|
||||
statusType: 'ended',
|
||||
registerDate: '2025/06/30-2025/07/01',
|
||||
dateRange: '2025/07/05 12:00-2025/07/05 18:00',
|
||||
cover: '/static/img/guest.png'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'xxxxxxxxxxxxx活动',
|
||||
location: '福清市音西街道融侨馨苑',
|
||||
statusText: '已结束',
|
||||
statusType: 'ended',
|
||||
registerDate: '2025/06/30-2025/07/01',
|
||||
dateRange: '2025/07/05 12:00-2025/07/05 18:00',
|
||||
cover: '/static/img/guest.png'
|
||||
}
|
||||
]);
|
||||
const activityList = ref([]);
|
||||
|
||||
// 页面加载
|
||||
onLoad(() => {
|
||||
const userInfo = sheep.$store('user').userInfo;
|
||||
communityName.value = userInfo?.currentCommunityName || '';
|
||||
loadActivityList();
|
||||
});
|
||||
|
||||
// 渲染完成后计算scroll-view高度
|
||||
onMounted(() => {
|
||||
const instance = getCurrentInstance();
|
||||
nextTick(() => {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
uni.createSelectorQuery()
|
||||
.in(instance)
|
||||
.select('.list-header')
|
||||
.boundingClientRect((rect) => {
|
||||
if (rect) {
|
||||
// scroll-view高度 = 屏幕可用高度 - 头部区域高度 - 头部距顶部距离
|
||||
scrollViewHeight.value = sysInfo.windowHeight - rect.height - rect.top;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
});
|
||||
|
||||
// 加载活动列表
|
||||
async function loadActivityList() {
|
||||
// TODO: 调用API获取活动列表
|
||||
if (loading.value || finished.value) return;
|
||||
loading.value = true;
|
||||
|
||||
const { code, data } = await ActivityApi.getPage({
|
||||
pageNo: pageNo.value,
|
||||
pageSize: pageSize.value,
|
||||
});
|
||||
|
||||
if (code === 0 && data) {
|
||||
const list = (data.list || []).map((item) => ({
|
||||
id: item.id,
|
||||
title: item.title || '',
|
||||
cover: item.coverImage || '/static/img/guest.png',
|
||||
status: item.status,
|
||||
statusText: getStatusText(item.status),
|
||||
statusType: getStatusType(item.status),
|
||||
registerDate: item.registrationStartTime && item.registrationEndTime
|
||||
? `${sheep.$helper.timeFormat(item.registrationStartTime, 'yyyy/mm/dd')} - ${sheep.$helper.timeFormat(item.registrationEndTime, 'yyyy/mm/dd')}`
|
||||
: '',
|
||||
dateRange: item.activityStartTime && item.activityEndTime
|
||||
? `${sheep.$helper.timeFormat(item.activityStartTime, 'yyyy/mm/dd hh:MM')} - ${sheep.$helper.timeFormat(item.activityEndTime, 'yyyy/mm/dd hh:MM')}`
|
||||
: '',
|
||||
location: item.location || '',
|
||||
}));
|
||||
|
||||
activityList.value = pageNo.value === 1 ? list : [...activityList.value, ...list];
|
||||
total.value = data.total || 0;
|
||||
|
||||
if (activityList.value.length >= total.value) {
|
||||
finished.value = true;
|
||||
} else {
|
||||
pageNo.value++;
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 状态映射
|
||||
function getStatusText(status) {
|
||||
const map = { 0: '报名中', 1: '进行中', 2: '已结束' };
|
||||
return map[status] || '未知';
|
||||
}
|
||||
|
||||
function getStatusType(status) {
|
||||
const map = { 0: 'registering', 1: 'ongoing', 2: 'ended' };
|
||||
return map[status] || 'ended';
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
function onRefresh() {
|
||||
pageNo.value = 1;
|
||||
finished.value = false;
|
||||
activityList.value = [];
|
||||
loadActivityList();
|
||||
}
|
||||
|
||||
// 滚动到底部加载更多
|
||||
function onLoadMore() {
|
||||
loadActivityList();
|
||||
}
|
||||
|
||||
// 跳转详情
|
||||
function goDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub/activity/detail?id=${item.id}`
|
||||
url: `/pages/sub/activity/detail?id=${item.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
// 浮动按钮点击
|
||||
// 浮动按钮点击 - 跳转我的报名
|
||||
function handleFloatBtn() {
|
||||
// TODO: 发布活动或跳转相关页面
|
||||
uni.navigateTo({ url: '/pages/sub/activity/my-registration' });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -174,7 +178,8 @@ function handleFloatBtn() {
|
|||
.activity-list-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
height: calc(100vh - 176rpx);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
@ -205,9 +210,10 @@ function handleFloatBtn() {
|
|||
}
|
||||
}
|
||||
|
||||
/* 活动列表滚动区域 - 高度由JS动态绑定 */
|
||||
/* 活动列表滚动区域:flex:1 占满剩余空间,height:0 是小程序 scroll-view 配合 flex 的关键 */
|
||||
.activity-scroll {
|
||||
flex: 1; /* 占据剩余空间 */
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 列表内容包裹层 */
|
||||
|
|
@ -254,6 +260,15 @@ function handleFloatBtn() {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8rpx;
|
||||
|
||||
&.nowrap {
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.item-location {
|
||||
|
|
@ -279,23 +294,28 @@ function handleFloatBtn() {
|
|||
}
|
||||
|
||||
.item-value {
|
||||
font-size: 24rpx;
|
||||
font-size: 22rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-date {
|
||||
font-size: 24rpx;
|
||||
font-size: 20rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more{
|
||||
text-align: center;
|
||||
padding-top: 30rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
flex: 1;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,314 @@
|
|||
<!-- 我的报名页面 -->
|
||||
<template>
|
||||
<s-layout title="我的报名">
|
||||
<view class="my-registration-page">
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="list.length === 0 && !loading">
|
||||
<text class="empty-text">暂无报名记录</text>
|
||||
</view>
|
||||
|
||||
<!-- 报名列表 -->
|
||||
<scroll-view
|
||||
v-else
|
||||
class="registration-scroll"
|
||||
scroll-y
|
||||
@scrolltolower="onLoadMore"
|
||||
@refresherrefresh="onRefresh"
|
||||
:refresher-triggered="loading"
|
||||
refresher-enabled
|
||||
>
|
||||
<view class="list-inner">
|
||||
<view
|
||||
class="registration-item"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
@tap="goDetail(item)"
|
||||
>
|
||||
<!-- 左侧封面图 -->
|
||||
<image class="item-cover" :src="item.cover" mode="aspectFill" />
|
||||
|
||||
<!-- 右侧内容区 -->
|
||||
<view class="item-content">
|
||||
<text class="item-title">{{ item.title }}</text>
|
||||
<view class="item-row">
|
||||
<text class="item-location">{{ item.location }}</text>
|
||||
<text class="item-status" :class="'status-' + item.statusType">{{ item.statusText }}</text>
|
||||
</view>
|
||||
<view class="item-row nowrap">
|
||||
<text class="item-label">报名日期:</text>
|
||||
<text class="item-value">{{ item.registerDate }}</text>
|
||||
</view>
|
||||
<view class="item-row wrap">
|
||||
<text class="item-date">{{ item.dateRange }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view class="load-more" v-if="list.length > 0">
|
||||
<text class="load-text">{{ finished ? '没有更多了' : loading ? '加载中...' : '上拉加载更多' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部固定返回按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="back-btn" @tap="goBack">返回</view>
|
||||
</view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import ActivityApi from '@/sheep/api/community/activity';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 分页参数
|
||||
const pageNo = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const total = ref(0);
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
|
||||
// 报名列表数据
|
||||
const list = ref([]);
|
||||
|
||||
// 页面加载
|
||||
onLoad(() => {
|
||||
loadList();
|
||||
});
|
||||
|
||||
// 加载报名列表
|
||||
async function loadList() {
|
||||
if (loading.value || finished.value) return;
|
||||
loading.value = true;
|
||||
|
||||
const { code, data } = await ActivityApi.getMyRegistrationPage({
|
||||
pageNo: pageNo.value,
|
||||
pageSize: pageSize.value,
|
||||
});
|
||||
|
||||
if (code === 0 && data) {
|
||||
const mapped = (data.list || []).map((item) => ({
|
||||
id: item.id,
|
||||
activityId: item.activityId,
|
||||
title: item.activityTitle || '',
|
||||
cover: item.activityCoverImage || '/static/img/guest.png',
|
||||
status: item.status,
|
||||
statusText: getStatusText(item.status),
|
||||
statusType: getStatusType(item.status),
|
||||
// 接口暂未返回以下字段,先留空兼容;若后端补充后可自动展示
|
||||
location: item.location || '',
|
||||
registerDate: item.registrationStartTime && item.registrationEndTime
|
||||
? `${sheep.$helper.timeFormat(item.registrationStartTime, 'yyyy/mm/dd')} - ${sheep.$helper.timeFormat(item.registrationEndTime, 'yyyy/mm/dd')}`
|
||||
: '',
|
||||
dateRange: item.activityStartTime && item.activityEndTime
|
||||
? `${sheep.$helper.timeFormat(item.activityStartTime, 'yyyy/mm/dd hh:MM')} - ${sheep.$helper.timeFormat(item.activityEndTime, 'yyyy/mm/dd hh:MM')}`
|
||||
: '',
|
||||
}));
|
||||
|
||||
list.value = pageNo.value === 1 ? mapped : [...list.value, ...mapped];
|
||||
total.value = data.total || 0;
|
||||
|
||||
if (list.value.length >= total.value) {
|
||||
finished.value = true;
|
||||
} else {
|
||||
pageNo.value++;
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 报名状态映射(0-待审核 1-已通过 2-已拒绝 3-已取消)
|
||||
function getStatusText(status) {
|
||||
const map = { 0: '待审核', 1: '已通过', 2: '已拒绝', 3: '已取消' };
|
||||
return map[status] || '未知';
|
||||
}
|
||||
|
||||
function getStatusType(status) {
|
||||
const map = { 0: 'pending', 1: 'passed', 2: 'rejected', 3: 'cancelled' };
|
||||
return map[status] || 'cancelled';
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
function onRefresh() {
|
||||
pageNo.value = 1;
|
||||
finished.value = false;
|
||||
list.value = [];
|
||||
loadList();
|
||||
}
|
||||
|
||||
// 滚动到底部加载更多
|
||||
function onLoadMore() {
|
||||
loadList();
|
||||
}
|
||||
|
||||
// 跳转详情(使用活动ID)
|
||||
function goDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub/activity/detail?id=${item.activityId}`,
|
||||
});
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 页面容器 - flex布局,底部按钮固定 */
|
||||
.my-registration-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 176rpx);
|
||||
overflow: hidden;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
/* 报名列表滚动区域 */
|
||||
.registration-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 列表内容包裹层 */
|
||||
.list-inner {
|
||||
padding: 24rpx 32rpx;
|
||||
}
|
||||
|
||||
/* 报名卡片 */
|
||||
.registration-item {
|
||||
display: flex;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
.item-cover {
|
||||
width: 200rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
margin-left: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.item-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #1890FF;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8rpx;
|
||||
|
||||
&.nowrap {
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.item-location {
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// 状态标签颜色(0-待审核 1-已通过 2-已拒绝 3-已取消)
|
||||
.item-status {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.status-passed { color: #52C41A; } // 已通过-绿色
|
||||
&.status-pending { color: #FAAD14; } // 待审核-橙色
|
||||
&.status-rejected { color: #FF4D4F; } // 已拒绝-红色
|
||||
&.status-cancelled { color: #999999; } // 已取消-灰色
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-date {
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
|
||||
.load-text {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部固定栏 */
|
||||
.bottom-bar {
|
||||
flex-shrink: 0;
|
||||
padding: 24rpx 40rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
// background-color: #FFFFFF;
|
||||
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.back-btn {
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
background-color: #FA7E49;
|
||||
color: #FFFFFF;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 44rpx;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import request from '@/sheep/request';
|
||||
|
||||
const ActivityApi = {
|
||||
// 获取活动列表
|
||||
getPage: (data) => {
|
||||
return request({
|
||||
url: '/community/activity/page',
|
||||
method: 'GET',
|
||||
params: data,
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 获取活动详情
|
||||
getDetail: (id) => {
|
||||
return request({
|
||||
url: '/community/activity/get',
|
||||
method: 'GET',
|
||||
params: { id },
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 活动报名
|
||||
register: (data) => {
|
||||
return request({
|
||||
url: '/community/activity/registration',
|
||||
method: 'POST',
|
||||
data,
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 取消报名
|
||||
cancelRegister: (id) => {
|
||||
return request({
|
||||
url: '/community/activity/registration/cancel',
|
||||
method: 'POST',
|
||||
params: { id },
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 我的报名列表
|
||||
getMyRegistrationPage: (data) => {
|
||||
return request({
|
||||
url: '/community/activity/registration/page',
|
||||
method: 'GET',
|
||||
params: data,
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default ActivityApi;
|
||||
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |