| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- <template>
- <view>
- <view class="form_item">
- <view class="form_item_key font-bold" :class="{ 'require': required }">{{ phoneLabel }}</view>
- <view class="form_item_val">
- <input
- class="input"
- type="number"
- :disabled="readOnly"
- placeholder="请输入手机号"
- :value="phoneValue"
- @input="onPhoneInputChange"
- />
- </view>
- </view>
- <view class="form_item">
- <view class="form_item_key font-bold" :class="{ 'require': required }">{{ codeLabel }}</view>
- <view class="form_item_val">
- <input
- class="input"
- type="number"
- placeholder="请输入验证码"
- :value="codeValue"
- @input="onCodeInputChange"
- />
- </view>
- <button
- class="img_captcha"
- @click="sendCode"
- :disabled="isCountingDown"
- >
- {{ isCountingDown ? countdownFormatter(countdownText, countdown) : sendCodeText }}
- </button>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { defineProps, defineEmits, ref, defineExpose } from 'vue';
- const props = defineProps({
- phoneLabel: {
- type: String,
- default: '手机号'
- },
- codeLabel: {
- type: String,
- default: '验证码'
- },
- sendCodeText: {
- type: String,
- default: '发送验证码'
- },
- countdownText: {
- type: String,
- default: '重新发送(%s)s'
- },
- readOnly: {
- type: Boolean,
- default: false
- },
- required: {
- type: Boolean,
- default: false
- },
- errorMsg: {
- type: String,
- default: '输入不合法'
- },
- phoneValue: {
- type: String,
- default: '',
- },
- codeValue: {
- type: String,
- default: ''
- },
- });
- const emit = defineEmits(['phoneInputChange', 'codeInputChange', 'sendCodeRequest', 'update:phoneValue', 'update:codeValue']);
- const isCountingDown = ref(false);
- const countdown = ref(0);
- const phoneError = ref(false);
- const codeError = ref(false);
- const onPhoneInputChange = (e: any) => {
- const val = e.detail.value;
- emit('phoneInputChange', { phoneValue: val });
- emit('update:phoneValue', val);
- };
- const onCodeInputChange = (e: any) => {
- const val = e.detail.value;
- emit('codeInputChange', { codeValue: val });
- emit('update:codeValue', val);
- };
- const sendCode = () => {
- emit('sendCodeRequest', { phoneValue: props.phoneValue });
- };
- const startCountdown = () => {
- let cd = 60;
- isCountingDown.value = true;
- countdown.value = cd;
-
- const timer = setInterval(() => {
- cd--;
- if (cd <= 0) {
- clearInterval(timer);
- isCountingDown.value = false;
- countdown.value = 0;
- } else {
- countdown.value = cd;
- }
- }, 1000);
- };
- const handleValidationResult = (e: any) => {
- const { key, isValid } = e.detail || e;
- if (key === 'phone') {
- phoneError.value = !isValid;
- } else if (key === 'code') {
- codeError.value = !isValid;
- }
- };
- const countdownFormatter = (text: string, count: number) => {
- if (!text) return "";
- return text.replace(/%s/g, count.toString());
- };
- defineExpose({
- startCountdown,
- handleValidationResult
- });
- </script>
- <style>
- /* 表单 start */
- .form_item {
- position: relative;
- width: inherit;
- padding: 0 10upx;
- font-size: 30upx;
- height: 120upx;
- display: flex;
- align-items: center;
- }
- .form_item + .form_item {
- border-top: 2upx solid #e5e5e5;
- }
- .form_item .form_item_key {
- padding-left: 20upx;
- position: relative;
- width: 220upx;
- }
- .form_item .form_item_val {
- flex: 1 0 0;
- text-align: right;
- height: inherit;
- padding: 0 20upx;
- }
- .form_item .form_item_val .input {
- height: inherit;
- }
- .img_captcha {
- width: 180upx !important;
- height: 70%;
- min-width: 80upx;
- background-color: var(--dominantColor);
- color: #fff;
- font-size: 30upx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .require::after {
- content: '*';
- position: absolute;
- color: #FF1D1F;
- top: 10%;
- left: 0;
- }
- /* 表单 end */
- .font-bold {
- font-weight: bold;
- }
- </style>
|