Vue3.0
组件库之输入框组件
从零搭建一.设计输入框属性
modelValue: { type: [String, Number], default: "" }, // v-model绑定的值
type: { type: String, default: "text" }, // 当前input类型 password text
placeholder: { type: String }, // 输入提示
disabled: { type: Boolean, default: false }, // 是否禁用
readonly: { type: Boolean, default: false }, // 是否仅读
clearable: { type: Boolean }, // 是否带有清空按钮
showPassword: { type: Boolean, default: false }, // 密码框是否显示密码
suffixIcon: { type: String, default: "" }, // 前icon
prefixIcon: { type: String, default: "" }, // 后icon
label: { type: String }, // input配合的label属性
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
二.设计输入框结构
<div :class="classs">
<!-- 前元素 -->
<div v-if="$slots.prepend" class="z-input-group__prepend">
<slot name="prepend"></slot>
</div>
<!-- 核心input -->
<input
:type="showPassword ? (passwordVisible ? 'text' : 'password') : type"
ref="input"
class="z-input__inner"
:disabled="disabled"
:readonly="readonly"
v-bind="attrs"
:placeholder="placeholder"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
@keydown="handleKeydown"
/>
<span v-if="prefixIcon" class="z-input__prefix">
<i :class="prefixIcon"></i>
</span>
<span v-if="suffixIcon" class="z-input__suffix">
<!-- 第一种清空 不是清空 同时没有显示密码的 -->
<i :class="suffixIcon" v-if="!showClear && !showPwdVisible"></i>
<i
v-if="showClear"
class="z-icon-delete"
@click="clear"
@mousedown.prevent
></i>
<i
v-if="showPwdVisible"
class="z-icon-eye-close"
@click="handlePasswordVisible"
></i>
</span>
<!-- 后元素 -->
<div v-if="$slots.append" class="z-input-group__append">
<slot name="append"></slot>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
1.表单样式
const classs = computed(() => [
"z-input",
{
"z-input-group": ctx.slots.prepend || ctx.slots.append,
"z-input--prefix": props.prefixIcon,
"z-input--suffix":
props.suffixIcon || props.clearable || props.showPassword,
},
]);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2.设置输入框值及更新icon位置
const input = ref<HTMLInputElement>(null);
const setNativeInputValue = () => {
const ele = input.value;
ele.value = String(props.modelValue);
};
const calcIconOffset = (place) => {
const { el } = instance.vnode;
let ele = el.querySelector(".z-input__" + place);
if (!ele) return;
const pendent = PENDANT_MAP[place];
if (ctx.slots[pendent]) {
ele.style.transform = `translateX(${place === "suffix" ? "-" : ""}${
el.querySelector(`.z-input-group__${pendent}`).offsetWidth
}px)`;
}
// 将前后icon 移动到 前位置或者后位置
};
onMounted(() => {
setNativeInputValue(); // 2.设置输入框的值
updateIconOffset(); // 3.更新icon位置
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
input
组件样式设定
3.@import "mixins/mixins";
@import "common/var";
// 正常z-input
@include b(input) {
// z-input
position: relative;
display: inline-block;
width: 100%;
@include e(suffix) {
position: absolute;
top: 0;
right: 5px;
}
@include m(suffix) { // 有icon 加padding
.z-input__inner {
padding-right: 30px;
}
}
@include m(prefix) {
.z-input__inner {
padding-left: 30px;
}
}
@include e(inner) { // 输入框 无样式 并且border-box
box-sizing: border-box;
-webkit-appearance: none;
outline: none;
width: 100%;
}
@include e(prefix) {
position: absolute;
left: 5px;
top: 0;
}
} // z-input__suffix
// 增加前后元素 变成网格 让前后元素 跑到一行去
@include b(input-group) {
display: inline-table;
.z-input__inner {
vertical-align: middle;
display: table-cell;
}
@include e(append) {
width: 1px;
display: table-cell;
white-space: nowrap;
padding: 0 15px;
}
@include e(prepend) {
width: 1px;
display: table-cell;
white-space: nowrap;
padding: 0 15px;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
4.icon显示设置
const showClear = computed( // 显示清除按钮
() =>
props.clearable &&
!props.readonly &&
!props.disabled &&
props.modelValue
);
const showPwdVisible = computed( // 显示密码按钮
() =>
props.showPassword &&
!props.disabled &&
!props.readonly &&
props.modelValue
);
const passwordVisible = ref(false);
const focus = () => {
nextTick(() => { // 切换后再次获取焦点
input.value.focus();
});
};
const handlePasswordVisible = () => { // 切换输入框显示内容
passwordVisible.value = !passwordVisible.value;
focus();
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
5.事件处理
const clear = () => { // 清除实现
ctx.emit("update:modelValue", "");
ctx.emit("change", "");
ctx.emit("clear");
};
const handleInput = (e) => { // 输入事件
let v = e.target.value;
ctx.emit("update:modelValue", v);
ctx.emit("input", v);
};
const handleFocus = (event) => { // 获取焦点
ctx.emit("focus", event);
};
const handleBlur = (event) => { // 处理失去焦点
ctx.emit("blur", event);
zformItem.formItemMitt?.emit("z.form.blur", [props.modelValue]);
};
const handleChange = (event) => { // 处理事件变化
ctx.emit("change", event.target.value);
};
const handleKeydown = (e) => { // 处理键盘
ctx.emit("keydown", e);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
6.监控值变化更新输入框中内容
watch(
() => props.modelValue,
(val) => {
setNativeInputValue();
}
);
1
2
3
4
5
6
2
3
4
5
6
三.响应式attr实现
const instance = getCurrentInstance();
const attrs = ref({});
instance.attrs = reactive(instance.attrs);
watchEffect(() => {
// 监控attrs的变化 重新做赋值操作
const rest = {};
Object.entries(instance.attrs).forEach(([key, value]) => {
rest[key] = value;
});
attrs.value = rest;
});
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12