..
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user