This commit is contained in:
那些花儿
2025-04-28 15:32:41 +08:00
parent cb3bcb57bc
commit 12c3afb4a6
42 changed files with 3272 additions and 1627 deletions
+397 -30
View File
@@ -1,53 +1,60 @@
<template>
<view class="page-container">
<t-dropdown-menu>
<t-dropdown-menu class="top-dropdown">
<t-dropdown-item :options="actOptions" :value="selectActId" @change="getSignInfo"/>
</t-dropdown-menu>
<view class="content-container">
<view class="scan-area" @click="handleScan">
<view class="scan-icon-wrapper">
<view class="scan-area animate__animated animate__fadeIn" @click="handleScan">
<view class="scan-icon-wrapper pulse-animation">
<t-icon name="scan" size="80" color="#f45a5a"></t-icon>
<view class="scan-circles"></view>
</view>
<t-button shape="round"
theme="primary"
size="medium">扫描二维码签到
size="medium"
class="scan-button">扫描二维码签到
</t-button>
</view>
<view class="guide-area">
<view class="guide-area animate__animated animate__fadeInUp">
<view class="guide-title">签到说明</view>
<view class="guide-list">
<view class="guide-item">
<uni-icons class="guide-icon" type="info" size="16" color="#999999"/>
<view class="guide-item animate__animated animate__fadeInLeft" style="animation-delay: 0.1s">
<t-icon name="notification" size="16" color="#ff5f5c" class="guide-icon"></t-icon>
<text class="guide-text">请对准活动二维码进行扫描</text>
</view>
<view class="guide-item">
<uni-icons class="guide-icon" type="info" size="16" color="#999999"/>
<view class="guide-item animate__animated animate__fadeInLeft" style="animation-delay: 0.2s">
<t-icon name="notification" size="16" color="#ff5f5c" class="guide-icon"></t-icon>
<text class="guide-text">确保手机相机权限已开启</text>
</view>
<view class="guide-item">
<uni-icons class="guide-icon" type="info" size="16" color="#999999"/>
<view class="guide-item animate__animated animate__fadeInLeft" style="animation-delay: 0.3s">
<t-icon name="notification" size="16" color="#ff5f5c" class="guide-icon"></t-icon>
<text class="guide-text">保持二维码清晰可见</text>
</view>
</view>
</view>
<view class="current-status-area">
<view class="current-status-area animate__animated animate__fadeInUp" style="animation-delay: 0.2s">
<view class="status-header">
<text class="status-title">当前签到状态</text>
</view>
<view v-if="signInfo" class="status-content">
<template v-if="signInfo.isSignIn">
<view class="sign-success">
<t-icon name="task-checked-1" size="48" color="#07c160"></t-icon>
<div class="success-icon-wrapper">
<t-icon name="task-checked-1" size="48" color="#07c160"></t-icon>
<svg class="checkmark-circle" viewBox="0 0 52 52">
<circle class="checkmark-circle__circle" cx="26" cy="26" r="25" fill="none" />
</svg>
</div>
<text class="success-text">已完成签到</text>
<view class="sign-detail">
<view class="detail-item">
<uni-icons type="calendar" size="14" color="#666666" />
<uni-icons type="calendar" size="14" color="#666666"/>
<text>签到时间:{{ signInfo.signInTime }}</text>
</view>
<view class="detail-item">
<uni-icons type="location" size="14" color="#666666" />
<text>签到地点:{{ signInfo.location }}</text>
</view>
<!-- <view class="detail-item">-->
<!-- <uni-icons type="location" size="14" color="#666666"/>-->
<!-- <text>签到地点:{{ signInfo.location }}</text>-->
<!-- </view>-->
</view>
</view>
</template>
@@ -61,6 +68,29 @@
</view>
</view>
</view>
<div class="bg-decoration">
<svg class="wave" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path fill="#f45a5a" fill-opacity="0.1" d="M0,160L48,154.7C96,149,192,139,288,144C384,149,480,171,576,176C672,181,768,171,864,154.7C960,139,1056,117,1152,128C1248,139,1344,181,1392,202.7L1440,224L1440,0L1392,0C1344,0,1248,0,1152,0C1056,0,960,0,864,0C768,0,672,0,576,0C480,0,384,0,288,0C192,0,96,0,48,0L0,0Z"></path>
</svg>
<svg class="dots" width="100" height="100" viewBox="0 0 100 100">
<circle fill="#f45a5a" fill-opacity="0.2" cx="10" cy="10" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="30" cy="10" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="50" cy="10" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="70" cy="10" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="90" cy="10" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="10" cy="30" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="30" cy="30" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="50" cy="30" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="70" cy="30" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="90" cy="30" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="10" cy="50" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="30" cy="50" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="50" cy="50" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="70" cy="50" r="3" />
<circle fill="#f45a5a" fill-opacity="0.2" cx="90" cy="50" r="3" />
</svg>
</div>
</view>
<ScanQRCode
@@ -73,7 +103,7 @@
<script setup>
import {ref, computed, onMounted} from 'vue';
import {useRequest} from '@/utils/request';
import {Toast} from 'tdesign-mobile-vue';
import {Dialog, Toast} from 'tdesign-mobile-vue';
import moment from 'moment';
import ScanQRCode from '@/components/scanQrCode.vue'
@@ -99,15 +129,16 @@ onMounted(() => {
// 获取活动列表
async function listMineAct() {
try {
const {data, code} = await request.get('/client/activity/list-mine-act')
const {data, code, msg} = await request.get('/client/activity/list-mine-act')
if (code === 0) {
list.value = data
if (list.value.length) {
selectActId.value = list.value[0].id
console.log(selectActId.value)
await getSignInfo();
}
} else {
Toast.error(data.msg)
Toast.error(msg)
}
} catch (e) {
console.error(e)
@@ -116,9 +147,9 @@ async function listMineAct() {
}
// 获取签到信息
async function getSignInfo(val){
async function getSignInfo(val) {
try {
const {code, data, msg} = await request.get('/client/activity/sign-info/' + (val ? val :selectActId.value))
const {code, data, msg} = await request.get('/client/activity/sign-in-info/' + (val ? val : selectActId.value))
if (code === 0) {
signInfo.value = data
} else {
@@ -135,26 +166,125 @@ function handleScan() {
qrScannerRef.value.startScan()
}
function handleScanSuccess(val){
console.log(val)
Toast.success(val)
// 扫码成功回调
function handleScanSuccess(val) {
console.log('扫描成功,活动ID为' + val, "当前选择活动ID为" + selectActId.value)
if (!val) {
Toast.error("请扫描合法的二维码")
return
}
if (String(val) !== String(selectActId.value)) {
Toast.error('请扫码签到当前活动,扫描成功,活动ID为' + val + ",当前选择活动ID为" + selectActId.value)
return
}
signIn()
}
// 签到
async function signIn() {
try {
await uni.showLoading({
title: '签到中...',
mask: true,
})
const {code, msg} = await request.post('/client/activity/sign-in/' + selectActId.value)
uni.hideLoading()
Dialog.alert({
title: code === 0 ? '签到成功' : '签到失败',
content: code === 0 ? '祝您活动顺利' : msg,
confirmBtn: '确定'
})
if (code === 0) {
await getSignInfo()
}
} catch (e) {
Toast.error(e.message)
console.error(e)
} finally {
uni.hideLoading()
}
}
</script>
<style scoped lang="less">
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes ripple {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
@keyframes checkmark-circle {
0% {
stroke-dashoffset: 166;
}
100% {
stroke-dashoffset: 0;
}
}
@keyframes float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0);
}
}
.top-dropdown {
flex-shrink: 0;
z-index: 10;
position: relative;
}
.content-container {
padding: 15px;
z-index: 9;
position: relative;
flex: 1;
overflow-y: auto;
}
.scan-area {
background-color: #ffffff;
border-radius: 16rpx;
padding: 64rpx 0;
border-radius: 16px;
padding: 64px 0;
margin-bottom: 32rpx;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
}
.scan-icon-wrapper {
@@ -164,6 +294,62 @@ function handleScanSuccess(val){
align-items: center;
justify-content: center;
margin-bottom: 32rpx;
position: relative;
}
.pulse-animation {
animation: pulse 2s infinite ease-in-out;
}
.scan-circles {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
transform: translate(-50%, -50%);
&:before, &:after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
border: 2px solid rgba(244, 90, 90, 0.3);
animation: ripple 2s infinite;
}
&:after {
animation-delay: 0.5s;
}
}
.scan-button {
position: relative;
overflow: hidden;
transition: all 0.3s ease;
&:active {
transform: translateY(2px);
}
&:after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: rgba(255, 255, 255, 0.2);
transform: rotate(30deg);
transition: all 0.3s ease;
}
&:hover:after {
transform: rotate(30deg) translate(-30%, -30%);
}
}
.guide-area {
@@ -171,6 +357,9 @@ function handleScanSuccess(val){
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 32rpx;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
}
.guide-title {
@@ -178,18 +367,37 @@ function handleScanSuccess(val){
font-weight: 500;
color: #333333;
margin-bottom: 24rpx;
position: relative;
&:after {
content: '';
position: absolute;
bottom: -8rpx;
left: 0;
width: 40rpx;
height: 4rpx;
background-color: #f45a5a;
border-radius: 2rpx;
}
}
.guide-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
padding: 12rpx 16rpx;
border-radius: 8rpx;
transition: all 0.3s ease;
&:hover {
background-color: rgba(244, 90, 90, 0.05);
}
}
.guide-icon {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
margin-right: 16px;
flex-shrink: 0;
}
@@ -202,7 +410,9 @@ function handleScanSuccess(val){
background-color: #ffffff;
border-radius: 16rpx;
padding: 32rpx;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
}
.status-header {
@@ -214,6 +424,18 @@ function handleScanSuccess(val){
font-weight: 500;
color: #333333;
margin-bottom: 0.75rem;
position: relative;
&:after {
content: '';
position: absolute;
bottom: -8rpx;
left: 0;
width: 40rpx;
height: 4rpx;
background-color: #f45a5a;
border-radius: 2rpx;
}
}
.status-content {
@@ -230,6 +452,34 @@ function handleScanSuccess(val){
text-align: center;
}
.success-icon-wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 96rpx;
height: 96rpx;
}
.checkmark-circle {
width: 96rpx;
height: 96rpx;
position: absolute;
top: 0;
left: 0;
&__circle {
stroke-dasharray: 166;
stroke-dashoffset: 166;
stroke-width: 2;
stroke: #07c160;
stroke-miterlimit: 10;
fill: none;
animation: checkmark-circle 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
animation-delay: 0.2s;
}
}
.success-text {
font-size: 20px;
color: #07c160;
@@ -239,6 +489,9 @@ function handleScanSuccess(val){
.sign-detail {
margin-top: 32rpx;
background-color: rgba(7, 193, 96, 0.05);
padding: 16rpx 24rpx;
border-radius: 8rpx;
}
.detail-item {
@@ -247,6 +500,7 @@ function handleScanSuccess(val){
margin-bottom: 16rpx;
color: #666666;
font-size: 14px;
font-weight: 400;
}
.detail-item uni-icons {
@@ -265,4 +519,117 @@ function handleScanSuccess(val){
color: #999999;
}
.bg-decoration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
overflow: hidden;
}
.wave {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 200px;
}
.dots {
position: absolute;
bottom: 5%;
right: 5%;
animation: float 4s ease-in-out infinite;
}
/* 导入Animate.css的部分动画 */
.animate__animated {
animation-duration: 1s;
animation-fill-mode: both;
}
.animate__fadeIn {
animation-name: fadeIn;
}
.animate__fadeInUp {
animation-name: fadeInUp;
}
.animate__fadeInLeft {
animation-name: fadeInLeft;
}
.animate__bounceIn {
animation-name: bounceIn;
}
.animate__pulse {
animation-name: pulse;
}
.animate__infinite {
animation-iteration-count: infinite;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translate3d(0, 40px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translate3d(-40px, 0, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes bounceIn {
from, 20%, 40%, 60%, 80%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
transform: scale3d(0.97, 0.97, 0.97);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
</style>