467 lines
11 KiB
Vue
467 lines
11 KiB
Vue
<template>
|
||
<div class="md-captcha" v-show="isInline || value || visible">
|
||
<template v-if="isInline">
|
||
<div class="md-captcha-content">
|
||
<h2 class="md-captcha-title" v-if="title" v-text="title"></h2>
|
||
<div class="md-captcha-message">
|
||
<slot></slot>
|
||
</div>
|
||
</div>
|
||
<md-codebox
|
||
ref="codebox"
|
||
v-model="code"
|
||
:maxlength="maxlength"
|
||
:system="system"
|
||
:mask="mask"
|
||
@submit="$_onSubmit"
|
||
:closable="false"
|
||
:isView="true"
|
||
:justify="true"
|
||
:autofocus="false"
|
||
:input-type="inputType"
|
||
>
|
||
<footer class="md-captcha-footer">
|
||
<div class="md-captcha-error" v-if="errorMsg" v-text="errorMsg"></div>
|
||
<div class="md-captcha-brief" v-else v-text="brief"></div>
|
||
<button
|
||
class="md-captcha-btn"
|
||
v-if="count"
|
||
v-text="countBtnText"
|
||
:disabled="this.isCounting"
|
||
@click="$_onResend"
|
||
></button>
|
||
</footer>
|
||
</md-codebox>
|
||
</template>
|
||
<template v-if="type === 'halfScreen'">
|
||
<md-popup
|
||
:value="value"
|
||
:hasMask="true"
|
||
position="bottom"
|
||
:maskClosable="false"
|
||
@input="$_onInput"
|
||
@show="$_onShow"
|
||
@hide="$_onHide"
|
||
>
|
||
<div class="md-captcha-half-container">
|
||
<md-popup-title-bar
|
||
only-close
|
||
large-radius
|
||
:title="title"
|
||
:describe="subtitle"
|
||
title-align="left"
|
||
@cancel="close"
|
||
></md-popup-title-bar>
|
||
<div class="md-captcha-half-content">
|
||
<slot></slot>
|
||
</div>
|
||
<md-codebox
|
||
ref="codebox"
|
||
v-model="code"
|
||
:maxlength="maxlength"
|
||
:system="system"
|
||
:mask="mask"
|
||
@submit="$_onSubmit"
|
||
:disabled="disableSend"
|
||
:closable="false"
|
||
:isView="true"
|
||
:justify="true"
|
||
:autofocus="false"
|
||
:input-type="inputType"
|
||
:isErrorStyle="isShowErrorStyle"
|
||
>
|
||
<footer
|
||
class="md-captcha-footer"
|
||
:class="{ halfStyle: isKeyboard }"
|
||
>
|
||
<div
|
||
class="md-captcha-error"
|
||
v-if="errorMsg"
|
||
v-text="errorMsg"
|
||
></div>
|
||
<div class="md-captcha-brief" v-else v-text="brief"></div>
|
||
<button
|
||
class="md-captcha-btn"
|
||
:class="[disableSend && 'is-disabled-send']"
|
||
v-if="count"
|
||
v-text="countBtnText"
|
||
:disabled="this.isCounting"
|
||
@click="$_onResend"
|
||
></button>
|
||
</footer>
|
||
</md-codebox>
|
||
</div>
|
||
</md-popup>
|
||
</template>
|
||
<template v-if="type === 'dialog'">
|
||
<md-dialog
|
||
:value="value"
|
||
:closable="true"
|
||
:appendTo="false"
|
||
position="center"
|
||
@input="$_onInput"
|
||
@show="$_onShow"
|
||
@hide="$_onHide"
|
||
>
|
||
<div class="md-captcha-content">
|
||
<h2 class="md-captcha-title" v-if="title" v-text="title"></h2>
|
||
<div class="md-captcha-message">
|
||
<slot></slot>
|
||
</div>
|
||
</div>
|
||
<md-codebox
|
||
ref="codebox"
|
||
v-model="code"
|
||
:maxlength="maxlength"
|
||
:system="system"
|
||
:closable="false"
|
||
:mask="mask"
|
||
:justify="true"
|
||
:autofocus="false"
|
||
:input-type="inputType"
|
||
@submit="$_onSubmit"
|
||
>
|
||
<footer class="md-captcha-footer">
|
||
<div class="md-captcha-error" v-if="errorMsg" v-text="errorMsg"></div>
|
||
<div class="md-captcha-brief" v-else v-text="brief"></div>
|
||
<button
|
||
class="md-captcha-btn"
|
||
v-if="count"
|
||
v-text="countBtnText"
|
||
:disabled="this.isCounting"
|
||
@click="$_onResend"
|
||
></button>
|
||
</footer>
|
||
</md-codebox>
|
||
</md-dialog>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import Dialog from '../dialog'
|
||
import Popup from '../popup'
|
||
import PopupTitlebar from '../popup/title-bar'
|
||
import Codebox from '../codebox'
|
||
import Button from '../button'
|
||
import {mdDocument} from '../_util'
|
||
import {t} from '../_locale'
|
||
|
||
export default {
|
||
name: 'md-captcha',
|
||
|
||
components: {
|
||
[Dialog.name]: Dialog,
|
||
[Popup.name]: Popup,
|
||
[PopupTitlebar.name]: PopupTitlebar,
|
||
[Codebox.name]: Codebox,
|
||
[Button.name]: Button,
|
||
},
|
||
|
||
props: {
|
||
title: {
|
||
type: String,
|
||
},
|
||
subtitle: {
|
||
type: String,
|
||
},
|
||
brief: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
value: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
maxlength: {
|
||
type: [Number, String],
|
||
default: 4,
|
||
},
|
||
mask: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
system: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
autoSend: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
autoCountdown: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
appendTo: {
|
||
default: () => mdDocument.body,
|
||
},
|
||
count: {
|
||
type: Number,
|
||
default: 60,
|
||
},
|
||
countNormalText: {
|
||
type: String,
|
||
default: t('md.captcha.sendCaptcha'),
|
||
},
|
||
countActiveText: {
|
||
type: String,
|
||
default: t('md.captcha.countdown'),
|
||
},
|
||
isView: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
type: {
|
||
type: String,
|
||
default: 'dialog',
|
||
},
|
||
inputType: {
|
||
type: String,
|
||
default: 'tel',
|
||
},
|
||
disableSend: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
},
|
||
|
||
data() {
|
||
return {
|
||
code: '',
|
||
visible: false,
|
||
errorMsg: '',
|
||
isCounting: false,
|
||
firstShown: false,
|
||
countBtnText: this.countNormalText,
|
||
isKeyboard: false,
|
||
originHeight: 0,
|
||
}
|
||
},
|
||
|
||
watch: {
|
||
value(val) {
|
||
if (val) {
|
||
this.code = ''
|
||
if (!this.firstShown) {
|
||
this.firstShown = true
|
||
this.$_emitSend()
|
||
}
|
||
}
|
||
},
|
||
code(val) {
|
||
if (val && this.errorMsg) {
|
||
this.errorMsg = ''
|
||
}
|
||
},
|
||
},
|
||
computed: {
|
||
isInline() {
|
||
return this.isView || this.type === 'inline'
|
||
},
|
||
isShowErrorStyle() {
|
||
return this.errorMsg !== '' && !this.disableSend
|
||
},
|
||
},
|
||
mounted() {
|
||
// Andriod 键盘收起:Andriod 键盘弹起或收起页面高度会发生变化,以此为依据获知键盘收起
|
||
if (this.judgeDeviceType().isAndroid && this.type === 'halfScreen') {
|
||
// 记录初始高度
|
||
this.originHeight = document.documentElement.clientHeight || document.body.clientHeight
|
||
window.addEventListener('resize', this.listenResize, false)
|
||
}
|
||
if (this.appendTo && !this.isInline) {
|
||
this.appendTo.appendChild(this.$el)
|
||
}
|
||
if (this.value || this.isInline) {
|
||
this.firstShown = true
|
||
this.$_emitSend()
|
||
}
|
||
},
|
||
|
||
beforeDestroy() {
|
||
if (this.judgeDeviceType().isAndroid && this.type === 'halfScreen') {
|
||
window.removeEventListener('resize', this.listenResize)
|
||
}
|
||
if (this.appendTo && !this.isInline) {
|
||
this.appendTo.removeChild(this.$el)
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
// MARK: events handler, 如 $_onButtonClick
|
||
$_onInput(val) {
|
||
this.$emit('input', val)
|
||
},
|
||
$_onShow() {
|
||
this.visible = true
|
||
this.$refs.codebox.focus()
|
||
this.$emit('show')
|
||
},
|
||
$_onHide() {
|
||
this.visible = false
|
||
this.$refs.codebox.blur()
|
||
this.$emit('hide')
|
||
},
|
||
$_onSubmit(code) {
|
||
this.$emit('submit', code)
|
||
},
|
||
$_onResend() {
|
||
if (this.autoCountdown) {
|
||
this.countdown()
|
||
}
|
||
this.$emit('send', this.countdown)
|
||
},
|
||
$_emitSend() {
|
||
this.autoSend && this.$_onResend()
|
||
},
|
||
// MARK: public methods
|
||
listenResize() {
|
||
let resizeHeight = document.documentElement.clientHeight || document.body.clientHeight
|
||
if (this.originHeight < resizeHeight) {
|
||
this.isKeyboard = false
|
||
} else {
|
||
this.isKeyboard = true
|
||
}
|
||
this.originHeight = resizeHeight
|
||
},
|
||
// 判断设备类型
|
||
judgeDeviceType() {
|
||
let ua = window.navigator.userAgent.toLocaleLowerCase()
|
||
let isIOS = /iphone|ipad|ipod/.test(ua)
|
||
let isAndroid = /android/.test(ua)
|
||
return {
|
||
isIOS: isIOS,
|
||
isAndroid: isAndroid,
|
||
}
|
||
},
|
||
countdown() {
|
||
if (!this.count) {
|
||
return
|
||
}
|
||
clearInterval(this.__counter__)
|
||
const timestamp = Date.now()
|
||
let i = this.count
|
||
this.isCounting = true
|
||
this.countBtnText = this.countActiveText.replace('{$1}', i)
|
||
/* istanbul ignore next */
|
||
this.__counter__ = setInterval(() => {
|
||
if (i <= 1) {
|
||
this.resetcount()
|
||
} else {
|
||
i = this.count - Math.floor((Date.now() - timestamp) / 1000)
|
||
this.countBtnText = this.countActiveText.replace('{$1}', i)
|
||
}
|
||
}, 1000)
|
||
},
|
||
resetcount() {
|
||
this.isCounting = false
|
||
this.countBtnText = this.countNormalText
|
||
clearInterval(this.__counter__)
|
||
},
|
||
setError(msg) {
|
||
this.$nextTick(() => {
|
||
this.errorMsg = msg
|
||
// this.code = ''
|
||
})
|
||
},
|
||
close() {
|
||
this.$emit('input', false)
|
||
},
|
||
},
|
||
}
|
||
|
||
</script>
|
||
|
||
<style lang="stylus">
|
||
.md-captcha
|
||
.md-dialog
|
||
.md-popup
|
||
z-index captcha-zindex
|
||
.md-dialog-body
|
||
padding 60px 60px 30px 60px
|
||
.md-dialog-content
|
||
margin-bottom number-keyboard-height
|
||
.md-codebox
|
||
margin-bottom 28px
|
||
|
||
.md-captcha-content
|
||
font-size captcha-font-size
|
||
color captcha-color
|
||
text-align center
|
||
line-height 1.2
|
||
margin-bottom 50px
|
||
|
||
.md-captcha-title
|
||
color captcha-title-color
|
||
font-size captcha-title-font-size
|
||
font-weight normal
|
||
line-height 1.15
|
||
margin 0 0 16px 0
|
||
|
||
.md-captcha-footer
|
||
margin 28px 0
|
||
display flex
|
||
font-size captcha-footer-font-size
|
||
justify-content space-between
|
||
align-items center
|
||
overflow hidden
|
||
|
||
.md-captcha-error, .md-captcha-brief
|
||
flex 1 1 0%
|
||
.md-captcha-error
|
||
color captcha-error-color
|
||
.md-captcha-brief
|
||
color captcha-brief-color
|
||
|
||
.md-captcha-btn
|
||
display inline-block
|
||
color captcha-btn-color
|
||
font-size captcha-footer-font-size
|
||
padding 0
|
||
margin 0 0 0 12px
|
||
border 0
|
||
border-radius 0
|
||
background none
|
||
&:disabled
|
||
color color-text-disabled
|
||
// 半屏样式
|
||
.md-captcha-half-container
|
||
background-color #fff
|
||
border-radius 40px 40px 0 0
|
||
.md-popup-title-bar
|
||
height auto !important
|
||
margin-bottom 24px
|
||
.title-bar-title
|
||
.title
|
||
font-weight bold
|
||
line-height 56px
|
||
p.describe
|
||
font-size 24px !important
|
||
color #91989F !important
|
||
line-height 33px !important
|
||
margin-top: 4px !important
|
||
.md-captcha-half-content
|
||
padding 0px 40px
|
||
.md-codebox-wrapper
|
||
.md-codebox
|
||
padding 75px 40px 0px
|
||
margin 0px
|
||
.md-captcha-footer
|
||
padding 32px 40px 745px
|
||
margin 0px
|
||
&.halfStyle
|
||
padding-bottom 40px
|
||
.md-captcha-btn
|
||
padding 5px 16px
|
||
background-color color-primary
|
||
border-radius radius-medium
|
||
color color-text-base-inverse
|
||
&:disabled
|
||
color #61686F
|
||
background none
|
||
border-radius inherit
|
||
padding 10px 0px
|
||
&.is-disabled-send
|
||
background rgba(145,152,159,0.1)
|
||
color #61686F
|
||
</style>
|