Merge remote-tracking branch 'origin/feature/v1.2灵机空间-内容上传审核_rxd' into test

# Conflicts:
#	src/views/creative-generation-workshop/manuscript/check/components/content-card/highlight-textarea.vue
This commit is contained in:
rd
2025-08-13 14:47:54 +08:00
2 changed files with 115 additions and 67 deletions

View File

@ -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 {

View File

@ -1,21 +1,19 @@
<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"
@ -24,8 +22,7 @@
</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类型
@ -42,9 +39,7 @@ const props = defineProps<{
modelValue?: string;
prohibitedWords?: ViolationItem[];
levelMap?: Map<number, LevelMapItem>;
placeholder?: string;
disabled?: boolean;
maxLength?: number;
}>();
const emit = defineEmits<{
@ -52,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());
@ -61,7 +59,7 @@ watch(
() => props.modelValue,
(newVal) => {
if (newVal !== inputValue.value) {
inputValue.value = newVal || '';
inputValue.value = (newVal || '').slice(0, 1000);
}
},
);
@ -113,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">
@ -123,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%;
@ -134,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;
}
}
}
/* 字数统计样式 */