hookehuyr

feat(payCard): 添加支付协议勾选功能及弹窗展示

在支付卡片组件中添加协议勾选区域,用户需同意协议才能进行支付。新增协议弹窗展示详细内容,并实现协议状态检查与更新逻辑。同时调整了相关样式确保UI一致性。
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>
......