index.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <template>
  2. <view>
  3. <view class="form_item">
  4. <view class="form_item_key font-bold" :class="{ 'require': required }">{{ phoneLabel }}</view>
  5. <view class="form_item_val">
  6. <input
  7. class="input"
  8. type="number"
  9. :disabled="readOnly"
  10. placeholder="请输入手机号"
  11. :value="phoneValue"
  12. @input="onPhoneInputChange"
  13. />
  14. </view>
  15. </view>
  16. <view class="form_item">
  17. <view class="form_item_key font-bold" :class="{ 'require': required }">{{ codeLabel }}</view>
  18. <view class="form_item_val">
  19. <input
  20. class="input"
  21. type="number"
  22. placeholder="请输入验证码"
  23. :value="codeValue"
  24. @input="onCodeInputChange"
  25. />
  26. </view>
  27. <button
  28. class="img_captcha"
  29. @click="sendCode"
  30. :disabled="isCountingDown"
  31. >
  32. {{ isCountingDown ? countdownFormatter(countdownText, countdown) : sendCodeText }}
  33. </button>
  34. </view>
  35. </view>
  36. </template>
  37. <script setup lang="ts">
  38. import { defineProps, defineEmits, ref, defineExpose } from 'vue';
  39. const props = defineProps({
  40. phoneLabel: {
  41. type: String,
  42. default: '手机号'
  43. },
  44. codeLabel: {
  45. type: String,
  46. default: '验证码'
  47. },
  48. sendCodeText: {
  49. type: String,
  50. default: '发送验证码'
  51. },
  52. countdownText: {
  53. type: String,
  54. default: '重新发送(%s)s'
  55. },
  56. readOnly: {
  57. type: Boolean,
  58. default: false
  59. },
  60. required: {
  61. type: Boolean,
  62. default: false
  63. },
  64. errorMsg: {
  65. type: String,
  66. default: '输入不合法'
  67. },
  68. phoneValue: {
  69. type: String,
  70. default: '',
  71. },
  72. codeValue: {
  73. type: String,
  74. default: ''
  75. },
  76. });
  77. const emit = defineEmits(['phoneInputChange', 'codeInputChange', 'sendCodeRequest', 'update:phoneValue', 'update:codeValue']);
  78. const isCountingDown = ref(false);
  79. const countdown = ref(0);
  80. const phoneError = ref(false);
  81. const codeError = ref(false);
  82. const onPhoneInputChange = (e: any) => {
  83. const val = e.detail.value;
  84. emit('phoneInputChange', { phoneValue: val });
  85. emit('update:phoneValue', val);
  86. };
  87. const onCodeInputChange = (e: any) => {
  88. const val = e.detail.value;
  89. emit('codeInputChange', { codeValue: val });
  90. emit('update:codeValue', val);
  91. };
  92. const sendCode = () => {
  93. emit('sendCodeRequest', { phoneValue: props.phoneValue });
  94. };
  95. const startCountdown = () => {
  96. let cd = 60;
  97. isCountingDown.value = true;
  98. countdown.value = cd;
  99. const timer = setInterval(() => {
  100. cd--;
  101. if (cd <= 0) {
  102. clearInterval(timer);
  103. isCountingDown.value = false;
  104. countdown.value = 0;
  105. } else {
  106. countdown.value = cd;
  107. }
  108. }, 1000);
  109. };
  110. const handleValidationResult = (e: any) => {
  111. const { key, isValid } = e.detail || e;
  112. if (key === 'phone') {
  113. phoneError.value = !isValid;
  114. } else if (key === 'code') {
  115. codeError.value = !isValid;
  116. }
  117. };
  118. const countdownFormatter = (text: string, count: number) => {
  119. if (!text) return "";
  120. return text.replace(/%s/g, count.toString());
  121. };
  122. defineExpose({
  123. startCountdown,
  124. handleValidationResult
  125. });
  126. </script>
  127. <style>
  128. /* 表单 start */
  129. .form_item {
  130. position: relative;
  131. width: inherit;
  132. padding: 0 10upx;
  133. font-size: 30upx;
  134. height: 120upx;
  135. display: flex;
  136. align-items: center;
  137. }
  138. .form_item + .form_item {
  139. border-top: 2upx solid #e5e5e5;
  140. }
  141. .form_item .form_item_key {
  142. padding-left: 20upx;
  143. position: relative;
  144. width: 220upx;
  145. }
  146. .form_item .form_item_val {
  147. flex: 1 0 0;
  148. text-align: right;
  149. height: inherit;
  150. padding: 0 20upx;
  151. }
  152. .form_item .form_item_val .input {
  153. height: inherit;
  154. }
  155. .img_captcha {
  156. width: 180upx !important;
  157. height: 70%;
  158. min-width: 80upx;
  159. background-color: var(--dominantColor);
  160. color: #fff;
  161. font-size: 30upx;
  162. display: flex;
  163. align-items: center;
  164. justify-content: center;
  165. }
  166. .require::after {
  167. content: '*';
  168. position: absolute;
  169. color: #FF1D1F;
  170. top: 10%;
  171. left: 0;
  172. }
  173. /* 表单 end */
  174. .font-bold {
  175. font-weight: bold;
  176. }
  177. </style>