hookehuyr

✨ feat(手机验证码组件): 提交表单数据后,校验后弹框手机号绑定

...@@ -24,6 +24,7 @@ declare module '@vue/runtime-core' { ...@@ -24,6 +24,7 @@ declare module '@vue/runtime-core' {
24 GroupField: typeof import('./src/components/GroupField/index.vue')['default'] 24 GroupField: typeof import('./src/components/GroupField/index.vue')['default']
25 IdentityField: typeof import('./src/components/IdentityField/index.vue')['default'] 25 IdentityField: typeof import('./src/components/IdentityField/index.vue')['default']
26 ImageUploaderField: typeof import('./src/components/ImageUploaderField/index.vue')['default'] 26 ImageUploaderField: typeof import('./src/components/ImageUploaderField/index.vue')['default']
27 + LoginBox: typeof import('./src/components/LoginBox/index.vue')['default']
27 MarqueeField: typeof import('./src/components/MarqueeField/index.vue')['default'] 28 MarqueeField: typeof import('./src/components/MarqueeField/index.vue')['default']
28 MultiRuleField: typeof import('./src/components/MultiRuleField/index.vue')['default'] 29 MultiRuleField: typeof import('./src/components/MultiRuleField/index.vue')['default']
29 MyComponent: typeof import('./src/components/AppointmentField/MyComponent.vue')['default'] 30 MyComponent: typeof import('./src/components/AppointmentField/MyComponent.vue')['default']
......
1 +<template>
2 + <div class="login-section">
3 + <van-config-provider :theme-vars="themeVars">
4 + <van-form ref="form" @submit="onSubmit">
5 + <van-cell-group inset style="border: 1px solid #EAEAEA;">
6 + <van-field v-if="use_widget" v-model="phone" name="phone" label="手机号" placeholder="手机号" readonly clickable
7 + :rules="[{ validator, message: '请输入正确手机号' }]"
8 + @touchstart.stop="showKeyboard" />
9 + <van-field v-else v-model="phone" name="phone" label="手机号" placeholder="手机号"
10 + :rules="[{ validator, message: '请输入正确手机号' }]" />
11 + <van-field v-model="code" center clearable name="code" type="digit" label="短信验证码" placeholder="请输入短信验证码"
12 + :formatter="formatter" :rules="[{ required: true, message: '请填写验证码' }]">
13 + <template #button>
14 + <van-button @click="sendCode" v-if="countDown.current.value.total === limit" size="small" type="primary"
15 + :disabled="disabled">
16 + <span>发送验证码</span>
17 + </van-button>
18 + <van-button v-else size="small" type="primary" :disabled="disabled">
19 + <span>{{ countDown.current.value.seconds }} 秒重新发送</span>
20 + </van-button>
21 + </template>
22 + </van-field>
23 + </van-cell-group>
24 + </van-form>
25 + </van-config-provider>
26 + </div>
27 +
28 + <van-number-keyboard v-model="phone" :show="keyboard_show" :maxlength="11" @blur="onBlur" />
29 +</template>
30 +
31 +<script setup>
32 +
33 +import { ref, onMounted } from 'vue'
34 +import { useRoute, useRouter } from 'vue-router'
35 +import { useCountDown } from '@vant/use';
36 +import { wxInfo } from '@/utils/tools';
37 +import { styleColor } from '@/constant.js';
38 +
39 +import { Cookies, $, _, axios, storeToRefs, mainStore, Toast, useTitle } from '@/utils/generatePackage.js'
40 +//import { } from '@/utils/generateModules.js'
41 +//import { } from '@/utils/generateIcons.js'
42 +//import { } from '@/composables'
43 +const $route = useRoute();
44 +const $router = useRouter();
45 +
46 +const emit = defineEmits(['on-submit'])
47 +
48 +const form = ref(null);
49 +
50 +const submit = () => {
51 + let valid = form.value.validate();
52 + valid
53 + .then(() => {
54 + form.value.submit();
55 + })
56 + .catch(error => {
57 + console.error(error);
58 + Toast({
59 + message: '请检查后再次提交',
60 + icon: 'cross',
61 + });
62 + })
63 +}
64 +
65 +defineExpose({
66 + submit
67 +})
68 +
69 +const themeVars = {
70 + buttonPrimaryBackground: styleColor.baseColor,
71 + buttonPrimaryBorderColor: styleColor.baseColor,
72 + buttonPrimaryColor: styleColor.baseFontColor,
73 + CellVerticalPadding: '14px'
74 +};
75 +
76 +const onSubmit = () => {
77 + emit('on-submit', {
78 + phone: phone.value,
79 + code: code.value,
80 + })
81 +}
82 +
83 +// 判断是否显示控件
84 +let use_widget = ref(true);
85 +/**
86 + * 手机号码校验
87 + * 函数返回 true 表示校验通过,false 表示不通过
88 + * @param {*} val
89 + */
90 +const validator = (val) => {
91 + let flag = false;
92 + // 简单判断手机号位数
93 + if (/1\d{10}/.test(val) && phone.value.length === 11) {
94 + disabled.value = false;
95 + flag = true;
96 + } else {
97 + disabled.value = true;
98 + flag = false;
99 + }
100 + return flag
101 +};
102 +
103 +
104 +const phone = ref('');
105 +const code = ref('');
106 +// TAG: 开发环境测试数据
107 +if (import.meta.env.DEV) {
108 + phone.value = import.meta.env.VITE_ID
109 + code.value = import.meta.env.VITE_PIN
110 +}
111 +
112 +onMounted(() => {
113 + /**
114 + * 判断微信环境看是否弹出控件框
115 + * 桌面微信直接输入
116 + * 其他环境弹出输入框
117 + */
118 + if (wxInfo().isiOS || wxInfo().isAndroid) {
119 + use_widget.value = true;
120 + } else {
121 + use_widget.value = false;
122 + }
123 +})
124 +
125 +// 手机号输入控件控制
126 +const keyboard_show = ref(false);
127 +const showKeyboard = () => { // 弹出数字弹框
128 + keyboard_show.value = true;
129 +};
130 +const onBlur = () => { // 数字键盘失焦回调
131 + keyboard_show.value = false;
132 + if (phone.value.length === 11) {
133 + disabled.value = false;
134 + }
135 +};
136 +
137 +// 设置发送短信倒计时
138 +// TAG: vant 自带倒计时函数
139 +const limit = ref(60000); // 配置倒计时秒数
140 +const countDown = useCountDown({
141 + // 倒计时 24 小时
142 + time: limit.value,
143 + onFinish: () => {
144 + countDown.reset();
145 + }
146 +});
147 +
148 +const sendCode = () => { // 发送验证码
149 + countDown.start();
150 + axios.post('/srv/?a=bind_phone&t=get_code', {
151 + phone: phone.value
152 + })
153 + .then(res => {
154 + if (res.data.code === 1) {
155 + Toast.success('发送成功');
156 + } else {
157 + console.warn(res.data);
158 + if (!res.data.show) return false;
159 + Toast({
160 + message: res.data.msg,
161 + icon: 'close',
162 + });
163 + }
164 + })
165 + .catch(err => {
166 + console.error(err);
167 + })
168 +};
169 +
170 +const disabled = ref(true);
171 +// 过滤输入的数字 只能四位
172 +const formatter = (value) => value.substring(0, 4);
173 +
174 +</script>
175 +
176 +<style lang="less" scoped>
177 +</style>
1 +<template>
2 + <login-box ref="form" @on-submit="onSubmit"></login-box>
3 + <div class="btn" @click="submit">
4 + 登&nbsp;录
5 + </div>
6 +</template>
7 +
8 +<script setup>
9 +import LoginBox from '@/components/LoginBox'
10 +import { onMounted, ref } from 'vue';
11 +import { useRoute, useRouter } from 'vue-router';
12 +
13 +import {
14 + Cookies,
15 + $,
16 + _,
17 + axios,
18 + storeToRefs,
19 + mainStore,
20 + Toast,
21 + useTitle,
22 +} from '@/utils/generatePackage.js';
23 +//import { } from '@/utils/generateModules.js'
24 +//import { } from '@/utils/generateIcons.js'
25 +//import { } from '@/composables'
26 +const $route = useRoute();
27 +const $router = useRouter();
28 +useTitle($route.meta.title);
29 +
30 +const form = ref(null);
31 +
32 +const submit = () => {
33 + form.value.submit();
34 +}
35 +
36 +const onSubmit = (values) => {
37 + axios.post('/srv/?a=b_login', {
38 + phone: values.phone,
39 + pin: values.code,
40 + })
41 + .then(res => {
42 + if (res.data.code === 1) {
43 + $router.push({
44 + path: '/business/index'
45 + });
46 + } else {
47 + console.warn(res.data);
48 + Toast({
49 + message: res.data.msg,
50 + icon: 'close',
51 + });
52 + }
53 + })
54 + .catch(err => {
55 + console.error(err);
56 + })
57 +};
58 +</script>
59 +
60 +<style
61 + lang="less"
62 + scoped>
63 +</style>
1 <!-- 1 <!--
2 * @Date: 2022-07-18 10:22:22 2 * @Date: 2022-07-18 10:22:22
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2024-06-24 16:30:22 4 + * @LastEditTime: 2024-06-26 10:29:41
5 * @FilePath: /data-table/src/views/index.vue 5 * @FilePath: /data-table/src/views/index.vue
6 * @Description: 首页 6 * @Description: 首页
7 --> 7 -->
...@@ -113,6 +113,20 @@ ...@@ -113,6 +113,20 @@
113 close-on-click-action 113 close-on-click-action
114 @cancel="onApprovalCancel" 114 @cancel="onApprovalCancel"
115 /> 115 />
116 +
117 + <van-overlay :show="bind_tel_show" z-index="100">
118 + <div class="bind-tel-wrapper">
119 + <div class="block">
120 + <div style="text-align: center; padding: 1rem; padding-top: 0; font-weight: bold;">手机号绑定</div>
121 + <login-box ref="bindForm" @on-submit="onBindSubmit"></login-box>
122 + <div style="padding: 1rem; padding-bottom: 0;">
123 + <van-button round block type="primary" :color="styleColor.baseColor" @click="bindSubmit">
124 + 绑&nbsp;定
125 + </van-button>
126 + </div>
127 + </div>
128 + </div>
129 + </van-overlay>
116 </template> 130 </template>
117 131
118 <script setup> 132 <script setup>
...@@ -131,11 +145,12 @@ import { ...@@ -131,11 +145,12 @@ import {
131 import { useRoute } from "vue-router"; 145 import { useRoute } from "vue-router";
132 import { queryFormAPI, postVerifyPasswordAPI } from "@/api/form.js"; 146 import { queryFormAPI, postVerifyPasswordAPI } from "@/api/form.js";
133 import { addFormDataAPI, queryFormDataAPI, modiFormDataAPI } from "@/api/data.js"; 147 import { addFormDataAPI, queryFormDataAPI, modiFormDataAPI } from "@/api/data.js";
134 -import { showSuccessToast, showFailToast } from "vant"; 148 +import { showSuccessToast, showFailToast, showConfirmDialog } from "vant";
135 import { wxInfo, getUrlParams } from "@/utils/tools"; 149 import { wxInfo, getUrlParams } from "@/utils/tools";
136 import { styleColor } from "@/constant.js"; 150 import { styleColor } from "@/constant.js";
137 import { sharePage } from '@/composables/useShare.js' 151 import { sharePage } from '@/composables/useShare.js'
138 import wx from 'weixin-js-sdk' 152 import wx from 'weixin-js-sdk'
153 +import LoginBox from '@/components/LoginBox/index.vue';
139 154
140 // 获取表单设置 155 // 获取表单设置
141 const store = mainStore(); 156 const store = mainStore();
...@@ -259,6 +274,53 @@ const onApprovalCancel = () => { ...@@ -259,6 +274,53 @@ const onApprovalCancel = () => {
259 274
260 } 275 }
261 276
277 +// TODO: 等待调试发送短信接口
278 +const bind_tel_show = ref(false);
279 +// setTimeout(() => {
280 +// showConfirmDialog({
281 +// title: '温馨提示',
282 +// message:
283 +// '您还没有绑定手机号,是否去绑定?',
284 +// confirmButtonColor: styleColor.baseColor
285 +// })
286 +// .then(() => {
287 +// // on confirm
288 +// bind_tel_show.value = true;
289 +// })
290 +// .catch(() => {
291 +// // on cancel
292 +// });
293 +// }, 2000);
294 +
295 +const bindForm = ref(null);
296 +
297 +const bindSubmit = () => {
298 + bindForm.value.submit();
299 +}
300 +
301 +const onBindSubmit = (values) => {
302 + axios.post('/srv/?a=b_login', {
303 + phone: values.phone,
304 + pin: values.code,
305 + })
306 + .then(res => {
307 + if (res.data.code === 1) {
308 + $router.push({
309 + path: '/business/index'
310 + });
311 + } else {
312 + console.warn(res.data);
313 + Toast({
314 + message: res.data.msg,
315 + icon: 'close',
316 + });
317 + }
318 + })
319 + .catch(err => {
320 + console.error(err);
321 + })
322 +};
323 +
262 onMounted(async () => { 324 onMounted(async () => {
263 // TAG: 全局背景色 325 // TAG: 全局背景色
264 document 326 document
...@@ -1024,6 +1086,20 @@ const onSubmit = async (values) => { ...@@ -1024,6 +1086,20 @@ const onSubmit = async (values) => {
1024 } 1086 }
1025 } 1087 }
1026 1088
1089 +.bind-tel-wrapper {
1090 + display: flex;
1091 + align-items: center;
1092 + justify-content: center;
1093 + height: 100%;
1094 +
1095 + .block {
1096 + width: 80vw;
1097 + background-color: #fff;
1098 + padding: 1rem;
1099 + border-radius: 5px;
1100 + }
1101 +}
1102 +
1027 .PHeader-Text { 1103 .PHeader-Text {
1028 padding: 1rem; 1104 padding: 1rem;
1029 font-weight: bold; 1105 font-weight: bold;
......