fix(ci): add file
This commit is contained in:
28
.gitlab-ci.yml
Normal file
28
.gitlab-ci.yml
Normal file
@@ -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"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export const BASE_LABEL_API =
|
export const BASE_LABEL_API =
|
||||||
process.env.NEXT_PUBLIC_ENV === "production"
|
process.env.NEXT_PUBLIC_ENV === "production"
|
||||||
? "https://label.cowarobot.com"
|
? "https://label.soft.cowarobot.com"
|
||||||
: process.env.NEXT_PUBLIC_ENV === "staging"
|
: process.env.NEXT_PUBLIC_ENV === "staging"
|
||||||
? "http://172.16.115.128:9110"
|
? "http://172.16.115.128:9110"
|
||||||
: "https://label.softtest.cowarobot.com"
|
: "https://label.softtest.cowarobot.com"
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ import "@mantine/tiptap/styles.css"
|
|||||||
|
|
||||||
export const splitWord = "@1024-"
|
export const splitWord = "@1024-"
|
||||||
|
|
||||||
|
const getEditorHtml = (rawValue?: string) => {
|
||||||
|
if (!rawValue) return ""
|
||||||
|
const [html] = rawValue.split(splitWord)
|
||||||
|
return html ?? rawValue
|
||||||
|
}
|
||||||
|
|
||||||
interface EditorContainerProps {
|
interface EditorContainerProps {
|
||||||
value: string
|
value: string
|
||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
@@ -41,11 +47,11 @@ const EditorContainer = ({
|
|||||||
Image,
|
Image,
|
||||||
Placeholder.configure({ placeholder: "请输入内容..." }),
|
Placeholder.configure({ placeholder: "请输入内容..." }),
|
||||||
],
|
],
|
||||||
content: value,
|
content: getEditorHtml(value),
|
||||||
editable: !disabled && !isView,
|
editable: !disabled && !isView,
|
||||||
onUpdate: ({ editor }) => {
|
onUpdate: ({ editor }) => {
|
||||||
// Preserve splitWord format
|
const html = getEditorHtml(editor.getHTML())
|
||||||
onChange(`${editor.getHTML()}${splitWord}${editor.getText()}`)
|
onChange(`${html}${splitWord}${editor.getText()}`)
|
||||||
},
|
},
|
||||||
onFocus: () => {
|
onFocus: () => {
|
||||||
useKeyEventStore.getState().setFocusInput(true)
|
useKeyEventStore.getState().setFocusInput(true)
|
||||||
@@ -63,7 +69,7 @@ const EditorContainer = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor) return
|
if (!editor) return
|
||||||
const incomingHtml = value ? value : "<p></p>"
|
const incomingHtml = getEditorHtml(value) || "<p></p>"
|
||||||
if (editor.getHTML() !== incomingHtml) {
|
if (editor.getHTML() !== incomingHtml) {
|
||||||
// Keep editor content in sync with external state, including empty value switch.
|
// Keep editor content in sync with external state, including empty value switch.
|
||||||
editor.commands.setContent(incomingHtml, { emitUpdate: false })
|
editor.commands.setContent(incomingHtml, { emitUpdate: false })
|
||||||
@@ -91,7 +97,7 @@ const EditorContainer = ({
|
|||||||
padding: "4px",
|
padding: "4px",
|
||||||
}}>
|
}}>
|
||||||
<RichTextEditor editor={editor} style={{ border: 0 }}>
|
<RichTextEditor editor={editor} style={{ border: 0 }}>
|
||||||
<RichTextEditor.Toolbar sticky stickyOffset={60}>
|
<RichTextEditor.Toolbar>
|
||||||
<RichTextEditor.ControlsGroup>
|
<RichTextEditor.ControlsGroup>
|
||||||
<RichTextEditor.Bold />
|
<RichTextEditor.Bold />
|
||||||
<RichTextEditor.Italic />
|
<RichTextEditor.Italic />
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { Task } from "../api/task/typing"
|
|||||||
import { useBottomToolsStore } from "../useBottomToolsStore"
|
import { useBottomToolsStore } from "../useBottomToolsStore"
|
||||||
import { useDescToolsStore } from "../useDescToolsStore"
|
import { useDescToolsStore } from "../useDescToolsStore"
|
||||||
import { useTopToolsStore } from "../useTopToolsStore"
|
import { useTopToolsStore } from "../useTopToolsStore"
|
||||||
|
import { safeClone } from "../utils/clone"
|
||||||
import EditorContainer from "./EditorContainer"
|
import EditorContainer from "./EditorContainer"
|
||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
@@ -40,46 +41,67 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isUpdatingFromStore = useRef(false)
|
const isUpdatingFromStore = useRef(false)
|
||||||
|
const syncTimerRef = useRef<ReturnType<typeof setTimeout> | 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
|
if (isUpdatingFromStore.current) return
|
||||||
|
|
||||||
const data = form.values as any
|
const currentDescData = safeClone(useDescToolsStore.getState().descData)
|
||||||
let dataMap = new Map()
|
const updateMap = new Map()
|
||||||
taskDetail?.data_name.forEach((detail_name) => {
|
Object.entries(values).forEach(([label_class, val]: any) => {
|
||||||
if (detail_name.name[0] !== activeImage) {
|
let value = { ...val }
|
||||||
if (useDescToolsStore.getState().descData.has(detail_name.name[0])) {
|
if (value.is_correct !== undefined) {
|
||||||
dataMap.set(
|
value.is_correct = value.is_correct === "true"
|
||||||
detail_name.name[0],
|
}
|
||||||
useDescToolsStore.getState().descData.get(detail_name.name[0])
|
const keys = Object.keys(value)
|
||||||
)
|
if (keys.includes("value")) {
|
||||||
}
|
updateMap.set(label_class, value)
|
||||||
} else {
|
} else {
|
||||||
const updateMap = new Map()
|
updateMap.set(label_class, { value: value })
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setDescData(dataMap)
|
currentDescData.set(activeImage, updateMap)
|
||||||
|
setDescData(currentDescData)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [form.values])
|
}, [form.values])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (syncTimerRef.current) {
|
||||||
|
clearTimeout(syncTimerRef.current)
|
||||||
|
syncTimerRef.current = null
|
||||||
|
}
|
||||||
isUpdatingFromStore.current = true
|
isUpdatingFromStore.current = true
|
||||||
form.reset()
|
form.reset()
|
||||||
if (activeImage) {
|
if (activeImage) {
|
||||||
@@ -109,6 +131,10 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (updateDescDataFlag) {
|
if (updateDescDataFlag) {
|
||||||
|
if (syncTimerRef.current) {
|
||||||
|
clearTimeout(syncTimerRef.current)
|
||||||
|
syncTimerRef.current = null
|
||||||
|
}
|
||||||
isUpdatingFromStore.current = true
|
isUpdatingFromStore.current = true
|
||||||
const formData = useDescToolsStore
|
const formData = useDescToolsStore
|
||||||
.getState()
|
.getState()
|
||||||
@@ -133,6 +159,15 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [updateDescDataFlag])
|
}, [updateDescDataFlag])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (syncTimerRef.current) {
|
||||||
|
clearTimeout(syncTimerRef.current)
|
||||||
|
syncTimerRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box h="100%">
|
<Box h="100%">
|
||||||
<Box
|
<Box
|
||||||
@@ -156,7 +191,9 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
activeImage,
|
activeImage,
|
||||||
operation.label_class
|
operation.label_class
|
||||||
)
|
)
|
||||||
const hasComment = currentData?.comment
|
const hasComment =
|
||||||
|
!!(form.values as any)?.[operation.label_class]?.comment ||
|
||||||
|
!!currentData?.comment
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={operation.label_class}>
|
<Box key={operation.label_class}>
|
||||||
@@ -171,9 +208,17 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
{operation.label_class}
|
{operation.label_class}
|
||||||
</Text>
|
</Text>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
{...form.getInputProps(
|
value={
|
||||||
`${operation.label_class}.is_correct`
|
(form.values as any)[operation.label_class]
|
||||||
)}
|
?.is_correct || ""
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
setOperationField(
|
||||||
|
operation.label_class,
|
||||||
|
"is_correct",
|
||||||
|
val
|
||||||
|
)
|
||||||
|
}}
|
||||||
size="xs">
|
size="xs">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Radio
|
<Radio
|
||||||
@@ -195,8 +240,9 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.setFieldValue(
|
setOperationField(
|
||||||
`${operation.label_class}.comment`,
|
operation.label_class,
|
||||||
|
"comment",
|
||||||
"<p></p>"
|
"<p></p>"
|
||||||
)
|
)
|
||||||
}}>
|
}}>
|
||||||
@@ -215,9 +261,18 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
placeholder={`请输入${subDesc.chinese_name}`}
|
placeholder={`请输入${subDesc.chinese_name}`}
|
||||||
size="xs"
|
size="xs"
|
||||||
required={!!subDesc.isrequired}
|
required={!!subDesc.isrequired}
|
||||||
{...form.getInputProps(
|
value={
|
||||||
`${operation.label_class}.${subDesc.chinese_name}`
|
(form.values as any)[operation.label_class]?.[
|
||||||
)}
|
subDesc.chinese_name
|
||||||
|
] || ""
|
||||||
|
}
|
||||||
|
onChange={(event) => {
|
||||||
|
setOperationField(
|
||||||
|
operation.label_class,
|
||||||
|
subDesc.chinese_name,
|
||||||
|
event.currentTarget.value
|
||||||
|
)
|
||||||
|
}}
|
||||||
disabled={!!isView}
|
disabled={!!isView}
|
||||||
/>
|
/>
|
||||||
) : subDesc.select_mode === 1 ? (
|
) : subDesc.select_mode === 1 ? (
|
||||||
@@ -225,9 +280,18 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
label={subDesc.chinese_name}
|
label={subDesc.chinese_name}
|
||||||
size="xs"
|
size="xs"
|
||||||
required={!!subDesc.isrequired}
|
required={!!subDesc.isrequired}
|
||||||
{...form.getInputProps(
|
value={
|
||||||
`${operation.label_class}.${subDesc.chinese_name}`
|
(form.values as any)[operation.label_class]?.[
|
||||||
)}>
|
subDesc.chinese_name
|
||||||
|
] || ""
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
setOperationField(
|
||||||
|
operation.label_class,
|
||||||
|
subDesc.chinese_name,
|
||||||
|
val
|
||||||
|
)
|
||||||
|
}}>
|
||||||
<Group gap="xs" mt={4}>
|
<Group gap="xs" mt={4}>
|
||||||
{subDesc.optional_item.map((option) => (
|
{subDesc.optional_item.map((option) => (
|
||||||
<Radio
|
<Radio
|
||||||
@@ -244,9 +308,18 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
label={subDesc.chinese_name}
|
label={subDesc.chinese_name}
|
||||||
size="xs"
|
size="xs"
|
||||||
required={!!subDesc.isrequired}
|
required={!!subDesc.isrequired}
|
||||||
{...form.getInputProps(
|
value={
|
||||||
`${operation.label_class}.${subDesc.chinese_name}`
|
(form.values as any)[operation.label_class]?.[
|
||||||
)}>
|
subDesc.chinese_name
|
||||||
|
] || []
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
setOperationField(
|
||||||
|
operation.label_class,
|
||||||
|
subDesc.chinese_name,
|
||||||
|
val
|
||||||
|
)
|
||||||
|
}}>
|
||||||
<Group gap="xs" mt={4}>
|
<Group gap="xs" mt={4}>
|
||||||
{subDesc.optional_item.map((option) => (
|
{subDesc.optional_item.map((option) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -266,13 +339,11 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
<Box mb="xs">
|
<Box mb="xs">
|
||||||
<EditorContainer
|
<EditorContainer
|
||||||
value={
|
value={
|
||||||
(form.values as any)[operation.label_class]?.value
|
(form.values as any)[operation.label_class]?.value ||
|
||||||
|
""
|
||||||
}
|
}
|
||||||
onChange={(val: any) =>
|
onChange={(val: any) =>
|
||||||
form.setFieldValue(
|
setOperationField(operation.label_class, "value", val)
|
||||||
`${operation.label_class}.value`,
|
|
||||||
val
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -282,14 +353,16 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
<EditorContainer
|
<EditorContainer
|
||||||
color="border-danger"
|
color="border-danger"
|
||||||
value={
|
value={
|
||||||
(form.values as any)[operation.label_class]?.comment
|
(form.values as any)[operation.label_class]
|
||||||
|
?.comment || ""
|
||||||
}
|
}
|
||||||
onChange={(val: any) =>
|
onChange={(val: any) => {
|
||||||
form.setFieldValue(
|
setOperationField(
|
||||||
`${operation.label_class}.comment`,
|
operation.label_class,
|
||||||
|
"comment",
|
||||||
val
|
val
|
||||||
)
|
)
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Login } from "./types"
|
|||||||
|
|
||||||
const BASE_PATH =
|
const BASE_PATH =
|
||||||
process.env.NEXT_PUBLIC_ENV === "production"
|
process.env.NEXT_PUBLIC_ENV === "production"
|
||||||
? "https://label.cowarobot.com"
|
? "https://label.soft.cowarobot.com"
|
||||||
: process.env.NEXT_PUBLIC_ENV === "staging"
|
: process.env.NEXT_PUBLIC_ENV === "staging"
|
||||||
? "http://172.16.115.128:9110"
|
? "http://172.16.115.128:9110"
|
||||||
: "https://label.softtest.cowarobot.com"
|
: "https://label.softtest.cowarobot.com"
|
||||||
|
|||||||
Reference in New Issue
Block a user