hookehuyr

fix(scan-checkin): 完善扫码打卡列表分页与交互细节

- ESLint comma-dangle 改为 always-multiline,允许多行尾随逗号
- ScanCheckinList 接入分页加载,增加加载中/空状态/加载更多提示
- ScanCheckinDetail 打卡成功后等用户确认再跳转列表页

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
...@@ -15,7 +15,7 @@ module.exports = { ...@@ -15,7 +15,7 @@ module.exports = {
15 indent: ['error', 2, { SwitchCase: 1 }], // 缩进 15 indent: ['error', 2, { SwitchCase: 1 }], // 缩进
16 quotes: ['error', 'single', { avoidEscape: true }], // 单引号 16 quotes: ['error', 'single', { avoidEscape: true }], // 单引号
17 semi: ['error', 'never'], // 不使用分号 17 semi: ['error', 'never'], // 不使用分号
18 - 'comma-dangle': ['error', 'never'], // 不使用尾随逗号 18 + 'comma-dangle': ['error', 'always-multiline'], // 多行时必须尾随逗号,单行不允许
19 'space-before-function-paren': [ 19 'space-before-function-paren': [
20 'error', 20 'error',
21 { 21 {
......
...@@ -214,21 +214,23 @@ const handleScanCheckin = async () => { ...@@ -214,21 +214,23 @@ const handleScanCheckin = async () => {
214 detail.isChecked = true 214 detail.isChecked = true
215 detail.lastScanCode = scannedCode 215 detail.lastScanCode = scannedCode
216 216
217 - await Taro.showModal({ 217 + const modalResult = await Taro.showModal({
218 title: '打卡成功', 218 title: '打卡成功',
219 content: '您已完成当前扫码打卡,可前往列表查看状态。', 219 content: '您已完成当前扫码打卡,可前往列表查看状态。',
220 showCancel: false, 220 showCancel: false,
221 confirmText: '查看列表', 221 confirmText: '查看列表',
222 }) 222 })
223 223
224 + if (modalResult.confirm) {
224 const params = new URLSearchParams({ 225 const params = new URLSearchParams({
225 activityId: detail.activityId, 226 activityId: detail.activityId,
226 }) 227 })
227 228
228 - // 扫码打卡完成后回列表页,方便用户继续处理同一活动下的其他关卡。 229 + // 用户确认“查看列表”后回到列表页,方便继续处理同一活动下的其他关卡。
229 Taro.redirectTo({ 230 Taro.redirectTo({
230 url: `/pages/ScanCheckinList/index?${params.toString()}`, 231 url: `/pages/ScanCheckinList/index?${params.toString()}`,
231 }) 232 })
233 + }
232 return 234 return
233 } 235 }
234 236
......
1 .scan-checkin-list-page { 1 .scan-checkin-list-page {
2 min-height: 100vh; 2 min-height: 100vh;
3 - padding: 32rpx 24rpx 40rpx; 3 + padding: 32rpx 24rpx 220rpx;
4 background: linear-gradient(180deg, #f6f8fb 0%, #eef2f5 100%); 4 background: linear-gradient(180deg, #f6f8fb 0%, #eef2f5 100%);
5 box-sizing: border-box; 5 box-sizing: border-box;
6 position: relative; 6 position: relative;
...@@ -36,6 +36,13 @@ ...@@ -36,6 +36,13 @@
36 box-shadow: 0 12rpx 40rpx rgba(15, 23, 42, 0.08); 36 box-shadow: 0 12rpx 40rpx rgba(15, 23, 42, 0.08);
37 } 37 }
38 38
39 +.scan-checkin-list-status {
40 + padding: 88rpx 0;
41 + text-align: center;
42 + font-size: 28rpx;
43 + color: #8b98a7;
44 +}
45 +
39 .scan-checkin-list-leading { 46 .scan-checkin-list-leading {
40 width: 68rpx; 47 width: 68rpx;
41 height: 68rpx; 48 height: 68rpx;
...@@ -80,6 +87,21 @@ ...@@ -80,6 +87,21 @@
80 flex-shrink: 0; 87 flex-shrink: 0;
81 } 88 }
82 89
90 +.scan-checkin-list-load-more,
91 +.scan-checkin-list-no-more {
92 + padding: 24rpx 0 8rpx;
93 + text-align: center;
94 + font-size: 26rpx;
95 +}
96 +
97 +.scan-checkin-list-load-more {
98 + color: #36b5bb;
99 +}
100 +
101 +.scan-checkin-list-no-more {
102 + color: #98a3af;
103 +}
104 +
83 .scan-checkin-list-floating-button { 105 .scan-checkin-list-floating-button {
84 position: fixed; 106 position: fixed;
85 right: 24rpx; 107 right: 24rpx;
......
...@@ -11,6 +11,11 @@ ...@@ -11,6 +11,11 @@
11 <text class="scan-checkin-list-subtitle">请选择一个打卡点,进入详情后完成扫码打卡</text> 11 <text class="scan-checkin-list-subtitle">请选择一个打卡点,进入详情后完成扫码打卡</text>
12 </view> 12 </view>
13 13
14 + <view v-if="loading && pointList.length === 0" class="scan-checkin-list-status">
15 + 加载中...
16 + </view>
17 +
18 + <template v-else>
14 <view v-for="point in pointList" :key="point.id" class="scan-checkin-list-card"> 19 <view v-for="point in pointList" :key="point.id" class="scan-checkin-list-card">
15 <view class="scan-checkin-list-leading"> 20 <view class="scan-checkin-list-leading">
16 <IconFont size="30" name="https://cdn.ipadbiz.cn/lls_prog/icon/check_list_logo.png" /> 21 <IconFont size="30" name="https://cdn.ipadbiz.cn/lls_prog/icon/check_list_logo.png" />
...@@ -25,6 +30,23 @@ ...@@ -25,6 +30,23 @@
25 </view> 30 </view>
26 </view> 31 </view>
27 32
33 + <view v-if="!loading && pointList.length === 0" class="scan-checkin-list-status">
34 + 暂无扫码打卡点
35 + </view>
36 +
37 + <view
38 + v-if="hasMore && pointList.length > 0"
39 + class="scan-checkin-list-load-more"
40 + @click="loadMore"
41 + >
42 + {{ loadingMore ? '加载中...' : '加载更多' }}
43 + </view>
44 +
45 + <view v-if="!hasMore && pointList.length > 0" class="scan-checkin-list-no-more">
46 + 没有更多数据了
47 + </view>
48 + </template>
49 +
28 <view class="scan-checkin-list-floating-button" @click="handleShowBoothMap"> 50 <view class="scan-checkin-list-floating-button" @click="handleShowBoothMap">
29 <IconFont 51 <IconFont
30 class="scan-checkin-list-floating-icon" 52 class="scan-checkin-list-floating-icon"
...@@ -45,6 +67,18 @@ import { getScanStageListAPI } from '@/api/map' ...@@ -45,6 +67,18 @@ import { getScanStageListAPI } from '@/api/map'
45 67
46 const pointList = ref([]) 68 const pointList = ref([])
47 const activityId = ref('') 69 const activityId = ref('')
70 +const loading = ref(false)
71 +const loadingMore = ref(false)
72 +const hasMore = ref(true)
73 +const currentPage = ref(0)
74 +const pageSize = ref(10)
75 +
76 +const mapStageList = stageList =>
77 + stageList.map(stage => ({
78 + id: stage.id,
79 + title: stage.title,
80 + isChecked: stage.is_checked,
81 + }))
48 82
49 const goToDetail = point => { 83 const goToDetail = point => {
50 const params = new URLSearchParams({ 84 const params = new URLSearchParams({
...@@ -74,9 +108,35 @@ useLoad(options => { ...@@ -74,9 +108,35 @@ useLoad(options => {
74 loadStageList() 108 loadStageList()
75 }) 109 })
76 110
77 -const loadStageList = async () => { 111 +const loadStageList = async (isLoadMore = false) => {
112 + if (!activityId.value) {
113 + pointList.value = []
114 + hasMore.value = false
115 + return
116 + }
117 +
118 + if (isLoadMore) {
119 + if (loading.value || loadingMore.value || !hasMore.value) {
120 + return
121 + }
122 + loadingMore.value = true
123 + } else {
124 + if (loading.value) {
125 + return
126 + }
127 + loading.value = true
128 + currentPage.value = 0
129 + hasMore.value = true
130 + pointList.value = []
131 + }
132 +
133 + const page = currentPage.value
134 +
135 + try {
78 const result = await getScanStageListAPI({ 136 const result = await getScanStageListAPI({
79 activity_id: activityId.value, 137 activity_id: activityId.value,
138 + page,
139 + limit: pageSize.value,
80 }) 140 })
81 141
82 if (result?.code !== 1) { 142 if (result?.code !== 1) {
...@@ -87,11 +147,25 @@ const loadStageList = async () => { ...@@ -87,11 +147,25 @@ const loadStageList = async () => {
87 return 147 return
88 } 148 }
89 149
90 - pointList.value = (result?.data?.stages || []).map(stage => ({ 150 + const stageList = mapStageList(result?.data?.stages || [])
91 - id: stage.id, 151 +
92 - title: stage.title, 152 + pointList.value = isLoadMore ? [...pointList.value, ...stageList] : stageList
93 - isChecked: stage.is_checked, 153 + hasMore.value = stageList.length === pageSize.value
94 - })) 154 + currentPage.value = page + 1
155 + } catch (error) {
156 + console.error('获取扫码打卡关卡列表失败:', error)
157 + Taro.showToast({
158 + title: '获取关卡列表失败',
159 + icon: 'none',
160 + })
161 + } finally {
162 + loading.value = false
163 + loadingMore.value = false
164 + }
165 +}
166 +
167 +const loadMore = () => {
168 + loadStageList(true)
95 } 169 }
96 </script> 170 </script>
97 171
......