加入加载东湖
This commit is contained in:
@@ -35,7 +35,8 @@
|
||||
<view class="QR">
|
||||
<image :src="image" mode="" :style="{width: Math.min(width, 500) + 'rpx',height: Math.min(height, 500) + 'rpx' } "></image>
|
||||
</view>
|
||||
<view v-if="!image || STQRC" class="loadingBox" :style="{width: Math.min(width, 500) + 'rpx',height: Math.min(height, 500) + 'rpx' }">
|
||||
<!-- 加载动画区域 - 设置最小尺寸确保进入页面时可见 -->
|
||||
<view v-if="!image || STQRC" class="loadingBox" :style="{width: (width > 0 ? Math.min(width, 500) : 500) + 'rpx',height: (height > 0 ? Math.min(height, 500) : 500) + 'rpx' }">
|
||||
<view class="loading-spinner">
|
||||
<view v-if="!isCheckIn" class="spinner-circle"></view>
|
||||
<view v-else>
|
||||
@@ -268,10 +269,13 @@ import QrStatus from '@/components/QRCode/StatusCard.vue'
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
uni.showLoading({
|
||||
title: '生成签到二维码...',
|
||||
mask: true
|
||||
})
|
||||
// 测试模式下不显示全局loading,让页面内的加载动画显示
|
||||
if (!TEST_MODE) {
|
||||
uni.showLoading({
|
||||
title: '生成签到二维码...',
|
||||
mask: true
|
||||
})
|
||||
}
|
||||
|
||||
// 测试模式:直接生成假二维码,内容为"欢迎来到活氧舱"
|
||||
if (TEST_MODE) {
|
||||
@@ -315,37 +319,67 @@ import QrStatus from '@/components/QRCode/StatusCard.vue'
|
||||
})
|
||||
|
||||
// 测试模式:生成假二维码(内容为"欢迎来到活氧舱")
|
||||
const generateTestQRCode = () => {
|
||||
// 使用 canvas 生成简单的二维码图案(模拟)
|
||||
// 由于无法直接生成真正的二维码图片,使用一个静态图片或占位图
|
||||
// 在实际测试环境中,可以使用第三方库生成二维码
|
||||
// 不发送请求,使用缓存机制避免重复请求,二维码内容保持不变
|
||||
// @param {boolean} isRefresh - 是否是刷新操作(true=点击刷新按钮,false=首次进入页面)
|
||||
const generateTestQRCode = (isRefresh = false) => {
|
||||
// 检查是否有缓存的二维码图片
|
||||
const cachedQRImage = getCacheData("TestQRImage")
|
||||
const qrContent = "欢迎来到活氧舱"
|
||||
|
||||
// 模拟网络延迟
|
||||
if (cachedQRImage) {
|
||||
// 使用缓存的二维码图片,不发送请求
|
||||
console.log("测试模式:使用缓存的二维码图片")
|
||||
image.value = cachedQRImage.image
|
||||
width.value = cachedQRImage.width
|
||||
height.value = cachedQRImage.height
|
||||
qrcode.value = qrContent
|
||||
status.value = 'waiting'
|
||||
QRStatus.value = '请出示二维码签到'
|
||||
uni.hideLoading()
|
||||
|
||||
// 只有刷新操作才显示提示
|
||||
if (isRefresh) {
|
||||
uni.showToast({
|
||||
title: '二维码已刷新',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 首次生成,发送一次请求并缓存
|
||||
console.log("测试模式:首次生成二维码,发送请求并缓存")
|
||||
setTimeout(() => {
|
||||
// 使用一个在线二维码生成服务生成内容为"欢迎来到活氧舱"的二维码
|
||||
const qrContent = "欢迎来到活氧舱"
|
||||
qrcode.value = qrContent
|
||||
|
||||
// 使用在线API生成二维码图片
|
||||
// 注意:实际使用时应该考虑离线方案
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(qrContent)}`
|
||||
|
||||
// 将在线图片下载为base64(在uniapp中)
|
||||
uni.request({
|
||||
url: qrUrl,
|
||||
method: 'GET',
|
||||
responseType: 'arraybuffer',
|
||||
success: (res) => {
|
||||
const base64 = uni.arrayBufferToBase64(res.data)
|
||||
image.value = `data:image/png;base64,${base64}`
|
||||
const qrImage = `data:image/png;base64,${base64}`
|
||||
|
||||
image.value = qrImage
|
||||
width.value = 500
|
||||
height.value = 500
|
||||
status.value = 'waiting'
|
||||
QRStatus.value = '请出示二维码签到'
|
||||
|
||||
// 缓存二维码图片,后续刷新不再请求
|
||||
setCacheData("TestQRImage", {
|
||||
image: qrImage,
|
||||
width: 500,
|
||||
height: 500,
|
||||
content: qrContent
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
},
|
||||
fail: () => {
|
||||
// 如果无法获取在线二维码,使用默认占位图
|
||||
image.value = ''
|
||||
width.value = 500
|
||||
height.value = 500
|
||||
@@ -354,7 +388,7 @@ import QrStatus from '@/components/QRCode/StatusCard.vue'
|
||||
uni.hideLoading()
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 页面卸载时关闭WebSocket连接(不清除缓存,让缓存自然过期)
|
||||
@@ -401,8 +435,20 @@ import QrStatus from '@/components/QRCode/StatusCard.vue'
|
||||
return
|
||||
}
|
||||
|
||||
// 清空图片,显示加载状态
|
||||
image.value = ""
|
||||
QRStatus.value = "正在刷新二维码..."
|
||||
|
||||
// 测试模式:重新生成二维码,但不发送请求,内容不变
|
||||
if (TEST_MODE) {
|
||||
// 延迟显示,让用户看到刷新效果
|
||||
setTimeout(() => {
|
||||
generateTestQRCode(true) // 传递 isRefresh=true
|
||||
}, 300)
|
||||
return
|
||||
}
|
||||
|
||||
// 非测试模式:正常刷新逻辑
|
||||
setTimeout(() => {
|
||||
getStorage(null)
|
||||
}, 500)
|
||||
@@ -439,7 +485,13 @@ import QrStatus from '@/components/QRCode/StatusCard.vue'
|
||||
onlyFromCamera: false,
|
||||
scanType: ['qrCode'],
|
||||
success: (res) => {
|
||||
console.log(res)
|
||||
console.log('扫码结果:', res)
|
||||
// 测试模式下,如果扫描的是我们生成的二维码内容,直接模拟签到成功
|
||||
if (TEST_MODE && res.result === '欢迎来到活氧舱') {
|
||||
console.log('测试模式:模拟签到成功')
|
||||
handleTestModeCheckIn()
|
||||
return
|
||||
}
|
||||
checkIn(res.result)
|
||||
},
|
||||
fail: (err) => {
|
||||
@@ -452,6 +504,28 @@ import QrStatus from '@/components/QRCode/StatusCard.vue'
|
||||
})
|
||||
}
|
||||
|
||||
// 测试模式:模拟签到成功(不请求后端)
|
||||
const handleTestModeCheckIn = () => {
|
||||
closeWebSocket()
|
||||
const now = new Date()
|
||||
const dateTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
|
||||
|
||||
status.value = 'scanned'
|
||||
errorText.value = ''
|
||||
QRStatus.value = `${dateTime} 成功签到`
|
||||
isCheckIn.value = true
|
||||
STQRC.value = true
|
||||
|
||||
setCacheData("checkInTime", QRStatus.value)
|
||||
setCacheData("isCheckIn", isCheckIn.value)
|
||||
|
||||
uni.showToast({
|
||||
title: '签到成功!',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
// 手动签到接口
|
||||
const checkIn = (qrContent) => {
|
||||
console.log(qrContent)
|
||||
@@ -776,7 +850,8 @@ import QrStatus from '@/components/QRCode/StatusCard.vue'
|
||||
align-items: center;
|
||||
margin: 0 auto 64rpx;
|
||||
max-width: 100%;
|
||||
|
||||
min-height: 580rpx; /* 设置最小高度确保加载动画区域可见 */
|
||||
|
||||
.QR {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
@@ -138,7 +138,13 @@
|
||||
class="course-card"
|
||||
@tap="handleCourseClick(course)"
|
||||
>
|
||||
<!-- 图片容器 -->
|
||||
<view class="card-image-wrapper">
|
||||
<!-- 占位符(灰色背景) -->
|
||||
<view class="card-image-placeholder">
|
||||
<view class="placeholder-spinner"></view>
|
||||
</view>
|
||||
<!-- 实际图片 -->
|
||||
<image
|
||||
class="card-image"
|
||||
:src="course.image"
|
||||
@@ -223,12 +229,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
|
||||
// 测试模式
|
||||
const TEST_MODE = true
|
||||
|
||||
|
||||
|
||||
// 搜索关键词
|
||||
const keyword = ref('')
|
||||
// 当前页
|
||||
@@ -241,7 +249,7 @@ const hasMore = ref(true)
|
||||
const loadingMore = ref(false)
|
||||
// 活动筛选标签
|
||||
const activeFilter = ref('all')
|
||||
// 课程列表
|
||||
// 课程列表(不包含图片URL,只包含基本信息)
|
||||
const courses = ref([])
|
||||
// 骨架屏数量
|
||||
const skeletonCount = ref(pageSize)
|
||||
@@ -299,6 +307,7 @@ const sortOptions = [
|
||||
|
||||
// 处理滚动事件
|
||||
const handleScroll = (e) => {
|
||||
// 收起高级筛选
|
||||
if (showAdvancedFilter.value && !isScrolling.value) {
|
||||
isScrolling.value = true
|
||||
showAdvancedFilter.value = false
|
||||
@@ -633,18 +642,32 @@ const fetchCourses = async (searchKeyword = '', filter = 'all', page = 0, size =
|
||||
}
|
||||
|
||||
if (result && result.content && result.content.length > 0) {
|
||||
// 课程对象直接包含图片URL
|
||||
const processedCourses = result.content.map(course => ({
|
||||
id: course.id,
|
||||
image: course.image,
|
||||
tag: course.tag,
|
||||
tagType: course.tagType,
|
||||
courseType: course.courseType,
|
||||
name: course.name,
|
||||
duration: course.duration,
|
||||
level: course.level,
|
||||
participants: course.participants,
|
||||
rawData: course.rawData
|
||||
}))
|
||||
|
||||
if (append) {
|
||||
// 追加模式:逐个添加数据,同时移除骨架屏
|
||||
for (let i = 0; i < result.content.length; i++) {
|
||||
for (let i = 0; i < processedCourses.length; i++) {
|
||||
await Promise.resolve()
|
||||
courses.value.push(result.content[i])
|
||||
courses.value.push(processedCourses[i])
|
||||
if (skeletonCount.value > 0) {
|
||||
skeletonCount.value--
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 重置模式:直接替换
|
||||
courses.value = result.content
|
||||
courses.value = processedCourses
|
||||
skeletonCount.value = 0
|
||||
}
|
||||
|
||||
@@ -757,6 +780,14 @@ onMounted(() => {
|
||||
currentFilterSignature = getFilterSignature()
|
||||
fetchCourses()
|
||||
})
|
||||
|
||||
// 组件卸载时清理资源
|
||||
onUnmounted(() => {
|
||||
// 清理滚动定时器
|
||||
if (scrollTimer) {
|
||||
clearTimeout(scrollTimer)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -1058,12 +1089,36 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 2; /* 确保图片在占位符之上 */
|
||||
|
||||
.course-card:active & {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
}
|
||||
|
||||
/* 图片占位符 */
|
||||
.card-image-placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f1f5f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.placeholder-spinner {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 4rpx solid #e2e8f0;
|
||||
border-top-color: #f97316;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.image-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
Reference in New Issue
Block a user