feat(payCard): 添加支付协议勾选功能及弹窗展示
在支付卡片组件中添加协议勾选区域,用户需同意协议才能进行支付。新增协议弹窗展示详细内容,并实现协议状态检查与更新逻辑。同时调整了相关样式确保UI一致性。
Showing
1 changed file
with
217 additions
and
5 deletions
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2023-12-20 14:11:11 | 2 | * @Date: 2023-12-20 14:11:11 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-07-31 15:30:59 | 4 | + * @LastEditTime: 2025-08-08 10:33:57 |
| 5 | - * @FilePath: /jgdl/src/components/payCard.vue | 5 | + * @FilePath: /jgdl/src/components/paycard.vue |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | --> | 7 | --> |
| 8 | <template> | 8 | <template> |
| ... | @@ -12,9 +12,56 @@ | ... | @@ -12,9 +12,56 @@ |
| 12 | <view style="font-size: 32rpx;">实付金额</view> | 12 | <view style="font-size: 32rpx;">实付金额</view> |
| 13 | <view style="color: red; margin: 10rpx 0;"><text style="font-size: 50rpx;">¥</text><text style="font-size: 80rpx;">{{ price }}</text></view> | 13 | <view style="color: red; margin: 10rpx 0;"><text style="font-size: 50rpx;">¥</text><text style="font-size: 80rpx;">{{ price }}</text></view> |
| 14 | <view style="font-size: 28rpx; margin-bottom: 20rpx;">支付剩余时间 <text style="color: red;">{{ formatTime(remain_time) }}</text></view> | 14 | <view style="font-size: 28rpx; margin-bottom: 20rpx;">支付剩余时间 <text style="color: red;">{{ formatTime(remain_time) }}</text></view> |
| 15 | - <nut-button block color="#fb923c" @tap="goToPay">立即支付</nut-button> | 15 | + |
| 16 | + <!-- 协议勾选区域 --> | ||
| 17 | + <div v-if="!hasAgreed" class="agreement-section"> | ||
| 18 | + <nut-checkbox v-model="isChecked" class="agreement-checkbox"> | ||
| 19 | + <view class="checkbox-text"> | ||
| 20 | + <text>我已阅读并同意</text> | ||
| 21 | + <text class="agreement-link" @tap.stop="showProtocol"> | ||
| 22 | + 《支付协议》 | ||
| 23 | + </text> | ||
| 24 | + </view> | ||
| 25 | + </nut-checkbox> | ||
| 26 | + </div> | ||
| 27 | + | ||
| 28 | + <nut-button | ||
| 29 | + block | ||
| 30 | + color="#fb923c" | ||
| 31 | + :disabled="!hasAgreed && !isChecked" | ||
| 32 | + @tap="goToPay" | ||
| 33 | + > | ||
| 34 | + 立即支付 | ||
| 35 | + </nut-button> | ||
| 16 | </view> | 36 | </view> |
| 17 | </nut-action-sheet> | 37 | </nut-action-sheet> |
| 38 | + | ||
| 39 | + <!-- 支付协议弹框 --> | ||
| 40 | + <nut-popup | ||
| 41 | + v-model:visible="protocolVisible" | ||
| 42 | + position="right" | ||
| 43 | + :close-on-click-overlay="true" | ||
| 44 | + :safe-area-inset-bottom="true" | ||
| 45 | + :style="{ width: '100%', height: '100%' }" | ||
| 46 | + @close="protocolVisible = false" | ||
| 47 | + > | ||
| 48 | + <view class="protocol-container"> | ||
| 49 | + <!-- 标题栏 --> | ||
| 50 | + <view class="protocol-header"> | ||
| 51 | + <text class="protocol-title">支付协议</text> | ||
| 52 | + <view class="close-btn" @click="protocolVisible = false"> | ||
| 53 | + <text class="close-text">×</text> | ||
| 54 | + </view> | ||
| 55 | + </view> | ||
| 56 | + | ||
| 57 | + <!-- 内容区域 --> | ||
| 58 | + <scroll-view class="protocol-scroll" :scroll-y="true"> | ||
| 59 | + <view class="protocol-body"> | ||
| 60 | + <view class="protocol-text">{{ protocolContent }}</view> | ||
| 61 | + </view> | ||
| 62 | + </scroll-view> | ||
| 63 | + </view> | ||
| 64 | + </nut-popup> | ||
| 18 | </div> | 65 | </div> |
| 19 | </template> | 66 | </template> |
| 20 | 67 | ||
| ... | @@ -22,7 +69,8 @@ | ... | @@ -22,7 +69,8 @@ |
| 22 | import Taro from '@tarojs/taro' | 69 | import Taro from '@tarojs/taro' |
| 23 | import { ref, watch, onMounted, onUnmounted } from 'vue' | 70 | import { ref, watch, onMounted, onUnmounted } from 'vue' |
| 24 | import { getCurrentPageUrl } from "@/utils/weapp"; | 71 | import { getCurrentPageUrl } from "@/utils/weapp"; |
| 25 | -import { payAPI, payCheckAPI } from '@/api/index' | 72 | +import { payAPI, payCheckAPI, getProfileAPI, updateProfileAPI } from '@/api/index' |
| 73 | +import { useUserStore } from '@/stores/user' | ||
| 26 | 74 | ||
| 27 | /** | 75 | /** |
| 28 | * 格式化时间 | 76 | * 格式化时间 |
| ... | @@ -53,8 +101,24 @@ const props = defineProps({ | ... | @@ -53,8 +101,24 @@ const props = defineProps({ |
| 53 | 101 | ||
| 54 | const emit = defineEmits(['close', 'paySuccess']); | 102 | const emit = defineEmits(['close', 'paySuccess']); |
| 55 | 103 | ||
| 104 | +const userStore = useUserStore() | ||
| 105 | + | ||
| 56 | const visible = ref(false); | 106 | const visible = ref(false); |
| 57 | 107 | ||
| 108 | +// 协议相关状态 | ||
| 109 | +const isChecked = ref(false); | ||
| 110 | +const hasAgreed = ref(false); | ||
| 111 | +const protocolVisible = ref(false); | ||
| 112 | + | ||
| 113 | +// 支付协议内容 | ||
| 114 | +const protocolContent = ref(` | ||
| 115 | +1. 用户在使用捡个电驴支付服务时,需遵守相关法律法规。 | ||
| 116 | +2. 平台有权对异常交易进行风险控制。 | ||
| 117 | +3. 用户应确保支付信息的真实性和准确性。 | ||
| 118 | +4. 平台将按照约定收取相应的服务费用。 | ||
| 119 | +5. 如有争议,双方应友好协商解决。 | ||
| 120 | +`) | ||
| 121 | + | ||
| 58 | const onClose = () => { | 122 | const onClose = () => { |
| 59 | visible.value = false; | 123 | visible.value = false; |
| 60 | } | 124 | } |
| ... | @@ -67,12 +131,14 @@ let timeId = null; | ... | @@ -67,12 +131,14 @@ let timeId = null; |
| 67 | 131 | ||
| 68 | watch( | 132 | watch( |
| 69 | () => props.visible, | 133 | () => props.visible, |
| 70 | - (val) => { | 134 | + async (val) => { |
| 71 | visible.value = val; | 135 | visible.value = val; |
| 72 | if (val) { | 136 | if (val) { |
| 73 | id.value = props.data.id; | 137 | id.value = props.data.id; |
| 74 | price.value = props.data.price; | 138 | price.value = props.data.price; |
| 75 | remain_time.value = props.data.remain_time; | 139 | remain_time.value = props.data.remain_time; |
| 140 | + // 检查用户协议状态 | ||
| 141 | + await checkAgreementStatus(); | ||
| 76 | } | 142 | } |
| 77 | } | 143 | } |
| 78 | ) | 144 | ) |
| ... | @@ -101,7 +167,77 @@ onUnmounted(() => { | ... | @@ -101,7 +167,77 @@ onUnmounted(() => { |
| 101 | timeId && clearInterval(timeId); | 167 | timeId && clearInterval(timeId); |
| 102 | }) | 168 | }) |
| 103 | 169 | ||
| 170 | +/** | ||
| 171 | + * 检查用户是否已同意过协议 | ||
| 172 | + */ | ||
| 173 | +const checkAgreementStatus = async () => { | ||
| 174 | + try { | ||
| 175 | + // 调用API获取用户信息 | ||
| 176 | + const result = await getProfileAPI() | ||
| 177 | + | ||
| 178 | + if (result.code && result.data) { | ||
| 179 | + hasAgreed.value = result.data.is_signed || false | ||
| 180 | + // 更新用户store中的状态 | ||
| 181 | + if (userStore.userInfo) { | ||
| 182 | + userStore.userInfo.is_signed = result.data.is_signed | ||
| 183 | + } | ||
| 184 | + } else { | ||
| 185 | + // 如果API调用失败,使用store中的数据作为备选 | ||
| 186 | + hasAgreed.value = userStore.userInfo?.is_signed || false | ||
| 187 | + } | ||
| 188 | + } catch (error) { | ||
| 189 | + console.error('获取用户协议状态失败:', error) | ||
| 190 | + // 如果API调用失败,使用store中的数据作为备选 | ||
| 191 | + hasAgreed.value = userStore.userInfo?.is_signed || false | ||
| 192 | + } | ||
| 193 | +} | ||
| 194 | + | ||
| 195 | +/** | ||
| 196 | + * 显示支付协议 | ||
| 197 | + */ | ||
| 198 | +const showProtocol = () => { | ||
| 199 | + protocolVisible.value = true | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +/** | ||
| 203 | + * 处理协议同意 | ||
| 204 | + */ | ||
| 205 | +const handleAgreeProtocol = async () => { | ||
| 206 | + if (!isChecked.value) return | ||
| 207 | + | ||
| 208 | + try { | ||
| 209 | + // 调用API更新用户协议状态 | ||
| 210 | + const result = await updateProfileAPI({ | ||
| 211 | + is_signed: true | ||
| 212 | + }) | ||
| 213 | + | ||
| 214 | + if (result.code) { | ||
| 215 | + hasAgreed.value = true | ||
| 216 | + // 更新用户store中的状态 | ||
| 217 | + if (userStore.userInfo) { | ||
| 218 | + userStore.userInfo.is_signed = true | ||
| 219 | + } | ||
| 220 | + } | ||
| 221 | + } catch (error) { | ||
| 222 | + console.error('更新协议状态失败:', error) | ||
| 223 | + } | ||
| 224 | +} | ||
| 225 | + | ||
| 104 | const goToPay = async () => { | 226 | const goToPay = async () => { |
| 227 | + // 检查协议状态 | ||
| 228 | + if (!hasAgreed.value && !isChecked.value) { | ||
| 229 | + Taro.showToast({ | ||
| 230 | + title: '请先同意支付协议', | ||
| 231 | + icon: 'none' | ||
| 232 | + }) | ||
| 233 | + return | ||
| 234 | + } | ||
| 235 | + | ||
| 236 | + // 如果用户勾选了协议但还未提交,先处理协议同意 | ||
| 237 | + if (!hasAgreed.value && isChecked.value) { | ||
| 238 | + await handleAgreeProtocol() | ||
| 239 | + } | ||
| 240 | + | ||
| 105 | if (price.value > 0) { // 金额大于0 | 241 | if (price.value > 0) { // 金额大于0 |
| 106 | // 获取支付参数 | 242 | // 获取支付参数 |
| 107 | const { code, data } = await payAPI({ order_id: id.value }); | 243 | const { code, data } = await payAPI({ order_id: id.value }); |
| ... | @@ -158,5 +294,81 @@ const goToPay = async () => { | ... | @@ -158,5 +294,81 @@ const goToPay = async () => { |
| 158 | </script> | 294 | </script> |
| 159 | 295 | ||
| 160 | <style lang="less"> | 296 | <style lang="less"> |
| 297 | +.pay-card { | ||
| 298 | + .agreement-section { | ||
| 299 | + margin: 20rpx 0; | ||
| 300 | + padding: 0 20rpx; | ||
| 301 | + | ||
| 302 | + .agreement-checkbox { | ||
| 303 | + // display: flex; | ||
| 304 | + // align-items: center; | ||
| 305 | + // justify-content: center; | ||
| 306 | + | ||
| 307 | + :deep(.nut-checkbox__label) { | ||
| 308 | + margin-left: 8rpx; | ||
| 309 | + } | ||
| 310 | + | ||
| 311 | + .checkbox-text { | ||
| 312 | + font-size: 28rpx; | ||
| 313 | + color: #666; | ||
| 314 | + | ||
| 315 | + .agreement-link { | ||
| 316 | + color: #fb923c; | ||
| 317 | + text-decoration: underline; | ||
| 318 | + margin-left: 8rpx; | ||
| 319 | + } | ||
| 320 | + } | ||
| 321 | + } | ||
| 322 | + } | ||
| 323 | +} | ||
| 324 | + | ||
| 325 | +.protocol-container { | ||
| 326 | + display: flex; | ||
| 327 | + flex-direction: column; | ||
| 328 | + height: 100%; | ||
| 329 | + background-color: #fff; | ||
| 330 | + | ||
| 331 | + .protocol-header { | ||
| 332 | + display: flex; | ||
| 333 | + align-items: center; | ||
| 334 | + justify-content: space-between; | ||
| 335 | + padding: 20rpx 30rpx; | ||
| 336 | + border-bottom: 1rpx solid #eee; | ||
| 337 | + | ||
| 338 | + .protocol-title { | ||
| 339 | + font-size: 36rpx; | ||
| 340 | + font-weight: bold; | ||
| 341 | + color: #333; | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + .close-btn { | ||
| 345 | + width: 60rpx; | ||
| 346 | + height: 60rpx; | ||
| 347 | + display: flex; | ||
| 348 | + align-items: center; | ||
| 349 | + justify-content: center; | ||
| 350 | + | ||
| 351 | + .close-text { | ||
| 352 | + font-size: 48rpx; | ||
| 353 | + color: #999; | ||
| 354 | + line-height: 1; | ||
| 355 | + } | ||
| 356 | + } | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + .protocol-scroll { | ||
| 360 | + flex: 1; | ||
| 361 | + | ||
| 362 | + .protocol-body { | ||
| 363 | + padding: 30rpx; | ||
| 161 | 364 | ||
| 365 | + .protocol-text { | ||
| 366 | + font-size: 28rpx; | ||
| 367 | + line-height: 1.6; | ||
| 368 | + color: #333; | ||
| 369 | + white-space: pre-line; | ||
| 370 | + } | ||
| 371 | + } | ||
| 372 | + } | ||
| 373 | +} | ||
| 162 | </style> | 374 | </style> | ... | ... |
-
Please register or login to post a comment