"use client" import { Accordion, ActionIcon, Box, Button, Flex, Group, NumberInput, Radio, Stack, Text, TextInput, Tooltip, } from "@mantine/core" import { useForm } from "@mantine/form" import { IconEdit } from "@tabler/icons-react" import dayjs from "dayjs" import parse from "html-react-parser" import { ArrowUpIcon, ArrowUpToLineIcon } from "lucide-react" import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState, } from "react" import { getWrongWordList } from "../api/scheme" import { Task } from "../api/task/typing" import { useKeyEventStore } from "../store" import { usePermissionStore } from "../store/auth" import { useBottomToolsStore } from "../useBottomToolsStore" import { useDescToolsStore } from "../useDescToolsStore" import { useTopToolsStore } from "../useTopToolsStore" import CustomModal from "./CustomModal" import EditorContainer, { splitWord } from "./EditorContainer" interface ComponentProps { taskDetail?: Task.DataProps // projectDetail?: Project.DetailResponse; } interface EditFormProps { question_name: string value?: string round_id: number } // 处理转义字符 const escapeRegExp = (str: string) => { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") } const RightQAContent = forwardRef( ({ taskDetail }: ComponentProps, ref: React.Ref) => { const { isView } = useTopToolsStore() const { activeImage } = useBottomToolsStore() const { qaData, setQaData, countAllTurnsAndQaNumber, updateDescDataFlag, updateFlag, } = useDescToolsStore() const currentQaData = qaData.get(activeImage) ?? qaData.get("init") ?? {} const isReview = [4, 6].includes(taskDetail?.label_status || 0) const [selectedQuestion, setSelectedQuestion] = useState(null) const form = useForm({ initialValues: { value: "", round: 1, tag: 0, }, }) const textForm = useForm({ initialValues: { question_name: "", value: "", round_id: 1, }, }) const counts = useMemo(() => { return countAllTurnsAndQaNumber(activeImage) // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeImage, qaData, updateDescDataFlag, countAllTurnsAndQaNumber]) const [open, setOpen] = useState({ status: false, label: "", idx: -1, isEdit: false, }) // 新增问题 const onClickAdd = (label: string, index: number) => { setOpen({ status: true, label, idx: index, isEdit: false }) } // 编辑问题名称 const onClickEdit = (label: string, index: number, val: EditFormProps) => { textForm.setValues({ question_name: val.question_name, value: val.value || "", round_id: val.round_id, }) setOpen({ status: true, label, idx: index, isEdit: true }) } type QaObj = Record const handleFormUpdate = (changeValues: any) => { if (!selectedQuestion) return const keys = Object.keys(changeValues) const hasTagChange = keys.includes("tag") const formData = form.values 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" ? formData.value : 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" ? formData.value : detail.comment, flag: hasTagChange ? 0 : detail.tag === 2 ? 1 : 0, }, }) } else { newArr.push(qaObj) } }) try { const finalData = structuredClone(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 } catch (error) { console.log(error) } } const onFieldChange = (field: string, value: any) => { form.setFieldValue(field, value) handleFormUpdate({ [field]: value }) } useEffect(() => { if (!selectedQuestion) return if ( selectedQuestion.type === "value" || selectedQuestion.type === "comment" ) { const { value: v } = selectedQuestion let htmlText = "" if (selectedQuestion.type === "value") { htmlText = v.value ? v.value.split(splitWord)[0] : "" } else { htmlText = v.comment ? v.comment.split(splitWord)[0] : "" } const obj = { value: htmlText, round: v.id, tag: v.tag, } form.setValues(obj) } }, [selectedQuestion, form]) useEffect(() => { if (updateDescDataFlag) { updateFlag(false) } }, [updateDescDataFlag, updateFlag]) // 处理问题上移 const handleQuestionUp = (category: string, index: number) => { const finalData = structuredClone(currentQaData) let arr = finalData[category] let temp = arr[index] arr[index] = arr[index - 1] arr[index - 1] = temp finalData[category] = arr qaData.set(activeImage, finalData) setQaData(new Map(qaData)) updateFlag(true) } // 处理问题置顶 const handleQuestionUpToTop = (category: string, index: number) => { const finalData = structuredClone(currentQaData) let arr = finalData[category] const element = arr.splice(index, 1)[0] arr.unshift(element) finalData[category] = arr qaData.set(activeImage, finalData) setQaData(new Map(qaData)) updateFlag(true) } // 错词检测 const checkWrongWords = useCallback(async () => { if (useTopToolsStore.getState().isView) return const res = await getWrongWordList() const dictionary = res.data const wrongWords = dictionary.map((item) => item.error) const rightWords = dictionary.map((item) => item.correct) // currentQaData const currentImg = useBottomToolsStore.getState().activeImage const qaData = useDescToolsStore.getState().qaData const currentQaData = qaData.get(currentImg) ?? qaData.get("init") ?? {} const newQaData = structuredClone(currentQaData) const handleText = (v: string) => { // 提取文本内容 const text = v.split(splitWord)[1] let textContent = text.replace(/<[^>]+>/g, "") // 替换错词 for (let i = 0; i < wrongWords.length; i++) { const wrong = escapeRegExp(wrongWords[i]) const right = rightWords[i] const regex = new RegExp(wrong, "g") if (textContent.match(regex)) { textContent = textContent.replace( regex, `${wrong}` ) return textContent } } return text } type QaObj = Record Object.values(newQaData).forEach((questionArr: QaObj[]) => { questionArr.forEach((questionObj: QaObj) => { const [name, questionData] = Object.entries(questionObj)[0] const { value, comment } = questionData const update = (v: string, key: "value" | "comment") => { const checkValue = v.split(splitWord)[1] const updatedValue = handleText(v) if (checkValue !== updatedValue) { let newValue = `

${updatedValue}

${splitWord}${updatedValue}` questionData[key] = newValue if ( selectedQuestion?.label === name && selectedQuestion?.type === key ) form.setFieldValue("value", `

${updatedValue}

`) } } if (value) update(value, "value") if (comment) update(comment, "comment") }) }) qaData.set(currentImg, newQaData) setQaData(new Map(qaData)) // Force update updateFlag(true) // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedQuestion, qaData, setQaData, updateFlag]) useImperativeHandle(ref, () => ({ checkWrongWords, })) return ( {`总轮数:${counts.turns}, 总问题数:${counts.question_size},新增单轮数:${counts.new_single_turns},新增多轮数:${counts.new_multiple_turns}`} {Object.entries(currentQaData).map(([qaCategory, qaDataArr]) => { const counts = useDescToolsStore .getState() .countTurnsAndQaNumber(activeImage, qaCategory) return ( {`${qaCategory} 轮数:${counts.turns},问题数:${counts.question_size}`} {qaDataArr.map((qaObj, index) => { const [question_name, detail] = Object.entries(qaObj)[0] const htmlText = detail.value ? detail.value.split(splitWord)[0] : "" const commentHtmlText = detail.comment ? detail.comment.split(splitWord)[0] : "" return ( {question_name} {!detail.is_pre && !isView ? ( { onClickEdit(qaCategory, index, { question_name, value: htmlText, round_id: detail.id, }) }}> ) : null} {detail.tag === 2 ? ( 错误 ) : detail.tag === 1 ? ( 正确 ) : null} 轮次:{detail.id} {!isView ? ( {!detail.is_pre ? ( ) : null} {isReview && !detail.comment ? ( ) : null} {index !== 0 ? ( <> { handleQuestionUp(qaCategory, index) }}> { handleQuestionUpToTop( qaCategory, index ) }}> ) : null} ) : null} { setSelectedQuestion({ label: question_name, value: detail, type: "value", category: qaCategory, }) console.log("点击批注", htmlText) form.setValues({ value: htmlText, round: detail.id, tag: detail.tag, }) }}> {parse(htmlText)} {commentHtmlText ? ( { setSelectedQuestion({ label: question_name, value: detail, type: "comment", category: qaCategory, }) form.setValues({ value: commentHtmlText, round: detail.id, tag: detail.tag, }) }}> {parse(commentHtmlText)} ) : null} ) })} ) })} 问题 {selectedQuestion?.label || ""} 轮次 onFieldChange("round", val)} disabled={ !selectedQuestion || (selectedQuestion && selectedQuestion.value?.is_pre) } /> onFieldChange("tag", Number(val))}> onFieldChange("value", val)} disabled={!selectedQuestion} /> { const currentOperation = currentQaData[open.label] // Validate const { hasErrors } = textForm.validate() if (hasErrors) return // Duplicate check const formData = textForm.values const arr = useDescToolsStore .getState() .getQuestionList(activeImage) // If adding new, check duplicates if (!open.isEdit && arr.includes(formData.question_name)) { textForm.setFieldError("question_name", "问题名称不能重复!") return } // If editing, if name changed and new name exists, error if (open.isEdit) { const originalName = Object.keys(currentOperation[open.idx])[0] if ( originalName !== formData.question_name && arr.includes(formData.question_name) ) { textForm.setFieldError("question_name", "问题名称不能重复!") return } } const finalData = structuredClone(currentQaData) const updateData = structuredClone(currentOperation) let obj = { value: formData.value ?? "

", id: formData.round_id, is_pre: false, tag: 0, uid: 0, create_timestamp: 0, modify_uid: 0, modify_timestamp: 0, comment: "", flag: 0, } if (open.isEdit) { const prevData = Object.values(currentOperation[open.idx])[0] obj.uid = prevData.uid obj.create_timestamp = prevData.create_timestamp obj.modify_uid = usePermissionStore.getState().user_id obj.modify_timestamp = dayjs().unix() obj.comment = prevData.comment obj.flag = prevData.flag updateData[open.idx] = { [formData.question_name]: obj } } else { obj.uid = usePermissionStore.getState().user_id obj.create_timestamp = dayjs().unix() updateData.splice(open.idx + 1, 0, { [formData.question_name]: obj, }) } finalData[open.label] = updateData qaData.set(activeImage, finalData) setQaData(new Map(qaData)) updateFlag(true) setOpen({ status: false, label: "", idx: -1, isEdit: false }) textForm.reset() }} onCancel={() => { textForm.reset() setOpen({ status: false, label: "", idx: -1, isEdit: false }) }}> { useKeyEventStore.getState().setFocusInput(true) }} onBlur={() => { useKeyEventStore.getState().setFocusInput(false) }} /> textForm.setFieldValue("value", val)} />
) } ) const RightQATools = ( { taskDetail }: ComponentProps, ref: React.Ref ) => { const { activeImage } = useBottomToolsStore() return } export default forwardRef(RightQATools)