Commit 4477f605da7ff298d6199eaf0454792ecfdeb6ac
1 parent
cec216f8
更改打包配置
Showing
5 changed files
with
405 additions
and
125 deletions
apps/web-payment/.env.production
apps/web-payment/src/api/payment.ts
| ... | ... | @@ -51,3 +51,30 @@ export async function listUserCards( |
| 51 | 51 | `/card/payment/listUserCards?pipelineId=${pipelineId}&userId=${userId}`, |
| 52 | 52 | ); |
| 53 | 53 | } |
| 54 | + | |
| 55 | +/** | |
| 56 | + * 查询支付状态 | |
| 57 | + * @param paymentId | |
| 58 | + * @param mode | |
| 59 | + * @returns | |
| 60 | + */ | |
| 61 | +export async function paymentState(paymentId: number | string, mode: 'online') { | |
| 62 | + return requestClient.post<any>( | |
| 63 | + `/payment/cashier/paymentState?paymentId=${paymentId}&mode=${mode}`, | |
| 64 | + ); | |
| 65 | +} | |
| 66 | + | |
| 67 | +/** | |
| 68 | + * 中瑞特供查询 | |
| 69 | + * @param paymentId | |
| 70 | + * @param mode | |
| 71 | + * @returns | |
| 72 | + */ | |
| 73 | +export async function zrPaymentState( | |
| 74 | + paymentId: number | string, | |
| 75 | + mode: 'online', | |
| 76 | +) { | |
| 77 | + return requestClient.post<any>( | |
| 78 | + `/payment/cashier/zrPaymentState?paymentId=${paymentId}&mode=${mode}`, | |
| 79 | + ); | |
| 80 | +} | ... | ... |
apps/web-payment/src/views/payment/index.vue
| ... | ... | @@ -5,6 +5,7 @@ import { useRoute, useRouter } from 'vue-router'; |
| 5 | 5 | import { fenToYuan } from '@vben/utils'; |
| 6 | 6 | |
| 7 | 7 | import { |
| 8 | + Close, | |
| 8 | 9 | CreditCard, |
| 9 | 10 | SuccessFilled, |
| 10 | 11 | Wallet, |
| ... | ... | @@ -13,7 +14,13 @@ import { |
| 13 | 14 | import { ElButton, ElDialog, ElIcon, ElMessage, ElRadio } from 'element-plus'; |
| 14 | 15 | import qs from 'qs'; |
| 15 | 16 | |
| 16 | -import { getOpenId, listUserCards, orderInfo, orderPayment } from '#/api'; | |
| 17 | +import { | |
| 18 | + getOpenId, | |
| 19 | + listUserCards, | |
| 20 | + orderInfo, | |
| 21 | + orderPayment, | |
| 22 | + zrPaymentState, | |
| 23 | +} from '#/api'; | |
| 17 | 24 | import EnvironmentDetector from '#/composables/environmentDetector'; |
| 18 | 25 | |
| 19 | 26 | import PasswordInput from './component/PasswordInput.vue'; |
| ... | ... | @@ -49,7 +56,7 @@ const REDIRECT_CHANNEL_ID = 29; |
| 49 | 56 | |
| 50 | 57 | // 检查参数是否有效 |
| 51 | 58 | const hasValidParams = computed(() => { |
| 52 | - return !!(token.value && code.value); | |
| 59 | + return !!(token.value || code.value); | |
| 53 | 60 | }); |
| 54 | 61 | |
| 55 | 62 | const cardList = ref<Card[]>([]); |
| ... | ... | @@ -118,28 +125,23 @@ const getPaymentIcon = (channelName: string) => { |
| 118 | 125 | } |
| 119 | 126 | }; |
| 120 | 127 | |
| 121 | -const jumpTest = () => { | |
| 122 | - // jWeixin.miniProgram.redirectTo({ | |
| 123 | - // url: `/packageA/pages/wePay/index?amount=${displayAmount.value}&businessType=3&redirect=${true}`, | |
| 124 | - // }); | |
| 125 | - router.push({ | |
| 126 | - path: '/paymentSuccess', | |
| 127 | - query: { | |
| 128 | - amount: displayAmount.value, | |
| 129 | - success: 'true', | |
| 130 | - payType: '园区卡支付', | |
| 131 | - }, | |
| 132 | - }); | |
| 128 | +// 判断是否是园区卡支付 | |
| 129 | +const isCardPayment = (pipeline: Pipeline) => { | |
| 130 | + const channelName = pipeline.channelName.toLowerCase(); | |
| 131 | + return channelName.includes('园区卡') || channelName.includes('卡'); | |
| 133 | 132 | }; |
| 134 | 133 | |
| 135 | 134 | // 获取支付方式描述 |
| 136 | 135 | const getPaymentDesc = (pipeline: Pipeline) => { |
| 136 | + // 只有当前选中的支付方式是园区卡支付,且已选择卡时,才显示卡号 | |
| 137 | 137 | if ( |
| 138 | 138 | pipeline.pipelineId === currentPaymentMethod.value && |
| 139 | + isCardPayment(pipeline) && | |
| 139 | 140 | selectedCard.value |
| 140 | 141 | ) { |
| 141 | 142 | return `卡号:${selectedCard.value.cardNo}`; |
| 142 | 143 | } |
| 144 | + | |
| 143 | 145 | const channelName = pipeline.channelName.toLowerCase(); |
| 144 | 146 | if (channelName.includes('微信')) { |
| 145 | 147 | return '推荐使用'; |
| ... | ... | @@ -181,6 +183,9 @@ const handlepayBtnShowClick = async (pipeline: Pipeline) => { |
| 181 | 183 | } |
| 182 | 184 | showCardDialog.value = true; |
| 183 | 185 | } else { |
| 186 | + // 切换到其他支付方式时,清空已选择的园区卡 | |
| 187 | + selectedCard.value = null; | |
| 188 | + | |
| 184 | 189 | if (pipeline.channelId === REDIRECT_CHANNEL_ID) { |
| 185 | 190 | payBtnShow.value = true; |
| 186 | 191 | return; |
| ... | ... | @@ -192,7 +197,7 @@ const handlepayBtnShowClick = async (pipeline: Pipeline) => { |
| 192 | 197 | const handleCardSelect = (card: Card) => { |
| 193 | 198 | selectedCard.value = card; |
| 194 | 199 | showCardDialog.value = false; |
| 195 | - ElMessage.success(`已选择 ${card.name} 的园区卡`); | |
| 200 | + // ElMessage.success(`已选择 ${card.name} 的园区卡`); | |
| 196 | 201 | payBtnShow.value = true; |
| 197 | 202 | }; |
| 198 | 203 | |
| ... | ... | @@ -203,7 +208,6 @@ const handlePasswordCancel = () => { |
| 203 | 208 | }; |
| 204 | 209 | |
| 205 | 210 | const handlePasswordComplete = async (password: string) => { |
| 206 | - showPasswordDialog.value = false; | |
| 207 | 211 | try { |
| 208 | 212 | const params = { |
| 209 | 213 | tradeId: orderInfoData.value.tradeId, |
| ... | ... | @@ -216,14 +220,17 @@ const handlePasswordComplete = async (password: string) => { |
| 216 | 220 | }; |
| 217 | 221 | |
| 218 | 222 | const data = await orderPayment(params); |
| 219 | - router.push({ | |
| 220 | - path: '/paymentSuccess', | |
| 221 | - query: { | |
| 222 | - amount: displayAmount.value, | |
| 223 | - success: 'true', | |
| 224 | - payType: '园区卡支付', | |
| 225 | - }, | |
| 226 | - }); | |
| 223 | + // 园区卡支付成功开始轮询结果 | |
| 224 | + checkPayResult(data.paymentId); | |
| 225 | + // router.push({ | |
| 226 | + // path: '/paymentSuccess', | |
| 227 | + // query: { | |
| 228 | + // amount: displayAmount.value, | |
| 229 | + // payee: orderInfoData.value.mchName, | |
| 230 | + // success: 'true', | |
| 231 | + // payType: '园区卡支付', | |
| 232 | + // }, | |
| 233 | + // }); | |
| 227 | 234 | } catch (error) { |
| 228 | 235 | errorMessage.value = error?.message || '支付失败'; |
| 229 | 236 | payErrorDialog.value = true; |
| ... | ... | @@ -251,11 +258,10 @@ const queryPayment = async () => { |
| 251 | 258 | payType: currentPayType.value.channelName, |
| 252 | 259 | redirect: true, |
| 253 | 260 | payee: orderInfoData.value.mchName, |
| 254 | - redirectUrl: orderInfoData.value.redirectUrl, | |
| 255 | 261 | }; |
| 256 | 262 | const queryString = qs.stringify(params); |
| 257 | 263 | if (typeof jWeixin !== 'undefined' && jWeixin.miniProgram) { |
| 258 | - jWeixin.miniProgram.navigateTo({ | |
| 264 | + jWeixin.miniProgram.redirectTo({ | |
| 259 | 265 | url: `/packageA/pages/wxPay/index?${queryString}`, |
| 260 | 266 | }); |
| 261 | 267 | loading.value = false; |
| ... | ... | @@ -276,7 +282,6 @@ const queryPayment = async () => { |
| 276 | 282 | goods: orderInfoData.value.goods, |
| 277 | 283 | amount: orderInfoData.value.amount, |
| 278 | 284 | payType: currentPayType.value.channelName, |
| 279 | - redirectUrl: orderInfoData.value.redirectUrl, | |
| 280 | 285 | payee: orderInfoData.value.mchName, |
| 281 | 286 | }; |
| 282 | 287 | const queryString = qs.stringify(pramsData); |
| ... | ... | @@ -312,17 +317,17 @@ const handlePay = async () => { |
| 312 | 317 | } |
| 313 | 318 | |
| 314 | 319 | const channelName = currentPayType.value.channelName.toLowerCase(); |
| 315 | - const isCardPayment = | |
| 320 | + const isCardPaymentMethod = | |
| 316 | 321 | channelName.includes('园区卡') || channelName.includes('卡支付'); |
| 317 | 322 | |
| 318 | - if (isCardPayment && !selectedCard.value) { | |
| 323 | + if (isCardPaymentMethod && !selectedCard.value) { | |
| 319 | 324 | ElMessage.warning('请先选择园区卡'); |
| 320 | 325 | return; |
| 321 | 326 | } |
| 322 | 327 | |
| 323 | 328 | loading.value = true; |
| 324 | 329 | |
| 325 | - if (isCardPayment) { | |
| 330 | + if (isCardPaymentMethod) { | |
| 326 | 331 | // 园区卡支付逻辑 |
| 327 | 332 | try { |
| 328 | 333 | showPasswordDialog.value = true; |
| ... | ... | @@ -345,6 +350,12 @@ const handleCloseDialog = () => { |
| 345 | 350 | payBtnShow.value = false; |
| 346 | 351 | }; |
| 347 | 352 | |
| 353 | +// 点击遮罩层关闭 | |
| 354 | +const handleMaskClick = () => { | |
| 355 | + showCardDialog.value = false; | |
| 356 | + payBtnShow.value = false; | |
| 357 | +}; | |
| 358 | + | |
| 348 | 359 | // 获取订单信息 支付方式等数据 |
| 349 | 360 | const getOrderInfo = async () => { |
| 350 | 361 | try { |
| ... | ... | @@ -378,6 +389,86 @@ const getListUserCards = async () => { |
| 378 | 389 | } |
| 379 | 390 | }; |
| 380 | 391 | |
| 392 | +let pollTimer: any = null; | |
| 393 | +let pollCount = 0; | |
| 394 | +// 最大轮询次数 | |
| 395 | +const maxPollCount = 30; | |
| 396 | +// 轮询间隔(毫秒) | |
| 397 | +const pollInterval = 2000; | |
| 398 | + | |
| 399 | +const stopPollingPayResult = () => { | |
| 400 | + showPasswordDialog.value = false; | |
| 401 | + if (pollTimer) { | |
| 402 | + clearTimeout(pollTimer); | |
| 403 | + pollTimer = null; | |
| 404 | + } | |
| 405 | + pollCount = 0; | |
| 406 | +}; | |
| 407 | + | |
| 408 | +const paySuccessHandler = (data: any) => { | |
| 409 | + router.replace({ | |
| 410 | + path: '/paymentSuccess', | |
| 411 | + query: { | |
| 412 | + amount: displayAmount.value, | |
| 413 | + payee: orderInfoData.value.mchName, | |
| 414 | + success: 'true', | |
| 415 | + payType: '园区卡支付', | |
| 416 | + redirectUrl: data.redirectUrl, | |
| 417 | + }, | |
| 418 | + }); | |
| 419 | +}; | |
| 420 | + | |
| 421 | +const payFailHandler = (msg: any) => {}; | |
| 422 | + | |
| 423 | +const payTimeoutHandler = () => { | |
| 424 | + // errorMessage: '支付结果查询超时,请稍后在订单中查看支付状态' | |
| 425 | +}; | |
| 426 | + | |
| 427 | +const checkPayResult = async (paymentId: number | string) => { | |
| 428 | + try { | |
| 429 | + const data = await zrPaymentState(paymentId, 'online'); | |
| 430 | + if (data?.state === 4) { | |
| 431 | + stopPollingPayResult(); | |
| 432 | + paySuccessHandler(data); | |
| 433 | + } else if (data?.state === 6) { | |
| 434 | + // 支付失败 | |
| 435 | + stopPollingPayResult(); | |
| 436 | + payFailHandler(data?.message || '支付失败'); | |
| 437 | + } else { | |
| 438 | + pollCount++; | |
| 439 | + if (pollCount < maxPollCount) { | |
| 440 | + pollTimer = setTimeout(() => { | |
| 441 | + checkPayResult(paymentId); | |
| 442 | + }, pollInterval); | |
| 443 | + } else { | |
| 444 | + // 超过最大轮询次数,停止轮询 | |
| 445 | + console.log('支付结果查询超时'); | |
| 446 | + stopPollingPayResult(); | |
| 447 | + payTimeoutHandler(); | |
| 448 | + } | |
| 449 | + } | |
| 450 | + } catch (error) { | |
| 451 | + ElMessage.error(JSON.stringify(error)); | |
| 452 | + console.error('支付结果查询失败:', error); | |
| 453 | + if (error?.code === 'E504') { | |
| 454 | + stopPollingPayResult(); | |
| 455 | + payFailHandler(error?.msg || '支付失败'); | |
| 456 | + } else { | |
| 457 | + // 查询失败,继续轮询 | |
| 458 | + pollCount++; | |
| 459 | + | |
| 460 | + if (pollCount < maxPollCount) { | |
| 461 | + pollTimer = setTimeout(() => { | |
| 462 | + checkPayResult(paymentId); | |
| 463 | + }, pollInterval); | |
| 464 | + } else { | |
| 465 | + stopPollingPayResult(); | |
| 466 | + payTimeoutHandler(); | |
| 467 | + } | |
| 468 | + } | |
| 469 | + } | |
| 470 | +}; | |
| 471 | + | |
| 381 | 472 | const init = async () => { |
| 382 | 473 | token.value = (route.query?.token as string) || ''; |
| 383 | 474 | openId.value = (route.query?.openId as string) || ''; |
| ... | ... | @@ -547,39 +638,51 @@ init(); |
| 547 | 638 | </div> |
| 548 | 639 | </div> |
| 549 | 640 | |
| 550 | - <!-- 园区卡选择弹窗 --> | |
| 551 | - <ElDialog | |
| 552 | - v-model="showCardDialog" | |
| 553 | - title="选择园区卡" | |
| 554 | - width="90%" | |
| 555 | - :style="{ maxWidth: '500px' }" | |
| 556 | - class="card-dialog" | |
| 557 | - :close-on-click-modal="false" | |
| 558 | - > | |
| 559 | - <div class="card-list-dialog"> | |
| 560 | - <div | |
| 561 | - v-for="card in cardList" | |
| 562 | - :key="card.cardNo" | |
| 563 | - class="card-item-dialog" | |
| 564 | - @click="handleCardSelect(card)" | |
| 565 | - > | |
| 566 | - <div class="card-item-info"> | |
| 567 | - <p class="card-holder">{{ card.name }}</p> | |
| 568 | - <p class="card-number">{{ card.cardNo }}</p> | |
| 641 | + <!-- 园区卡选择底部弹出层 --> | |
| 642 | + <transition name="fade"> | |
| 643 | + <div | |
| 644 | + v-if="showCardDialog" | |
| 645 | + class="bottom-sheet-mask" | |
| 646 | + @click="handleMaskClick" | |
| 647 | + ></div> | |
| 648 | + </transition> | |
| 649 | + <transition name="slide-up"> | |
| 650 | + <div v-if="showCardDialog" class="bottom-sheet"> | |
| 651 | + <div class="bottom-sheet-header"> | |
| 652 | + <h3 class="bottom-sheet-title">选择园区卡</h3> | |
| 653 | + <div class="bottom-sheet-close" @click="handleCloseDialog"> | |
| 654 | + <ElIcon :size="24" color="#6b7280"> | |
| 655 | + <Close /> | |
| 656 | + </ElIcon> | |
| 569 | 657 | </div> |
| 570 | - <div class="card-balance"> | |
| 571 | - <p class="balance-label">余额</p> | |
| 572 | - <p class="balance-value">¥{{ card.amount }}</p> | |
| 658 | + </div> | |
| 659 | + <div class="bottom-sheet-body"> | |
| 660 | + <div class="card-list"> | |
| 661 | + <div | |
| 662 | + v-for="card in cardList" | |
| 663 | + :key="card.cardNo" | |
| 664 | + class="card-item" | |
| 665 | + @click="handleCardSelect(card)" | |
| 666 | + > | |
| 667 | + <div class="card-item-info"> | |
| 668 | + <p class="card-holder">{{ card.name }}</p> | |
| 669 | + <p class="card-number">{{ card.cardNo }}</p> | |
| 670 | + </div> | |
| 671 | + <div class="card-balance"> | |
| 672 | + <p class="balance-label">余额</p> | |
| 673 | + <p class="balance-value">¥{{ fenToYuan(card.amount) }}</p> | |
| 674 | + </div> | |
| 675 | + </div> | |
| 676 | + <div v-if="cardList.length === 0" class="empty-card"> | |
| 677 | + <p>暂无可用园区卡</p> | |
| 678 | + </div> | |
| 573 | 679 | </div> |
| 574 | 680 | </div> |
| 575 | - <div v-if="cardList.length === 0" class="empty-card"> | |
| 576 | - <p>暂无可用园区卡</p> | |
| 681 | + <div class="bottom-sheet-footer"> | |
| 682 | + <button class="cancel-button" @click="handleCloseDialog">取消</button> | |
| 577 | 683 | </div> |
| 578 | 684 | </div> |
| 579 | - <template #footer> | |
| 580 | - <ElButton @click="handleCloseDialog">取消</ElButton> | |
| 581 | - </template> | |
| 582 | - </ElDialog> | |
| 685 | + </transition> | |
| 583 | 686 | |
| 584 | 687 | <ElDialog |
| 585 | 688 | v-model="payErrorDialog" |
| ... | ... | @@ -680,6 +783,28 @@ init(); |
| 680 | 783 | } |
| 681 | 784 | } |
| 682 | 785 | |
| 786 | +// 遮罩层动画 | |
| 787 | +.fade-enter-active, | |
| 788 | +.fade-leave-active { | |
| 789 | + transition: opacity 0.3s ease; | |
| 790 | +} | |
| 791 | + | |
| 792 | +.fade-enter-from, | |
| 793 | +.fade-leave-to { | |
| 794 | + opacity: 0; | |
| 795 | +} | |
| 796 | + | |
| 797 | +// 底部弹出动画 | |
| 798 | +.slide-up-enter-active, | |
| 799 | +.slide-up-leave-active { | |
| 800 | + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| 801 | +} | |
| 802 | + | |
| 803 | +.slide-up-enter-from, | |
| 804 | +.slide-up-leave-to { | |
| 805 | + transform: translateY(100%); | |
| 806 | +} | |
| 807 | + | |
| 683 | 808 | .cashier-container { |
| 684 | 809 | min-height: 100vh; |
| 685 | 810 | background: linear-gradient( |
| ... | ... | @@ -994,90 +1119,201 @@ init(); |
| 994 | 1119 | } |
| 995 | 1120 | } |
| 996 | 1121 | |
| 997 | -// 园区卡选择弹窗 | |
| 998 | -.card-dialog { | |
| 999 | - :deep(.el-dialog__header) { | |
| 1000 | - padding: 20px 24px; | |
| 1122 | +// 底部弹出层样式 | |
| 1123 | +.bottom-sheet-mask { | |
| 1124 | + position: fixed; | |
| 1125 | + inset: 0; | |
| 1126 | + z-index: 1000; | |
| 1127 | + background-color: rgb(0 0 0 / 50%); | |
| 1128 | +} | |
| 1129 | + | |
| 1130 | +.bottom-sheet { | |
| 1131 | + position: fixed; | |
| 1132 | + right: 0; | |
| 1133 | + bottom: 0; | |
| 1134 | + left: 0; | |
| 1135 | + z-index: 1001; | |
| 1136 | + display: flex; | |
| 1137 | + flex-direction: column; | |
| 1138 | + max-height: 70vh; | |
| 1139 | + background: #fff; | |
| 1140 | + border-radius: 16px 16px 0 0; | |
| 1141 | + box-shadow: 0 -4px 20px rgb(0 0 0 / 10%); | |
| 1142 | + | |
| 1143 | + .bottom-sheet-header { | |
| 1144 | + display: flex; | |
| 1145 | + flex-shrink: 0; | |
| 1146 | + align-items: center; | |
| 1147 | + justify-content: space-between; | |
| 1148 | + padding: 20px 24px 16px; | |
| 1001 | 1149 | border-bottom: 1px solid #f0f0f0; |
| 1002 | - } | |
| 1003 | 1150 | |
| 1004 | - :deep(.el-dialog__title) { | |
| 1005 | - font-size: 18px; | |
| 1006 | - font-weight: 600; | |
| 1007 | - color: #1f2937; | |
| 1151 | + .bottom-sheet-title { | |
| 1152 | + margin: 0; | |
| 1153 | + font-size: 18px; | |
| 1154 | + font-weight: 600; | |
| 1155 | + color: #1f2937; | |
| 1156 | + } | |
| 1157 | + | |
| 1158 | + .bottom-sheet-close { | |
| 1159 | + display: flex; | |
| 1160 | + align-items: center; | |
| 1161 | + justify-content: center; | |
| 1162 | + width: 32px; | |
| 1163 | + height: 32px; | |
| 1164 | + cursor: pointer; | |
| 1165 | + border-radius: 50%; | |
| 1166 | + transition: background-color 0.2s; | |
| 1167 | + | |
| 1168 | + &:hover { | |
| 1169 | + background-color: #f3f4f6; | |
| 1170 | + } | |
| 1171 | + | |
| 1172 | + &:active { | |
| 1173 | + background-color: #e5e7eb; | |
| 1174 | + } | |
| 1175 | + } | |
| 1008 | 1176 | } |
| 1009 | 1177 | |
| 1010 | - :deep(.el-dialog__body) { | |
| 1011 | - padding: 24px; | |
| 1178 | + .bottom-sheet-body { | |
| 1179 | + flex: 1; | |
| 1180 | + overflow: hidden; | |
| 1012 | 1181 | } |
| 1013 | -} | |
| 1014 | 1182 | |
| 1015 | -.card-list-dialog { | |
| 1016 | - display: flex; | |
| 1017 | - flex-direction: column; | |
| 1018 | - gap: 12px; | |
| 1019 | - max-height: 400px; | |
| 1020 | - overflow-y: auto; | |
| 1183 | + .card-list { | |
| 1184 | + display: flex; | |
| 1185 | + flex-direction: column; | |
| 1186 | + gap: 12px; | |
| 1187 | + max-height: calc(70vh - 160px); | |
| 1188 | + padding: 36px 24px; | |
| 1189 | + overflow-y: auto; | |
| 1190 | + -webkit-overflow-scrolling: touch; | |
| 1191 | + | |
| 1192 | + &::-webkit-scrollbar { | |
| 1193 | + width: 4px; | |
| 1194 | + } | |
| 1021 | 1195 | |
| 1022 | - .empty-card { | |
| 1023 | - padding: 40px 20px; | |
| 1024 | - color: #9ca3af; | |
| 1025 | - text-align: center; | |
| 1026 | - } | |
| 1027 | -} | |
| 1196 | + &::-webkit-scrollbar-thumb { | |
| 1197 | + background-color: rgb(0 0 0 / 20%); | |
| 1198 | + border-radius: 2px; | |
| 1199 | + } | |
| 1028 | 1200 | |
| 1029 | -.card-item-dialog { | |
| 1030 | - display: flex; | |
| 1031 | - align-items: center; | |
| 1032 | - justify-content: space-between; | |
| 1033 | - padding: 16px; | |
| 1034 | - cursor: pointer; | |
| 1035 | - background: linear-gradient(135deg, #f0fdf4 0%, #d1fae5 100%); | |
| 1036 | - border: 2px solid transparent; | |
| 1037 | - border-radius: 12px; | |
| 1038 | - transition: all 0.3s ease; | |
| 1039 | - | |
| 1040 | - &:hover { | |
| 1041 | - background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); | |
| 1042 | - border-color: #10b981; | |
| 1043 | - box-shadow: 0 4px 12px rgb(16 185 129 / 20%); | |
| 1044 | - transform: translateY(-2px); | |
| 1201 | + &::-webkit-scrollbar-track { | |
| 1202 | + background-color: transparent; | |
| 1203 | + } | |
| 1204 | + | |
| 1205 | + .empty-card { | |
| 1206 | + padding: 40px 20px; | |
| 1207 | + color: #9ca3af; | |
| 1208 | + text-align: center; | |
| 1209 | + } | |
| 1045 | 1210 | } |
| 1046 | 1211 | |
| 1047 | - .card-item-info { | |
| 1048 | - .card-holder { | |
| 1049 | - margin: 0 0 4px; | |
| 1050 | - font-size: 16px; | |
| 1051 | - font-weight: 600; | |
| 1052 | - color: #1f2937; | |
| 1212 | + .card-item { | |
| 1213 | + display: flex; | |
| 1214 | + flex-shrink: 0; | |
| 1215 | + align-items: center; | |
| 1216 | + justify-content: space-between; | |
| 1217 | + padding: 16px; | |
| 1218 | + cursor: pointer; | |
| 1219 | + background: #fff; | |
| 1220 | + border: 2px solid #f0f0f0; | |
| 1221 | + border-radius: 12px; | |
| 1222 | + transition: all 0.3s ease; | |
| 1223 | + | |
| 1224 | + &:hover { | |
| 1225 | + background: linear-gradient(135deg, #fff9f1 0%, #ffe9d4 100%); | |
| 1226 | + border-color: #ea4200; | |
| 1227 | + box-shadow: 0 4px 12px rgb(234 66 0 / 20%); | |
| 1228 | + transform: translateY(-2px); | |
| 1053 | 1229 | } |
| 1054 | 1230 | |
| 1055 | - .card-number { | |
| 1056 | - margin: 0; | |
| 1057 | - font-family: 'Courier New', monospace; | |
| 1058 | - font-size: 13px; | |
| 1059 | - color: #6b7280; | |
| 1231 | + &:active { | |
| 1232 | + background: linear-gradient(135deg, #ffe9d4 0%, #ffd9b8 100%); | |
| 1233 | + transform: translateY(0); | |
| 1234 | + } | |
| 1235 | + | |
| 1236 | + .card-item-info { | |
| 1237 | + .card-holder { | |
| 1238 | + margin: 0 0 4px; | |
| 1239 | + font-size: 16px; | |
| 1240 | + font-weight: 600; | |
| 1241 | + color: #1f2937; | |
| 1242 | + } | |
| 1243 | + | |
| 1244 | + .card-number { | |
| 1245 | + margin: 0; | |
| 1246 | + font-family: 'Courier New', monospace; | |
| 1247 | + font-size: 16px; | |
| 1248 | + color: #6b7280; | |
| 1249 | + } | |
| 1250 | + } | |
| 1251 | + | |
| 1252 | + .card-balance { | |
| 1253 | + text-align: right; | |
| 1254 | + | |
| 1255 | + .balance-label { | |
| 1256 | + margin: 0 0 4px; | |
| 1257 | + font-size: 12px; | |
| 1258 | + color: #6b7280; | |
| 1259 | + } | |
| 1260 | + | |
| 1261 | + .balance-value { | |
| 1262 | + margin: 0; | |
| 1263 | + font-size: 18px; | |
| 1264 | + font-weight: bold; | |
| 1265 | + color: #ea4200; | |
| 1266 | + } | |
| 1060 | 1267 | } |
| 1061 | 1268 | } |
| 1062 | 1269 | |
| 1063 | - .card-balance { | |
| 1064 | - text-align: right; | |
| 1270 | + .bottom-sheet-footer { | |
| 1271 | + flex-shrink: 0; | |
| 1272 | + padding: 16px 24px; | |
| 1273 | + padding-bottom: calc(16px + env(safe-area-inset-bottom)); | |
| 1274 | + border-top: 1px solid #f0f0f0; | |
| 1065 | 1275 | |
| 1066 | - .balance-label { | |
| 1067 | - margin: 0 0 4px; | |
| 1068 | - font-size: 12px; | |
| 1276 | + .cancel-button { | |
| 1277 | + width: 100%; | |
| 1278 | + height: 44px; | |
| 1279 | + font-size: 16px; | |
| 1280 | + font-weight: 600; | |
| 1069 | 1281 | color: #6b7280; |
| 1070 | - } | |
| 1282 | + cursor: pointer; | |
| 1283 | + background: #f3f4f6; | |
| 1284 | + border: none; | |
| 1285 | + border-radius: 8px; | |
| 1286 | + transition: all 0.2s; | |
| 1287 | + | |
| 1288 | + &:hover { | |
| 1289 | + background: #e5e7eb; | |
| 1290 | + } | |
| 1071 | 1291 | |
| 1072 | - .balance-value { | |
| 1073 | - margin: 0; | |
| 1074 | - font-size: 18px; | |
| 1075 | - font-weight: bold; | |
| 1076 | - color: #10b981; | |
| 1292 | + &:active { | |
| 1293 | + transform: scale(0.98); | |
| 1294 | + } | |
| 1077 | 1295 | } |
| 1078 | 1296 | } |
| 1079 | 1297 | } |
| 1080 | 1298 | |
| 1299 | +// 原有的card-dialog样式(用于错误提示弹窗) | |
| 1300 | +.card-dialog { | |
| 1301 | + :deep(.el-dialog__header) { | |
| 1302 | + padding: 20px 24px; | |
| 1303 | + border-bottom: 1px solid #f0f0f0; | |
| 1304 | + } | |
| 1305 | + | |
| 1306 | + :deep(.el-dialog__title) { | |
| 1307 | + font-size: 18px; | |
| 1308 | + font-weight: 600; | |
| 1309 | + color: #1f2937; | |
| 1310 | + } | |
| 1311 | + | |
| 1312 | + :deep(.el-dialog__body) { | |
| 1313 | + padding: 24px; | |
| 1314 | + } | |
| 1315 | +} | |
| 1316 | + | |
| 1081 | 1317 | // 成功对话框 |
| 1082 | 1318 | .success-dialog { |
| 1083 | 1319 | :deep(.el-dialog__body) { | ... | ... |
apps/web-payment/src/views/payment/paySuccess.vue
| ... | ... | @@ -16,7 +16,9 @@ const init = async () => { |
| 16 | 16 | }; |
| 17 | 17 | |
| 18 | 18 | const handleBack = () => { |
| 19 | - if ( | |
| 19 | + if (queryData.value.redirectUrl) { | |
| 20 | + window.location.href = queryData.value.redirectUrl; | |
| 21 | + } else if ( | |
| 20 | 22 | typeof jWeixin !== 'undefined' && |
| 21 | 23 | jWeixin.miniProgram && |
| 22 | 24 | detector.value?.env.isMiniProgram |
| ... | ... | @@ -25,6 +27,15 @@ const handleBack = () => { |
| 25 | 27 | url: `/pages/newhome/newhome`, |
| 26 | 28 | }); |
| 27 | 29 | } |
| 30 | + // if ( | |
| 31 | + // typeof jWeixin !== 'undefined' && | |
| 32 | + // jWeixin.miniProgram && | |
| 33 | + // detector.value?.env.isMiniProgram | |
| 34 | + // ) { | |
| 35 | + // jWeixin.miniProgram.switchTab({ | |
| 36 | + // url: `/pages/newhome/newhome`, | |
| 37 | + // }); | |
| 38 | + // } | |
| 28 | 39 | }; |
| 29 | 40 | |
| 30 | 41 | onMounted(() => { |
| ... | ... | @@ -35,7 +46,6 @@ onMounted(() => { |
| 35 | 46 | |
| 36 | 47 | <template> |
| 37 | 48 | <div class="cashier-container" v-loading="loadLoading"> |
| 38 | - {{ detector?.env }} | |
| 39 | 49 | <!-- 正常支付界面 --> |
| 40 | 50 | <div class="cashier-wrapper"> |
| 41 | 51 | <div class="pt-[40px]"> |
| ... | ... | @@ -52,6 +62,12 @@ onMounted(() => { |
| 52 | 62 | <div>支付金额</div> |
| 53 | 63 | <div>{{ queryData?.amount }} 元</div> |
| 54 | 64 | </div> |
| 65 | + <div class="flex justify-between"> | |
| 66 | + <div>收款方</div> | |
| 67 | + <div class="w-[70%] break-words"> | |
| 68 | + {{ queryData?.payee }} | |
| 69 | + </div> | |
| 70 | + </div> | |
| 55 | 71 | <div class="mt-10 flex justify-between"> |
| 56 | 72 | <ElButton |
| 57 | 73 | class="pay-button" | ... | ... |
apps/web-payment/vite.config.mts
| ... | ... | @@ -6,6 +6,7 @@ export default defineConfig(async () => { |
| 6 | 6 | return { |
| 7 | 7 | application: {}, |
| 8 | 8 | vite: { |
| 9 | + base: '/pages/', | |
| 9 | 10 | plugins: [ |
| 10 | 11 | ElementPlus({ |
| 11 | 12 | format: 'esm', |
| ... | ... | @@ -17,7 +18,7 @@ export default defineConfig(async () => { |
| 17 | 18 | changeOrigin: true, |
| 18 | 19 | rewrite: (path) => path.replace(/^\/api/, ''), |
| 19 | 20 | // mock代理目标地址 |
| 20 | - target: 'http://10.28.3.34:8686', | |
| 21 | + target: 'http://cashier.test.gszdtop.com', | |
| 21 | 22 | ws: true, |
| 22 | 23 | }, |
| 23 | 24 | }, | ... | ... |