From 72f1610fc3c56dc905c3f559177bc6b25cef96af Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 27 Mar 2026 15:13:41 +0800 Subject: [PATCH 01/11] fix(bug): fix router --- app/project/detail/OwnTaskTableContainer.tsx | 2 +- app/project/detail/TaskTableContainer.tsx | 2 +- app/project/detail/page.tsx | 2 +- build.sh | 10 ++++---- components/label/components/TopTools.tsx | 26 ++++++++++---------- components/label/store/auth.ts | 13 +++++++--- components/layout/common.ts | 10 ++++---- 7 files changed, 35 insertions(+), 30 deletions(-) diff --git a/app/project/detail/OwnTaskTableContainer.tsx b/app/project/detail/OwnTaskTableContainer.tsx index 39d4269..e552b97 100644 --- a/app/project/detail/OwnTaskTableContainer.tsx +++ b/app/project/detail/OwnTaskTableContainer.tsx @@ -188,7 +188,7 @@ export default function OwnTaskTableContainer(props: { const backUrl = type ? `/project/detail?id=${projectId}&type=${type}` : `/project/detail?id=${projectId}` - setBackProps(backUrl, "own") + setBackProps(backUrl, "own", "/image") if (info && [4, 5, 6, 7].includes(info.label_type)) { router.push( diff --git a/app/project/detail/TaskTableContainer.tsx b/app/project/detail/TaskTableContainer.tsx index c70a1cf..bed3b6a 100644 --- a/app/project/detail/TaskTableContainer.tsx +++ b/app/project/detail/TaskTableContainer.tsx @@ -456,7 +456,7 @@ export default function TaskTableContainer(props: { const backUrl = type ? `/project/detail?id=${projectId}&type=${type}` : `/project/detail?id=${projectId}` - setBackProps(backUrl, "all") + setBackProps(backUrl, "all", "/image") if (info && [4, 5, 6, 7].includes(info.label_type)) { router.push( diff --git a/app/project/detail/page.tsx b/app/project/detail/page.tsx index c58b729..712bbf6 100644 --- a/app/project/detail/page.tsx +++ b/app/project/detail/page.tsx @@ -167,7 +167,7 @@ export default function CollectionDetailPage() { const backUrl = type ? `/project/detail?id=${projectId}&type=${type}` : `/project/detail?id=${projectId}` - setBackProps(backUrl, next) + setBackProps(backUrl, next, "/image") }} style={{ paddingLeft: 14, diff --git a/build.sh b/build.sh index 9621c45..06b9d0a 100755 --- a/build.sh +++ b/build.sh @@ -51,11 +51,11 @@ FLAG_PROD="${ENV}_latest" cd $SHELL_FOLDER -# 检查git状态 -echo -e "${GREEN}Checking git status...${NC}" -git fetch -git merge -if [[ $? != 0 ]]; then echo -e "${RED}git pull error${NC}"; exit 100; fi +# # 检查git状态 +# echo -e "${GREEN}Checking git status...${NC}" +# git fetch +# git merge +# if [[ $? != 0 ]]; then echo -e "${RED}git pull error${NC}"; exit 100; fi BRANCH_NAME=`git branch | grep "*"` echo -e "${GREEN}Current branch: ${BRANCH_NAME/* /}${NC}" diff --git a/components/label/components/TopTools.tsx b/components/label/components/TopTools.tsx index c3fd693..0526d72 100644 --- a/components/label/components/TopTools.tsx +++ b/components/label/components/TopTools.tsx @@ -156,7 +156,7 @@ const TopTools = ( isLabelTask, renderPolygons, } = props - const { backUrl } = useBackUrlStore() + const { backUrl, basePath } = useBackUrlStore() const { mode, loadingData } = usePaperStore() const router = useRouter() const user_id = usePermissionStore.getState().user_id @@ -1091,9 +1091,9 @@ const TopTools = ( useObjectStore.getState().resetPathAndOperationStatus() } else { // 无id返回时跳转回任务列表页 - const url = backUrl || "/" - router.push(url) - // window.location.href = url + const url = backUrl ? basePath + backUrl : "/" + // router.push(url) + window.location.href = url // 重置图片比例及选中标注对象 usePaperStore.getState().resetRasterScale() useObjectStore.getState().resetPathAndOperationStatus() @@ -1757,9 +1757,9 @@ const TopTools = ( c="dark" onClick={() => { if (isView) { - const url = backUrl || "/" - router.push(url) - // window.location.href = url + const url = backUrl ? basePath + backUrl : "/" + // router.push(url) + window.location.href = url // handleBackup("auto_backup"); // 重置图片比例及选中标注对象 usePaperStore.getState().resetRasterScale() @@ -2450,9 +2450,9 @@ const TopTools = ( return } setConfirmOpen(false) - const url = backUrl || "/" - router.push(url) - // window.location.href = url + const url = backUrl ? basePath + backUrl : "/" + // router.push(url) + window.location.href = url // 重置图片比例及选中标注对象 usePaperStore.getState().resetRasterScale() useObjectStore.getState().resetPathAndOperationStatus() @@ -2461,9 +2461,9 @@ const TopTools = ( try { setConfirmOpen(false) handleBackup("auto_backup") - const url = backUrl || "/" - router.push(url) - // window.location.href = url + const url = backUrl ? basePath + backUrl : "/" + // router.push(url) + window.location.href = url // 重置图片比例及选中标注对象 usePaperStore.getState().resetRasterScale() useObjectStore.getState().resetPathAndOperationStatus() diff --git a/components/label/store/auth.ts b/components/label/store/auth.ts index 55e668d..5ef8fc5 100644 --- a/components/label/store/auth.ts +++ b/components/label/store/auth.ts @@ -1,8 +1,8 @@ -import { ALL_CODE } from "../utils/constants" +import dayjs from "dayjs" import { create } from "zustand" import { persist } from "zustand/middleware" import { storage } from "../store" -import dayjs from "dayjs" +import { ALL_CODE } from "../utils/constants" const initUserInfo = { user_id: null, @@ -263,8 +263,13 @@ export const useBackUrlStore = create( (set) => ({ backUrl: null, backTitle: null, - setBackProps: (url: string, title?: string) => - set({ backUrl: url, backTitle: title || null }), + basePath: "", + setBackProps: (url: string, title?: string, basePath?: string) => + set({ + backUrl: url, + backTitle: title || null, + basePath: basePath || "", + }), }), { name: "back_url", diff --git a/components/layout/common.ts b/components/layout/common.ts index cad1a1e..d6f3276 100644 --- a/components/layout/common.ts +++ b/components/layout/common.ts @@ -35,11 +35,11 @@ export const componentList: MenuItem[] = [ url: "video", icon: "SystemIcon", }, - { - title: "点云标注", - url: "lidar", - icon: "SystemIcon", - }, + // { + // title: "点云标注", + // url: "lidar", + // icon: "SystemIcon", + // }, { title: "管理中心", url: "mgt", From d0697549f0499eb6282f59fdfa89a832c61e2bfb Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 27 Mar 2026 16:19:00 +0800 Subject: [PATCH 02/11] fix(sam2): fix api --- components/label/api/label/index.ts | 2 +- components/label/components/PaperContainer.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/label/api/label/index.ts b/components/label/api/label/index.ts index b4a3e1b..8762c52 100644 --- a/components/label/api/label/index.ts +++ b/components/label/api/label/index.ts @@ -122,7 +122,7 @@ export const getAuxiliaryAnnotation = (data: any) => { export const getAuxiliaryAnnotation2 = (data: { project_id: string - image_name: string + image: string prompt: { foreground_points?: Array<[number, number]> background_points?: Array<[number, number]> diff --git a/components/label/components/PaperContainer.tsx b/components/label/components/PaperContainer.tsx index 60af9e9..ad87855 100644 --- a/components/label/components/PaperContainer.tsx +++ b/components/label/components/PaperContainer.tsx @@ -514,7 +514,7 @@ const PaperContainer = ( try { const data = await getAuxiliaryAnnotation2({ project_id: projectId, - image_name: activeImage, + image: activeImage, // project_id: "image_test_bk", prompt: { foreground_points: promptPoints, From 02ae9d6c724b7bc36e5b3ccb387c205115980a24 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 27 Mar 2026 19:40:48 +0800 Subject: [PATCH 03/11] fix(auth): header --- app/project/layout.tsx | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/app/project/layout.tsx b/app/project/layout.tsx index 11900c0..c78bb51 100644 --- a/app/project/layout.tsx +++ b/app/project/layout.tsx @@ -1,17 +1,28 @@ +"use client" + +import { usePermissionStore } from "@/components/label/store/auth" import AppLayout from "@/components/layout/AppLayout" import { PathnameRecorder } from "@/components/layout/PathnameRecorder" import { componentList, showList } from "@/components/layout/common" +import { useMemo } from "react" -async function getMenu() { - return [...componentList, ...showList] -} +// async function getMenu() { +// return [...componentList, ...showList] +// } + +export default function Layout({ children }: { children: React.ReactNode }) { + const detailInfo = usePermissionStore((s) => s.detailInfo) + + const menu = useMemo(() => { + if (detailInfo?.roles?.length) { + return [...componentList, ...showList] + } else { + return [...componentList, ...showList].filter( + (item) => item.title !== "管理中心" + ) + } + }, [detailInfo?.roles?.length]) -export default async function Layout({ - children, -}: { - children: React.ReactNode -}) { - const menu = await getMenu() return ( {children} From 9f0fd187d0d85bc382515871cfa66e6e554a2bc9 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Sat, 28 Mar 2026 13:54:44 +0800 Subject: [PATCH 04/11] fix(env): docker --- .env.development | 2 +- .env.staging | 2 +- build.sh | 12 ++++++------ components/label/api/const.ts | 2 +- components/login/api/index.ts | 2 +- docker-compose.yml | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.env.development b/.env.development index f0c4a53..7e209f2 100644 --- a/.env.development +++ b/.env.development @@ -5,7 +5,7 @@ NEXT_PUBLIC_ENV=development # redis -# NEXT_PUBLIC_REDIS_URL="172.16.112.128" +# NEXT_PUBLIC_REDIS_URL="172.16.115.128" NEXT_PUBLIC_REDIS_URL="localhost" NEXT_PUBLIC_REDIS_PORT=9765 NEXT_PUBLIC_REDIS_PASSWORD=password diff --git a/.env.staging b/.env.staging index 9dcef7b..f4fc254 100644 --- a/.env.staging +++ b/.env.staging @@ -3,7 +3,7 @@ NEXT_PUBLIC_BASE_PATH="/image" NEXT_PUBLIC_ENV=staging # redis -NEXT_PUBLIC_REDIS_URL="172.30.21.213" +NEXT_PUBLIC_REDIS_URL="172.16.115.128" NEXT_PUBLIC_REDIS_PORT=9765 NEXT_PUBLIC_REDIS_PASSWORD=password NEXT_PUBLIC_REDIS_USERNAME=default diff --git a/build.sh b/build.sh index 06b9d0a..d28eda5 100755 --- a/build.sh +++ b/build.sh @@ -71,23 +71,23 @@ pnpm run $BUILD_FLAG if [[ $? != 0 ]]; then echo -e "${RED}build error${NC}"; exit 100; fi # Docker构建和推送 -MAIN_NAME=digitalspace_labelimage +MAIN_NAME=web_digitalspace_labelimage DOCKER_FILE=$SHELL_FOLDER/Dockerfile if [[ $OUTPUT_TYPE == export ]]; then DOCKER_FILE=$SHELL_FOLDER/docker/nginx.dockerfile - MAIN_NAME=digitalspace_labelimage_static + MAIN_NAME=web_digitalspace_labelimage_static elif [[ $OUTPUT_TYPE == standalone ]]; then DOCKER_FILE=$SHELL_FOLDER/docker/node.dockerfile - MAIN_NAME=digitalspace_labelimage_ssr + MAIN_NAME=web_digitalspace_labelimage_ssr fi echo -e "${GREEN}Building docker image...${NC}" -sudo docker build -f $DOCKER_FILE -t $MAIN_NAME $SHELL_FOLDER +docker build -f $DOCKER_FILE -t $MAIN_NAME $SHELL_FOLDER if [[ $? != 0 ]]; then echo -e "${RED}build docker with error${NC}"; exit 100; fi echo -e "${GREEN}Tagging and pushing docker image...${NC}" -sudo docker tag $MAIN_NAME harbor.cowarobot.cn/gopro/$MAIN_NAME:$FLAG_PROD -# docker push harbor.cowarobot.cn/gopro/$MAIN_NAME:$FLAG_PROD +docker tag $MAIN_NAME harbor.cowarobot.cn/softrepo/$MAIN_NAME:$FLAG_PROD +docker push harbor.cowarobot.cn/softrepo/$MAIN_NAME:$FLAG_PROD echo -e "${GREEN}Build completed successfully${NC}" diff --git a/components/label/api/const.ts b/components/label/api/const.ts index 8b520b4..85a96fa 100644 --- a/components/label/api/const.ts +++ b/components/label/api/const.ts @@ -2,5 +2,5 @@ export const BASE_LABEL_API = process.env.NEXT_PUBLIC_ENV === "production" ? "https://label.cowarobot.com" : process.env.NEXT_PUBLIC_ENV === "staging" - ? "https://label.softtest.cowarobot.com" + ? "http://172.16.115.128:9110" : "https://label.softtest.cowarobot.com" diff --git a/components/login/api/index.ts b/components/login/api/index.ts index a4d5890..c3d63e8 100644 --- a/components/login/api/index.ts +++ b/components/login/api/index.ts @@ -6,7 +6,7 @@ const BASE_PATH = process.env.NEXT_PUBLIC_ENV === "production" ? "https://label.cowarobot.com" : process.env.NEXT_PUBLIC_ENV === "staging" - ? "https://label.softtest.cowarobot.com" + ? "http://172.16.115.128:9110" : "https://label.softtest.cowarobot.com" // 获取验证码 diff --git a/docker-compose.yml b/docker-compose.yml index 975c08c..73dc848 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ version: "3" services: - digitalspace_labelimage_ssr: - container_name: digitalspace_labelimage_ssr - image: harbor.cowarobot.cn/gopro/digitalspace_labelimage_ssr:stage_latest + web_digitalspace_labelimage_ssr: + container_name: web_digitalspace_labelimage_ssr + image: harbor.cowarobot.cn/softrepo/web_digitalspace_labelimage_ssr:stage_latest restart: unless-stopped ports: - 5532:5532 From 28660de4ed756aae6205795feda0e19a27ba3b12 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Sat, 28 Mar 2026 15:23:08 +0800 Subject: [PATCH 05/11] fix(qa): bug fix --- .env.development | 4 +- .../label/components/EditorContainer.tsx | 5 +- components/label/components/RightQATools.tsx | 101 +++++++++++++----- 3 files changed, 77 insertions(+), 33 deletions(-) diff --git a/.env.development b/.env.development index 7e209f2..c0db470 100644 --- a/.env.development +++ b/.env.development @@ -6,9 +6,9 @@ NEXT_PUBLIC_ENV=development # redis # NEXT_PUBLIC_REDIS_URL="172.16.115.128" -NEXT_PUBLIC_REDIS_URL="localhost" +NEXT_PUBLIC_REDIS_URL="172.30.21.213" NEXT_PUBLIC_REDIS_PORT=9765 NEXT_PUBLIC_REDIS_PASSWORD=password NEXT_PUBLIC_REDIS_USERNAME=default -NEXT_PUBLIC_REDIS_DB=0 +NEXT_PUBLIC_REDIS_DB=1 diff --git a/components/label/components/EditorContainer.tsx b/components/label/components/EditorContainer.tsx index b72d431..1c0a26d 100644 --- a/components/label/components/EditorContainer.tsx +++ b/components/label/components/EditorContainer.tsx @@ -9,7 +9,7 @@ import Placeholder from "@tiptap/extension-placeholder" import { RichTextEditor } from "@mantine/tiptap" import { useTopToolsStore } from "../useTopToolsStore" import { useKeyEventStore } from "../store" -import { useEffect, useRef } from "react" +import { useEffect, useRef, type ChangeEvent } from "react" import { Box } from "@mantine/core" import { IconPhoto } from "@tabler/icons-react" import "@mantine/tiptap/styles.css" @@ -33,6 +33,7 @@ const EditorContainer = ({ const fileInputRef = useRef(null) const editor = useEditor({ + immediatelyRender: false, extensions: [ StarterKit, Underline, @@ -72,7 +73,7 @@ const EditorContainer = ({ } }, [value, editor]) - const handleImageUpload = (e: React.ChangeEvent) => { + const handleImageUpload = (e: ChangeEvent) => { const file = e.target.files?.[0] if (file && editor) { const reader = new FileReader() diff --git a/components/label/components/RightQATools.tsx b/components/label/components/RightQATools.tsx index 68f1de5..62b03df 100644 --- a/components/label/components/RightQATools.tsx +++ b/components/label/components/RightQATools.tsx @@ -19,6 +19,7 @@ import { IconEdit } from "@tabler/icons-react" import dayjs from "dayjs" import parse from "html-react-parser" import { ArrowUpIcon, ArrowUpToLineIcon } from "lucide-react" +import dynamic from "next/dynamic" import { forwardRef, useCallback, @@ -36,7 +37,11 @@ import { useDescToolsStore } from "../useDescToolsStore" import { useTopToolsStore } from "../useTopToolsStore" import { safeClone } from "../utils/clone" import CustomModal from "./CustomModal" -import EditorContainer, { splitWord } from "./EditorContainer" +import { splitWord } from "./EditorContainer" + +const EditorContainer = dynamic(() => import("./EditorContainer"), { + ssr: false, +}) interface ComponentProps { taskDetail?: Task.DataProps @@ -54,6 +59,25 @@ const escapeRegExp = (str: string) => { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") } +const getHtmlFromStoredValue = (value?: string) => { + if (!value) return "" + const [html] = value.split(splitWord) + return html ?? value +} + +const getTextFromHtml = (html: string) => { + return html + .replace(/<[^>]+>/g, "") + .replace(/ /g, " ") + .trim() +} + +const ensureStoredValue = (value?: string) => { + if (!value) return "" + if (value.includes(splitWord)) return value + return `${value}${splitWord}${getTextFromHtml(value)}` +} + const RightQAContent = forwardRef( ({ taskDetail }: ComponentProps, ref: React.Ref) => { const { isView } = useTopToolsStore() @@ -114,11 +138,21 @@ const RightQAContent = forwardRef( type QaObj = Record - const handleFormUpdate = (changeValues: any) => { + const handleFormUpdate = ( + changeValues: Record, + rawEditorValue?: string + ) => { if (!selectedQuestion) return const keys = Object.keys(changeValues) const hasTagChange = keys.includes("tag") - const formData = form.values + const formData = { + ...form.values, + ...changeValues, + } + const nextStoredValue = + typeof rawEditorValue === "string" + ? ensureStoredValue(rawEditorValue) + : ensureStoredValue(formData.value) const { label, type, category } = selectedQuestion const currentQaDataArr = currentQaData[category] ?? [] let newArr: { [x: string]: any }[] = [] @@ -127,7 +161,7 @@ const RightQAContent = forwardRef( if (question_name === label) { newArr.push({ [question_name]: { - value: type === "value" ? formData.value : detail.value, + value: type === "value" ? nextStoredValue : detail.value, id: formData.round, is_pre: detail.is_pre, tag: formData.tag, @@ -139,7 +173,7 @@ const RightQAContent = forwardRef( : dayjs().unix(), modify_uid: usePermissionStore.getState().user_id, modify_timestamp: dayjs().unix(), - comment: type === "comment" ? formData.value : detail.comment, + comment: type === "comment" ? nextStoredValue : detail.comment, flag: hasTagChange ? 0 : detail.tag === 2 ? 1 : 0, }, }) @@ -158,9 +192,13 @@ const RightQAContent = forwardRef( } } - const onFieldChange = (field: string, value: any) => { + const onFieldChange = ( + field: string, + value: any, + rawEditorValue?: string + ) => { form.setFieldValue(field, value) - handleFormUpdate({ [field]: value }) + handleFormUpdate({ [field]: value }, rawEditorValue) } useEffect(() => { @@ -170,12 +208,10 @@ const RightQAContent = forwardRef( 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 htmlText = + selectedQuestion.type === "value" + ? getHtmlFromStoredValue(v.value) + : getHtmlFromStoredValue(v.comment) const obj = { value: htmlText, round: v.id, @@ -183,7 +219,8 @@ const RightQAContent = forwardRef( } form.setValues(obj) } - }, [selectedQuestion, form]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedQuestion]) useEffect(() => { if (updateDescDataFlag) { @@ -230,9 +267,10 @@ const RightQAContent = forwardRef( const newQaData = safeClone(currentQaData) const handleText = (v: string) => { - // 提取文本内容 - const text = v.split(splitWord)[1] - let textContent = text.replace(/<[^>]+>/g, "") + // 优先使用存储中的纯文本部分;若历史数据不规范则兜底从 HTML 中提取 + const text = + v.split(splitWord)[1] ?? getTextFromHtml(getHtmlFromStoredValue(v)) + let textContent = text // 替换错词 for (let i = 0; i < wrongWords.length; i++) { const wrong = escapeRegExp(wrongWords[i]) @@ -256,17 +294,20 @@ const RightQAContent = forwardRef( const { value, comment } = questionData const update = (v: string, key: "value" | "comment") => { - const checkValue = v.split(splitWord)[1] + const checkValue = + v.split(splitWord)[1] ?? + getTextFromHtml(getHtmlFromStoredValue(v)) const updatedValue = handleText(v) if (checkValue !== updatedValue) { - let newValue = `

${updatedValue}

${splitWord}${updatedValue}` + const htmlValue = `

${updatedValue}

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

${updatedValue}

`) + form.setFieldValue("value", htmlValue) } } @@ -312,12 +353,10 @@ const RightQAContent = forwardRef( {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] - : "" + const htmlText = getHtmlFromStoredValue(detail.value) + const commentHtmlText = getHtmlFromStoredValue( + detail.comment + ) return ( onFieldChange("value", val)} + onChange={(val: string) => + onFieldChange("value", getHtmlFromStoredValue(val), val) + } disabled={!selectedQuestion} /> @@ -601,7 +642,7 @@ const RightQAContent = forwardRef( const finalData = safeClone(currentQaData) const updateData = safeClone(currentOperation) let obj = { - value: formData.value ?? "

", + value: ensureStoredValue(formData.value ?? "

"), id: formData.round_id, is_pre: false, tag: 0, @@ -657,7 +698,9 @@ const RightQAContent = forwardRef( textForm.setFieldValue("value", val)} + onChange={(val: string) => + textForm.setFieldValue("value", getHtmlFromStoredValue(val)) + } /> Date: Sat, 28 Mar 2026 15:23:23 +0800 Subject: [PATCH 06/11] 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))}> + + + + + + + + + 内容编辑 + From 76bfb30fefebb5f6f1091ca305d52d999ca0aa9e Mon Sep 17 00:00:00 2001 From: zhangheng Date: Sat, 28 Mar 2026 16:39:27 +0800 Subject: [PATCH 07/11] fix(ci): add file --- .gitlab-ci.yml | 28 +++ components/label/api/const.ts | 2 +- .../label/components/EditorContainer.tsx | 16 +- .../label/components/RightDescTools.tsx | 177 +++++++++++++----- components/login/api/index.ts | 2 +- 5 files changed, 166 insertions(+), 59 deletions(-) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..610a3a5 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,28 @@ +stages: + - sync_github + +sync_github: + stage: sync_github + tags: + - webfront + image: harbor.cowarobot.cn/softrepo/rust_build:1.94 + script: + - echo "代码已合并到 main, 开始执行发布逻辑..." + - | + for i in {1..10}; do + if command -v docker >/dev/null 2>&1 && [ -S /var/run/docker.sock ]; then + echo "Docker is ready!" + break + fi + echo "Waiting for Docker..." + sleep 2 + done + - docker info + - echo ${CI_PROJECT_NAME} ${CI_PROJECT_PATH} ${CI_PROJECT_DIR} ${CI_PROJECT_URL} ${CI_COMMIT_SHA} + - echo ${HARBORKEY} | docker login harbor.cowarobot.cn -u 'robot$softpro+cibuild' --password-stdin + - rm -rf ${CI_PROJECT_NAME} + - git clone https://oauth2:glpat-EdU8a_84jpSH2nOlNTkpnW86MQp1OjY1CA.01.0y1rtht99@git.cowarobot.com/${CI_PROJECT_PATH} + - cd ${CI_PROJECT_NAME} + - bash build.sh prod + rules: + - if: $CI_COMMIT_BRANCH == "main" diff --git a/components/label/api/const.ts b/components/label/api/const.ts index 85a96fa..5e1fb07 100644 --- a/components/label/api/const.ts +++ b/components/label/api/const.ts @@ -1,6 +1,6 @@ export const BASE_LABEL_API = process.env.NEXT_PUBLIC_ENV === "production" - ? "https://label.cowarobot.com" + ? "https://label.soft.cowarobot.com" : process.env.NEXT_PUBLIC_ENV === "staging" ? "http://172.16.115.128:9110" : "https://label.softtest.cowarobot.com" diff --git a/components/label/components/EditorContainer.tsx b/components/label/components/EditorContainer.tsx index 7cadec3..89813b1 100644 --- a/components/label/components/EditorContainer.tsx +++ b/components/label/components/EditorContainer.tsx @@ -16,6 +16,12 @@ import "@mantine/tiptap/styles.css" export const splitWord = "@1024-" +const getEditorHtml = (rawValue?: string) => { + if (!rawValue) return "" + const [html] = rawValue.split(splitWord) + return html ?? rawValue +} + interface EditorContainerProps { value: string onChange: (value: string) => void @@ -41,11 +47,11 @@ const EditorContainer = ({ Image, Placeholder.configure({ placeholder: "请输入内容..." }), ], - content: value, + content: getEditorHtml(value), editable: !disabled && !isView, onUpdate: ({ editor }) => { - // Preserve splitWord format - onChange(`${editor.getHTML()}${splitWord}${editor.getText()}`) + const html = getEditorHtml(editor.getHTML()) + onChange(`${html}${splitWord}${editor.getText()}`) }, onFocus: () => { useKeyEventStore.getState().setFocusInput(true) @@ -63,7 +69,7 @@ const EditorContainer = ({ useEffect(() => { if (!editor) return - const incomingHtml = value ? value : "

" + const incomingHtml = getEditorHtml(value) || "

" if (editor.getHTML() !== incomingHtml) { // Keep editor content in sync with external state, including empty value switch. editor.commands.setContent(incomingHtml, { emitUpdate: false }) @@ -91,7 +97,7 @@ const EditorContainer = ({ padding: "4px", }}> - + diff --git a/components/label/components/RightDescTools.tsx b/components/label/components/RightDescTools.tsx index caa8de8..ede4b50 100644 --- a/components/label/components/RightDescTools.tsx +++ b/components/label/components/RightDescTools.tsx @@ -18,6 +18,7 @@ import { Task } from "../api/task/typing" import { useBottomToolsStore } from "../useBottomToolsStore" import { useDescToolsStore } from "../useDescToolsStore" import { useTopToolsStore } from "../useTopToolsStore" +import { safeClone } from "../utils/clone" import EditorContainer from "./EditorContainer" interface ComponentProps { @@ -40,46 +41,67 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => { }) const isUpdatingFromStore = useRef(false) + const syncTimerRef = useRef | null>(null) - const handleFormUpdate = () => { + const setOperationField = ( + labelClass: string, + field: string, + value: string | string[] + ) => { + const current = ((form.values as any)[labelClass] ?? {}) as Record< + string, + any + > + form.setFieldValue(labelClass, { + ...current, + [field]: value, + }) + } + + const handleFormUpdate = (values: any) => { if (isUpdatingFromStore.current) return - const data = form.values as any - let dataMap = new Map() - taskDetail?.data_name.forEach((detail_name) => { - if (detail_name.name[0] !== activeImage) { - if (useDescToolsStore.getState().descData.has(detail_name.name[0])) { - dataMap.set( - detail_name.name[0], - useDescToolsStore.getState().descData.get(detail_name.name[0]) - ) - } + const currentDescData = safeClone(useDescToolsStore.getState().descData) + const updateMap = new Map() + Object.entries(values).forEach(([label_class, val]: any) => { + let value = { ...val } + if (value.is_correct !== undefined) { + value.is_correct = value.is_correct === "true" + } + const keys = Object.keys(value) + if (keys.includes("value")) { + updateMap.set(label_class, value) } else { - const updateMap = new Map() - Object.entries(data).forEach(([label_class, val]: any) => { - let value = { ...val } - if (value.is_correct !== undefined) { - value.is_correct = value.is_correct === "true" - } - const keys = Object.keys(value) - if (keys.includes("value")) { - updateMap.set(label_class, value) - } else { - updateMap.set(label_class, { value: value }) - } - }) - dataMap.set(activeImage, updateMap) + updateMap.set(label_class, { value: value }) } }) - setDescData(dataMap) + currentDescData.set(activeImage, updateMap) + setDescData(currentDescData) } useEffect(() => { - handleFormUpdate() + if (isUpdatingFromStore.current) return + if (syncTimerRef.current) { + clearTimeout(syncTimerRef.current) + } + syncTimerRef.current = setTimeout(() => { + handleFormUpdate(form.values) + syncTimerRef.current = null + }, 180) + return () => { + if (syncTimerRef.current) { + clearTimeout(syncTimerRef.current) + syncTimerRef.current = null + } + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [form.values]) useEffect(() => { + if (syncTimerRef.current) { + clearTimeout(syncTimerRef.current) + syncTimerRef.current = null + } isUpdatingFromStore.current = true form.reset() if (activeImage) { @@ -109,6 +131,10 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => { useEffect(() => { if (updateDescDataFlag) { + if (syncTimerRef.current) { + clearTimeout(syncTimerRef.current) + syncTimerRef.current = null + } isUpdatingFromStore.current = true const formData = useDescToolsStore .getState() @@ -133,6 +159,15 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [updateDescDataFlag]) + useEffect(() => { + return () => { + if (syncTimerRef.current) { + clearTimeout(syncTimerRef.current) + syncTimerRef.current = null + } + } + }, []) + return ( { activeImage, operation.label_class ) - const hasComment = currentData?.comment + const hasComment = + !!(form.values as any)?.[operation.label_class]?.comment || + !!currentData?.comment return ( @@ -171,9 +208,17 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => { {operation.label_class} { + setOperationField( + operation.label_class, + "is_correct", + val + ) + }} size="xs"> { variant="subtle" size="xs" onClick={() => { - form.setFieldValue( - `${operation.label_class}.comment`, + setOperationField( + operation.label_class, + "comment", "

" ) }}> @@ -215,9 +261,18 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => { placeholder={`请输入${subDesc.chinese_name}`} size="xs" required={!!subDesc.isrequired} - {...form.getInputProps( - `${operation.label_class}.${subDesc.chinese_name}` - )} + value={ + (form.values as any)[operation.label_class]?.[ + subDesc.chinese_name + ] || "" + } + onChange={(event) => { + setOperationField( + operation.label_class, + subDesc.chinese_name, + event.currentTarget.value + ) + }} disabled={!!isView} /> ) : subDesc.select_mode === 1 ? ( @@ -225,9 +280,18 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => { label={subDesc.chinese_name} size="xs" required={!!subDesc.isrequired} - {...form.getInputProps( - `${operation.label_class}.${subDesc.chinese_name}` - )}> + value={ + (form.values as any)[operation.label_class]?.[ + subDesc.chinese_name + ] || "" + } + onChange={(val) => { + setOperationField( + operation.label_class, + subDesc.chinese_name, + val + ) + }}> {subDesc.optional_item.map((option) => ( { label={subDesc.chinese_name} size="xs" required={!!subDesc.isrequired} - {...form.getInputProps( - `${operation.label_class}.${subDesc.chinese_name}` - )}> + value={ + (form.values as any)[operation.label_class]?.[ + subDesc.chinese_name + ] || [] + } + onChange={(val) => { + setOperationField( + operation.label_class, + subDesc.chinese_name, + val + ) + }}> {subDesc.optional_item.map((option) => ( { - form.setFieldValue( - `${operation.label_class}.value`, - val - ) + setOperationField(operation.label_class, "value", val) } /> @@ -282,14 +353,16 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => { - form.setFieldValue( - `${operation.label_class}.comment`, + onChange={(val: any) => { + setOperationField( + operation.label_class, + "comment", val ) - } + }} />
diff --git a/components/login/api/index.ts b/components/login/api/index.ts index c3d63e8..2075b43 100644 --- a/components/login/api/index.ts +++ b/components/login/api/index.ts @@ -4,7 +4,7 @@ import { Login } from "./types" const BASE_PATH = process.env.NEXT_PUBLIC_ENV === "production" - ? "https://label.cowarobot.com" + ? "https://label.soft.cowarobot.com" : process.env.NEXT_PUBLIC_ENV === "staging" ? "http://172.16.115.128:9110" : "https://label.softtest.cowarobot.com" From 6e5a40f821bbdefc1c25e76db7a5b85dcce3a6cc Mon Sep 17 00:00:00 2001 From: zhangheng Date: Sat, 28 Mar 2026 18:42:50 +0800 Subject: [PATCH 08/11] fix(ci): fix --- .gitlab-ci.yml | 4 ++-- build.sh | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 610a3a5..4380e73 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ stages: sync_github: stage: sync_github tags: - - webfront + - webbuild image: harbor.cowarobot.cn/softrepo/rust_build:1.94 script: - echo "代码已合并到 main, 开始执行发布逻辑..." @@ -21,7 +21,7 @@ sync_github: - echo ${CI_PROJECT_NAME} ${CI_PROJECT_PATH} ${CI_PROJECT_DIR} ${CI_PROJECT_URL} ${CI_COMMIT_SHA} - echo ${HARBORKEY} | docker login harbor.cowarobot.cn -u 'robot$softpro+cibuild' --password-stdin - rm -rf ${CI_PROJECT_NAME} - - git clone https://oauth2:glpat-EdU8a_84jpSH2nOlNTkpnW86MQp1OjY1CA.01.0y1rtht99@git.cowarobot.com/${CI_PROJECT_PATH} + - git clone https://oauth2:${TOKEN}@git.cowarobot.com/${CI_PROJECT_PATH} - cd ${CI_PROJECT_NAME} - bash build.sh prod rules: diff --git a/build.sh b/build.sh index d28eda5..e588ca2 100755 --- a/build.sh +++ b/build.sh @@ -73,6 +73,10 @@ if [[ $? != 0 ]]; then echo -e "${RED}build error${NC}"; exit 100; fi # Docker构建和推送 MAIN_NAME=web_digitalspace_labelimage DOCKER_FILE=$SHELL_FOLDER/Dockerfile +HARBOR_REPO=softrepo +if [[ $ENV == prod ]]; then + HARBOR_REPO=softpro +fi if [[ $OUTPUT_TYPE == export ]]; then DOCKER_FILE=$SHELL_FOLDER/docker/nginx.dockerfile @@ -87,7 +91,7 @@ docker build -f $DOCKER_FILE -t $MAIN_NAME $SHELL_FOLDER if [[ $? != 0 ]]; then echo -e "${RED}build docker with error${NC}"; exit 100; fi echo -e "${GREEN}Tagging and pushing docker image...${NC}" -docker tag $MAIN_NAME harbor.cowarobot.cn/softrepo/$MAIN_NAME:$FLAG_PROD -docker push harbor.cowarobot.cn/softrepo/$MAIN_NAME:$FLAG_PROD +docker tag $MAIN_NAME harbor.cowarobot.cn/$HARBOR_REPO/$MAIN_NAME:$FLAG_PROD +docker push harbor.cowarobot.cn/$HARBOR_REPO/$MAIN_NAME:$FLAG_PROD echo -e "${GREEN}Build completed successfully${NC}" From 9e9ad0a590e2acf3affee255899c4f9be637442a Mon Sep 17 00:00:00 2001 From: zhangheng Date: Mon, 30 Mar 2026 11:40:39 +0800 Subject: [PATCH 09/11] fix(env): fix prod redis url --- .env.production | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.production b/.env.production index a68234f..63edf82 100644 --- a/.env.production +++ b/.env.production @@ -3,8 +3,8 @@ NEXT_PUBLIC_BASE_PATH="/image" NEXT_PUBLIC_ENV=production # redis -NEXT_PUBLIC_REDIS_URL="172.16.105.154" -NEXT_PUBLIC_REDIS_PORT=8015 -NEXT_PUBLIC_REDIS_PASSWORD=aAXJM2uMzPu76L93 +NEXT_PUBLIC_REDIS_URL="172.16.104.13" +NEXT_PUBLIC_REDIS_PORT=6379 +NEXT_PUBLIC_REDIS_PASSWORD=123456 NEXT_PUBLIC_REDIS_USERNAME=default NEXT_PUBLIC_REDIS_DB=10 \ No newline at end of file From d757aab517206d0a3c9755664cdb1457a77d3bef Mon Sep 17 00:00:00 2001 From: zhangheng Date: Tue, 31 Mar 2026 10:39:56 +0800 Subject: [PATCH 10/11] fix(copy): fix --- .../label/components/PaperContainer.tsx | 22 ++++++++++++++++--- components/label/components/TopTools.tsx | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/components/label/components/PaperContainer.tsx b/components/label/components/PaperContainer.tsx index ad87855..5ce17ae 100644 --- a/components/label/components/PaperContainer.tsx +++ b/components/label/components/PaperContainer.tsx @@ -1311,7 +1311,7 @@ const PaperContainer = ( }, hollow.map((item: any) => item.map((p: any) => { - if (paper instanceof paper.Segment) { + if (p instanceof paper.Segment) { return [ p.point.x / activeScale, p.point.y / activeScale, @@ -1376,8 +1376,10 @@ const PaperContainer = ( let findIndexInArr = arr.findIndex((v) => v[0] === id) let oldData = arr.find((a) => a[0] === id)! let arr2 = needCopyMap.get(needCopyCategoryIds[index])! + let copiedItem = arr2.find((b: any) => b[0] === id) + if (!copiedItem) return let newData = { - ...arr2.find((b: any) => b[0] === id), + ...copiedItem[2], uid: oldData[2].uid, create_timestamp: oldData[2].create_timestamp, first_modified_timestamp: @@ -1444,7 +1446,21 @@ const PaperContainer = ( data.set(imageId, newCategoryMap) } } else { - data.set(imageId, needCopyMap) + let newCategoryMap = new Map() + for (let [key, needCopyItemsArr] of needCopyMap) { + let arr = needCopyItemsArr.map((item: any) => [ + item[0], + item[1].map((path: any) => + path.map(([x, y]: any) => [x * picScale, y * picScale]) + ), + item[2], + item[3].map((path: any) => + path.map(([x, y]: any) => [x * picScale, y * picScale]) + ), + ]) + newCategoryMap.set(key, arr) + } + data.set(imageId, newCategoryMap) } }) notifications.show({ message: "复制成功", color: "green" }) diff --git a/components/label/components/TopTools.tsx b/components/label/components/TopTools.tsx index 0526d72..ae55a0d 100644 --- a/components/label/components/TopTools.tsx +++ b/components/label/components/TopTools.tsx @@ -1827,7 +1827,7 @@ const TopTools = ( - + {activeImage} From 0cc5bb160ae13884acf495b48f5ee2cd0a2fc1c2 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Wed, 1 Apr 2026 16:24:43 +0800 Subject: [PATCH 11/11] fix(hide): fix bug --- .../label/components/EditorContainer.tsx | 53 +++++++++++---- .../label/components/RightObjectTools.tsx | 65 ++++++++++--------- 2 files changed, 76 insertions(+), 42 deletions(-) diff --git a/components/label/components/EditorContainer.tsx b/components/label/components/EditorContainer.tsx index 89813b1..d0fe8f8 100644 --- a/components/label/components/EditorContainer.tsx +++ b/components/label/components/EditorContainer.tsx @@ -1,18 +1,18 @@ "use client" +import { Box } from "@mantine/core" +import { RichTextEditor } from "@mantine/tiptap" +import "@mantine/tiptap/styles.css" +import { IconPhoto } from "@tabler/icons-react" +import Image from "@tiptap/extension-image" +import Link from "@tiptap/extension-link" +import Placeholder from "@tiptap/extension-placeholder" +import Underline from "@tiptap/extension-underline" import { useEditor } from "@tiptap/react" import StarterKit from "@tiptap/starter-kit" -import Underline from "@tiptap/extension-underline" -import Link from "@tiptap/extension-link" -import Image from "@tiptap/extension-image" -import Placeholder from "@tiptap/extension-placeholder" -import { RichTextEditor } from "@mantine/tiptap" -import { useTopToolsStore } from "../useTopToolsStore" -import { useKeyEventStore } from "../store" import { useEffect, useRef, type ChangeEvent } from "react" -import { Box } from "@mantine/core" -import { IconPhoto } from "@tabler/icons-react" -import "@mantine/tiptap/styles.css" +import { useKeyEventStore } from "../store" +import { useTopToolsStore } from "../useTopToolsStore" export const splitWord = "@1024-" @@ -22,6 +22,30 @@ const getEditorHtml = (rawValue?: string) => { return html ?? rawValue } +const editorLabels = { + boldControlLabel: "加粗", + italicControlLabel: "斜体", + underlineControlLabel: "下划线", + strikeControlLabel: "删除线", + clearFormattingControlLabel: "清除格式", + codeControlLabel: "行内代码", + h1ControlLabel: "一级标题", + h2ControlLabel: "二级标题", + h3ControlLabel: "三级标题", + h4ControlLabel: "四级标题", + bulletListControlLabel: "无序列表", + orderedListControlLabel: "有序列表", + blockquoteControlLabel: "引用", + hrControlLabel: "分割线", + linkControlLabel: "插入链接", + unlinkControlLabel: "移除链接", + linkEditorInputLabel: "输入链接地址", + linkEditorInputPlaceholder: "请输入链接,例如:https://example.com", + linkEditorExternalLink: "在新标签页打开", + linkEditorInternalLink: "在当前标签页打开", + linkEditorSave: "保存", +} + interface EditorContainerProps { value: string onChange: (value: string) => void @@ -96,7 +120,10 @@ const EditorContainer = ({ borderRadius: "8px", padding: "4px", }}> - + @@ -129,8 +156,8 @@ const EditorContainer = ({ fileInputRef.current?.click()} - aria-label="Upload image" - title="Upload image"> + aria-label="上传图片" + title="上传图片"> diff --git a/components/label/components/RightObjectTools.tsx b/components/label/components/RightObjectTools.tsx index 315101a..b51b728 100644 --- a/components/label/components/RightObjectTools.tsx +++ b/components/label/components/RightObjectTools.tsx @@ -379,6 +379,33 @@ const RightObjectTools: React.FC = (props) => { }) } + const getTagVisibility = (tagCategory?: string) => { + const { showTags, showTagsConfigs } = useTopToolsStore.getState() + if (!showTags) return false + if (tagCategory === "mask") return showTagsConfigs.includes("外框") + if (tagCategory === "point_text") return showTagsConfigs.includes("点ID") + return true + } + + const syncObjectItemsVisibility = useCallback( + (objectId: number, hide: boolean) => { + const sameIdItems = getItemsById(objectId) + if (!sameIdItems?.length) return + sameIdItems.forEach((item) => { + if (hide) { + item.visible = false + return + } + if (item.data?.type === "tag") { + item.visible = getTagVisibility(item.data?.tag_category) + return + } + item.visible = true + }) + }, + [getItemsById] + ) + const operationHide = useCallback( (operationId: string, hide: boolean) => { updateOperationStatus(activeImage + operationId, "HIDE", hide) @@ -388,19 +415,15 @@ const RightObjectTools: React.FC = (props) => { ?.get(operationId) ?.map((child) => { updatePathStatus(activeImage + child[0], "HIDE", hide) - if (getItemsById(child[0]) && getItemsById(child[0]).length) { - getItemsById(child[0]).forEach((item) => { - item.visible = !hide - }) - // 隐藏时清空选中状态 - if (hide) setSelectedItemFalse(child[0]) - } + syncObjectItemsVisibility(child[0], hide) + // 隐藏时清空选中状态 + if (hide) setSelectedItemFalse(child[0]) }) }, [ activeImage, - getItemsById, storeLabel, + syncObjectItemsVisibility, updateOperationStatus, updatePathStatus, ] @@ -410,11 +433,9 @@ const RightObjectTools: React.FC = (props) => { (operationId: string, objectId: number, hide: boolean) => { if (hide) { updatePathStatus(activeImage + objectId, "HIDE", hide) - if (getItemsById(objectId) && getItemsById(objectId).length) { - getItemsById(objectId).forEach((item) => (item.visible = !hide)) - // 隐藏时清空选中状态 - setSelectedItemFalse(objectId) - } + syncObjectItemsVisibility(objectId, hide) + // 隐藏时清空选中状态 + setSelectedItemFalse(objectId) let childrenHideStatus = storeLabel .get(activeImage) ?.get(operationId) @@ -427,28 +448,14 @@ const RightObjectTools: React.FC = (props) => { } else { updateOperationStatus(activeImage + operationId, "HIDE", hide) updatePathStatus(activeImage + objectId, "HIDE", hide) - const configs = useTopToolsStore.getState().showTagsConfigs - if (getItemsById(objectId) && getItemsById(objectId).length) - getItemsById(objectId).forEach((item) => { - let visible = !hide - if (item.data.type === "tag") { - if (item.data.tag_category === "mask") { - visible = configs.includes("外框") - } else if (item.data.tag_category === "point_text") { - visible = configs.includes("点ID") - } else { - visible = true - } - } - item.visible = visible - }) + syncObjectItemsVisibility(objectId, hide) } }, [ activeImage, getItemById, - getItemsById, storeLabel, + syncObjectItemsVisibility, updateOperationStatus, updatePathStatus, ]