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 - const params = new URLSearchParams({ 224 + if (modalResult.confirm) {
225 - activityId: detail.activityId, 225 + const params = new URLSearchParams({
226 - }) 226 + activityId: detail.activityId,
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,19 +11,41 @@ ...@@ -11,19 +11,41 @@
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-for="point in pointList" :key="point.id" class="scan-checkin-list-card"> 14 + <view v-if="loading && pointList.length === 0" class="scan-checkin-list-status">
15 - <view class="scan-checkin-list-leading"> 15 + 加载中...
16 - <IconFont size="30" name="https://cdn.ipadbiz.cn/lls_prog/icon/check_list_logo.png" /> 16 + </view>
17 +
18 + <template v-else>
19 + <view v-for="point in pointList" :key="point.id" class="scan-checkin-list-card">
20 + <view class="scan-checkin-list-leading">
21 + <IconFont size="30" name="https://cdn.ipadbiz.cn/lls_prog/icon/check_list_logo.png" />
22 + </view>
23 +
24 + <view class="scan-checkin-list-content">
25 + <text class="scan-checkin-list-name">{{ point.title }}</text>
26 + </view>
27 +
28 + <view class="scan-checkin-list-action" @click="goToDetail(point)">
29 + <Scan2 size="20" />
30 + </view>
17 </view> 31 </view>
18 32
19 - <view class="scan-checkin-list-content"> 33 + <view v-if="!loading && pointList.length === 0" class="scan-checkin-list-status">
20 - <text class="scan-checkin-list-name">{{ point.title }}</text> 34 + 暂无扫码打卡点
21 </view> 35 </view>
22 36
23 - <view class="scan-checkin-list-action" @click="goToDetail(point)"> 37 + <view
24 - <Scan2 size="20" /> 38 + v-if="hasMore && pointList.length > 0"
39 + class="scan-checkin-list-load-more"
40 + @click="loadMore"
41 + >
42 + {{ loadingMore ? '加载中...' : '加载更多' }}
25 </view> 43 </view>
26 - </view> 44 +
45 + <view v-if="!hasMore && pointList.length > 0" class="scan-checkin-list-no-more">
46 + 没有更多数据了
47 + </view>
48 + </template>
27 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
...@@ -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,24 +108,64 @@ useLoad(options => { ...@@ -74,24 +108,64 @@ useLoad(options => {
74 loadStageList() 108 loadStageList()
75 }) 109 })
76 110
77 -const loadStageList = async () => { 111 +const loadStageList = async (isLoadMore = false) => {
78 - const result = await getScanStageListAPI({ 112 + if (!activityId.value) {
79 - activity_id: activityId.value, 113 + pointList.value = []
80 - }) 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 {
136 + const result = await getScanStageListAPI({
137 + activity_id: activityId.value,
138 + page,
139 + limit: pageSize.value,
140 + })
81 141
82 - if (result?.code !== 1) { 142 + if (result?.code !== 1) {
143 + Taro.showToast({
144 + title: result?.msg || '获取关卡列表失败',
145 + icon: 'none',
146 + })
147 + return
148 + }
149 +
150 + const stageList = mapStageList(result?.data?.stages || [])
151 +
152 + pointList.value = isLoadMore ? [...pointList.value, ...stageList] : stageList
153 + hasMore.value = stageList.length === pageSize.value
154 + currentPage.value = page + 1
155 + } catch (error) {
156 + console.error('获取扫码打卡关卡列表失败:', error)
83 Taro.showToast({ 157 Taro.showToast({
84 - title: result?.msg || '获取关卡列表失败', 158 + title: '获取关卡列表失败',
85 icon: 'none', 159 icon: 'none',
86 }) 160 })
87 - return 161 + } finally {
162 + loading.value = false
163 + loadingMore.value = false
88 } 164 }
165 +}
89 166
90 - pointList.value = (result?.data?.stages || []).map(stage => ({ 167 +const loadMore = () => {
91 - id: stage.id, 168 + loadStageList(true)
92 - title: stage.title,
93 - isChecked: stage.is_checked,
94 - }))
95 } 169 }
96 </script> 170 </script>
97 171
......