Commit 532a824366bb118847256793fa923954f7199782
1 parent
bf9336df
收银台页面
Showing
12 changed files
with
884 additions
and
51 deletions
apps/web-payment/public/fail.png
0 → 100644
3.96 KB
apps/web-payment/public/favicon.ico
No preview for this file type
apps/web-payment/public/success.png
0 → 100644
1.72 KB
apps/web-payment/src/api/payment.ts
| ... | ... | @@ -2,14 +2,25 @@ import { requestClient } from '#/api/request'; |
| 2 | 2 | |
| 3 | 3 | export namespace PaymentApi { |
| 4 | 4 | export interface orderPaymentParams { |
| 5 | - tradeId: string; | |
| 6 | - pipelineId: string; | |
| 7 | - params: { openId: string }; | |
| 5 | + tradeId: number | string; | |
| 6 | + pipelineId: number | string; | |
| 7 | + params: { | |
| 8 | + accountId?: number | string; | |
| 9 | + cardNo?: number | string; | |
| 10 | + openId?: number | string; | |
| 11 | + password?: number | string; | |
| 12 | + }; | |
| 8 | 13 | } |
| 9 | 14 | } |
| 10 | 15 | |
| 16 | +// 换取openId | |
| 17 | +export async function getOpenId(pipelineId: number | string, code: string) { | |
| 18 | + return requestClient.post<any>( | |
| 19 | + `/wechat/payment/openId.do?pipelineId=${pipelineId}&code=${code}`, | |
| 20 | + ); | |
| 21 | +} | |
| 11 | 22 | /** |
| 12 | - * 订单 | |
| 23 | + * 园区卡支付 微信支付共用接口 | |
| 13 | 24 | * @param data |
| 14 | 25 | * @returns |
| 15 | 26 | */ | ... | ... |
apps/web-payment/src/router/routes/core.ts
| ... | ... | @@ -39,10 +39,19 @@ const coreRoutes: RouteRecordRaw[] = [ |
| 39 | 39 | path: '/payment', |
| 40 | 40 | component: () => import('#/views/payment/index.vue'), |
| 41 | 41 | meta: { |
| 42 | - icon: 'lucide:area-chart', | |
| 42 | + icon: '', | |
| 43 | 43 | title: '订单支付', |
| 44 | 44 | }, |
| 45 | 45 | }, |
| 46 | + { | |
| 47 | + name: 'PaymentSuccess', | |
| 48 | + path: '/paymentSuccess', | |
| 49 | + component: () => import('#/views/payment/PaySuccess.vue'), | |
| 50 | + meta: { | |
| 51 | + icon: '', | |
| 52 | + title: '支付成功', | |
| 53 | + }, | |
| 54 | + }, | |
| 46 | 55 | ]; |
| 47 | 56 | |
| 48 | 57 | export { coreRoutes, fallbackNotFoundRoute }; | ... | ... |
apps/web-payment/src/views/payment/component/PasswordInput.vue
0 → 100644
| 1 | +<script setup lang="ts"> | |
| 2 | +import { nextTick, ref, watch } from 'vue'; | |
| 3 | + | |
| 4 | +interface Props { | |
| 5 | + modelValue: boolean; | |
| 6 | + title?: string; | |
| 7 | + length?: number; | |
| 8 | +} | |
| 9 | + | |
| 10 | +interface Emits { | |
| 11 | + (e: 'update:modelValue', value: boolean): void; | |
| 12 | + (e: 'complete', password: string): void; | |
| 13 | + (e: 'cancel'): void; | |
| 14 | +} | |
| 15 | + | |
| 16 | +const props = withDefaults(defineProps<Props>(), { | |
| 17 | + title: '请输入支付密码', | |
| 18 | + length: 6, | |
| 19 | +}); | |
| 20 | + | |
| 21 | +const emit = defineEmits<Emits>(); | |
| 22 | + | |
| 23 | +// 密码输入框数组 | |
| 24 | +const passwordValues = ref<string[]>( | |
| 25 | + Array.from({ length: props.length }).fill(''), | |
| 26 | +); | |
| 27 | +const inputRefs = ref<HTMLInputElement[]>([]); | |
| 28 | +const currentIndex = ref(0); | |
| 29 | + | |
| 30 | +// 设置输入框引用 | |
| 31 | +const setInputRef = (el: any, index: number) => { | |
| 32 | + if (el) { | |
| 33 | + inputRefs.value[index] = el; | |
| 34 | + } | |
| 35 | +}; | |
| 36 | + | |
| 37 | +// 处理输入 | |
| 38 | +const handleInput = (index: number, event: Event) => { | |
| 39 | + const target = event.target as HTMLInputElement; | |
| 40 | + let value = target.value; | |
| 41 | + | |
| 42 | + // 只允许输入数字 | |
| 43 | + value = value.replaceAll(/\D/g, ''); | |
| 44 | + | |
| 45 | + // 只保留第一个字符 | |
| 46 | + if (value.length > 1) { | |
| 47 | + value = value.charAt(0); | |
| 48 | + } | |
| 49 | + | |
| 50 | + passwordValues.value[index] = value; | |
| 51 | + target.value = value; | |
| 52 | + | |
| 53 | + // 如果输入了数字,自动聚焦到下一个输入框 | |
| 54 | + if (value && index < props.length - 1) { | |
| 55 | + currentIndex.value = index + 1; | |
| 56 | + nextTick(() => { | |
| 57 | + inputRefs.value[index + 1]?.focus(); | |
| 58 | + }); | |
| 59 | + } | |
| 60 | + | |
| 61 | + // 检查是否已完成输入 | |
| 62 | + checkComplete(); | |
| 63 | +}; | |
| 64 | + | |
| 65 | +// 处理键盘事件 | |
| 66 | +const handleKeydown = (index: number, event: KeyboardEvent) => { | |
| 67 | + // 处理退格键 | |
| 68 | + if (event.key === 'Backspace') { | |
| 69 | + event.preventDefault(); | |
| 70 | + | |
| 71 | + if (passwordValues.value[index]) { | |
| 72 | + // 如果当前框有值,清空当前框 | |
| 73 | + passwordValues.value[index] = ''; | |
| 74 | + (event.target as HTMLInputElement).value = ''; | |
| 75 | + } else if (index > 0) { | |
| 76 | + // 如果当前框没值,回退到上一个框并清空 | |
| 77 | + currentIndex.value = index - 1; | |
| 78 | + passwordValues.value[index - 1] = ''; | |
| 79 | + nextTick(() => { | |
| 80 | + const prevInput = inputRefs.value[index - 1]; | |
| 81 | + if (prevInput) { | |
| 82 | + prevInput.value = ''; | |
| 83 | + prevInput.focus(); | |
| 84 | + } | |
| 85 | + }); | |
| 86 | + } | |
| 87 | + } | |
| 88 | + | |
| 89 | + // 禁用左右箭头键 | |
| 90 | + if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { | |
| 91 | + event.preventDefault(); | |
| 92 | + } | |
| 93 | +}; | |
| 94 | + | |
| 95 | +// 处理粘贴事件 | |
| 96 | +const handlePaste = (index: number, event: ClipboardEvent) => { | |
| 97 | + event.preventDefault(); | |
| 98 | + const pasteData = event.clipboardData?.getData('text') || ''; | |
| 99 | + const numbers = pasteData.replaceAll(/\D/g, ''); | |
| 100 | + | |
| 101 | + if (numbers) { | |
| 102 | + // 从当前位置开始填充 | |
| 103 | + for (let i = 0; i < numbers.length && index + i < props.length; i++) { | |
| 104 | + passwordValues.value[index + i] = numbers[i]; | |
| 105 | + if (inputRefs.value[index + i]) { | |
| 106 | + inputRefs.value[index + i].value = numbers[i]; | |
| 107 | + } | |
| 108 | + } | |
| 109 | + | |
| 110 | + // 聚焦到最后一个填充的位置或下一个空位 | |
| 111 | + const nextEmptyIndex = passwordValues.value.findIndex( | |
| 112 | + (v, i) => i >= index && !v, | |
| 113 | + ); | |
| 114 | + if (nextEmptyIndex === -1) { | |
| 115 | + currentIndex.value = props.length - 1; | |
| 116 | + nextTick(() => { | |
| 117 | + inputRefs.value[props.length - 1]?.focus(); | |
| 118 | + }); | |
| 119 | + } else { | |
| 120 | + currentIndex.value = nextEmptyIndex; | |
| 121 | + nextTick(() => { | |
| 122 | + inputRefs.value[nextEmptyIndex]?.focus(); | |
| 123 | + }); | |
| 124 | + } | |
| 125 | + | |
| 126 | + checkComplete(); | |
| 127 | + } | |
| 128 | +}; | |
| 129 | + | |
| 130 | +// 处理点击输入框 - 禁止点击跳转 | |
| 131 | +const handleFocus = (index: number, event: FocusEvent) => { | |
| 132 | + event.preventDefault(); | |
| 133 | + // 找到第一个空的输入框位置 | |
| 134 | + const firstEmptyIndex = passwordValues.value.findIndex((v) => !v); | |
| 135 | + const targetIndex = | |
| 136 | + firstEmptyIndex === -1 ? props.length - 1 : firstEmptyIndex; | |
| 137 | + | |
| 138 | + // 如果点击的不是当前应该输入的位置,则重新聚焦到正确位置 | |
| 139 | + if (index !== targetIndex) { | |
| 140 | + nextTick(() => { | |
| 141 | + inputRefs.value[targetIndex]?.focus(); | |
| 142 | + }); | |
| 143 | + } | |
| 144 | + currentIndex.value = targetIndex; | |
| 145 | +}; | |
| 146 | + | |
| 147 | +// 检查是否完成输入 | |
| 148 | +const checkComplete = () => { | |
| 149 | + const password = passwordValues.value.join(''); | |
| 150 | + if (password.length === props.length) { | |
| 151 | + // 延迟一点点,让用户看到最后一个数字输入 | |
| 152 | + setTimeout(() => { | |
| 153 | + emit('complete', password); | |
| 154 | + }, 100); | |
| 155 | + } | |
| 156 | +}; | |
| 157 | + | |
| 158 | +// 重置密码 | |
| 159 | +const reset = () => { | |
| 160 | + passwordValues.value = Array.from({ length: props.length }).fill(''); | |
| 161 | + inputRefs.value.forEach((input) => { | |
| 162 | + if (input) input.value = ''; | |
| 163 | + }); | |
| 164 | + currentIndex.value = 0; | |
| 165 | + nextTick(() => { | |
| 166 | + inputRefs.value[0]?.focus(); | |
| 167 | + }); | |
| 168 | +}; | |
| 169 | + | |
| 170 | +// 关闭弹窗 | |
| 171 | +const handleClose = () => { | |
| 172 | + emit('update:modelValue', false); | |
| 173 | + emit('cancel'); | |
| 174 | +}; | |
| 175 | + | |
| 176 | +// 点击遮罩关闭 | |
| 177 | +const handleMaskClick = (event: MouseEvent) => { | |
| 178 | + if (event.target === event.currentTarget) { | |
| 179 | + handleClose(); | |
| 180 | + } | |
| 181 | +}; | |
| 182 | + | |
| 183 | +// 监听弹窗打开,自动清空并聚焦第一个输入框 | |
| 184 | +watch( | |
| 185 | + () => props.modelValue, | |
| 186 | + (newVal) => { | |
| 187 | + if (newVal) { | |
| 188 | + nextTick(() => { | |
| 189 | + reset(); | |
| 190 | + }); | |
| 191 | + } | |
| 192 | + }, | |
| 193 | +); | |
| 194 | + | |
| 195 | +// 暴露方法给父组件 | |
| 196 | +defineExpose({ | |
| 197 | + reset, | |
| 198 | +}); | |
| 199 | +</script> | |
| 200 | + | |
| 201 | +<template> | |
| 202 | + <Teleport to="body"> | |
| 203 | + <Transition name="modal"> | |
| 204 | + <div | |
| 205 | + v-if="modelValue" | |
| 206 | + class="password-modal-overlay" | |
| 207 | + @click="handleMaskClick" | |
| 208 | + > | |
| 209 | + <Transition name="slide-up"> | |
| 210 | + <div v-if="modelValue" class="password-modal-content"> | |
| 211 | + <!-- 头部 --> | |
| 212 | + <div class="modal-header"> | |
| 213 | + <h3 class="modal-title">{{ title }}</h3> | |
| 214 | + <button class="close-btn" @click="handleClose"> | |
| 215 | + <svg | |
| 216 | + width="24" | |
| 217 | + height="24" | |
| 218 | + viewBox="0 0 24 24" | |
| 219 | + fill="none" | |
| 220 | + stroke="currentColor" | |
| 221 | + stroke-width="2" | |
| 222 | + stroke-linecap="round" | |
| 223 | + stroke-linejoin="round" | |
| 224 | + > | |
| 225 | + <line x1="18" y1="6" x2="6" y2="18" /> | |
| 226 | + <line x1="6" y1="6" x2="18" y2="18" /> | |
| 227 | + </svg> | |
| 228 | + </button> | |
| 229 | + </div> | |
| 230 | + | |
| 231 | + <!-- 主体内容 --> | |
| 232 | + <div class="modal-body"> | |
| 233 | + <div class="password-input-group"> | |
| 234 | + <input | |
| 235 | + v-for="(value, index) in passwordValues" | |
| 236 | + :key="index" | |
| 237 | + :ref="(el) => setInputRef(el, index)" | |
| 238 | + type="tel" | |
| 239 | + inputmode="numeric" | |
| 240 | + maxlength="1" | |
| 241 | + class="password-input" | |
| 242 | + :class="{ | |
| 243 | + active: currentIndex === index, | |
| 244 | + filled: passwordValues[index], | |
| 245 | + }" | |
| 246 | + @input="handleInput(index, $event)" | |
| 247 | + @keydown="handleKeydown(index, $event)" | |
| 248 | + @paste="handlePaste(index, $event)" | |
| 249 | + @focus="handleFocus(index, $event)" | |
| 250 | + @mousedown.prevent | |
| 251 | + /> | |
| 252 | + </div> | |
| 253 | + | |
| 254 | + <div class="password-tips"> | |
| 255 | + <p>为了您的资金安全,请输入支付密码</p> | |
| 256 | + </div> | |
| 257 | + </div> | |
| 258 | + | |
| 259 | + <!-- 底部 --> | |
| 260 | + <div class="modal-footer"> | |
| 261 | + <button class="cancel-btn" @click="handleClose">取消</button> | |
| 262 | + </div> | |
| 263 | + </div> | |
| 264 | + </Transition> | |
| 265 | + </div> | |
| 266 | + </Transition> | |
| 267 | + </Teleport> | |
| 268 | +</template> | |
| 269 | + | |
| 270 | +<style scoped lang="scss"> | |
| 271 | +// 脉动动画 | |
| 272 | +@keyframes pulse { | |
| 273 | + 0%, | |
| 274 | + 100% { | |
| 275 | + box-shadow: 0 0 0 3px rgb(234 66 0 / 10%); | |
| 276 | + } | |
| 277 | + | |
| 278 | + 50% { | |
| 279 | + box-shadow: 0 0 0 6px rgb(234 66 0 / 5%); | |
| 280 | + } | |
| 281 | +} | |
| 282 | + | |
| 283 | +// 移动端优化 | |
| 284 | +@media (max-width: 767px) { | |
| 285 | + .password-input-group { | |
| 286 | + gap: 6px; | |
| 287 | + } | |
| 288 | + | |
| 289 | + .password-input { | |
| 290 | + max-width: 45px; | |
| 291 | + height: 48px; | |
| 292 | + font-size: 22px; | |
| 293 | + } | |
| 294 | +} | |
| 295 | + | |
| 296 | +.modal-enter-active, | |
| 297 | +.modal-leave-active { | |
| 298 | + transition: opacity 0.3s ease; | |
| 299 | +} | |
| 300 | + | |
| 301 | +.modal-enter-from, | |
| 302 | +.modal-leave-to { | |
| 303 | + opacity: 0; | |
| 304 | +} | |
| 305 | + | |
| 306 | +// 内容区域滑入动画 | |
| 307 | +.slide-up-enter-active { | |
| 308 | + transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1); | |
| 309 | +} | |
| 310 | + | |
| 311 | +.slide-up-leave-active { | |
| 312 | + transition: transform 0.25s cubic-bezier(0.4, 0, 1, 1); | |
| 313 | +} | |
| 314 | + | |
| 315 | +.slide-up-enter-from { | |
| 316 | + transform: translateY(100%); | |
| 317 | +} | |
| 318 | + | |
| 319 | +.slide-up-leave-to { | |
| 320 | + transform: translateY(100%); | |
| 321 | +} | |
| 322 | + | |
| 323 | +// 遮罩层 | |
| 324 | +.password-modal-overlay { | |
| 325 | + position: fixed; | |
| 326 | + top: 0; | |
| 327 | + left: 0; | |
| 328 | + z-index: 9999; | |
| 329 | + display: flex; | |
| 330 | + align-items: flex-end; | |
| 331 | + justify-content: center; | |
| 332 | + width: 100%; | |
| 333 | + height: 100%; | |
| 334 | + background-color: rgb(0 0 0 / 50%); | |
| 335 | +} | |
| 336 | + | |
| 337 | +// 弹窗内容 | |
| 338 | +.password-modal-content { | |
| 339 | + position: relative; | |
| 340 | + width: 100%; | |
| 341 | + max-width: 100%; | |
| 342 | + background: #fff; | |
| 343 | + border-radius: 20px 20px 0 0; | |
| 344 | + box-shadow: 0 -4px 20px rgb(0 0 0 / 10%); | |
| 345 | +} | |
| 346 | + | |
| 347 | +// 头部 | |
| 348 | +.modal-header { | |
| 349 | + position: relative; | |
| 350 | + padding: 20px 20px 10px; | |
| 351 | + border-bottom: 1px solid #f0f0f0; | |
| 352 | + | |
| 353 | + .modal-title { | |
| 354 | + margin: 0; | |
| 355 | + font-size: 18px; | |
| 356 | + font-weight: 600; | |
| 357 | + color: #1f2937; | |
| 358 | + text-align: center; | |
| 359 | + } | |
| 360 | + | |
| 361 | + .close-btn { | |
| 362 | + position: absolute; | |
| 363 | + top: 20px; | |
| 364 | + right: 20px; | |
| 365 | + display: flex; | |
| 366 | + align-items: center; | |
| 367 | + justify-content: center; | |
| 368 | + width: 32px; | |
| 369 | + height: 32px; | |
| 370 | + padding: 0; | |
| 371 | + color: #909399; | |
| 372 | + cursor: pointer; | |
| 373 | + outline: none; | |
| 374 | + background: transparent; | |
| 375 | + border: none; | |
| 376 | + transition: all 0.3s ease; | |
| 377 | + | |
| 378 | + &:hover { | |
| 379 | + color: #606266; | |
| 380 | + background: #f5f5f5; | |
| 381 | + border-radius: 50%; | |
| 382 | + } | |
| 383 | + | |
| 384 | + &:active { | |
| 385 | + transform: scale(0.95); | |
| 386 | + } | |
| 387 | + | |
| 388 | + svg { | |
| 389 | + display: block; | |
| 390 | + } | |
| 391 | + } | |
| 392 | +} | |
| 393 | + | |
| 394 | +// 主体 | |
| 395 | +.modal-body { | |
| 396 | + display: flex; | |
| 397 | + flex-direction: column; | |
| 398 | + align-items: center; | |
| 399 | + padding: 30px 20px 20px; | |
| 400 | +} | |
| 401 | + | |
| 402 | +.password-input-group { | |
| 403 | + display: flex; | |
| 404 | + gap: 8px; | |
| 405 | + justify-content: center; | |
| 406 | + width: 100%; | |
| 407 | + max-width: 360px; | |
| 408 | + margin-bottom: 24px; | |
| 409 | + | |
| 410 | + @media (min-width: 768px) { | |
| 411 | + gap: 12px; | |
| 412 | + max-width: 420px; | |
| 413 | + } | |
| 414 | +} | |
| 415 | + | |
| 416 | +.password-input { | |
| 417 | + flex: 1; | |
| 418 | + max-width: 50px; | |
| 419 | + height: 50px; | |
| 420 | + font-size: 24px; | |
| 421 | + font-weight: 600; | |
| 422 | + color: #1f2937; | |
| 423 | + text-align: center; | |
| 424 | + appearance: none; | |
| 425 | + cursor: pointer; | |
| 426 | + caret-color: transparent; | |
| 427 | + user-select: none; | |
| 428 | + outline: none; | |
| 429 | + background: #f9fafb; | |
| 430 | + border: 2px solid #e5e7eb; | |
| 431 | + border-radius: 12px; | |
| 432 | + transition: all 0.3s ease; | |
| 433 | + | |
| 434 | + @media (min-width: 768px) { | |
| 435 | + max-width: 60px; | |
| 436 | + height: 60px; | |
| 437 | + font-size: 28px; | |
| 438 | + } | |
| 439 | + | |
| 440 | + &:focus { | |
| 441 | + background: #fff; | |
| 442 | + border-color: #ea4200; | |
| 443 | + box-shadow: 0 0 0 3px rgb(234 66 0 / 10%); | |
| 444 | + } | |
| 445 | + | |
| 446 | + &.active { | |
| 447 | + background: #fff; | |
| 448 | + border-color: #ea4200; | |
| 449 | + animation: pulse 1.5s ease-in-out infinite; | |
| 450 | + } | |
| 451 | + | |
| 452 | + &.filled { | |
| 453 | + // 显示为星号 | |
| 454 | + -webkit-text-security: disc; | |
| 455 | + text-security: disc; | |
| 456 | + font-family: text-security-disc; | |
| 457 | + background: #fff; | |
| 458 | + border-color: #ea4200; | |
| 459 | + } | |
| 460 | + | |
| 461 | + // iOS 样式重置 | |
| 462 | + &::-webkit-inner-spin-button, | |
| 463 | + &::-webkit-outer-spin-button { | |
| 464 | + margin: 0; | |
| 465 | + appearance: none; | |
| 466 | + } | |
| 467 | + | |
| 468 | + // 禁用选中效果 | |
| 469 | + &::selection { | |
| 470 | + background: transparent; | |
| 471 | + } | |
| 472 | +} | |
| 473 | + | |
| 474 | +.password-tips { | |
| 475 | + text-align: center; | |
| 476 | + | |
| 477 | + p { | |
| 478 | + margin: 0; | |
| 479 | + font-size: 13px; | |
| 480 | + line-height: 1.5; | |
| 481 | + color: #6b7280; | |
| 482 | + } | |
| 483 | +} | |
| 484 | + | |
| 485 | +// 底部 | |
| 486 | +.modal-footer { | |
| 487 | + display: flex; | |
| 488 | + justify-content: center; | |
| 489 | + width: 100%; | |
| 490 | + padding: 0 20px 20px; | |
| 491 | + padding-bottom: calc(20px + env(safe-area-inset-bottom)); | |
| 492 | + | |
| 493 | + .cancel-btn { | |
| 494 | + width: 100%; | |
| 495 | + max-width: 360px; | |
| 496 | + height: 44px; | |
| 497 | + font-size: 16px; | |
| 498 | + font-weight: 500; | |
| 499 | + color: #6b7280; | |
| 500 | + cursor: pointer; | |
| 501 | + outline: none; | |
| 502 | + background: #f3f4f6; | |
| 503 | + border: none; | |
| 504 | + border-radius: 12px; | |
| 505 | + transition: all 0.3s ease; | |
| 506 | + | |
| 507 | + &:hover { | |
| 508 | + color: #374151; | |
| 509 | + background: #e5e7eb; | |
| 510 | + } | |
| 511 | + | |
| 512 | + &:active { | |
| 513 | + transform: scale(0.98); | |
| 514 | + } | |
| 515 | + } | |
| 516 | +} | |
| 517 | +</style> | ... | ... |
apps/web-payment/src/views/payment/index.vue
| 1 | 1 | <script setup lang="ts"> |
| 2 | 2 | import { computed, ref } from 'vue'; |
| 3 | -import { useRoute } from 'vue-router'; | |
| 3 | +import { useRoute, useRouter } from 'vue-router'; | |
| 4 | + | |
| 5 | +import { fenToYuan } from '@vben/utils'; | |
| 4 | 6 | |
| 5 | 7 | import { |
| 6 | 8 | CreditCard, |
| ... | ... | @@ -11,9 +13,10 @@ import { |
| 11 | 13 | import { ElButton, ElDialog, ElIcon, ElMessage, ElRadio } from 'element-plus'; |
| 12 | 14 | import qs from 'qs'; |
| 13 | 15 | |
| 14 | -import { listUserCards, orderInfo, orderPayment } from '#/api'; | |
| 16 | +import { getOpenId, listUserCards, orderInfo, orderPayment } from '#/api'; | |
| 15 | 17 | import EnvironmentDetector from '#/composables/environmentDetector'; |
| 16 | 18 | |
| 19 | +import PasswordInput from './component/PasswordInput.vue'; | |
| 17 | 20 | // 类型定义 |
| 18 | 21 | interface Card { |
| 19 | 22 | customerId: number | string; |
| ... | ... | @@ -33,15 +36,20 @@ const detector = new EnvironmentDetector(); |
| 33 | 36 | detector.logEnvironment(); |
| 34 | 37 | |
| 35 | 38 | const route = useRoute(); |
| 39 | +const router = useRouter(); | |
| 36 | 40 | const loadLoading = ref(false); |
| 41 | +const showPasswordDialog = ref(false); | |
| 37 | 42 | const token = ref<string>(''); |
| 38 | 43 | const openId = ref<string>(''); |
| 44 | +const code = ref<string>(''); | |
| 39 | 45 | const env = ref(); |
| 40 | 46 | env.value = detector.env; |
| 41 | 47 | |
| 48 | +const REDIRECT_CHANNEL_ID = 29; | |
| 49 | + | |
| 42 | 50 | // 检查参数是否有效 |
| 43 | 51 | const hasValidParams = computed(() => { |
| 44 | - return !!(token.value && openId.value); | |
| 52 | + return !!(token.value && code.value); | |
| 45 | 53 | }); |
| 46 | 54 | |
| 47 | 55 | const cardList = ref<Card[]>([]); |
| ... | ... | @@ -55,10 +63,12 @@ const showCardDialog = ref<boolean>(false); |
| 55 | 63 | const selectedCard = ref<Card | null>(null); |
| 56 | 64 | const loading = ref<boolean>(false); |
| 57 | 65 | const paymentSuccess = ref<boolean>(false); |
| 66 | +const payErrorDialog = ref<boolean>(false); | |
| 67 | +const errorMessage = ref<string>('支付失败'); | |
| 58 | 68 | |
| 59 | 69 | // 计算显示金额 |
| 60 | 70 | const displayAmount = computed(() => { |
| 61 | - return orderInfoData.value?.amount || 0; | |
| 71 | + return fenToYuan(orderInfoData.value?.amount || 0); | |
| 62 | 72 | }); |
| 63 | 73 | |
| 64 | 74 | // 获取支付方式列表 |
| ... | ... | @@ -108,6 +118,20 @@ const getPaymentIcon = (channelName: string) => { |
| 108 | 118 | } |
| 109 | 119 | }; |
| 110 | 120 | |
| 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 | + }); | |
| 133 | +}; | |
| 134 | + | |
| 111 | 135 | // 获取支付方式描述 |
| 112 | 136 | const getPaymentDesc = (pipeline: Pipeline) => { |
| 113 | 137 | if ( |
| ... | ... | @@ -128,6 +152,19 @@ const getPaymentDesc = (pipeline: Pipeline) => { |
| 128 | 152 | } |
| 129 | 153 | }; |
| 130 | 154 | |
| 155 | +const loadOpenId = async () => { | |
| 156 | + try { | |
| 157 | + if (openId.value) { | |
| 158 | + payBtnShow.value = true; | |
| 159 | + return; | |
| 160 | + } | |
| 161 | + const data = await getOpenId(currentPayType.value?.pipelineId, code.value); | |
| 162 | + openId.value = data || null; | |
| 163 | + // 其他支付方式直接显示支付按钮\ | |
| 164 | + payBtnShow.value = true; | |
| 165 | + } catch {} | |
| 166 | +}; | |
| 167 | + | |
| 131 | 168 | // 方法 |
| 132 | 169 | const handlepayBtnShowClick = async (pipeline: Pipeline) => { |
| 133 | 170 | currentPaymentMethod.value = pipeline.pipelineId; |
| ... | ... | @@ -144,8 +181,11 @@ const handlepayBtnShowClick = async (pipeline: Pipeline) => { |
| 144 | 181 | } |
| 145 | 182 | showCardDialog.value = true; |
| 146 | 183 | } else { |
| 147 | - // 其他支付方式直接显示支付按钮 | |
| 148 | - payBtnShow.value = true; | |
| 184 | + if (pipeline.channelId === REDIRECT_CHANNEL_ID) { | |
| 185 | + payBtnShow.value = true; | |
| 186 | + return; | |
| 187 | + } | |
| 188 | + await loadOpenId(); | |
| 149 | 189 | } |
| 150 | 190 | }; |
| 151 | 191 | |
| ... | ... | @@ -156,12 +196,73 @@ const handleCardSelect = (card: Card) => { |
| 156 | 196 | payBtnShow.value = true; |
| 157 | 197 | }; |
| 158 | 198 | |
| 199 | +// 处理密码输入取消 | |
| 200 | +const handlePasswordCancel = () => { | |
| 201 | + showPasswordDialog.value = false; | |
| 202 | + loading.value = false; | |
| 203 | +}; | |
| 204 | + | |
| 205 | +const handlePasswordComplete = async (password: string) => { | |
| 206 | + showPasswordDialog.value = false; | |
| 207 | + try { | |
| 208 | + const params = { | |
| 209 | + tradeId: orderInfoData.value.tradeId, | |
| 210 | + pipelineId: currentPayType.value.pipelineId, | |
| 211 | + params: { | |
| 212 | + accountId: selectedCard.value?.accountId, | |
| 213 | + cardNo: selectedCard.value?.cardNo, | |
| 214 | + password, | |
| 215 | + }, | |
| 216 | + }; | |
| 217 | + | |
| 218 | + const data = await orderPayment(params); | |
| 219 | + router.push({ | |
| 220 | + path: '/paymentSuccess', | |
| 221 | + query: { | |
| 222 | + amount: displayAmount.value, | |
| 223 | + success: 'true', | |
| 224 | + payType: '园区卡支付', | |
| 225 | + }, | |
| 226 | + }); | |
| 227 | + } catch (error) { | |
| 228 | + errorMessage.value = error?.message || '支付失败'; | |
| 229 | + payErrorDialog.value = true; | |
| 230 | + console.error(error); | |
| 231 | + } finally { | |
| 232 | + loading.value = false; | |
| 233 | + } | |
| 234 | +}; | |
| 235 | + | |
| 159 | 236 | const queryPayment = async () => { |
| 160 | - if (!openId.value || !currentPayType.value) { | |
| 237 | + if ( | |
| 238 | + !(currentPayType.value.channelId === REDIRECT_CHANNEL_ID) && | |
| 239 | + !openId.value | |
| 240 | + ) { | |
| 161 | 241 | ElMessage.error('支付参数不完整'); |
| 162 | 242 | return false; |
| 163 | 243 | } |
| 164 | - | |
| 244 | + // 跳转云商户支付 | |
| 245 | + if (currentPayType.value.channelId === REDIRECT_CHANNEL_ID) { | |
| 246 | + const params = { | |
| 247 | + tradeId: orderInfoData.value.tradeId, | |
| 248 | + amount: orderInfoData.value.amount, | |
| 249 | + pipelineId: currentPayType.value.pipelineId, | |
| 250 | + goods: orderInfoData.value.goods, | |
| 251 | + payType: currentPayType.value.channelName, | |
| 252 | + redirect: true, | |
| 253 | + payee: orderInfoData.value.mchName, | |
| 254 | + redirectUrl: orderInfoData.value.redirectUrl, | |
| 255 | + }; | |
| 256 | + const queryString = qs.stringify(params); | |
| 257 | + if (typeof jWeixin !== 'undefined' && jWeixin.miniProgram) { | |
| 258 | + jWeixin.miniProgram.navigateTo({ | |
| 259 | + url: `/packageA/pages/wxPay/index?${queryString}`, | |
| 260 | + }); | |
| 261 | + loading.value = false; | |
| 262 | + } | |
| 263 | + return false; | |
| 264 | + } | |
| 265 | + // redirect: currentPayType.value.channelId === REDIRECT_CHANNEL_ID | |
| 165 | 266 | try { |
| 166 | 267 | const params = { |
| 167 | 268 | tradeId: orderInfoData.value.tradeId, |
| ... | ... | @@ -170,19 +271,27 @@ const queryPayment = async () => { |
| 170 | 271 | }; |
| 171 | 272 | |
| 172 | 273 | const data = await orderPayment(params); |
| 173 | - const queryString = qs.stringify(data); | |
| 274 | + const pramsData = { | |
| 275 | + ...data, | |
| 276 | + goods: orderInfoData.value.goods, | |
| 277 | + amount: orderInfoData.value.amount, | |
| 278 | + payType: currentPayType.value.channelName, | |
| 279 | + redirectUrl: orderInfoData.value.redirectUrl, | |
| 280 | + payee: orderInfoData.value.mchName, | |
| 281 | + }; | |
| 282 | + const queryString = qs.stringify(pramsData); | |
| 174 | 283 | |
| 175 | 284 | // 跳转到微信支付页面 |
| 176 | 285 | if (typeof jWeixin !== 'undefined' && jWeixin.miniProgram) { |
| 177 | - jWeixin.miniProgram.redirectTo({ | |
| 178 | - url: `/packageA/pages/wePay/index?${queryString}`, | |
| 286 | + jWeixin.miniProgram.navigateTo({ | |
| 287 | + url: `/packageA/pages/wxPay/index?${queryString}`, | |
| 179 | 288 | }); |
| 289 | + loading.value = false; | |
| 180 | 290 | } else { |
| 181 | 291 | console.warn('非微信小程序环境'); |
| 182 | 292 | // 模拟支付成功 |
| 183 | 293 | setTimeout(() => { |
| 184 | 294 | loading.value = false; |
| 185 | - paymentSuccess.value = true; | |
| 186 | 295 | setTimeout(() => { |
| 187 | 296 | paymentSuccess.value = false; |
| 188 | 297 | resetPaymentState(); |
| ... | ... | @@ -216,28 +325,8 @@ const handlePay = async () => { |
| 216 | 325 | if (isCardPayment) { |
| 217 | 326 | // 园区卡支付逻辑 |
| 218 | 327 | try { |
| 219 | - const params = { | |
| 220 | - tradeId: orderInfoData.value.tradeId, | |
| 221 | - pipelineId: currentPayType.value.pipelineId, | |
| 222 | - params: { | |
| 223 | - openId: openId.value, | |
| 224 | - cardNo: selectedCard.value?.cardNo, | |
| 225 | - }, | |
| 226 | - }; | |
| 227 | - | |
| 228 | - const data = await orderPayment(params); | |
| 229 | - loading.value = false; | |
| 230 | - paymentSuccess.value = true; | |
| 231 | - | |
| 232 | - setTimeout(() => { | |
| 233 | - paymentSuccess.value = false; | |
| 234 | - resetPaymentState(); | |
| 235 | - }, 2000); | |
| 236 | - } catch (error) { | |
| 237 | - console.error('园区卡支付失败:', error); | |
| 238 | - ElMessage.error('支付失败,请重试'); | |
| 239 | - loading.value = false; | |
| 240 | - } | |
| 328 | + showPasswordDialog.value = true; | |
| 329 | + } catch {} | |
| 241 | 330 | } else { |
| 242 | 331 | // 其他支付方式(微信、支付宝等) |
| 243 | 332 | await queryPayment(); |
| ... | ... | @@ -292,16 +381,17 @@ const getListUserCards = async () => { |
| 292 | 381 | const init = async () => { |
| 293 | 382 | token.value = (route.query?.token as string) || ''; |
| 294 | 383 | openId.value = (route.query?.openId as string) || ''; |
| 295 | - | |
| 296 | - if (!hasValidParams.value) { | |
| 384 | + code.value = (route.query?.code as string) || ''; | |
| 385 | + if (!token.value) { | |
| 297 | 386 | return; |
| 298 | 387 | } |
| 299 | - | |
| 300 | 388 | try { |
| 301 | 389 | loadLoading.value = true; |
| 302 | 390 | await getOrderInfo(); |
| 303 | - } catch { | |
| 304 | - ElMessage.error('加载订单信息失败'); | |
| 391 | + } catch (error) { | |
| 392 | + // ElMessage.error(error?.message || '获取订单信息失败'); | |
| 393 | + errorMessage.value = error?.message || '获取订单信息失败'; | |
| 394 | + payErrorDialog.value = true; | |
| 305 | 395 | } finally { |
| 306 | 396 | loadLoading.value = false; |
| 307 | 397 | } |
| ... | ... | @@ -442,7 +532,6 @@ init(); |
| 442 | 532 | </div> |
| 443 | 533 | </div> |
| 444 | 534 | </div> |
| 445 | - | |
| 446 | 535 | <!-- 底部支付按钮 --> |
| 447 | 536 | <div class="payment-footer"> |
| 448 | 537 | <ElButton |
| ... | ... | @@ -480,9 +569,7 @@ init(); |
| 480 | 569 | </div> |
| 481 | 570 | <div class="card-balance"> |
| 482 | 571 | <p class="balance-label">余额</p> |
| 483 | - <p class="balance-value"> | |
| 484 | - ¥{{ (Number(card.amount) / 100).toFixed(2) }} | |
| 485 | - </p> | |
| 572 | + <p class="balance-value">¥{{ card.amount }}</p> | |
| 486 | 573 | </div> |
| 487 | 574 | </div> |
| 488 | 575 | <div v-if="cardList.length === 0" class="empty-card"> |
| ... | ... | @@ -494,6 +581,39 @@ init(); |
| 494 | 581 | </template> |
| 495 | 582 | </ElDialog> |
| 496 | 583 | |
| 584 | + <ElDialog | |
| 585 | + v-model="payErrorDialog" | |
| 586 | + title="" | |
| 587 | + width="90%" | |
| 588 | + :style="{ maxWidth: '500px' }" | |
| 589 | + class="card-dialog" | |
| 590 | + :close-on-click-modal="true" | |
| 591 | + > | |
| 592 | + <div class="color-[#333] items-center"> | |
| 593 | + <img src="/fail.png" class="m-auto block h-[64px] w-[64px]" /> | |
| 594 | + <div class="py-[20px] text-center text-[20px] font-bold">支付失败</div> | |
| 595 | + <div class="w-full overflow-y-auto text-left text-[18px] leading-6"> | |
| 596 | + {{ errorMessage }} | |
| 597 | + </div> | |
| 598 | + <div class="mt-10"> | |
| 599 | + <ElButton | |
| 600 | + plain | |
| 601 | + @click="payErrorDialog = false" | |
| 602 | + class="confirm-button" | |
| 603 | + > | |
| 604 | + 确 认 | |
| 605 | + </ElButton> | |
| 606 | + </div> | |
| 607 | + </div> | |
| 608 | + </ElDialog> | |
| 609 | + | |
| 610 | + <!-- 支付密码输入弹窗 --> | |
| 611 | + <PasswordInput | |
| 612 | + v-model="showPasswordDialog" | |
| 613 | + @complete="handlePasswordComplete" | |
| 614 | + @cancel="handlePasswordCancel" | |
| 615 | + /> | |
| 616 | + | |
| 497 | 617 | <!-- 支付成功对话框 --> |
| 498 | 618 | <ElDialog |
| 499 | 619 | v-model="paymentSuccess" |
| ... | ... | @@ -994,4 +1114,29 @@ init(); |
| 994 | 1114 | color: #6b7280; |
| 995 | 1115 | } |
| 996 | 1116 | } |
| 1117 | + | |
| 1118 | +.confirm-button { | |
| 1119 | + width: 100%; | |
| 1120 | + height: 42px; | |
| 1121 | + font-size: 18px; | |
| 1122 | + font-weight: 600; | |
| 1123 | + color: #ea4200; | |
| 1124 | + background: #fff; | |
| 1125 | + border: 1px solid #ea4200; | |
| 1126 | + border-radius: 21px; | |
| 1127 | + | |
| 1128 | + &:hover:not(:disabled) { | |
| 1129 | + background: #f7f7f7; | |
| 1130 | + box-shadow: 0 6px 20px rgb(234 66 0 / 40%); | |
| 1131 | + } | |
| 1132 | + | |
| 1133 | + &:active:not(:disabled) { | |
| 1134 | + transform: scale(0.98); | |
| 1135 | + } | |
| 1136 | + | |
| 1137 | + &:disabled { | |
| 1138 | + cursor: not-allowed; | |
| 1139 | + opacity: 0.5; | |
| 1140 | + } | |
| 1141 | +} | |
| 997 | 1142 | </style> | ... | ... |
apps/web-payment/src/views/payment/payFailed.vue
0 → 100644
apps/web-payment/src/views/payment/paySuccess.vue
0 → 100644
| 1 | +<script setup lang="ts"> | |
| 2 | +import { onMounted, ref } from 'vue'; | |
| 3 | +import { useRoute } from 'vue-router'; | |
| 4 | + | |
| 5 | +import { ElButton } from 'element-plus'; | |
| 6 | + | |
| 7 | +import EnvironmentDetector from '#/composables/environmentDetector'; | |
| 8 | + | |
| 9 | +const route = useRoute(); | |
| 10 | +const loadLoading = ref(false); | |
| 11 | + | |
| 12 | +const queryData = ref<any>({}); | |
| 13 | +const detector = ref(); | |
| 14 | +const init = async () => { | |
| 15 | + queryData.value = route.query || {}; | |
| 16 | +}; | |
| 17 | + | |
| 18 | +const handleBack = () => { | |
| 19 | + if ( | |
| 20 | + typeof jWeixin !== 'undefined' && | |
| 21 | + jWeixin.miniProgram && | |
| 22 | + detector.value?.env.isMiniProgram | |
| 23 | + ) { | |
| 24 | + jWeixin.miniProgram.switchTab({ | |
| 25 | + url: `/pages/newhome/newhome`, | |
| 26 | + }); | |
| 27 | + } | |
| 28 | +}; | |
| 29 | + | |
| 30 | +onMounted(() => { | |
| 31 | + init(); | |
| 32 | + detector.value = new EnvironmentDetector(); | |
| 33 | +}); | |
| 34 | +</script> | |
| 35 | + | |
| 36 | +<template> | |
| 37 | + <div class="cashier-container" v-loading="loadLoading"> | |
| 38 | + {{ detector?.env }} | |
| 39 | + <!-- 正常支付界面 --> | |
| 40 | + <div class="cashier-wrapper"> | |
| 41 | + <div class="pt-[40px]"> | |
| 42 | + <div class="flex flex-col items-center justify-center"> | |
| 43 | + <img src="/success.png" class="w-[64px]" /> | |
| 44 | + <p class="color-[#49250B] mt-2 text-[18px] font-bold">支付成功</p> | |
| 45 | + </div> | |
| 46 | + <div class="color-[#333] flex flex-col gap-4 p-[40px]"> | |
| 47 | + <div class="flex justify-between"> | |
| 48 | + <div>支付方式</div> | |
| 49 | + <div>{{ queryData?.payType }}</div> | |
| 50 | + </div> | |
| 51 | + <div class="flex justify-between"> | |
| 52 | + <div>支付金额</div> | |
| 53 | + <div>{{ queryData?.amount }} 元</div> | |
| 54 | + </div> | |
| 55 | + <div class="mt-10 flex justify-between"> | |
| 56 | + <ElButton | |
| 57 | + class="pay-button" | |
| 58 | + plain | |
| 59 | + type="primary" | |
| 60 | + size="large" | |
| 61 | + @click="handleBack" | |
| 62 | + > | |
| 63 | + 完成 | |
| 64 | + </ElButton> | |
| 65 | + </div> | |
| 66 | + </div> | |
| 67 | + </div> | |
| 68 | + </div> | |
| 69 | + </div> | |
| 70 | +</template> | |
| 71 | + | |
| 72 | +<style scoped lang="scss"> | |
| 73 | +.cashier-container { | |
| 74 | + min-height: 100vh; | |
| 75 | + background: linear-gradient( | |
| 76 | + to bottom, | |
| 77 | + #fff9f1 0%, | |
| 78 | + #ffe9d4 20%, | |
| 79 | + #ffe3c8 29%, | |
| 80 | + #fff 45%, | |
| 81 | + #fff 100% | |
| 82 | + ); | |
| 83 | +} | |
| 84 | + | |
| 85 | +.cashier-wrapper { | |
| 86 | + display: flex; | |
| 87 | + flex-direction: column; | |
| 88 | + max-width: 500px; | |
| 89 | + min-height: 100vh; | |
| 90 | + margin: 0 auto; | |
| 91 | +} | |
| 92 | + | |
| 93 | +.pay-button { | |
| 94 | + width: 100%; | |
| 95 | + height: 42px; | |
| 96 | + font-size: 18px; | |
| 97 | + font-weight: 600; | |
| 98 | + color: #ea4200; | |
| 99 | + background: #fff; | |
| 100 | + border: 1px solid #ea4200; | |
| 101 | + border-radius: 21px; | |
| 102 | + | |
| 103 | + &:hover:not(:disabled) { | |
| 104 | + background: #f7f7f7; | |
| 105 | + box-shadow: 0 6px 20px rgb(234 66 0 / 40%); | |
| 106 | + } | |
| 107 | + | |
| 108 | + &:active:not(:disabled) { | |
| 109 | + transform: scale(0.98); | |
| 110 | + } | |
| 111 | + | |
| 112 | + &:disabled { | |
| 113 | + cursor: not-allowed; | |
| 114 | + opacity: 0.5; | |
| 115 | + } | |
| 116 | +} | |
| 117 | +</style> | ... | ... |
apps/web-payment/vite.config.mts
packages/@core/base/shared/src/utils/util.ts
| ... | ... | @@ -42,3 +42,36 @@ export function getNestedValue<T>(obj: T, path: string): any { |
| 42 | 42 | |
| 43 | 43 | return current; |
| 44 | 44 | } |
| 45 | + | |
| 46 | +/** | |
| 47 | + * 分转元 + 千分位格式化(TS 严格模式安全) | |
| 48 | + * @param amount 分 | |
| 49 | + * @param decimals 小数位,默认 2 | |
| 50 | + */ | |
| 51 | +export function fenToYuan( | |
| 52 | + amount: bigint | number | string, | |
| 53 | + decimals: number = 2, | |
| 54 | +): string { | |
| 55 | + if (amount === null || amount === undefined || amount === '') { | |
| 56 | + return (0).toFixed(decimals); | |
| 57 | + } | |
| 58 | + | |
| 59 | + const num = Number(amount); | |
| 60 | + if (Number.isNaN(num)) { | |
| 61 | + return (0).toFixed(decimals); | |
| 62 | + } | |
| 63 | + | |
| 64 | + const isNegative = num < 0; | |
| 65 | + const yuan = Math.abs(num) / 100; | |
| 66 | + | |
| 67 | + const fixed = yuan.toFixed(decimals); | |
| 68 | + | |
| 69 | + // 👇 关键修复点 | |
| 70 | + const parts = fixed.split('.'); | |
| 71 | + const integer = parts[0] ?? '0'; | |
| 72 | + const decimal = parts[1]; | |
| 73 | + | |
| 74 | + const thousand = integer.replaceAll(/\B(?=(\d{3})+(?!\d))/g, ','); | |
| 75 | + | |
| 76 | + return `${isNegative ? '-' : ''}${thousand}${decimal ? `.${decimal}` : ''}`; | |
| 77 | +} | ... | ... |
packages/effects/request/src/request-client/preset-interceptors.ts
| ... | ... | @@ -9,7 +9,7 @@ import axios from 'axios'; |
| 9 | 9 | export const defaultResponseInterceptor = ({ |
| 10 | 10 | codeField = 'code', |
| 11 | 11 | dataField = 'data', |
| 12 | - successCode = 0, | |
| 12 | + successCode = 200, | |
| 13 | 13 | }: { |
| 14 | 14 | /** 响应数据中代表访问结果的字段名 */ |
| 15 | 15 | codeField: string; |
| ... | ... | @@ -131,6 +131,7 @@ export const errorMessageResponseInterceptor = ( |
| 131 | 131 | } |
| 132 | 132 | |
| 133 | 133 | let errorMessage = ''; |
| 134 | + debugger; | |
| 134 | 135 | const status = error?.response?.status; |
| 135 | 136 | |
| 136 | 137 | switch (status) { |
| ... | ... | @@ -158,7 +159,7 @@ export const errorMessageResponseInterceptor = ( |
| 158 | 159 | errorMessage = $t('ui.fallback.http.internalServerError'); |
| 159 | 160 | } |
| 160 | 161 | } |
| 161 | - makeErrorMessage?.(errorMessage, error); | |
| 162 | + // makeErrorMessage?.(errorMessage, error); | |
| 162 | 163 | return Promise.reject(error); |
| 163 | 164 | }, |
| 164 | 165 | }; | ... | ... |