上次使用的是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>
至于樣式大家可根據自己需要進行調整。