hookehuyr

feat(支付): 添加收款信息验证和支付协议弹框功能

新增支付协议弹框组件,完善用户信息验证逻辑
在用户发布车辆前验证收款信息是否完整
添加支付协议弹框组件及相关交互逻辑
更新权限检查以包含收款信息字段
...@@ -41,10 +41,10 @@ declare module 'vue' { ...@@ -41,10 +41,10 @@ declare module 'vue' {
41 NutTextarea: typeof import('@nutui/nutui-taro')['Textarea'] 41 NutTextarea: typeof import('@nutui/nutui-taro')['Textarea']
42 NutToast: typeof import('@nutui/nutui-taro')['Toast'] 42 NutToast: typeof import('@nutui/nutui-taro')['Toast']
43 PayCard: typeof import('./src/components/payCard.vue')['default'] 43 PayCard: typeof import('./src/components/payCard.vue')['default']
44 + PaymentAgreementModal: typeof import('./src/components/PaymentAgreementModal.vue')['default']
44 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] 45 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
45 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 46 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
46 PrivacyAgreementModal: typeof import('./src/components/PrivacyAgreementModal.vue')['default'] 47 PrivacyAgreementModal: typeof import('./src/components/PrivacyAgreementModal.vue')['default']
47 - QrcodePay: typeof import('./src/components/qrcodePay.vue')['default']
48 RouterLink: typeof import('vue-router')['RouterLink'] 48 RouterLink: typeof import('vue-router')['RouterLink']
49 RouterView: typeof import('vue-router')['RouterView'] 49 RouterView: typeof import('vue-router')['RouterView']
50 SearchPopup: typeof import('./src/components/SearchPopup.vue')['default'] 50 SearchPopup: typeof import('./src/components/SearchPopup.vue')['default']
......
1 /* 1 /*
2 * @Date: 2023-12-22 10:29:37 2 * @Date: 2023-12-22 10:29:37
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-09 15:55:52 4 + * @LastEditTime: 2025-08-05 17:44:27
5 * @FilePath: /jgdl/src/api/index.js 5 * @FilePath: /jgdl/src/api/index.js
6 * @Description: 用户相关API接口 6 * @Description: 用户相关API接口
7 */ 7 */
...@@ -36,6 +36,10 @@ export const payCheckAPI = (params) => fn(fetch.post(Api.PAY_CHECK, params)); ...@@ -36,6 +36,10 @@ export const payCheckAPI = (params) => fn(fetch.post(Api.PAY_CHECK, params));
36 * @param phone 手机号 36 * @param phone 手机号
37 * @param sms_code 短信验证码 37 * @param sms_code 短信验证码
38 * @param school_id 学校id 38 * @param school_id 学校id
39 + * @param name 真实姓名
40 + * @param bank 开户行
41 + * @param bank_no 银行卡号
42 + * @param idcard 身份证号
39 * @returns 43 * @returns
40 */ 44 */
41 export const updateProfileAPI = (params) => fn(fetch.post(Api.UPDATE_PROFILE, params)); 45 export const updateProfileAPI = (params) => fn(fetch.post(Api.UPDATE_PROFILE, params));
......
1 +<template>
2 + <nut-popup
3 + v-model:visible="visible"
4 + position="bottom"
5 + :style="{ width: '100%', height: '100%' }"
6 + >
7 + <div class="payment-agreement-modal">
8 + <!-- 标题 -->
9 + <div class="modal-header">
10 + <h2 class="title">在捡个电驴收款</h2>
11 + </div>
12 +
13 + <!-- 内容区域 -->
14 + <div class="modal-content">
15 + <!-- 收款说明 -->
16 + <div class="info-row">
17 + <div class="label">收款说明</div>
18 + <div class="content">{{ paymentDescription }}</div>
19 + </div>
20 +
21 + <!-- 扣费说明 -->
22 + <div class="info-row">
23 + <div class="label">扣费说明</div>
24 + <div class="content">{{ feeDescription }}</div>
25 + </div>
26 +
27 + <!-- 协议勾选区域 -->
28 + <div v-if="!hasAgreed" class="agreement-section">
29 + <nut-checkbox v-model="isChecked" class="agreement-checkbox">
30 + <view class="checkbox-text">
31 + <text>我已阅读并同意</text>
32 + <text class="agreement-link" @tap.stop="showProtocol">
33 + 《支付协议》
34 + </text>
35 + </view>
36 + </nut-checkbox>
37 + </div>
38 + </div>
39 +
40 + <!-- 按钮区域 -->
41 + <div class="modal-footer">
42 + <div class="button-row">
43 + <nut-button
44 + type="default"
45 + class="close-button"
46 + @click="handleClose"
47 + >
48 + 关闭
49 + </nut-button>
50 + <nut-button
51 + v-if="!hasAgreed"
52 + :disabled="!isChecked"
53 + type="primary"
54 + class="main-button"
55 + color="orange"
56 + @click="handleAgree"
57 + >
58 + 同意
59 + </nut-button>
60 + <nut-button
61 + v-else
62 + type="primary"
63 + class="main-button"
64 + color="orange"
65 + @click="handleConfirm"
66 + >
67 + 确认
68 + </nut-button>
69 + </div>
70 + </div>
71 + </div>
72 +
73 + <!-- 支付协议弹框 -->
74 + <nut-popup
75 + v-model:visible="protocolVisible"
76 + position="bottom"
77 + :style="{ width: '100%', height: '70%' }"
78 + round
79 + closeable
80 + >
81 + <div class="protocol-modal">
82 + <div class="protocol-header">
83 + <h3>支付协议</h3>
84 + </div>
85 + <div class="protocol-content">
86 + <p>{{ protocolContent }}</p>
87 + </div>
88 + </div>
89 + </nut-popup>
90 + </nut-popup>
91 +</template>
92 +
93 +<script setup>
94 +import { ref, computed, onMounted } from 'vue'
95 +import { useUserStore } from '@/stores/user'
96 +
97 +/**
98 + * 用户收款说明组件
99 + * @param {Boolean} modelValue - 控制弹框显示隐藏
100 + * @param {Function} onAgree - 同意按钮回调
101 + * @param {Function} onConfirm - 确认按钮回调
102 + * @param {Function} onClose - 关闭弹框回调
103 + */
104 +const props = defineProps({
105 + modelValue: {
106 + type: Boolean,
107 + default: false
108 + }
109 +})
110 +
111 +const emit = defineEmits(['update:modelValue', 'agree', 'confirm', 'close'])
112 +
113 +const userStore = useUserStore()
114 +
115 +// 弹框显示状态
116 +const visible = computed({
117 + get: () => props.modelValue,
118 + set: (value) => emit('update:modelValue', value)
119 +})
120 +
121 +// 支付协议弹框显示状态
122 +const protocolVisible = ref(false)
123 +
124 +// 勾选状态
125 +const isChecked = ref(false)
126 +
127 +// 是否已同意过协议(mock数据)
128 +const hasAgreed = ref(false)
129 +
130 +// 收款说明内容
131 +const paymentDescription = ref('通过捡个电驴平台进行收款,资金安全有保障,支持多种收款方式。')
132 +
133 +// 扣费说明内容
134 +const feeDescription = ref('平台将收取交易金额的3%作为服务费,费用将在交易完成后自动扣除。')
135 +
136 +// 支付协议内容
137 +const protocolContent = ref(`
138 +1. 用户在使用捡个电驴收款服务时,需遵守相关法律法规。
139 +2. 平台有权对异常交易进行风险控制。
140 +3. 用户应确保收款信息的真实性和准确性。
141 +4. 平台将按照约定收取相应的服务费用。
142 +5. 如有争议,双方应友好协商解决。
143 +`)
144 +
145 +/**
146 + * 检查用户是否已同意过协议
147 + */
148 +const checkAgreementStatus = () => {
149 + // TODO: 实际项目中应该从userStore.userInfo中获取相关字段
150 + // 这里使用mock数据
151 + hasAgreed.value = userStore.userInfo?.paymentAgreementAccepted || false
152 +}
153 +
154 +/**
155 + * 显示支付协议
156 + */
157 +const showProtocol = () => {
158 + protocolVisible.value = true
159 +}
160 +
161 +/**
162 + * 处理同意按钮点击
163 + */
164 +const handleAgree = () => {
165 + if (!isChecked.value) return
166 +
167 + // TODO: 实际项目中应该调用API更新用户协议状态
168 + hasAgreed.value = true
169 +
170 + emit('agree')
171 +}
172 +
173 +/**
174 + * 处理确认按钮点击
175 + */
176 +const handleConfirm = () => {
177 + emit('confirm')
178 +}
179 +
180 +/**
181 + * 处理弹框关闭
182 + */
183 +const handleClose = () => {
184 + emit('close')
185 +}
186 +
187 +// 组件挂载时检查协议状态
188 +onMounted(() => {
189 + checkAgreementStatus()
190 +})
191 +</script>
192 +
193 +<style lang="less">
194 +.payment-agreement-modal {
195 + display: flex;
196 + flex-direction: column;
197 + height: 100%;
198 + padding: 32rpx;
199 + padding-top: 80rpx;
200 +
201 + .modal-header {
202 + text-align: center;
203 + margin-bottom: 40rpx;
204 +
205 + .title {
206 + font-size: 36rpx;
207 + font-weight: 600;
208 + color: #333;
209 + margin: 0;
210 + }
211 + }
212 +
213 + .modal-content {
214 + flex: 1;
215 + display: flex;
216 + flex-direction: column;
217 +
218 + .info-row {
219 + display: flex;
220 + margin-bottom: 32rpx;
221 +
222 + .label {
223 + width: 160rpx;
224 + font-size: 28rpx;
225 + font-weight: 500;
226 + color: #333;
227 + flex-shrink: 0;
228 + }
229 +
230 + .content {
231 + flex: 1;
232 + font-size: 28rpx;
233 + color: #666;
234 + line-height: 1.6;
235 + }
236 + }
237 +
238 + .agreement-section {
239 + margin-top: 40rpx;
240 + text-align: center;
241 +
242 + .agreement-checkbox {
243 + font-size: 28rpx;
244 +
245 + .checkbox-text {
246 + white-space: nowrap;
247 + display: inline-block;
248 + }
249 +
250 + .agreement-link {
251 + color: #ffa500;
252 + text-decoration: underline;
253 + cursor: pointer;
254 + }
255 + }
256 + }
257 + }
258 +
259 + .modal-footer {
260 + padding-top: 32rpx;
261 + border-top: 1rpx solid #eee;
262 +
263 + .button-row {
264 + display: flex;
265 + gap: 24rpx;
266 + align-items: center;
267 +
268 + .close-button {
269 + flex: 1;
270 + border: 1rpx solid #ddd;
271 + color: #666;
272 + }
273 +
274 + .main-button {
275 + flex: 1;
276 + }
277 + }
278 + }
279 +}
280 +
281 +.protocol-modal {
282 + padding: 32rpx;
283 + height: 100%;
284 + display: flex;
285 + flex-direction: column;
286 +
287 + .protocol-header {
288 + text-align: center;
289 + margin-bottom: 32rpx;
290 +
291 + h3 {
292 + font-size: 32rpx;
293 + font-weight: 600;
294 + color: #333;
295 + margin: 0;
296 + }
297 + }
298 +
299 + .protocol-content {
300 + flex: 1;
301 + overflow-y: auto;
302 +
303 + p {
304 + font-size: 28rpx;
305 + color: #666;
306 + line-height: 1.8;
307 + white-space: pre-line;
308 + margin: 0;
309 + }
310 + }
311 +}
312 +</style>
...@@ -318,6 +318,14 @@ ...@@ -318,6 +318,14 @@
318 <nut-picker v-model="tireWearValue" :columns="wearLevelOptions" title="选择轮胎磨损度" @confirm="onTireWearConfirm" 318 <nut-picker v-model="tireWearValue" :columns="wearLevelOptions" title="选择轮胎磨损度" @confirm="onTireWearConfirm"
319 @cancel="tireWearPickerVisible = false" /> 319 @cancel="tireWearPickerVisible = false" />
320 </nut-popup> 320 </nut-popup>
321 +
322 + <!-- 收款说明弹框 -->
323 + <PaymentAgreementModal
324 + v-model="paymentAgreementVisible"
325 + @agree="handlePaymentAgreementAgree"
326 + @confirm="handlePaymentAgreementConfirm"
327 + @close="handlePaymentAgreementClose"
328 + />
321 </view> 329 </view>
322 </template> 330 </template>
323 331
...@@ -327,6 +335,7 @@ import { Plus, Right, Location, Close } from '@nutui/icons-vue-taro' ...@@ -327,6 +335,7 @@ import { Plus, Right, Location, Close } from '@nutui/icons-vue-taro'
327 import Taro from '@tarojs/taro' 335 import Taro from '@tarojs/taro'
328 import BASE_URL from '@/utils/config'; 336 import BASE_URL from '@/utils/config';
329 import BrandModelPicker from '@/components/BrandModelPicker.vue' 337 import BrandModelPicker from '@/components/BrandModelPicker.vue'
338 +import PaymentAgreementModal from '@/components/PaymentAgreementModal.vue'
330 import { checkPermission, PERMISSION_TYPES } from '@/utils/permission' 339 import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
331 import './index.less' 340 import './index.less'
332 341
...@@ -410,6 +419,9 @@ const conditionPickerVisible = ref(false) ...@@ -410,6 +419,9 @@ const conditionPickerVisible = ref(false)
410 const brakeWearPickerVisible = ref(false) 419 const brakeWearPickerVisible = ref(false)
411 const tireWearPickerVisible = ref(false) 420 const tireWearPickerVisible = ref(false)
412 421
422 +// 收款说明弹框显示状态
423 +const paymentAgreementVisible = ref(false)
424 +
413 // 新的品牌型号选择器状态 425 // 新的品牌型号选择器状态
414 // 品牌型号选择器组件引用 426 // 品牌型号选择器组件引用
415 const brandModelPickerRef = ref(null) 427 const brandModelPickerRef = ref(null)
...@@ -1141,8 +1153,8 @@ onMounted(async () => { ...@@ -1141,8 +1153,8 @@ onMounted(async () => {
1141 // 可以通过 permissionResult.validFields 获取已通过校验的字段 1153 // 可以通过 permissionResult.validFields 获取已通过校验的字段
1142 Taro.showModal({ 1154 Taro.showModal({
1143 title: '温馨提示', 1155 title: '温馨提示',
1144 - content: `您还未填写${permissionResult.missingFields.includes('phone') ? '个人资料' : '收款信息'},展示无法发布`, 1156 + content: `您未填写${permissionResult.missingFields.includes('phone') ? '个人资料' : '收款信息'}, 将无法发布车辆`,
1145 - cancelText: '稍后设置', 1157 + cancelText: '关闭',
1146 confirmText: '立即设置', 1158 confirmText: '立即设置',
1147 success: (res) => { 1159 success: (res) => {
1148 if (res.confirm) { 1160 if (res.confirm) {
...@@ -1151,7 +1163,7 @@ onMounted(async () => { ...@@ -1151,7 +1163,7 @@ onMounted(async () => {
1151 Taro.navigateTo({ 1163 Taro.navigateTo({
1152 url: '/pages/register/index' 1164 url: '/pages/register/index'
1153 }) 1165 })
1154 - } else if (permissionResult.missingFields.includes('paymentInfo')) { 1166 + } else if (permissionResult.missingFields.includes('name') || permissionResult.missingFields.includes('bank') || permissionResult.missingFields.includes('bank_no') || permissionResult.missingFields.includes('idcard')) {
1155 // 收款信息未填写 1167 // 收款信息未填写
1156 Taro.navigateTo({ 1168 Taro.navigateTo({
1157 url: '/pages/collectionSettings/index' 1169 url: '/pages/collectionSettings/index'
...@@ -1166,6 +1178,9 @@ onMounted(async () => { ...@@ -1166,6 +1178,9 @@ onMounted(async () => {
1166 return 1178 return
1167 } 1179 }
1168 1180
1181 + // 权限验证通过,显示收款说明弹框
1182 + paymentAgreementVisible.value = true
1183 +
1169 // 加载基础数据 1184 // 加载基础数据
1170 await Promise.all([ 1185 await Promise.all([
1171 loadSchools(), 1186 loadSchools(),
...@@ -1194,4 +1209,50 @@ onMounted(async () => { ...@@ -1194,4 +1209,50 @@ onMounted(async () => {
1194 }); 1209 });
1195 } 1210 }
1196 }) 1211 })
1212 +
1213 +/**
1214 + * 处理收款说明弹框的同意操作
1215 + */
1216 +const handlePaymentAgreementAgree = () => {
1217 + // 用户同意收款协议,组件内部会处理关闭逻辑
1218 + paymentAgreementVisible.value = false
1219 + // 这里可以添加同意后的逻辑,比如更新用户状态等
1220 + Taro.showToast({
1221 + title: '已同意收款协议',
1222 + icon: 'success'
1223 + })
1224 +}
1225 +
1226 +/**
1227 + * 处理收款说明弹框的确认操作
1228 + */
1229 +const handlePaymentAgreementConfirm = () => {
1230 + // 用户确认收款说明,组件内部会处理关闭逻辑
1231 + paymentAgreementVisible.value = false
1232 + // 这里可以添加确认后的逻辑
1233 + Taro.showToast({
1234 + title: '已确认收款说明',
1235 + icon: 'success'
1236 + })
1237 +}
1238 +
1239 +/**
1240 + * 处理收款说明弹框的关闭操作
1241 + */
1242 +const handlePaymentAgreementClose = () => {
1243 + // 用户关闭收款说明弹框
1244 + paymentAgreementVisible.value = false
1245 +
1246 + // 判断是否有上一页,如果有就返回上一页,没有则返回首页
1247 + const pages = Taro.getCurrentPages()
1248 + if (pages.length > 1) {
1249 + // 有上一页,返回上一页
1250 + Taro.navigateBack()
1251 + } else {
1252 + // 没有上一页,跳转到首页
1253 + Taro.redirectTo({
1254 + url: '/pages/index/index'
1255 + })
1256 + }
1257 +}
1197 </script> 1258 </script>
......
1 /* 1 /*
2 * @Date: 2025-01-08 18:00:00 2 * @Date: 2025-01-08 18:00:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-24 10:52:42 4 + * @LastEditTime: 2025-08-05 17:45:17
5 * @FilePath: /jgdl/src/stores/user.js 5 * @FilePath: /jgdl/src/stores/user.js
6 * @Description: 用户状态管理 6 * @Description: 用户状态管理
7 */ 7 */
...@@ -24,6 +24,10 @@ export const useUserStore = defineStore('user', { ...@@ -24,6 +24,10 @@ export const useUserStore = defineStore('user', {
24 order_count: 0, 24 order_count: 0,
25 favorite_count: 0, 25 favorite_count: 0,
26 message_count: 0, 26 message_count: 0,
27 + name: '',
28 + bank: '',
29 + bank_no: '',
30 + idcard: '',
27 }, 31 },
28 isAuthenticated: false, 32 isAuthenticated: false,
29 isLoading: false 33 isLoading: false
...@@ -102,6 +106,10 @@ export const useUserStore = defineStore('user', { ...@@ -102,6 +106,10 @@ export const useUserStore = defineStore('user', {
102 order_count: 0, 106 order_count: 0,
103 favorite_count: 0, 107 favorite_count: 0,
104 message_count: 0, 108 message_count: 0,
109 + name: '',
110 + bank: '',
111 + bank_no: '',
112 + idcard: '',
105 } 113 }
106 this.isAuthenticated = false 114 this.isAuthenticated = false
107 }, 115 },
......
1 /* 1 /*
2 * @Date: 2025-01-08 18:00:00 2 * @Date: 2025-01-08 18:00:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-09 11:16:49 4 + * @LastEditTime: 2025-08-05 17:46:11
5 * @FilePath: /jgdl/src/utils/permission.js 5 * @FilePath: /jgdl/src/utils/permission.js
6 * @Description: 权限控制工具函数 6 * @Description: 权限控制工具函数
7 */ 7 */
...@@ -40,7 +40,7 @@ const PERMISSION_CONFIG = { ...@@ -40,7 +40,7 @@ const PERMISSION_CONFIG = {
40 [PERMISSION_TYPES.SELL_CAR]: { 40 [PERMISSION_TYPES.SELL_CAR]: {
41 message: '发布车源需要先完善个人信息', 41 message: '发布车源需要先完善个人信息',
42 redirectUrl: '/pages/register/index', 42 redirectUrl: '/pages/register/index',
43 - checkFields: ['phone'] 43 + checkFields: ['phone', 'name', 'bank', 'bank_no', 'idcard']
44 }, 44 },
45 [PERMISSION_TYPES.BUY_CAR]: { 45 [PERMISSION_TYPES.BUY_CAR]: {
46 message: '购买车辆需要先完善个人信息', 46 message: '购买车辆需要先完善个人信息',
......