feat: add cashier views
							parent
							
								
									7398bab010
								
							
						
					
					
						commit
						09942f0257
					
				|  | @ -0,0 +1,81 @@ | ||||||
|  | import { | ||||||
|  |   SvgAlipayAppIcon, | ||||||
|  |   SvgAlipayBarIcon, | ||||||
|  |   SvgAlipayPcIcon, | ||||||
|  |   SvgAlipayQrIcon, | ||||||
|  |   SvgAlipayWapIcon, | ||||||
|  |   SvgMockIcon, | ||||||
|  |   SvgWalletIcon, | ||||||
|  |   SvgWxAppIcon, | ||||||
|  |   SvgWxBarIcon, | ||||||
|  |   SvgWxLiteIcon, | ||||||
|  |   SvgWxNativeIcon, | ||||||
|  |   SvgWxPubIcon, | ||||||
|  | } from '@vben/icons'; | ||||||
|  | 
 | ||||||
|  | export const channelsAlipay = [ | ||||||
|  |   { | ||||||
|  |     name: '支付宝 PC 网站支付', | ||||||
|  |     icon: SvgAlipayPcIcon, | ||||||
|  |     code: 'alipay_pc', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '支付宝 Wap 网站支付', | ||||||
|  |     icon: SvgAlipayWapIcon, | ||||||
|  |     code: 'alipay_wap', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '支付宝 App 网站支付', | ||||||
|  |     icon: SvgAlipayAppIcon, | ||||||
|  |     code: 'alipay_app', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '支付宝扫码支付', | ||||||
|  |     icon: SvgAlipayQrIcon, | ||||||
|  |     code: 'alipay_qr', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '支付宝条码支付', | ||||||
|  |     icon: SvgAlipayBarIcon, | ||||||
|  |     code: 'alipay_bar', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | export const channelsWechat = [ | ||||||
|  |   { | ||||||
|  |     name: '微信公众号支付', | ||||||
|  |     icon: SvgWxPubIcon, | ||||||
|  |     code: 'wx_pub', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '微信小程序支付', | ||||||
|  |     icon: SvgWxLiteIcon, | ||||||
|  |     code: 'wx_lite', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '微信 App 支付', | ||||||
|  |     icon: SvgWxAppIcon, | ||||||
|  |     code: 'wx_app', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '微信扫码支付', | ||||||
|  |     icon: SvgWxNativeIcon, | ||||||
|  |     code: 'wx_native', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '微信条码支付', | ||||||
|  |     icon: SvgWxBarIcon, | ||||||
|  |     code: 'wx_bar', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | export const channelsMock = [ | ||||||
|  |   { | ||||||
|  |     name: '钱包支付', | ||||||
|  |     icon: SvgWalletIcon, | ||||||
|  |     code: 'wallet', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: '模拟支付', | ||||||
|  |     icon: SvgMockIcon, | ||||||
|  |     code: 'mock', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | @ -1,7 +1,171 @@ | ||||||
| <script lang="ts" setup></script> | <script setup lang="ts"> | ||||||
|  | import type { PayOrderApi } from '#/api/pay/order'; | ||||||
| 
 | 
 | ||||||
|  | import { onMounted, ref } from 'vue'; | ||||||
|  | import { useRoute, useRouter } from 'vue-router'; | ||||||
|  | 
 | ||||||
|  | import { Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { formatDate } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { Card, Descriptions, message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { getOrder } from '#/api/pay/order'; | ||||||
|  | import { PayChannelEnum, PayOrderStatusEnum } from '#/utils/constants'; | ||||||
|  | 
 | ||||||
|  | import { channelsAlipay, channelsMock, channelsWechat } from './data'; | ||||||
|  | import Form from './modules/form.vue'; | ||||||
|  | 
 | ||||||
|  | defineOptions({ name: 'PayCashier' }); | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: Form, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const id = ref(); // 支付单号 | ||||||
|  | const returnUrl = ref<string>(); // 支付完的回调地址 | ||||||
|  | const route = useRoute(); | ||||||
|  | const router = useRouter(); | ||||||
|  | const payOrder = ref<PayOrderApi.Order>(); | ||||||
|  | 
 | ||||||
|  | /** 获得支付信息 */ | ||||||
|  | async function getDetail() { | ||||||
|  |   // 1. 获取路由参数 | ||||||
|  |   id.value = route.query.id; | ||||||
|  |   if (route.query.returnUrl) { | ||||||
|  |     returnUrl.value = decodeURIComponent(route.query.returnUrl as string); | ||||||
|  |   } | ||||||
|  |   // 1.1 未传递订单编号 | ||||||
|  |   if (!id.value) { | ||||||
|  |     message.error('未传递支付单号,无法查看对应的支付信息'); | ||||||
|  |     // goReturnUrl('cancel') | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   const res = await getOrder(id.value); | ||||||
|  |   // 1.2 无法查询到支付信息 | ||||||
|  |   if (!res) { | ||||||
|  |     message.error('支付订单不存在,请检查!'); | ||||||
|  |     // goReturnUrl('cancel') | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   // 1.3 如果已支付、或者已关闭,则直接跳转 | ||||||
|  |   if (res.status === PayOrderStatusEnum.SUCCESS.status) { | ||||||
|  |     message.success('支付成功'); | ||||||
|  |     // goReturnUrl('success') | ||||||
|  |     return; | ||||||
|  |   } else if (res.status === PayOrderStatusEnum.CLOSED.status) { | ||||||
|  |     message.error('无法支付,原因:订单已关闭'); | ||||||
|  |     // goReturnUrl('close') | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   payOrder.value = res; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function handlePay(channelCode: string) { | ||||||
|  |   // 微信公众号、小程序支付,无法在 PC 网页中进行 | ||||||
|  |   if (channelCode === PayChannelEnum.WX_PUB.code) { | ||||||
|  |     message.error('微信公众号支付:不支持 PC 网站'); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (channelCode === PayChannelEnum.WX_LITE.code) { | ||||||
|  |     message.error('微信小程序:不支持 PC 网站'); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   const data = { | ||||||
|  |     id: id.value, | ||||||
|  |     channelCode, | ||||||
|  |     returnUrl: location.href, // 支付成功后,支付渠道跳转回当前页;再由当前页,跳转回 {@link returnUrl} 对应的地址 | ||||||
|  |   }; | ||||||
|  |   formModalApi.setData(data).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function success(data?: { channelCode: string; data?: any }) { | ||||||
|  |   if (!data) { | ||||||
|  |     clearQueryInterval(); | ||||||
|  |   } | ||||||
|  |   if (data.channelCode === PayChannelEnum.ALIPAY_BAR.code) { | ||||||
|  |     message.success('支付宝条码支付'); | ||||||
|  |   } else if (data.channelCode === PayChannelEnum.WX_BAR.code) { | ||||||
|  |     message.success('微信条码支付'); | ||||||
|  |   } else { | ||||||
|  |     message.success('支付成功'); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(async () => { | ||||||
|  |   await getDetail(); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
| <template> | <template> | ||||||
|   <div> |   <Page auto-content-height> | ||||||
|     <h1>收银台</h1> |     <FormModal @success="success" /> | ||||||
|   </div> | 
 | ||||||
|  |     <Card class="mt-4"> | ||||||
|  |       <Descriptions :column="3" :title="payOrder?.subject ?? '商品详情'"> | ||||||
|  |         <Descriptions.Item label="支付单号"> | ||||||
|  |           {{ payOrder?.id }} | ||||||
|  |         </Descriptions.Item> | ||||||
|  |         <Descriptions.Item label="商品标题"> | ||||||
|  |           {{ payOrder?.subject }} | ||||||
|  |         </Descriptions.Item> | ||||||
|  |         <Descriptions.Item label="商品内容"> | ||||||
|  |           {{ payOrder?.body }} | ||||||
|  |         </Descriptions.Item> | ||||||
|  |         <Descriptions.Item label="支付金额"> | ||||||
|  |           {{ `¥${(payOrder?.price ? payOrder.price / 100.0 : 0).toFixed(2)}` }} | ||||||
|  |         </Descriptions.Item> | ||||||
|  |         <Descriptions.Item label="创建时间"> | ||||||
|  |           {{ formatDate(payOrder?.createTime) }} | ||||||
|  |         </Descriptions.Item> | ||||||
|  |         <Descriptions.Item label="过期时间"> | ||||||
|  |           {{ formatDate(payOrder?.expireTime) }} | ||||||
|  |         </Descriptions.Item> | ||||||
|  |       </Descriptions> | ||||||
|  |     </Card> | ||||||
|  |     <Card title="选择支付宝支付" class="mt-4"> | ||||||
|  |       <div class="flex"> | ||||||
|  |         <div | ||||||
|  |           class="mr-4 w-40 cursor-pointer items-center border-2 border-gray-200 pb-1 pt-4 text-center hover:border-blue-500" | ||||||
|  |           v-for="channel in channelsAlipay" | ||||||
|  |           :key="channel.code" | ||||||
|  |           @click="handlePay(channel.code)" | ||||||
|  |         > | ||||||
|  |           <div class="flex items-center justify-center"> | ||||||
|  |             <component :is="channel.icon" class="h-10 w-10" /> | ||||||
|  |           </div> | ||||||
|  |           <div class="mt-2 pt-1 text-center">{{ channel.name }}</div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </Card> | ||||||
|  |     <Card title="选择微信支付" class="mt-4"> | ||||||
|  |       <div class="flex"> | ||||||
|  |         <div | ||||||
|  |           class="mr-4 w-40 cursor-pointer items-center border-2 border-gray-200 pb-1 pt-4 text-center hover:border-blue-500" | ||||||
|  |           v-for="channel in channelsWechat" | ||||||
|  |           :key="channel.code" | ||||||
|  |           @click="handlePay(channel.code)" | ||||||
|  |         > | ||||||
|  |           <div class="flex items-center justify-center"> | ||||||
|  |             <component :is="channel.icon" class="h-10 w-10" /> | ||||||
|  |           </div> | ||||||
|  |           <div class="mt-2 pt-1 text-center">{{ channel.name }}</div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </Card> | ||||||
|  |     <Card title="选择其它支付" class="mt-4"> | ||||||
|  |       <div class="flex"> | ||||||
|  |         <div | ||||||
|  |           class="mr-4 w-40 cursor-pointer items-center border-2 border-gray-200 pb-1 pt-4 text-center hover:border-blue-500" | ||||||
|  |           v-for="channel in channelsMock" | ||||||
|  |           :key="channel.code" | ||||||
|  |           @click="handlePay(channel.code)" | ||||||
|  |         > | ||||||
|  |           <div class="flex items-center justify-center"> | ||||||
|  |             <component :is="channel.icon" class="h-10 w-10" /> | ||||||
|  |           </div> | ||||||
|  |           <div class="mt-2 pt-1 text-center">{{ channel.name }}</div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </Card> | ||||||
|  |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,155 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { Button, Input, message, QRCode } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { submitOrder } from '#/api/pay/order'; | ||||||
|  | import { | ||||||
|  |   PayChannelEnum, | ||||||
|  |   PayDisplayModeEnum, | ||||||
|  |   PayOrderStatusEnum, | ||||||
|  | } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | const title = ref('支付订单'); | ||||||
|  | const channelCode = ref(''); | ||||||
|  | const formData = ref(); | ||||||
|  | 
 | ||||||
|  | /** 展示形式:二维码 */ | ||||||
|  | const qrCode = ref({ | ||||||
|  |   url: '', | ||||||
|  |   visible: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 展示形式:条形码 */ | ||||||
|  | const barCode = ref({ | ||||||
|  |   channelCode: '', | ||||||
|  |   value: '', | ||||||
|  |   visible: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       const submitParam = { | ||||||
|  |         id: formData.value.id, | ||||||
|  |         channelCode: formData.value.channelCode, | ||||||
|  |         returnUrl: formData.value.returnUrl, // 支付成功后,支付渠道跳转回当前页;再由当前页,跳转回 {@link returnUrl} 对应的地址 | ||||||
|  |         ...buildSubmitParam(formData.value.channelCode), | ||||||
|  |       }; | ||||||
|  |       const data = await submitOrder(submitParam); | ||||||
|  |       // 直接返回已支付的情况,例如说扫码支付 | ||||||
|  |       if (data.status === PayOrderStatusEnum.SUCCESS.status) { | ||||||
|  |         message.success('支付成功!'); | ||||||
|  |         emit('success'); | ||||||
|  |       } | ||||||
|  |       // 展示对应的界面 | ||||||
|  |       switch (data.displayMode) { | ||||||
|  |         case PayDisplayModeEnum.APP.mode: { | ||||||
|  |           await modalApi.close(); | ||||||
|  |           emit('success', { channelCode }); | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         case PayDisplayModeEnum.QR_CODE.mode: { | ||||||
|  |           await modalApi.close(); | ||||||
|  |           emit('success', { channelCode, data }); | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         case PayDisplayModeEnum.URL.mode: { | ||||||
|  |           await modalApi.close(); | ||||||
|  |           emit('success', { channelCode, data }); | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         // No default | ||||||
|  |       } | ||||||
|  |     } catch (error) { | ||||||
|  |       message.error(error as string); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData(); | ||||||
|  |     if (!data || !data.id) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     formData.value = data; | ||||||
|  |     try { | ||||||
|  |       channelCode.value = formData.value.channelCode; | ||||||
|  |       // 条形码支付,需要特殊处理 | ||||||
|  |       if (formData.value.channelCode === PayChannelEnum.ALIPAY_BAR.code) { | ||||||
|  |         title.value = '“支付宝”条码支付'; | ||||||
|  |         barCode.value = { | ||||||
|  |           channelCode: formData.value.channelCode, | ||||||
|  |           value: formData.value.channelExtras.auth_code, | ||||||
|  |           visible: true, | ||||||
|  |         }; | ||||||
|  |       } else if (formData.value.channelCode === PayChannelEnum.WX_BAR.code) { | ||||||
|  |         title.value = '“微信”条码支付'; | ||||||
|  |         barCode.value = { | ||||||
|  |           channelCode: formData.value.channelCode, | ||||||
|  |           value: formData.value.channelExtras.authCode, | ||||||
|  |           visible: true, | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 构建提交支付的额外参数 */ | ||||||
|  | function buildSubmitParam(channelCode: string) { | ||||||
|  |   // ① 支付宝 BarCode 支付时,需要传递 authCode 条形码 | ||||||
|  |   if (channelCode === PayChannelEnum.ALIPAY_BAR.code) { | ||||||
|  |     return { | ||||||
|  |       channelExtras: { | ||||||
|  |         auth_code: barCode.value.value, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |   // ② 微信 BarCode 支付时,需要传递 authCode 条形码 | ||||||
|  |   if (channelCode === PayChannelEnum.WX_BAR.code) { | ||||||
|  |     return { | ||||||
|  |       channelExtras: { | ||||||
|  |         authCode: barCode.value.value, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |   return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal class="w-[600px]" :title="title"> | ||||||
|  |     <QRCode v-if="qrCode.visible" :value="qrCode.url" /> | ||||||
|  |     <Input | ||||||
|  |       v-if="barCode.visible" | ||||||
|  |       v-model:value="barCode.value" | ||||||
|  |       placeholder="请输入条形码" | ||||||
|  |       required | ||||||
|  |     /> | ||||||
|  |     <div style="text-align: right" v-if="barCode.visible"> | ||||||
|  |       或使用 | ||||||
|  |       <Button | ||||||
|  |         type="link" | ||||||
|  |         danger | ||||||
|  |         target="_blank" | ||||||
|  |         href="https://baike.baidu.com/item/条码支付/10711903" | ||||||
|  |       > | ||||||
|  |         (扫码枪/扫码盒) | ||||||
|  |       </Button> | ||||||
|  |       扫码 | ||||||
|  |     </div> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
		Loading…
	
		Reference in New Issue
	
	 xingyu4j
						xingyu4j