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))}>
+
+
+
+
+
+
+
+
+ 内容编辑
+