fix(style): fix
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 : "<p></p>"
|
||||
if (editor.getHTML() !== incomingHtml) {
|
||||
// Keep editor content in sync with external state, including empty value switch.
|
||||
editor.commands.setContent(incomingHtml, { emitUpdate: false })
|
||||
}
|
||||
}, [value, editor])
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<Text size="xs" c="dimmed" className="qa-preview-empty">
|
||||
{emptyText}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
return <Box className="qa-preview-content">{parsedContent}</Box>
|
||||
}
|
||||
)
|
||||
|
||||
QaPreviewContent.displayName = "QaPreviewContent"
|
||||
|
||||
const RightQAContent = forwardRef(
|
||||
({ taskDetail }: ComponentProps, ref: React.Ref<unknown>) => {
|
||||
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<ReturnType<typeof setTimeout> | 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<string, any>,
|
||||
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 (
|
||||
<Box h="100%">
|
||||
<Text
|
||||
size="sm"
|
||||
fw={600}
|
||||
px="xs"
|
||||
mb="xs">{`总轮数:${counts.turns}, 总问题数:${counts.question_size},新增单轮数:${counts.new_single_turns},新增多轮数:${counts.new_multiple_turns}`}</Text>
|
||||
<Box
|
||||
<Box
|
||||
h="100%"
|
||||
style={{ display: "flex", flexDirection: "column", minHeight: 0 }}>
|
||||
<Box px="xs" pt="xs" pb={6}>
|
||||
<Text
|
||||
size="xs"
|
||||
fw={500}
|
||||
c="dimmed">{`总轮数:${counts.turns}, 总问题数:${counts.question_size},新增单轮数:${counts.new_single_turns},新增多轮数:${counts.new_multiple_turns}`}</Text>
|
||||
</Box>
|
||||
<ScrollArea
|
||||
className="qa-tools-container"
|
||||
h="calc(100% - 300px)"
|
||||
style={{ overflowY: "auto" }}>
|
||||
style={{ flex: 1, minHeight: 0 }}
|
||||
px="xs">
|
||||
<Accordion multiple>
|
||||
{Object.entries(currentQaData).map(([qaCategory, qaDataArr]) => {
|
||||
const counts = useDescToolsStore
|
||||
@@ -345,8 +427,9 @@ const RightQAContent = forwardRef(
|
||||
<Accordion.Item key={qaCategory} value={qaCategory}>
|
||||
<Accordion.Control>
|
||||
<Text
|
||||
size="sm"
|
||||
fw={
|
||||
600
|
||||
500
|
||||
}>{`${qaCategory} 轮数:${counts.turns},问题数:${counts.question_size}`}</Text>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
@@ -357,6 +440,9 @@ const RightQAContent = forwardRef(
|
||||
const commentHtmlText = getHtmlFromStoredValue(
|
||||
detail.comment
|
||||
)
|
||||
const hasValueContent = hasPreviewContent(htmlText)
|
||||
const hasCommentContent =
|
||||
hasPreviewContent(commentHtmlText)
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -371,7 +457,9 @@ const RightQAContent = forwardRef(
|
||||
}}>
|
||||
<Flex gap="xs" mb="xs">
|
||||
<Flex align="center">
|
||||
<Text>{question_name}</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
{question_name}
|
||||
</Text>
|
||||
{!detail.is_pre && !isView ? (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
@@ -390,9 +478,13 @@ const RightQAContent = forwardRef(
|
||||
</Flex>
|
||||
<Box>
|
||||
{detail.tag === 2 ? (
|
||||
<Text c="red">错误</Text>
|
||||
<Text size="xs" fw={500} c="red">
|
||||
错误
|
||||
</Text>
|
||||
) : detail.tag === 1 ? (
|
||||
<Text c="green">正确</Text>
|
||||
<Text size="xs" fw={500} c="green">
|
||||
正确
|
||||
</Text>
|
||||
) : null}
|
||||
</Box>
|
||||
</Flex>
|
||||
@@ -400,7 +492,7 @@ const RightQAContent = forwardRef(
|
||||
justify="space-between"
|
||||
align="center"
|
||||
mb="xs">
|
||||
<Text size="sm">轮次:{detail.id}</Text>
|
||||
<Text size="xs">轮次:{detail.id}</Text>
|
||||
{!isView ? (
|
||||
<Flex gap="xs">
|
||||
{!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)}
|
||||
<QaPreviewContent
|
||||
html={htmlText}
|
||||
hasContent={hasValueContent}
|
||||
emptyText="暂无内容"
|
||||
/>
|
||||
</Box>
|
||||
{commentHtmlText ? (
|
||||
<Box
|
||||
@@ -528,8 +626,10 @@ const RightQAContent = forwardRef(
|
||||
"1px solid var(--mantine-color-red-filled)",
|
||||
borderRadius: "var(--mantine-radius-md)",
|
||||
cursor: "pointer",
|
||||
minHeight: "40px",
|
||||
}}
|
||||
onClick={() => {
|
||||
flushPendingValueSync()
|
||||
setSelectedQuestion({
|
||||
label: question_name,
|
||||
value: detail,
|
||||
@@ -542,7 +642,11 @@ const RightQAContent = forwardRef(
|
||||
tag: detail.tag,
|
||||
})
|
||||
}}>
|
||||
{parse(commentHtmlText)}
|
||||
<QaPreviewContent
|
||||
html={commentHtmlText}
|
||||
hasContent={hasCommentContent}
|
||||
emptyText="暂无批注内容"
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
@@ -555,49 +659,84 @@ const RightQAContent = forwardRef(
|
||||
)
|
||||
})}
|
||||
</Accordion>
|
||||
</Box>
|
||||
</ScrollArea>
|
||||
<Box
|
||||
style={{ borderTop: "2px solid var(--mantine-color-gray-3)" }}
|
||||
style={{
|
||||
borderTop:
|
||||
"1px solid light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
pt="sm"
|
||||
px="sm">
|
||||
px="sm"
|
||||
pb="sm">
|
||||
<Stack
|
||||
gap="xs"
|
||||
opacity={!isView ? 1 : 0.5}
|
||||
style={{ pointerEvents: !isView ? "auto" : "none" }}>
|
||||
<Flex align="center" gap="xs">
|
||||
<Text fw={600}>问题</Text>
|
||||
<Text>{selectedQuestion?.label || ""}</Text>
|
||||
</Flex>
|
||||
<Flex align="center" gap="xs">
|
||||
<Text>轮次</Text>
|
||||
<NumberInput
|
||||
size="xs"
|
||||
min={1}
|
||||
{...form.getInputProps("round")}
|
||||
onChange={(val) => onFieldChange("round", val)}
|
||||
disabled={
|
||||
!selectedQuestion ||
|
||||
(selectedQuestion && selectedQuestion.value?.is_pre)
|
||||
}
|
||||
/>
|
||||
<Radio.Group
|
||||
size="xs"
|
||||
{...form.getInputProps("tag")}
|
||||
onChange={(val) => onFieldChange("tag", Number(val))}>
|
||||
<Group gap="xs">
|
||||
<Radio
|
||||
label="正确"
|
||||
value={1}
|
||||
disabled={!isReview || isView}
|
||||
<Box
|
||||
p="xs"
|
||||
style={{
|
||||
border:
|
||||
"1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4))",
|
||||
borderRadius: "var(--mantine-radius-md)",
|
||||
background:
|
||||
"light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6))",
|
||||
}}>
|
||||
<Flex
|
||||
align="flex-end"
|
||||
justify="space-between"
|
||||
gap="sm"
|
||||
wrap="wrap">
|
||||
<Box style={{ flex: 1, minWidth: 140 }}>
|
||||
<Text size="xs" c="dimmed">
|
||||
当前问题
|
||||
</Text>
|
||||
<Text size="sm" fw={500} truncate="end">
|
||||
{selectedQuestion?.label || "未选择问题"}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box w={100}>
|
||||
<Text size="xs" c="dimmed" mb={4}>
|
||||
轮次
|
||||
</Text>
|
||||
<NumberInput
|
||||
size="xs"
|
||||
min={1}
|
||||
{...form.getInputProps("round")}
|
||||
onChange={(val) => onFieldChange("round", val)}
|
||||
disabled={
|
||||
!selectedQuestion ||
|
||||
(selectedQuestion && selectedQuestion.value?.is_pre)
|
||||
}
|
||||
/>
|
||||
<Radio
|
||||
label="错误"
|
||||
value={2}
|
||||
disabled={!isReview || isView}
|
||||
/>
|
||||
</Group>
|
||||
</Radio.Group>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex align="center" gap="xs" mt="xs">
|
||||
<Text size="xs" c="dimmed">
|
||||
审核结果
|
||||
</Text>
|
||||
<Radio.Group
|
||||
size="xs"
|
||||
{...form.getInputProps("tag")}
|
||||
onChange={(val) => onFieldChange("tag", Number(val))}>
|
||||
<Group gap="xs">
|
||||
<Radio
|
||||
label="正确"
|
||||
value={1}
|
||||
disabled={!isReview || isView}
|
||||
/>
|
||||
<Radio
|
||||
label="错误"
|
||||
value={2}
|
||||
disabled={!isReview || isView}
|
||||
/>
|
||||
</Group>
|
||||
</Radio.Group>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Text size="xs" c="dimmed">
|
||||
内容编辑
|
||||
</Text>
|
||||
<EditorContainer
|
||||
value={form.values.value}
|
||||
onChange={(val: string) =>
|
||||
|
||||
Reference in New Issue
Block a user