blob: 41e071938784f6fccbaaac95b40079e588389baf [file] [log] [blame]
guangchao.xu070005a2020-12-07 09:56:40 +08001<template>
2 <view class="u-char-box">
3 <view class="u-char-flex">
4 <input :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxlength" class="u-input" @input="getVal"/>
5 <view v-for="(item, index) in loopCharArr" :key="index">
6 <view :class="[breathe && charArrLength == index ? 'u-breathe' : '', 'u-char-item',
7 charArrLength === index && mode == 'box' ? 'u-box-active' : '',
8 mode === 'box' ? 'u-box' : '']" :style="{
9 fontWeight: bold ? 'bold' : 'normal',
10 fontSize: fontSize + 'rpx',
11 width: width + 'rpx',
12 height: width + 'rpx',
13 color: inactiveColor,
14 borderColor: charArrLength === index && mode == 'box' ? activeColor : inactiveColor
15 }">
16 <view class="u-placeholder-line" :style="{
17 display: charArrLength === index ? 'block' : 'none',
18 height: width * 0.5 +'rpx'
19 }"
20 v-if="mode !== 'middleLine'"
21 ></view>
22 <view v-if="mode === 'middleLine' && charArrLength <= index" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-middle-line-active' : '']"
23 class="u-middle-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view>
24 <view v-if="mode === 'bottomLine'" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-buttom-line-active' : '']"
25 class="u-bottom-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view>
26 <block v-if="!dotFill"> {{ charArr[index] ? charArr[index] : ''}}</block>
27 <block v-else>
28 <text class="u-dot">{{ charArr[index] ? '●' : ''}}</text>
29 </block>
30 </view>
31 </view>
32 </view>
33 </view>
34</template>
35
36<script>
37 /**
38 * messageInput 验证码输入框
39 * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用
40 * @tutorial https://www.uviewui.com/components/messageInput.html
41 * @property {String Number} maxlength 输入字符个数(默认4)
42 * @property {Boolean} dot-fill 是否用圆点填充(默认false)
43 * @property {String} mode 模式选择,见上方"基本使用"说明(默认box)
44 * @property {String Number} value 预置值
45 * @property {Boolean} breathe 是否开启呼吸效果,见上方说明(默认true)
46 * @property {Boolean} focus 是否自动获取焦点(默认false)
47 * @property {Boolean} bold 字体和输入横线是否加粗(默认true)
48 * @property {String Number} font-size 字体大小,单位rpx(默认60)
49 * @property {String} active-color 当前激活输入框的样式(默认#2979ff)
50 * @property {String} inactive-color 非激活输入框的样式,文字颜色同此值(默认#606266)
51 * @property {String | Number} width 输入框宽度,单位rpx,高等于宽(默认80)
52 * @property {Boolean} disabled-keyboard 禁止点击输入框唤起系统键盘(默认false)
53 * @event {Function} change 输入内容发生改变时触发,具体见官网说明
54 * @event {Function} finish 输入字符个数达maxlength值时触发,见官网说明
55 * @example <u-message-input mode="bottomLine"></u-message-input>
56 */
57 export default {
58 name: "u-message-input",
59 props: {
60 // 最大输入长度
61 maxlength: {
62 type: [Number, String],
63 default: 4
64 },
65 // 是否用圆点填充
66 dotFill: {
67 type: Boolean,
68 default: false
69 },
70 // 显示模式,box-盒子模式,bottomLine-横线在底部模式,middleLine-横线在中部模式
71 mode: {
72 type: String,
73 default: "box"
74 },
75 // 预置值
76 value: {
77 type: [String, Number],
78 default: ''
79 },
80 // 当前激活输入item,是否带有呼吸效果
81 breathe: {
82 type: Boolean,
83 default: true
84 },
85 // 是否自动获取焦点
86 focus: {
87 type: Boolean,
88 default: false
89 },
90 // 字体是否加粗
91 bold: {
92 type: Boolean,
93 default: false
94 },
95 // 字体大小
96 fontSize: {
97 type: [String, Number],
98 default: 60
99 },
100 // 激活样式
101 activeColor: {
102 type: String,
103 default: '#2979ff'
104 },
105 // 未激活的样式
106 inactiveColor: {
107 type: String,
108 default: '#606266'
109 },
110 // 输入框的大小,单位rpx,宽等于高
111 width: {
112 type: [Number, String],
113 default: '80'
114 },
115 // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
116 disabledKeyboard: {
117 type: Boolean,
118 default: false
119 }
120 },
121 watch: {
122 // maxlength: {
123 // // 此值设置为true,会在组件加载后无需maxlength变化就会执行一次本监听函数,无需再created生命周期中处理
124 // immediate: true,
125 // handler(val) {
126 // this.maxlength = Number(val);
127 // }
128 // },
129 value: {
130 immediate: true,
131 handler(val) {
132 // 转为字符串
133 val = String(val);
134 // 超出部分截掉
135 this.valueModel = val.substring(0, this.maxlength);
136 }
137 },
138 },
139 data() {
140 return {
141 valueModel: ""
142 }
143 },
144 computed: {
145 // 是否显示呼吸灯效果
146 animationClass() {
147 return (index) => {
148 if (this.breathe && this.charArr.length == index) return 'u-breathe';
149 else return '';
150 }
151 },
152 // 用于显示字符
153 charArr() {
154 return this.valueModel.split('');
155 },
156 charArrLength() {
157 return this.charArr.length;
158 },
159 // 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
160 loopCharArr() {
161 return new Array(this.maxlength);
162 }
163 },
164 methods: {
165 getVal(e) {
166 let {
167 value
168 } = e.detail
169 this.valueModel = value;
170 // 判断长度是否超出了maxlength值,理论上不会发生,因为input组件设置了maxlength属性值
171 if (String(value).length > this.maxlength) return;
172 // 未达到maxlength之前,发送change事件,达到后发送finish事件
173 this.$emit('change', value);
174 if (String(value).length == this.maxlength) {
175 this.$emit('finish', value);
176 }
177 }
178 }
179 }
180</script>
181
182<style scoped lang="scss">
183 @import "../../libs/css/style.components.scss";
184
185 @keyframes breathe {
186 0% {
187 opacity: 0.3;
188 }
189
190 50% {
191 opacity: 1;
192 }
193
194 100% {
195 opacity: 0.3;
196 }
197 }
198
199 .u-char-box {
200 text-align: center;
201 }
202
203 .u-char-flex {
204 @include vue-flex;
205 justify-content: center;
206 flex-wrap: wrap;
207 position: relative;
208 }
209
210 .u-input {
211 position: absolute;
212 top: 0;
213 left: -100%;
214 width: 200%;
215 height: 100%;
216 text-align: left;
217 z-index: 9;
218 opacity: 0;
219 background: none;
220 }
221
222 .u-char-item {
223 position: relative;
224 width: 90rpx;
225 height: 90rpx;
226 margin: 10rpx 10rpx;
227 font-size: 60rpx;
228 font-weight: bold;
229 color: $u-main-color;
230 line-height: 90rpx;
231 @include vue-flex;
232 justify-content: center;
233 align-items: center;
234 }
235
236 .u-middle-line {
237 border: none;
238 }
239
240 .u-box {
241 box-sizing: border-box;
242 border: 2rpx solid #cccccc;
243 border-radius: 6rpx;
244 }
245
246 .u-box-active {
247 overflow: hidden;
248 animation-timing-function: ease-in-out;
249 animation-duration: 1500ms;
250 animation-iteration-count: infinite;
251 animation-direction: alternate;
252 border: 2rpx solid $u-type-primary;
253 }
254
255 .u-middle-line-active {
256 background: $u-type-primary;
257 }
258
259 .u-breathe {
260 animation: breathe 2s infinite ease;
261 }
262
263 .u-placeholder-line {
264 /* #ifndef APP-NVUE */
265 display: none;
266 /* #endif */
267 position: absolute;
268 left: 50%;
269 top: 50%;
270 transform: translate(-50%, -50%);
271 width: 2rpx;
272 height: 40rpx;
273 background: #333333;
274 animation: twinkling 1.5s infinite ease;
275 }
276
277 .u-animation-breathe {
278 animation-name: breathe;
279 }
280
281 .u-dot {
282 font-size: 34rpx;
283 line-height: 34rpx;
284 }
285
286 .u-middle-line {
287 height: 4px;
288 background: #000000;
289 width: 80%;
290 position: absolute;
291 border-radius: 2px;
292 top: 50%;
293 left: 50%;
294 transform: translate(-50%, -50%);
295 }
296
297 .u-buttom-line-active {
298 background: $u-type-primary;
299 }
300
301 .u-bottom-line {
302 height: 4px;
303 background: #000000;
304 width: 80%;
305 position: absolute;
306 border-radius: 2px;
307 bottom: 0;
308 left: 50%;
309 transform: translate(-50%);
310 }
311</style>