From 27d47b569252f8c014aec63e7aad02aa49512791 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Sat, 28 Mar 2026 15:23:23 +0800 Subject: [PATCH] fix(style): fix --- app/globals.css | 39 +++ .../label/components/EditorContainer.tsx | 13 +- components/label/components/RightQATools.tsx | 315 +++++++++++++----- 3 files changed, 271 insertions(+), 96 deletions(-) diff --git a/app/globals.css b/app/globals.css index e76a859..ab29833 100644 --- a/app/globals.css +++ b/app/globals.css @@ -25,3 +25,42 @@ body { :-webkit-full-screen::backdrop { background: var(--mantine-color-body); } + +.qa-preview-content { + font-size: 12px; + line-height: 1.5; + color: var(--mantine-color-text); + word-break: break-word; +} + +.qa-preview-content p { + margin: 0; +} + +.qa-preview-content h1, +.qa-preview-content h2, +.qa-preview-content h3, +.qa-preview-content h4, +.qa-preview-content h5, +.qa-preview-content h6 { + margin: 0; + font-size: 12px; + font-weight: 500; + line-height: 1.5; +} + +.qa-preview-content ul, +.qa-preview-content ol { + margin: 0; + padding-left: 16px; +} + +.qa-preview-content pre, +.qa-preview-content code { + font-size: 12px; + white-space: pre-wrap; +} + +.qa-preview-empty { + line-height: 24px; +} diff --git a/components/label/components/EditorContainer.tsx b/components/label/components/EditorContainer.tsx index 1c0a26d..7cadec3 100644 --- a/components/label/components/EditorContainer.tsx +++ b/components/label/components/EditorContainer.tsx @@ -62,14 +62,11 @@ const EditorContainer = ({ }, [disabled, isView, editor]) useEffect(() => { - // Sync external value changes to editor - // Check if editor content differs from value to avoid cursor jump and loops - if (editor && value && editor.getHTML() !== value) { - // Only set content if it's substantially different. - // Note: editor.getHTML() might return different string than value (e.g. attribute order). - // Ideally we should compare content, but string comparison is a basic check. - // If value is empty string, we should clear. - editor.commands.setContent(value) + if (!editor) return + const incomingHtml = value ? value : "

" + if (editor.getHTML() !== incomingHtml) { + // Keep editor content in sync with external state, including empty value switch. + editor.commands.setContent(incomingHtml, { emitUpdate: false }) } }, [value, editor]) diff --git a/components/label/components/RightQATools.tsx b/components/label/components/RightQATools.tsx index 62b03df..885d25a 100644 --- a/components/label/components/RightQATools.tsx +++ b/components/label/components/RightQATools.tsx @@ -9,6 +9,7 @@ import { Group, NumberInput, Radio, + ScrollArea, Stack, Text, TextInput, @@ -22,10 +23,12 @@ import { ArrowUpIcon, ArrowUpToLineIcon } from "lucide-react" import dynamic from "next/dynamic" import { forwardRef, + memo, useCallback, useEffect, useImperativeHandle, useMemo, + useRef, useState, } from "react" import { getWrongWordList } from "../api/scheme" @@ -78,6 +81,46 @@ const ensureStoredValue = (value?: string) => { return `${value}${splitWord}${getTextFromHtml(value)}` } +const hasPreviewContent = (html?: string) => { + if (!html) return false + if (/<(img|video|audio|iframe|hr)\b/i.test(html)) return true + return ( + html + .replace(/<[^>]+>/g, "") + .replace(/ /g, " ") + .trim().length > 0 + ) +} + +const QaPreviewContent = memo( + ({ + html, + hasContent, + emptyText, + }: { + html: string + hasContent: boolean + emptyText: string + }) => { + const parsedContent = useMemo(() => { + if (!hasContent) return null + return parse(html) + }, [hasContent, html]) + + if (!hasContent) { + return ( + + {emptyText} + + ) + } + + return {parsedContent} + } +) + +QaPreviewContent.displayName = "QaPreviewContent" + const RightQAContent = forwardRef( ({ taskDetail }: ComponentProps, ref: React.Ref) => { const { isView } = useTopToolsStore() @@ -112,7 +155,7 @@ const RightQAContent = forwardRef( const counts = useMemo(() => { return countAllTurnsAndQaNumber(activeImage) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeImage, qaData, updateDescDataFlag, countAllTurnsAndQaNumber]) + }, [activeImage, qaData, countAllTurnsAndQaNumber]) const [open, setOpen] = useState({ status: false, @@ -121,13 +164,17 @@ const RightQAContent = forwardRef( isEdit: false, }) + const valueSyncTimerRef = useRef | null>(null) + // 新增问题 const onClickAdd = (label: string, index: number) => { + flushPendingValueSync() setOpen({ status: true, label, idx: index, isEdit: false }) } // 编辑问题名称 const onClickEdit = (label: string, index: number, val: EditFormProps) => { + flushPendingValueSync() textForm.setValues({ question_name: val.question_name, value: val.value || "", @@ -140,7 +187,8 @@ const RightQAContent = forwardRef( const handleFormUpdate = ( changeValues: Record, - rawEditorValue?: string + rawEditorValue?: string, + options?: { notifyFlag?: boolean } ) => { if (!selectedQuestion) return const keys = Object.keys(changeValues) @@ -154,53 +202,84 @@ const RightQAContent = forwardRef( ? ensureStoredValue(rawEditorValue) : ensureStoredValue(formData.value) const { label, type, category } = selectedQuestion - const currentQaDataArr = currentQaData[category] ?? [] - let newArr: { [x: string]: any }[] = [] - currentQaDataArr.forEach((qaObj: QaObj) => { - const [question_name, detail] = Object.entries(qaObj)[0] - if (question_name === label) { - newArr.push({ - [question_name]: { - value: type === "value" ? nextStoredValue : detail.value, - id: formData.round, - is_pre: detail.is_pre, - tag: formData.tag, - uid: detail.uid - ? detail.uid - : usePermissionStore.getState().user_id, - create_timestamp: detail.create_timestamp - ? detail.create_timestamp - : dayjs().unix(), - modify_uid: usePermissionStore.getState().user_id, - modify_timestamp: dayjs().unix(), - comment: type === "comment" ? nextStoredValue : detail.comment, - flag: hasTagChange ? 0 : detail.tag === 2 ? 1 : 0, - }, - }) - } else { - newArr.push(qaObj) - } - }) try { - const finalData = safeClone(currentQaData) - finalData[category] = newArr - qaData.set(activeImage, finalData) - setQaData(new Map(qaData)) // Force update - updateFlag(true) // Ensure flag is updated to trigger recalculations if needed + const latestQaData = useDescToolsStore.getState().qaData + const latestCurrentQaData = + latestQaData.get(activeImage) ?? latestQaData.get("init") ?? {} + const latestQaDataArr = latestCurrentQaData[category] ?? [] + let latestNewArr: { [x: string]: any }[] = [] + latestQaDataArr.forEach((qaObj: QaObj) => { + const [question_name, detail] = Object.entries(qaObj)[0] + if (question_name === label) { + latestNewArr.push({ + [question_name]: { + value: type === "value" ? nextStoredValue : detail.value, + id: formData.round, + is_pre: detail.is_pre, + tag: formData.tag, + uid: detail.uid + ? detail.uid + : usePermissionStore.getState().user_id, + create_timestamp: detail.create_timestamp + ? detail.create_timestamp + : dayjs().unix(), + modify_uid: usePermissionStore.getState().user_id, + modify_timestamp: dayjs().unix(), + comment: type === "comment" ? nextStoredValue : detail.comment, + flag: hasTagChange ? 0 : detail.tag === 2 ? 1 : 0, + }, + }) + } else { + latestNewArr.push(qaObj) + } + }) + const finalData = safeClone(latestCurrentQaData) + finalData[category] = latestNewArr + latestQaData.set(activeImage, finalData) + setQaData(new Map(latestQaData)) // Force update + if (options?.notifyFlag) { + updateFlag(true) // Ensure flag is updated to trigger recalculations if needed + } } catch (error) { console.log(error) } } + const flushPendingValueSync = () => { + if (!valueSyncTimerRef.current) return + clearTimeout(valueSyncTimerRef.current) + valueSyncTimerRef.current = null + handleFormUpdate({ value: form.values.value }, form.values.value) + } + const onFieldChange = ( field: string, value: any, rawEditorValue?: string ) => { form.setFieldValue(field, value) - handleFormUpdate({ [field]: value }, rawEditorValue) + if (field === "value") { + if (valueSyncTimerRef.current) { + clearTimeout(valueSyncTimerRef.current) + } + valueSyncTimerRef.current = setTimeout(() => { + handleFormUpdate({ [field]: value }, rawEditorValue) + valueSyncTimerRef.current = null + }, 180) + return + } + handleFormUpdate({ [field]: value }, rawEditorValue, { notifyFlag: true }) } + useEffect(() => { + return () => { + if (valueSyncTimerRef.current) { + clearTimeout(valueSyncTimerRef.current) + valueSyncTimerRef.current = null + } + } + }, []) + useEffect(() => { if (!selectedQuestion) return if ( @@ -326,16 +405,19 @@ const RightQAContent = forwardRef( })) return ( - - {`总轮数:${counts.turns}, 总问题数:${counts.question_size},新增单轮数:${counts.new_single_turns},新增多轮数:${counts.new_multiple_turns}`} - + + {`总轮数:${counts.turns}, 总问题数:${counts.question_size},新增单轮数:${counts.new_single_turns},新增多轮数:${counts.new_multiple_turns}`} + + + style={{ flex: 1, minHeight: 0 }} + px="xs"> {Object.entries(currentQaData).map(([qaCategory, qaDataArr]) => { const counts = useDescToolsStore @@ -345,8 +427,9 @@ const RightQAContent = forwardRef( {`${qaCategory} 轮数:${counts.turns},问题数:${counts.question_size}`} @@ -357,6 +440,9 @@ const RightQAContent = forwardRef( const commentHtmlText = getHtmlFromStoredValue( detail.comment ) + const hasValueContent = hasPreviewContent(htmlText) + const hasCommentContent = + hasPreviewContent(commentHtmlText) return ( - {question_name} + + {question_name} + {!detail.is_pre && !isView ? ( {detail.tag === 2 ? ( - 错误 + + 错误 + ) : detail.tag === 1 ? ( - 正确 + + 正确 + ) : null} @@ -400,7 +492,7 @@ const RightQAContent = forwardRef( justify="space-between" align="center" mb="xs"> - 轮次:{detail.id} + 轮次:{detail.id} {!isView ? ( {!detail.is_pre ? ( @@ -503,22 +595,28 @@ const RightQAContent = forwardRef( : "1px solid var(--mantine-color-gray-3)", borderRadius: "var(--mantine-radius-md)", cursor: "pointer", + minHeight: "40px", }} onClick={() => { + flushPendingValueSync() setSelectedQuestion({ label: question_name, value: detail, type: "value", category: qaCategory, }) - console.log("点击批注", htmlText) + // console.log("点击批注", htmlText) form.setValues({ value: htmlText, round: detail.id, tag: detail.tag, }) }}> - {parse(htmlText)} + {commentHtmlText ? ( { + flushPendingValueSync() setSelectedQuestion({ label: question_name, value: detail, @@ -542,7 +642,11 @@ const RightQAContent = forwardRef( tag: detail.tag, }) }}> - {parse(commentHtmlText)} + ) : null} @@ -555,49 +659,84 @@ const RightQAContent = forwardRef( ) })} - + + px="sm" + pb="sm"> - - 问题 - {selectedQuestion?.label || ""} - - - 轮次 - onFieldChange("round", val)} - disabled={ - !selectedQuestion || - (selectedQuestion && selectedQuestion.value?.is_pre) - } - /> - onFieldChange("tag", Number(val))}> - - + + + + 当前问题 + + + {selectedQuestion?.label || "未选择问题"} + + + + + 轮次 + + onFieldChange("round", val)} + disabled={ + !selectedQuestion || + (selectedQuestion && selectedQuestion.value?.is_pre) + } /> - - - - + + + + + 审核结果 + + onFieldChange("tag", Number(val))}> + + + + + + + + + 内容编辑 +