上次使用的是p标签用的contenteditable代替的可编辑的input,最后实现还是选择了用el-input的textarea方式。
一开始考虑的是需要根据用户输入自动撑开输入框,所以选择了p标签可编辑。
最后发现还是el-input会更好一点,只不过需要处理输入框撑开的样式问题。
好的,话不多说!
代码如下:
<script setup lang="ts">
// 存储每个 input 框的值
const inputValues = ref<string[]>([]);
// 存储 input 元素的 ref
const inputRefs: any = ref([]);
const isInputFocused = ref<boolean[]>([]);
const isClearIconClicked = ref<boolean[]>([]); // 用于延迟隐藏清除图标的定时器
const clearIconHideTimer: any = ref([]);
// 提取重复的判断逻辑到一个函数中
const shouldDisableOrHide = (itemConf, index) => {
return (
itemConf.value.baseConf.isReadOnly ||
!(itemConf.value.customConf?.inputGroup[index]?.isSupportEdit ?? true)
);
};
// 计算属性,用于生成渲染内容
const renderedContent = computed(() => {
if (!itemConf.value.customConf?.inputHtml) return null;
const parts = itemConf.value.customConf.inputHtml.split(/_{1,}/);
let nodes: any = [];
parts.forEach((part, index) => {
if (part) {
const replacedSpaces = part.replace(/ /g, ' ');
const replacedPart = replacedSpaces.replace(/<div>/g, '<br>').replace(/<\/div>/g, '');
nodes.push(h('span', { class: 'custom-span', innerHTML: replacedPart }));
}
if (index < parts.length - 1) {
if (!inputValues.value[index]) {
inputValues.value[index] = '';
}
if (!isInputFocused.value[index]) {
isInputFocused.value[index] = false;
}
if (!isClearIconClicked.value[index]) {
isClearIconClicked.value[index] = false;
}
if (!clearIconHideTimer.value[index]) {
clearIconHideTimer.value[index] = 0;
}
const clearIcon = h(
ElIcon,
{
class: [
'clear_icon',
{
'is-hidden':
inputValues.value[index].length === 0 ||
shouldDisableOrHide(itemConf, index) ||
!isInputFocused.value[index],
},
],
onClick: () => {
if (!shouldDisableOrHide(itemConf, index)) {
isClearIconClicked.value[index] = true;
inputValues.value[index] = '';
adjustInputWidth(index);
handleChange(itemConf.value.customConf.inputGroup[index], '');
// 点击后清除隐藏定时器
clearTimeout(clearIconHideTimer.value[index]);
}
},
},
{ default: () => h(CircleClose) },
);
const inputNode = h(ElInput, {
type: 'textarea',
modelValue: inputValues.value[index],
'onUpdate:modelValue': (val: string) => {
inputValues.value[index] = val;
adjustInputWidth(index);
},
rows: 1,
clearable: true,
autosize: {
minRows: 1, // 最小行数
maxRows: 3, // 最大行数
},
class: [
'underline_input',
{
'is-disabled': shouldDisableOrHide(itemConf, index),
},
],
disabled: shouldDisableOrHide(itemConf, index),
placeholder: unref(itemConf).customConf?.inputGroup[index]?.placeholder || '请输入',
onFocus: () => {
isInputFocused.value[index] = true;
clearTimeout(clearIconHideTimer.value[index]);
},
onBlur: () => {
handleChange(itemConf.value.customConf.inputGroup[index], inputValues.value[index]);
clearIconHideTimer.value[index] = setTimeout(() => {
if (!isClearIconClicked.value[index]) {
isInputFocused.value[index] = false;
}
isClearIconClicked.value[index] = false;
}, 100);
},
// ref: (el) => (inputRefs.value[index] = el),
});
nodes.push(h('p', { class: 'underline_input_wrap' }, [inputNode, clearIcon]));
}
});
return h('div', nodes);
});
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 这个就是调整宽度的方法
const adjustInputWidth = (index: number) => {
const textarea = inputRefs.value?.[index] || undefined;
if (ctx && textarea) {
ctx.font = '14px system-ui'; // 必须与 CSS 字体设置一致
const text = inputValues.value[index] || textarea.placeholder;
const textWidth = ctx.measureText(text).width;
textarea.style.width = `${Math.max(textWidth + 30, 101)}px`; // 101px 为最小宽度
} else {
textarea.style.width = '101px';
}
};
// 在组件挂载时初始化输入框宽度
onMounted(() => {
nextTick(() => {
inputRefs.value = unref(wrap_component)?.querySelectorAll('.underline_input') || [];
inputRefs.value.forEach((textarea) => {
textarea.style.width = 'auto !important';
if (parseInt(textarea.style.width) < 101) {
textarea.style.width = '101px';
}
});
});
</script>
<style lang="less" scoped>
.underline_input_wrap {
display: inline-block;
position: relative;
margin-top: 20px;
margin-bottom: 0;
margin-right: 10px;
max-width: calc(100% - 50px);
}
:deep(.underline_input) {
width: 100%;
border-radius: 6px 6px 6px 6px;
border: none;
margin-top: 0;
margin-bottom: 0;
display: inline-block;
box-sizing: border-box;
vertical-align: middle;
color: #606266;
.el-textarea__inner {
background: #f5f7fb;
// 宽度高度自适应
min-height: 40px !important;
min-width: 101px !important;
max-width: 100%;
max-height: 100px;
line-height: 31px;
resize: none !important; // 禁止用户手动调整文本框大小
box-shadow: none;
white-space: pre-wrap;
word-break: break-all;
/* 隐藏滚动条 */
&::-webkit-scrollbar {
width: 0;
height: 0;
}
&::-webkit-scrollbar-thumb {
background: transparent;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&:focus {
outline: none;
border: 1px solid #1a77ff;
color: #606266;
}
&:disabled {
color: #bbbfc4;
cursor: not-allowed;
}
&::placeholder {
color: #a8abb2;
font-size: 14px;
}
}
}
.underline_input.is-disabled {
color: #bbbfc4;
cursor: not-allowed;
}
.underline_input[contenteditable='true']:empty::before,
.underline_input.is-disabled:empty::before {
content: attr(placeholder);
color: #bbbfc4;
}
:deep(.clear_icon) {
position: absolute;
width: 14px;
height: 14px;
right: 2px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #999;
z-index: 10; /* 增加 z-index 确保在最上层 */
&:hover {
color: #666;
}
&.is-hidden {
display: none;
}
}
</style>
至于样式大家可根据自己需要进行调整。