perf: 输入框样式调整
This commit is contained in:
@ -1,32 +1,28 @@
|
||||
<template>
|
||||
<div class="highlight-textarea-container">
|
||||
<!-- 透明输入层 -->
|
||||
<a-textarea
|
||||
ref="textareaRef"
|
||||
v-model="inputValue"
|
||||
placeholder="请输入作品描述"
|
||||
:disabled="disabled"
|
||||
show-word-limit
|
||||
maxlength="1000"
|
||||
:max-length="1000"
|
||||
size="large"
|
||||
class="textarea-input h-full w-full"
|
||||
@input="handleInput"
|
||||
@scroll="syncScroll"
|
||||
:style="textareaStyle"
|
||||
/>
|
||||
|
||||
<!-- 高亮显示层 -->
|
||||
<div
|
||||
ref="highlightRef"
|
||||
class="textarea-highlight"
|
||||
:style="{ visibility: inputValue ? 'visible' : 'hidden' }"
|
||||
v-html="highlightedHtml"
|
||||
@scroll="syncScroll"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, defineProps, defineEmits } from 'vue';
|
||||
import { isString } from '@/utils/is';
|
||||
import { ref, computed, watch, defineProps, defineEmits, onMounted, onUnmounted } from 'vue';
|
||||
import { escapeRegExp } from './constants';
|
||||
|
||||
// 定义Props类型
|
||||
@ -43,9 +39,7 @@ const props = defineProps<{
|
||||
modelValue?: string;
|
||||
prohibitedWords?: ViolationItem[];
|
||||
levelMap?: Map<number, LevelMapItem>;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
maxLength?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -53,6 +47,9 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
// 内部状态管理
|
||||
// 修复highlightRef类型定义
|
||||
const textareaRef = ref<HTMLTextAreaElement | null>(null);
|
||||
const highlightRef = ref<HTMLTextAreaElement | null>(null);
|
||||
const inputValue = ref(props.modelValue || '');
|
||||
const scrollTop = ref(0);
|
||||
const highlightedHtml = computed(() => generateHighlightedHtml());
|
||||
@ -62,7 +59,7 @@ watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal !== inputValue.value) {
|
||||
inputValue.value = newVal || '';
|
||||
inputValue.value = (newVal || '').slice(0, 1000);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -114,9 +111,26 @@ const generateHighlightedHtml = (): string => {
|
||||
const levelStyle = props.levelMap?.get(wordInfo.risk_level);
|
||||
const color = levelStyle?.color || '#F64B31';
|
||||
|
||||
return `<span class="s1" style="color: ${color};">${escapeHtml(match)}</span>`;
|
||||
return `<span class="text-14px font-400 lh-22px" style="color: ${color};">${escapeHtml(match)}</span>`;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.querySelector('.textarea-input .arco-textarea')?.addEventListener('scroll', handleTextareaScroll);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.querySelector('.textarea-input .arco-textarea')?.removeEventListener('scroll', handleTextareaScroll);
|
||||
});
|
||||
|
||||
const handleTextareaScroll = (e: Event) => {
|
||||
const _scrollTop = (e.target as HTMLTextAreaElement).scrollTop;
|
||||
|
||||
const highlightElement = document.querySelector('.textarea-highlight');
|
||||
if (highlightElement) {
|
||||
highlightElement.scrollTop = _scrollTop;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -124,8 +138,10 @@ const generateHighlightedHtml = (): string => {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.textarea-input,
|
||||
.textarea-highlight {
|
||||
@mixin textarea-padding {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
@mixin textarea-style {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -135,38 +151,46 @@ const generateHighlightedHtml = (): string => {
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
// font-family: inherit;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.s1 {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
font-weight: 400px;
|
||||
}
|
||||
.textarea-input {
|
||||
:deep(.arco-textarea) {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
caret-color: #211f24 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-highlight {
|
||||
@include textarea-style;
|
||||
padding: 8px 12px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
padding: 8px 12px;
|
||||
@include textarea-padding;
|
||||
pointer-events: none;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.arco-textarea-wrapper) {
|
||||
@include textarea-style;
|
||||
.arco-textarea {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
caret-color: #211f24 !important;
|
||||
@include textarea-padding;
|
||||
|
||||
// -webkit-text-fill-color: transparent !important;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.arco-textarea-word-limit {
|
||||
z-index: 2;
|
||||
}
|
||||
&.arco-textarea-disabled {
|
||||
.arco-textarea {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 字数统计样式 */
|
||||
.word-count {
|
||||
|
||||
@ -1,32 +1,28 @@
|
||||
<template>
|
||||
<div class="highlight-textarea-container">
|
||||
<!-- 透明输入层 -->
|
||||
<a-textarea
|
||||
ref="textareaRef"
|
||||
v-model="inputValue"
|
||||
placeholder="请输入作品描述"
|
||||
:disabled="disabled"
|
||||
show-word-limit
|
||||
maxlength="1000"
|
||||
:max-length="1000"
|
||||
size="large"
|
||||
class="textarea-input h-full w-full"
|
||||
@input="handleInput"
|
||||
@scroll="syncScroll"
|
||||
:style="textareaStyle"
|
||||
/>
|
||||
|
||||
<!-- 高亮显示层 -->
|
||||
<div
|
||||
ref="highlightRef"
|
||||
class="textarea-highlight"
|
||||
:style="{ visibility: inputValue ? 'visible' : 'hidden' }"
|
||||
v-html="highlightedHtml"
|
||||
@scroll="syncScroll"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, defineProps, defineEmits } from 'vue';
|
||||
import { isString } from '@/utils/is';
|
||||
import { ref, computed, watch, defineProps, defineEmits, onMounted, onUnmounted } from 'vue';
|
||||
import { escapeRegExp } from './constants';
|
||||
|
||||
// 定义Props类型
|
||||
@ -43,9 +39,7 @@ const props = defineProps<{
|
||||
modelValue?: string;
|
||||
prohibitedWords?: ViolationItem[];
|
||||
levelMap?: Map<number, LevelMapItem>;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
maxLength?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -53,6 +47,9 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
// 内部状态管理
|
||||
// 修复highlightRef类型定义
|
||||
const textareaRef = ref<HTMLTextAreaElement | null>(null);
|
||||
const highlightRef = ref<HTMLTextAreaElement | null>(null);
|
||||
const inputValue = ref(props.modelValue || '');
|
||||
const scrollTop = ref(0);
|
||||
const highlightedHtml = computed(() => generateHighlightedHtml());
|
||||
@ -62,7 +59,7 @@ watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal !== inputValue.value) {
|
||||
inputValue.value = newVal || '';
|
||||
inputValue.value = (newVal || '').slice(0, 1000);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -114,9 +111,26 @@ const generateHighlightedHtml = (): string => {
|
||||
const levelStyle = props.levelMap?.get(wordInfo.risk_level);
|
||||
const color = levelStyle?.color || '#F64B31';
|
||||
|
||||
return `<span class="s1" style="color: ${color};">${escapeHtml(match)}</span>`;
|
||||
return `<span class="text-14px font-400 lh-22px" style="color: ${color};">${escapeHtml(match)}</span>`;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.querySelector('.textarea-input .arco-textarea')?.addEventListener('scroll', handleTextareaScroll);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.querySelector('.textarea-input .arco-textarea')?.removeEventListener('scroll', handleTextareaScroll);
|
||||
});
|
||||
|
||||
const handleTextareaScroll = (e: Event) => {
|
||||
const _scrollTop = (e.target as HTMLTextAreaElement).scrollTop;
|
||||
|
||||
const highlightElement = document.querySelector('.textarea-highlight');
|
||||
if (highlightElement) {
|
||||
highlightElement.scrollTop = _scrollTop;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -124,8 +138,10 @@ const generateHighlightedHtml = (): string => {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.textarea-input,
|
||||
.textarea-highlight {
|
||||
@mixin textarea-padding {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
@mixin textarea-style {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -135,37 +151,44 @@ const generateHighlightedHtml = (): string => {
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
// font-family: inherit;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.s1 {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
font-weight: 400px;
|
||||
}
|
||||
.textarea-input {
|
||||
:deep(.arco-textarea) {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
caret-color: #211f24 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-highlight {
|
||||
@include textarea-style;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
padding: 8px 12px;
|
||||
pointer-events: none;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
@include textarea-padding;
|
||||
}
|
||||
:deep(.arco-textarea-wrapper) {
|
||||
@include textarea-style;
|
||||
.arco-textarea {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
caret-color: #211f24 !important;
|
||||
@include textarea-padding;
|
||||
|
||||
// -webkit-text-fill-color: transparent !important;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.arco-textarea-word-limit {
|
||||
z-index: 2;
|
||||
}
|
||||
&.arco-textarea-disabled {
|
||||
.arco-textarea {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 字数统计样式 */
|
||||
|
||||
Reference in New Issue
Block a user