..
This commit is contained in:
@@ -0,0 +1,516 @@
|
||||
<template>
|
||||
<view class="sign-page">
|
||||
<!-- 背景装饰 -->
|
||||
<view class="bg-decoration"></view>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<view class="content-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="title-area">
|
||||
<text class="title">活动签到</text>
|
||||
</view>
|
||||
|
||||
<!-- 活动选择区 -->
|
||||
<view class="selector-card">
|
||||
<t-dropdown-menu class="activity-dropdown">
|
||||
<t-dropdown-item
|
||||
v-model="selectedActivity"
|
||||
label="请选择活动"
|
||||
:options="activityOptions"
|
||||
@change="handleActivityChange"
|
||||
/>
|
||||
</t-dropdown-menu>
|
||||
</view>
|
||||
|
||||
<!-- 签到区域 -->
|
||||
<view class="sign-card" v-if="currentActivity">
|
||||
<view class="activity-title">{{ currentActivity.title }}</view>
|
||||
|
||||
<view class="qr-scan-area">
|
||||
<view class="qr-frame">
|
||||
<view class="corner top-left"></view>
|
||||
<view class="corner top-right"></view>
|
||||
<view class="corner bottom-left"></view>
|
||||
<view class="corner bottom-right"></view>
|
||||
<t-icon name="qrcode" size="120rpx" class="qr-icon" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<t-button size="large" class="scan-btn" @tap="handleScan" :disabled="isSignedIn">
|
||||
{{ isSignedIn ? '已完成签到' : '点击扫码签到' }}
|
||||
</t-button>
|
||||
|
||||
<view class="sign-status" v-if="signStatus !== 'unsigned'">
|
||||
<t-icon name="check-circle-filled" size="48rpx" class="status-icon success" v-if="signStatus === 'signed'" />
|
||||
<t-icon name="time" size="48rpx" class="status-icon warning" v-else-if="signStatus === 'late'" />
|
||||
<text class="status-text">{{ signStatusText }}</text>
|
||||
<text class="sign-time" v-if="latestSignTime">{{ latestSignTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无活动状态 -->
|
||||
<view class="empty-state" v-if="!currentActivity">
|
||||
<t-icon name="folder" size="120rpx" class="empty-icon" />
|
||||
<text class="empty-text">暂无可签到的活动</text>
|
||||
<t-button size="small" variant="outline" class="refresh-btn" @tap="getMyActivities">刷新</t-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRequest } from '@/utils/request';
|
||||
import { Toast } from 'tdesign-mobile-vue';
|
||||
import moment from 'moment';
|
||||
|
||||
// 模拟活动数据
|
||||
const mockActivities = [
|
||||
{
|
||||
id: '1',
|
||||
title: '2023年度篮球联赛',
|
||||
cover: 'https://img01.71360.com/file/read/www/M00/4C/98/rBsAFWQUdqaABB-oAAHYfGPYE3Y799.jpg',
|
||||
startTime: '2023-12-10 09:00:00',
|
||||
endTime: '2023-12-15 18:00:00',
|
||||
address: '市体育中心主场馆',
|
||||
signStartTime: '2023-11-20 00:00:00',
|
||||
signEndTime: '2023-12-05 23:59:59',
|
||||
signedIn: false,
|
||||
signTime: null
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '企业文化交流会',
|
||||
cover: 'https://img.zcool.cn/community/01a4d75b04a27ea801209e1f0fb01d.jpg@1280w_1l_2o_100sh.jpg',
|
||||
startTime: '2023-11-25 13:30:00',
|
||||
endTime: '2023-11-25 17:30:00',
|
||||
address: '国际会议中心3号厅',
|
||||
signStartTime: '2023-11-01 00:00:00',
|
||||
signEndTime: '2023-11-24 23:59:59',
|
||||
signedIn: true,
|
||||
signTime: '2023-11-25 13:20:14',
|
||||
status: 'signed'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '技术创新大赛决赛',
|
||||
cover: 'https://img.zcool.cn/community/01b1f95b04a255a8012182a7ebfe26.jpg@1280w_1l_2o_100sh.jpg',
|
||||
startTime: '2023-12-05 09:00:00',
|
||||
endTime: '2023-12-05 18:00:00',
|
||||
address: '科技园区创新中心',
|
||||
signStartTime: '2023-11-10 00:00:00',
|
||||
signEndTime: '2023-12-04 23:59:59',
|
||||
signedIn: true,
|
||||
signTime: '2023-12-05 09:15:22',
|
||||
status: 'late'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: '2023年迎新晚会',
|
||||
cover: 'https://img.zcool.cn/community/01f2b35bd123b1a801208f8bd8710a.jpg@1280w_1l_2o_100sh.jpg',
|
||||
startTime: '2023-12-31 19:00:00',
|
||||
endTime: '2024-01-01 00:30:00',
|
||||
address: '文化广场中心舞台',
|
||||
signStartTime: '2023-12-15 00:00:00',
|
||||
signEndTime: '2023-12-30 23:59:59',
|
||||
signedIn: false,
|
||||
signTime: null
|
||||
}
|
||||
];
|
||||
|
||||
// 判断环境是否为开发环境
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
// 活动列表
|
||||
const myActivities = ref([]);
|
||||
const activityOptions = computed(() => {
|
||||
return myActivities.value.map(item => ({
|
||||
label: item.title,
|
||||
value: item.id
|
||||
}));
|
||||
});
|
||||
|
||||
// 选中的活动ID
|
||||
const selectedActivity = ref('');
|
||||
const currentActivity = computed(() => {
|
||||
return myActivities.value.find(item => item.id === selectedActivity.value);
|
||||
});
|
||||
|
||||
// 是否已签到
|
||||
const isSignedIn = computed(() => {
|
||||
return currentActivity.value?.signedIn || false;
|
||||
});
|
||||
|
||||
// 最近签到时间
|
||||
const latestSignTime = computed(() => {
|
||||
if (!currentActivity.value?.signTime) return null;
|
||||
return `签到时间: ${moment(currentActivity.value.signTime).format('YYYY-MM-DD HH:mm:ss')}`;
|
||||
});
|
||||
|
||||
// 签到状态
|
||||
const signStatus = computed(() => {
|
||||
return currentActivity.value?.status || 'unsigned';
|
||||
});
|
||||
|
||||
const signStatusText = computed(() => {
|
||||
switch(signStatus.value) {
|
||||
case 'signed':
|
||||
return '签到成功';
|
||||
case 'late':
|
||||
return '迟到签到';
|
||||
default:
|
||||
return '未签到';
|
||||
}
|
||||
});
|
||||
|
||||
// 获取我的活动
|
||||
const getMyActivities = async () => {
|
||||
try {
|
||||
Toast({ message: '加载中...', theme: 'loading' });
|
||||
|
||||
if (isDev) {
|
||||
// 开发环境使用模拟数据
|
||||
setTimeout(() => {
|
||||
myActivities.value = mockActivities;
|
||||
if (myActivities.value.length > 0) {
|
||||
selectedActivity.value = myActivities.value[0].id;
|
||||
}
|
||||
Toast({ message: '加载成功', theme: 'success' });
|
||||
}, 800);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生产环境调用接口
|
||||
const response = await useRequest().get('/client/activity/myActivities');
|
||||
if (response.code === 0) {
|
||||
myActivities.value = response.data || [];
|
||||
if (myActivities.value.length > 0) {
|
||||
selectedActivity.value = myActivities.value[0].id;
|
||||
}
|
||||
Toast({ message: '加载成功', theme: 'success' });
|
||||
} else {
|
||||
Toast({ message: '获取活动失败', theme: 'error' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取活动失败', error);
|
||||
Toast({ message: '网络异常,请重试', theme: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// 活动切换
|
||||
const handleActivityChange = (value) => {
|
||||
// 切换时不需要额外操作
|
||||
};
|
||||
|
||||
// 扫码
|
||||
const handleScan = () => {
|
||||
if (!currentActivity.value) {
|
||||
Toast({ message: '请先选择活动', theme: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSignedIn.value) {
|
||||
Toast({ message: '已完成签到', theme: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
Toast({ message: '准备扫码', theme: 'loading' });
|
||||
setTimeout(() => {
|
||||
if (isDev) {
|
||||
// 开发环境模拟扫码成功
|
||||
setTimeout(() => {
|
||||
Toast({ message: '签到中...', theme: 'loading' });
|
||||
setTimeout(() => {
|
||||
const randomSuccess = Math.random() > 0.2;
|
||||
if (randomSuccess) {
|
||||
const isLate = Math.random() > 0.7;
|
||||
const status = isLate ? 'late' : 'signed';
|
||||
const statusText = isLate ? '迟到签到' : '签到成功';
|
||||
|
||||
// 更新活动签到状态
|
||||
const activity = myActivities.value.find(a => a.id === selectedActivity.value);
|
||||
if (activity) {
|
||||
activity.signedIn = true;
|
||||
activity.signTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
activity.status = status;
|
||||
}
|
||||
|
||||
Toast({ message: statusText, theme: isLate ? 'warning' : 'success' });
|
||||
} else {
|
||||
Toast({ message: '二维码无效', theme: 'error' });
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生产环境实际扫码
|
||||
uni.scanCode({
|
||||
success: async (res) => {
|
||||
try {
|
||||
Toast({ message: '签到中...', theme: 'loading' });
|
||||
// 将扫码结果发送到后端
|
||||
const response = await useRequest().post('/client/activity/sign', {
|
||||
activityId: selectedActivity.value,
|
||||
qrCode: res.result
|
||||
});
|
||||
|
||||
if (response.code === 0) {
|
||||
// 更新活动签到状态
|
||||
const activity = myActivities.value.find(a => a.id === selectedActivity.value);
|
||||
if (activity) {
|
||||
activity.signedIn = true;
|
||||
activity.signTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
activity.status = response.data.status || 'signed';
|
||||
}
|
||||
|
||||
const statusText = activity.status === 'late' ? '迟到签到' : '签到成功';
|
||||
Toast({ message: statusText, theme: activity.status === 'late' ? 'warning' : 'success' });
|
||||
} else {
|
||||
Toast({ message: response.message || '签到失败', theme: 'error' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('签到失败', error);
|
||||
Toast({ message: '网络异常,请重试', theme: 'error' });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
Toast({ message: '扫码取消', theme: 'warning' });
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getMyActivities();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.sign-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
padding-top: 30rpx;
|
||||
}
|
||||
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 380rpx;
|
||||
background: linear-gradient(to bottom, rgb(244, 90, 90, 0.8), rgb(244, 90, 90, 0.2));
|
||||
border-bottom-left-radius: 40rpx;
|
||||
border-bottom-right-radius: 40rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 0 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-area {
|
||||
margin-bottom: 40rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.selector-card {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.activity-dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.t-dropdown-menu) {
|
||||
background-color: transparent !important;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
:deep(.t-dropdown-menu__item) {
|
||||
font-size: 30rpx;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
:deep(.t-dropdown-item__content) {
|
||||
border-radius: 12rpx !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sign-card {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 40rpx;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.qr-scan-area {
|
||||
margin-bottom: 40rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.qr-frame {
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(244, 90, 90, 0.03);
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.corner {
|
||||
position: absolute;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-color: rgb(244, 90, 90);
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-top-width: 6rpx;
|
||||
border-left-width: 6rpx;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-top-width: 6rpx;
|
||||
border-right-width: 6rpx;
|
||||
}
|
||||
|
||||
.bottom-left {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-bottom-width: 6rpx;
|
||||
border-left-width: 6rpx;
|
||||
}
|
||||
|
||||
.bottom-right {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-bottom-width: 6rpx;
|
||||
border-right-width: 6rpx;
|
||||
}
|
||||
|
||||
.qr-icon {
|
||||
color: rgb(244, 90, 90);
|
||||
}
|
||||
|
||||
.scan-btn {
|
||||
width: 70%;
|
||||
height: 90rpx;
|
||||
background-color: rgb(244, 90, 90);
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 45rpx;
|
||||
box-shadow: 0 8rpx 20rpx rgba(244, 90, 90, 0.3);
|
||||
transition: all 0.3s;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.scan-btn:active {
|
||||
transform: scale(0.97);
|
||||
box-shadow: 0 4rpx 10rpx rgba(244, 90, 90, 0.2);
|
||||
}
|
||||
|
||||
.scan-btn[disabled] {
|
||||
background-color: #f0f0f0;
|
||||
color: #bbbbbb;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sign-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.status-icon.success {
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.status-icon.warning {
|
||||
color: #ff9f00;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.sign-time {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
color: #ddd;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 30rpx;
|
||||
color: #999;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
color: rgb(244, 90, 90);
|
||||
border-color: rgb(244, 90, 90);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user