Cashier 收银台
业务支付弹窗,支持支付渠道选择和支付验证码发送
引入
import { Cashier } from 'mand-mobile'
Vue.component(Cashier.name, Cashier)
代码演示
基本
<template>
<div class="md-example-child md-example-child-cashier">
<md-field
title="支付结果"
>
<md-radio-list
v-model="cashierResult"
:options="cashierResults"
/>
</md-field>
<md-field
title="支付配置"
>
<md-input-item
title="支付金额"
align="right"
type="money"
v-model="cashierAmount"
>
</md-input-item>
<md-field-item
title="发送验证码"
align="right"
>
<md-switch v-model="isCashierCaptcha"></md-switch>
</md-field-item>
</md-field>
<md-button @click="isCashierhow = !isCashierhow">{{ isCashierhow ? '收起收银台' : '唤起收银台' }}</md-button>
<md-cashier
ref="cashier"
v-model="isCashierhow"
:channels="cashierChannels"
:channel-limit="2"
:payment-amount="cashierAmount"
payment-describe="关于支付金额的特殊说明"
large-radius
@select="onCashierSelect"
@pay="onCashierPay"
@cancel="onCashierCancel"
></md-cashier>
</div>
</template>
<script>
import {Button, RadioList, Field, FieldItem, InputItem, Switch, Cashier, Toast} from 'mand-mobile'
export default {
name: 'cashier-demo',
components: {
[Button.name]: Button,
[RadioList.name]: RadioList,
[Field.name]: Field,
[FieldItem.name]: FieldItem,
[InputItem.name]: InputItem,
[Switch.name]: Switch,
[Cashier.name]: Cashier,
},
data() {
return {
isCashierhow: false,
isCashierCaptcha: false,
cashierAmount: '100.00',
cashierResult: 'success',
cashierResults: [
{
text: '支付成功',
value: 'success',
},
{
text: '支付失败',
value: 'fail',
},
],
cashierChannels: [
{
icon: 'cashier-icon-1',
text: '招商银行(0056)',
value: '001',
},
{
icon: 'cashier-icon-2',
text: '支付宝支付',
value: '002',
},
{
icon: 'cashier-icon-3',
text: '微信支付',
value: '003',
},
{
icon: 'cashier-icon-4',
text: 'QQ钱包支付',
value: '004',
},
{
icon: 'cashier-icon-5',
text: '一网通支付',
value: '005',
},
],
}
},
computed: {
cashier() {
return this.$refs.cashier
},
},
methods: {
doPay() {
if (this.isCashierCaptcha) {
this.cashier.next('captcha', {
text: 'Verification code sent to 156 **** 8965',
brief: 'The latest verification code is still valid',
autoCountdown: false,
countNormalText: 'Send Verification code',
countActiveText: 'Retransmission after {$1}s',
onSend: countdown => {
console.log('[Mand Mobile] Send Captcha')
this.sendCaptcha().then(() => {
countdown()
})
},
onSubmit: code => {
console.log(`[Mand Mobile] Send Submit ${code}`)
this.checkCaptcha(code).then(res => {
if (res) {
this.createPay().then(() => {
this.cashier.next(this.cashierResult)
})
}
})
},
})
} else {
this.createPay().then(() => {
this.cashier.next(this.cashierResult, {
buttonText: '好的',
handler: () => {
this.isCashierhow = false
Toast.info(`${this.cashierResult}点击`)
},
})
})
}
},
// Create a pay request & check pay result
createPay() {
this.cashier.next('loading')
return new Promise(resolve => {
this.timer = setTimeout(() => {
resolve()
}, 3000)
})
},
// Create a captcha sending request
sendCaptcha() {
return new Promise(resolve => {
this.timer = setTimeout(() => {
resolve()
}, 200)
})
},
// Create a captcha checking request
checkCaptcha(code) {
return new Promise(resolve => {
this.timer = setTimeout(() => {
resolve(!!code)
}, 200)
})
},
onCashierSelect(item) {
console.log(`[Mand Mobile] Select ${JSON.stringify(item)}`)
},
onCashierPay(item) {
console.log(`[Mand Mobile] Pay ${JSON.stringify(item)}`)
this.doPay()
},
onCashierCancel() {
// Abort pay request or checking request
this.timer && clearTimeout(this.timer)
},
},
}
</script>
<style lang="stylus">
.md-example-child-cashier
.md-field
margin-bottom 30px
.md-cashier-channel-item
.item-icon.cashier-icon-1
background url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAABHklEQVR4Ae2WgUYEURSGh70oQIB6gh6gHmBsqhfqHdq2hAJTUYAACMCytqQISUDAVpsEsWi09TX/XFcxYeJ0E/vzMf4z7uc6cJMQWi6l1egU5AUYkfszXZp8TVFmBfwyWbhZU0UcXKrbdWMJ5ZLwNaIwl5CY1BPen8HgAtoT1dn6pJ/dnRoKn64p01mpztQpj1eGwsNlyuTPsDX92W/P+A70j6FQ3BxR5nI3dP4bNDPeochmYZTD+xvsz8HBvP8evWhmLvScb1Cm3/Mo6sLcXLg5BcMHQhgO1BkL+8fUjP6NLLw9MRB+R0h19h+F4x3WYSystVe/rx/zJ0+MXtxHVNstxBO6ZqKw2tiLINxJQrzULenKxjvVWV3W3GLwfAD9KR4TBA12SgAAAABJRU5ErkJggg==') center no-repeat
background-size 26px
.item-icon.cashier-icon-2
background url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAB7ElEQVR4Ac2WA6weQRSFb62gtuPajetGtW0FNcO6cVK7XdRu2Ia149R2Z6bP9r6T+yb5bZ7k/Bh9gzt3l1hXfzUiS+4iU36GK2AnTq6AP/PYJ/42JhZ+oPAp7CTWYDDUVHu4IDneCaD8kkTgV4r4zA5+akBuivhMI56llyLv768wXkpfYNK3NGXAaHQhpxlHY9KAlpwc6Qojt6W2urZYnYoFWA5XhWhTSaf/dmHYDqc2/v+NFPiZDLmKzv7pxAPAZIvWZPwbjrqN8B242LU6edUtgMZGGjS3XI8R6IRTj/yI2xhiPNofA7CPCyiuRAL8wM9FUy6AH8I5urwE/gwb8FSdS311+ldz1KsIgGIpmWpNGEGSQYbaC7f1gWIyPClD3uaJBgWeld3x/S3MyCwkWwz1AfqsWCzV8EIfIAeJqd6GAcuHx7oFylz4AVlqGl116gQ/c3HABTTkFH1pywPD1Esy//V0wdQMlJe6Rex3MtQ2OvO/fRgZXbziGVqqP+CmXm0m/AED2WT9G0GOU4sg/sYVCZLCyngrLTHRJ9K9Gl4jWzShYDoj+5Ih7keQiSQWcwgTHqKBPrP8wanK/D+QLzwOH4DeOgDu+maeiPwmBS9RuFNJA1pyV9JfhF33BHS9vZVxhFTWjCn2kYZVAyRlGm3AoxGeAAAAAElFTkSuQmCC') center no-repeat
background-size 26px
.item-icon.cashier-icon-3
background url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAYCAYAAADpnJ2CAAABiklEQVR4AbWVAWRCURSGf8gIGQyYgUEAgyAAbCCAAAQQGBAYIGAwAAgIwAMJGIsqRTAgQPAQEsKDu//kXHLddV+v+w6f0HnvP/e8/5yLYPzgAXN0yICsSUoyZUeW+l/nlFs45miSRF9scpKRBDM0rhGqacXmRobhE8/wwsQtMZHYyjv/E2swYU9MZPZW1DVGWoLYEQt88ffR/W7DyEIHCn3ab4gVKpjhzbayHlOI9K2QHqZFNsSctKSSCEI78oE17h1PTJy8PnRwjYdNjlanpEdqsLHC04XnltA2+KuWiudoe9y7xQLvWKF6JlSVE5DjxZZf3CRs91nV3zqjXYxw55iuk9PlGQKJGaZ4hoR123lM8cqc3yu+dSqC40BS4lkSdf9zQcY4tSic2LQLQgc5K+jmrl3Wu0DiUkxy4+o72LEJnDISUrDjtKREwTHc0DkalSA2sfPqE62oKWKJDWRmgzd+BKENaUGjLMGjzmbbLoiigmvSk/tMnKa3y0DQ9vdIM9S6gKCK6FqLGX+Ik2Cgy7oRZQAAAABJRU5ErkJggg==') center no-repeat
background-size 26px
.item-icon.cashier-icon-4
background url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAcCAYAAAB75n/uAAADaElEQVR4AWIgADiAOAaI1wDxUxZAadUA5GoTBCd59Wzbtm3btm3btm3btm3btt/5+v/6uLXRl/q7qu+SRc/OzKbXIhAh5ZXB1QZLyf8ANz+noAseNZhc3ETb4NOaocUiX+JFlYJiEuU1cdNBzGQSjbVWN3bu3BkHDx5EgQIFQsb69u2LjRs3IkWKFHqgE+ICndUNFCB27drFIBxDoUKFcP/+fZLj9rIpL05wVV1co0YNEBQdP3484sWLx9OjXbt2JD5//mwvwBaHV1KvPctCsEyLFy9GxIgRQeErV67g2rVrePz4sf1eOEBGLtB56tQpEPPmzQO/x4gRAx8+fADRvXt3R02PJnaQw97inKnCYkiLTBjf2IJ5bQTkxJax0KdpdiSPK44CxBcdk5LE7qqWaGxDwZv5AmxyzjtTLKiVP3QfNfakTVBZdDzOHHPDhESREBxkQA3Br4Vh8WlyJAr568Leq634MC4yPs63onz2UPEVySL7vswcc6LoeJIx+vPXmWNgW8ooSBs+DDcgY2LBoYEWvBoaDVeaJMSNlvFD+G5sZJYLsaMGlTJiGOxPFRXUuJ8x+nnREO16+ui+nAzm4qSRUT5qWES2Bpbg7OCwuNcjJok1nawMzjlUix4Oq5NHhrr3WJqonnqj5/HUJ9IEnkAnx5n6rMSRQFLQ0VpmkSSslVlNkyAkD647/y8pkx9vC6XiYrf4tnh6dEocHapWsDdNVK/Y/Pnz8XvdYlOiH6rlx7fh3eFx8iDg54uyZcvq13W8jT1s3boVPyYPVYXg/+8vfJ4/htedayT42d/TAzoaNmyoB7gquj3QJXkqNQBPZwZ6gCBtgcpp06bZBPD79QMmQFNUhYMpj4M+dOYEHdT3y0f8nDsBX3u3wpdujU1l4OHhgQgRIsBqld+K7VNbBgU1mlhNx6SZuYtVq1ZRUL2e04TaGuLTbmnP7uDv379Ily4dxZ/bOqkGPt5Mk802i+bNm4MHy5ZccokzYJOMN/jn6WxBscxhwVfM29sbjsBSsmclMoc47x9q2BffLP00x/TfM0DQvkYqjB45BHyT+Q7zNeNvpWuntmhUIgq4RtsHaokObJS2nHRAf2a1s5/s3NFXtvOzauE6qeWoRHUMHjT4mQJBfGpws8HKyrrKHOOcso57DlJDFPwHtUxGlWBNgLkAAAAASUVORK5CYII=') center no-repeat
background-size 26px
.item-icon.cashier-icon-5
background url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAABPlBMVEUAAAC/AEDIFS3GFS3HFS3HFS3HFS3MADPGFC7GFS3FFSvHFS3GFS3HFS3HFS3KIjnooKnmmKPJHDTMLELqqLHmlaDIGjHONEnssLjkjJjHFi7////hgI3rqrLdcoDwwsjUS17KITjed4T44+XIGC/LJz3++fr119vQPFD//v7NMEXgf43ca3r78PLZX3DVUWPpoavut77IGTD44+bxxszmlqHxw8nHFy/44OPttr3LJjz99/jaZXXXWWrhgY7+/P3//f3XV2jzzdPgf4zut7/nmqTxxMrllJ/yyM7dcH/++vvMK0HWUmTQO0/ZYXHba3r77vDJHTTts7v22Nzxxcv55OffdoXed4XRP1PedIPaZHThg5D11dnVTmDJHzb77e/55+rLJj377/Hqp7DkjprTRlnlkZzRPlLNL0TXVmdZnHwTAAAAD3RSTlMABEqUzPH/BXDmMNZV+P145yZGAAABMklEQVR4AXTQ02KcARCG4Z/v2t5vVdv2to1t2/d/BcnEfE7H45xxPT8IIQx8z3WuikRjnItFI5dj8QRXJOIXsWSKa1LJ81iaG9Kn0XiKW6TiFoskuFXCtopyh+jRfTFMJpvLA4ViqQxUqrU6MdfxwJSkBtCUWkBb6oDn+Jh796UHD3n0WHoCT6Vnz8F3AsyLl6+k1zQk6Q1vpXdA4IQWe//h4yfp85evFvz2/YdlQOhgfkq/nkm/pT9S96/1NifBf9L/HpnePql/QIMYa0tHGhoekRkdk16NT0yCtQ2AKalIfVrSDO9npbl5TGCnPO9KC7AoaQmWpRUwvj1hVWoDa+vaKMPmlrbBePa+LzvVCsDu3j7AweEyxSAJghFfwOONMnyRjS+Z4Etg+JIm4USNNzsAAPHdK2mIKv4bAAAAAElFTkSuQmCC') center no-repeat
background-size 26px
</style>
使用插槽及其他配置
<template>
<div class="md-example-child md-example-child-cashier">
<md-button @click="isCashierhow = !isCashierhow">{{ isCashierhow ? '收起收银台' : '唤起收银台' }}</md-button>
<md-cashier
ref="cashier"
v-model="isCashierhow"
:channels="cashierChannels"
:payment-amount="cashierAmount"
payment-describe="关于支付金额的特殊说明"
large-radius
@show="onCashierShow"
@select="onCashierSelect"
@pay="onCashierPay"
@cancel="onCashierCancel"
>
<div slot-scope="{ scene }" slot="header">
<md-notice-bar
v-if="scene === 'choose'"
mode="closable"
icon="warn"
type="warning"
>
该银行3:00-12:00系统维护,请更换其他银行卡
</md-notice-bar>
</div>
<div slot-scope="{ scene }" slot="footer">
<div v-if="scene === 'choose' && !isCashierInitialed" class="cashier-loading">
<md-activity-indicator :size="30" vertical>加载中...</md-activity-indicator>
</div>
</div>
<div slot="payButton" style="display:flex;">
<md-icon name="checked"></md-icon>发起支付
</div>
<div slot="scene" class="custom-scene">
Custom Scene
</div>
</md-cashier>
</div>
</template>
<script>
import {Button, Icon, Cashier, Toast, NoticeBar, ActivityIndicator} from 'mand-mobile'
export default {
name: 'cashier-demo',
components: {
[Button.name]: Button,
[Cashier.name]: Cashier,
[Icon.name]: Icon,
[NoticeBar.name]: NoticeBar,
[ActivityIndicator.name]: ActivityIndicator,
},
data() {
return {
isCashierhow: false,
isCashierInitialed: false,
isCashierCaptcha: false,
cashierAmount: '100.00',
cashierResult: 'success',
cashierResults: [
{
text: '支付成功',
value: 'success',
},
{
text: '支付失败',
value: 'fail',
},
],
cashierChannels: [
{
img: 'https://pt-starimg.didistatic.com/static/starimg/img/rZBbFoIJEJ1546934427562.png',
text: 'XX银行(1234)',
desc: '当前银行维护中',
value: '001',
disabled: true,
action: {
text: '更换',
handler: () => {
Toast.info('点击更换银行卡')
},
},
},
],
}
},
computed: {
cashier() {
return this.$refs.cashier
},
},
methods: {
doPay() {
if (this.isCashierCaptcha) {
this.cashier.next('captcha', {
text: 'Verification code sent to 156 **** 8965',
autoCountdown: false,
countNormalText: 'Send Verification code',
countActiveText: 'Retransmission after {$1}s',
onSend: countdown => {
console.log('[Mand Mobile] Send Captcha')
this.sendCaptcha().then(() => {
countdown()
})
},
onSubmit: code => {
console.log(`[Mand Mobile] Send Submit ${code}`)
this.checkCaptcha(code).then(res => {
if (res) {
this.createPay().then(() => {
this.cashier.next(this.cashierResult)
})
}
})
},
})
} else {
this.createPay().then(() => {
this.cashier.next(this.cashierResult, {
actions: [
{
buttonText: '返回',
handler: () => {
this.cashier.next('choose')
},
},
{
buttonText: '重试',
handler: () => {
this.cashier.next('custom')
},
},
],
})
})
}
},
// Create a pay request & check pay result
createPay() {
this.cashier.next('loading')
return new Promise(resolve => {
this.timer = setTimeout(() => {
resolve()
}, 3000)
})
},
// Create a captcha sending request
sendCaptcha() {
return new Promise(resolve => {
this.timer = setTimeout(() => {
resolve()
}, 200)
})
},
// Create a captcha checking request
checkCaptcha(code) {
return new Promise(resolve => {
this.timer = setTimeout(() => {
resolve(!!code)
}, 200)
})
},
onCashierShow() {
setTimeout(() => {
this.isCashierInitialed = true
}, 2000)
},
onCashierSelect(item) {
console.log(`[Mand Mobile] Select ${JSON.stringify(item)}`)
},
onCashierPay(item) {
console.log(`[Mand Mobile] Pay ${JSON.stringify(item)}`)
this.doPay()
},
onCashierCancel() {
// Abort pay request or checking request
this.timer && clearTimeout(this.timer)
},
},
}
</script>
<style lang="stylus">
.md-example-child-cashier
.md-field
margin-bottom 30px
.custom-scene
min-height 300px
display flex
justify-content center
align-items center
font-size 32px
.cashier-loading
position absolute
top 0
left 0
right 0
bottom 0
background rgba(255, 255, 255, 0.95)
z-index 1400
display flex
align-items center
justify-content center
</style>
API
Cashier Props
属性 | 说明 | 类型 | 默认值 | 备注 |
---|---|---|---|---|
v-model | 收银台是否显示 | Boolean | false | - |
channels | 支付渠道数据源 | Array<{text, value, icon, iconSvg, img, action}> | [] | icon 可作为className 或组件Icon 的name 属性, iconSvg 为是否使用svg图标, img 为图标链接(与icon 二选一), action 为特殊动作回调 |
channel-limit | 支付渠道超出限制数目时展示更多支付渠道按钮 | Number | 2 | - |
default-index | 默认选中支付渠道索引 | Number | 0 | - |
title | 收银台弹窗标题 | String | 支付 | - |
large-radius 2.4.0+ | 选择器标题栏大圆角模式 | Boolean | false | - |
payment-title | 支付金额标题 | String | 支付金额(元) | 支持html fragment |
payment-amount | 支付金额 | String | 0.00 | 支持html fragment |
payment-describe | 支付金额说明 | String | - | 支持html fragment |
pay-button-text | 确认支付按钮文案 | String | 确认支付 | - |
pay-button-disabled | 禁用支付按钮 | Boolean | false | - |
more-button-text | 更多支付渠道按钮文案 | String | 更多支付方式 | 支持html fragment |
Cashier Methods
next(scene, option)
进入收银台下一步
参数 | 说明 | 类型 | 默认值 | 备注 |
---|---|---|---|---|
scene | 步骤场景标识 | String | - | choose (支付渠道选择)captcha (发送验证码)loading (支付中)success (支付成功)fail (支付失败)custom (自定义,使用插槽scene 填充内容) |
option | 当前步骤场景配置 | Object | 属性如下所示 | - |
captcha
option属性 说明 类型 默认值 备注 text 发送验证码说明 String - - brief 发送验证码简要描述 String - - maxlength 验证码位数 Number 4
若为 -1
则不限制输入长度count 验证码重新发送倒计时 Number 60
若为 0
则不显示重新发送autoCountdown 是否自动开始倒计时,否则需手动调用 countdown
Boolean true
- countNormalText 发送验证码正常状态文字 String 发送验证码
- countActiveText 发送验证码及倒计时按钮文案配置项 String {$1}秒后重发
- onSend 验证码发送回调 Function(countdown: Function) - countdown
为开始倒计时方法onSubmit 验证码提交回调 Function(code: String) - code
为输入的验证码loading
option属性 说明 类型 默认值 备注 text 支付中说明 String 支付结果查询中…
支持 html fragment
success
option属性 说明 类型 默认值 备注 text 支付成功说明 String 支付成功
支持 html fragment
buttonText 按钮文案 String 我知道了
支持 html fragment
handler 按钮点击回调 Function - - actions 按钮组 Array<{buttonText, handler}> - 有两个按钮时使用 fail
option属性 说明 类型 默认值 备注 text 支付失败说明 String 支付失败,请稍后重试
支持 html fragment
buttonText 按钮文案 String 我知道了
支持 html fragment
handler 按钮点击回调 Function - - actions 按钮组 Array<{buttonText, handler}> - 有两个按钮时使用
Captcha Slots
header
头部内容scoped插槽
<div slot-scope="{ scene }" slot="header">
<md-notice-bar
v-if="scene === 'choose'"
mode="closable"
icon="warn"
type="warning"
></md-notice-bar>
</div>
footer
底部内容scoped插槽
channel
支付渠道区域插槽,可用于添加支付渠道特殊操作,如添加银行卡
payButton
发起支付插槽
scene
自定义场景插槽,使用next('custom')
打开
Cashier Events
@select(item: {text, value})
支付渠道选中事件
@pay(item: {text, value})
支付渠道确认并发起支付事件
@cancel()
取消支付事件
@show()
收银台弹窗展示事件
@hide()
收银台弹窗隐藏事件