alipay-emulator/components/common/auto-width-input.vue

149 lines
3.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="auto-width-input-container" :class="{ 'is-textarea': type === 'textarea' }">
<!-- 测量层用于计算宽度的影藏文本必须保持与 input 相同的字体样式 -->
<text v-if="type !== 'textarea'" class="measure-text" :style="[inputStyle, { visibility: 'hidden', position: 'absolute', whiteSpace: 'nowrap' }]">
{{ modelValue || placeholder }}
</text>
<!-- 输入层 -->
<template v-if="type !== 'textarea'">
<input class="auto-input" :type="type" :value="modelValue" :placeholder="placeholder" :placeholder-style="placeholderStyle"
:style="[inputStyle, { width: inputWidth }]" @input="onInput" :maxlength="maxlength" />
</template>
<template v-else>
<textarea class="auto-textarea" :value="modelValue" :placeholder="placeholder" :placeholder-style="placeholderStyle"
:style="[inputStyle]" @input="onInput" :maxlength="maxlength" auto-height />
</template>
</view>
</template>
<script setup>
import { ref, computed, watch, nextTick, getCurrentInstance, onMounted } from 'vue';
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'text' // 支持所有原生类型如 'number', 'tel', 'digit' 或 'textarea'
},
placeholder: {
type: String,
default: '请输入'
},
placeholderStyle: {
type: String,
default: 'color: #999;'
},
fontSize: {
type: String,
default: '28rpx'
},
fontWeight: {
type: String,
default: 'normal'
},
color: {
type: String,
default: '#1A1A1A'
},
maxlength: {
type: Number,
default: 140
},
minWidth: {
type: String,
default: '20rpx'
},
extraWidth: {
type: Number,
default: 10 // 额外的缓冲像素,防止文字抖动
}
});
const emit = defineEmits(['update:modelValue', 'change']);
const instance = getCurrentInstance();
const inputWidth = ref(props.minWidth);
const inputStyle = computed(() => ({
fontSize: props.fontSize,
fontWeight: props.fontWeight,
color: props.color,
fontFamily: 'inherit'
}));
/**
* 核心逻辑:测量隐藏文本的物理宽度
*/
const updateWidth = () => {
if (props.type === 'textarea') return;
nextTick(() => {
const query = uni.createSelectorQuery().in(instance.proxy);
query.select('.measure-text').boundingClientRect(data => {
if (data && data.width) {
heatWidth(data.width);
}
}).exec();
});
};
const heatWidth = (width) => {
// 加上一点额外的空间,避免在某些平台上因为小数点或字体渲染导致的文字换行
inputWidth.value = (width + props.extraWidth) + 'px';
};
const onInput = (e) => {
const val = e.detail.value;
emit('update:modelValue', val);
emit('change', val);
};
// 监听值的变化实时更新宽度
watch(() => props.modelValue, () => {
updateWidth();
});
// 初始化
onMounted(() => {
updateWidth();
});
</script>
<style lang="less" scoped>
.auto-width-input-container {
position: relative;
display: inline-block;
vertical-align: middle;
&.is-textarea {
width: 100%;
}
.measure-text {
left: -9999rpx;
top: -9999rpx;
pointer-events: none;
}
.auto-input {
min-width: v-bind('props.minWidth');
padding: 0;
margin: 0;
text-align: inherit; // 继承父级的对齐方式
height: 1.4em; // 自动适应行高
line-height: 1.4em;
}
.auto-textarea {
width: 100%;
min-height: 1.4em;
padding: 0;
margin: 0;
line-height: 1.4em;
display: block;
}
}
</style>