diff --git a/frontend/README.md b/frontend/README.md index b4fcd04d0..014a0accf 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,4 +1,4 @@ -🚀 快速开始 +## 🚀 快速开始 ``` npm install # 安装依赖 @@ -6,7 +6,7 @@ npm run dev # 启动项目 npm run mock # 启动后台Mock服务(可选) ``` -📁 项目结构 +## 📁 项目结构 ``` frontend/ @@ -94,3 +94,10 @@ frontend/ ├── vite.config.ts # 开源协议 └── pom.xml # Maven根配置 ``` + +## 开发新功能 +- 安装开发依赖: + +```bash +npm install xxx +``` \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 90ce8d1f2..9ab17a993 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,12 +11,14 @@ "@reduxjs/toolkit": "^2.11.2", "@xyflow/react": "^12.8.3", "antd": "^5.27.0", + "i18next": "^25.8.0", "jssha": "^3.3.1", "lucide-react": "^0.539.0", "react": "^18.1.1", "react-dom": "^18.1.1", "react-force-graph-2d": "^1.29.0", "react-force-graph-3d": "^1.29.0", + "react-i18next": "^16.5.4", "react-markdown": "^10.1.0", "react-redux": "^9.2.0", "react-router": "^7.8.0", @@ -4664,6 +4666,15 @@ "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", "license": "CC0-1.0" }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -4701,6 +4712,37 @@ "node": ">= 0.8" } }, + "node_modules/i18next": { + "version": "25.8.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz", + "integrity": "sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -7722,6 +7764,33 @@ "react": "*" } }, + "node_modules/react-i18next": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.4.tgz", + "integrity": "sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 25.6.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -8831,7 +8900,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -9041,9 +9110,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -9219,6 +9288,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index f2e0399fc..21901e36a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,12 +14,14 @@ "@reduxjs/toolkit": "^2.11.2", "@xyflow/react": "^12.8.3", "antd": "^5.27.0", + "i18next": "^25.8.0", "jssha": "^3.3.1", "lucide-react": "^0.539.0", "react": "^18.1.1", "react-dom": "^18.1.1", "react-force-graph-2d": "^1.29.0", "react-force-graph-3d": "^1.29.0", + "react-i18next": "^16.5.4", "react-markdown": "^10.1.0", "react-redux": "^9.2.0", "react-router": "^7.8.0", diff --git a/frontend/public/config/error-code.json b/frontend/public/config/error-code.json new file mode 100644 index 000000000..17137f5e8 --- /dev/null +++ b/frontend/public/config/error-code.json @@ -0,0 +1,20 @@ +{ + "400": "请求参数错误", + "401": "登录已过期,请重新登录", + "403": "没有权限访问该资源", + "404": "请求的资源不存在", + "500": "服务器内部错误,请稍后重试", + "502": "网关错误", + "op.0001": "不支持的文件类型", + "op.0002": "算子中缺少元数据文件", + "op.0003": "缺少必要的字段", + "op.0004": "settings字段解析失败", + "op.0005": "算子ID已存在", + "op.0006": "算子名称已存在", + "op.0007": "算子已被编排在模板或未完成的任务中", + "op.0008": "预置算子无法删除", + "clean.0001": "清洗任务名称重复", + "clean.0002": "任务列表为空", + "clean.0003": "算子输入输出不匹配", + "clean.0004": "算子执行器不匹配" +} \ No newline at end of file diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts new file mode 100644 index 000000000..8b3935b8d --- /dev/null +++ b/frontend/src/i18n/index.ts @@ -0,0 +1,29 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import zhCommon from './locales/zh/common.json'; +import enCommon from './locales/en/common.json'; + +// Get saved language from localStorage, default to 'zh' +const savedLanguage = localStorage.getItem('language') || 'zh'; + +i18n + .use(initReactI18next) + .init({ + resources: { + zh: { + common: zhCommon, + }, + en: { + common: enCommon, + }, + }, + lng: savedLanguage, + fallbackLng: 'zh', + ns: ['common'], + defaultNS: 'common', + interpolation: { + escapeValue: false, + }, + }); + +export default i18n; diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json new file mode 100644 index 000000000..791fc98ce --- /dev/null +++ b/frontend/src/i18n/locales/en/common.json @@ -0,0 +1,275 @@ +{ + "header": { + "simplifiedChinese": "简体中文", + "english": "English" + }, + "user": { + "actions": { + "login": "Login", + "register": "Register", + "logout": "Logout" + }, + "messages": { + "loginSuccess": "Login successful", + "loginFailed": "Login failed, please try again later", + "signupSuccess": "Registration successful, auto-login", + "signupFailed": "Registration failed, please try again later", + "logoutSuccess": "Logged out successfully", + "passwordMismatch": "Passwords do not match" + }, + "loginDialog": { + "title": "Login", + "description": "Enter your username and password to log in to the system", + "submitButton": "Login", + "noAccount": "Don't have an account?", + "registerNow": "Register now" + }, + "signupDialog": { + "title": "Register", + "description": "Create your account to use DataMate system", + "submitButton": "Register", + "hasAccount": "Already have an account?", + "loginNow": "Login now" + }, + "fields": { + "username": "Username", + "email": "Email", + "password": "Password", + "confirmPassword": "Confirm Password" + }, + "placeholders": { + "username": "Enter username", + "email": "Enter email", + "password": "Enter password", + "confirmPassword": "Enter password again" + }, + "validations": { + "usernameRequired": "Please enter username", + "emailRequired": "Please enter email", + "emailInvalid": "Please enter a valid email address", + "passwordRequired": "Please enter password", + "passwordTooShort": "Password must be at least 6 characters", + "confirmPasswordRequired": "Please confirm password", + "passwordMismatch": "Passwords do not match" + } + }, + "dataCollection": { + "title": "Data Collection", + "tabs": { + "taskManagement": "Task Management", + "taskExecution": "Execution Records", + "taskTemplate": "Template Management" + }, + "taskManagement": { + "filters": { + "statusFilter": "Status Filter", + "allStatus": "All Status", + "searchPlaceholder": "Search task name..." + }, + "columns": { + "taskName": "Task Name", + "status": "Status", + "template": "Template", + "syncMode": "Sync Mode", + "cronExpression": "Cron Expression", + "timeout": "Timeout", + "description": "Description", + "createdAt": "Created At", + "updatedAt": "Updated At", + "actions": "Actions" + }, + "actions": { + "start": "Start", + "stop": "Stop", + "executionRecords": "Execution Records", + "delete": "Delete" + }, + "messages": { + "startSuccess": "Task start request sent", + "stopSuccess": "Task stop request sent", + "deleteSuccess": "Task deleted", + "deleteConfirm": "Are you sure you want to delete this task? This action cannot be undone.", + "confirmDelete": "Delete", + "cancel": "Cancel" + } + }, + "templateManagement": { + "filters": { + "templateType": "Template Type", + "all": "All", + "builtIn": "Built-in", + "custom": "Custom", + "searchPlaceholder": "Search template name..." + }, + "columns": { + "templateName": "Template Name", + "templateType": "Template Type", + "source": "Source", + "target": "Target", + "description": "Description", + "createdAt": "Created At", + "updatedAt": "Updated At" + }, + "messages": { + "refreshFailed": "Refresh failed" + } + }, + "execution": { + "filters": { + "statusFilter": "Status Filter", + "allStatus": "All Status", + "running": "Running", + "success": "Success", + "failed": "Failed", + "stopped": "Stopped", + "searchPlaceholder": "Search task name..." + }, + "duration": { + "minutesSuffix": "min", + "secondsSuffix": "s" + }, + "messages": { + "loadLogFailed": "Failed to load log" + }, + "triggerType": { + "manual": "Manual", + "scheduled": "Scheduled", + "api": "API" + }, + "columns": { + "taskName": "Task Name", + "status": "Status", + "startTime": "Start Time", + "endTime": "End Time", + "duration": "Duration", + "errorMessage": "Error Message", + "actions": "Actions" + }, + "actions": { + "viewLog": "View Log" + }, + "modal": { + "title": "Execution Log", + "downloadLog": "Download Log", + "close": "Close", + "loading": "Loading...", + "empty": "(empty)" + }, + "datePicker": { + "startTime": "Start Time", + "endTime": "End Time" + } + }, + "createTask": { + "title": "Create Collection Task", + "back": "Back", + "cancel": "Cancel", + "submit": "Create Task", + "actions": { + "formatJson": "Format JSON" + }, + "basicInfo": { + "title": "Basic Information", + "name": "Name", + "namePlaceholder": "Please enter task name", + "nameRequired": "Please enter task name", + "timeout": "Timeout (seconds)", + "timeoutPlaceholder": "Default 3600", + "timeoutRequired": "Please enter timeout", + "description": "Description", + "descriptionPlaceholder": "Please enter task description" + }, + "syncConfig": { + "title": "Sync Configuration", + "syncMode": { + "label": "Sync Mode", + "once": "Once", + "scheduled": "Scheduled" + }, + "cronRequired": "Please enter Cron expression" + }, + "templateConfig": { + "title": "Template Configuration", + "selectTemplate": "Select Template", + "selectTemplatePlaceholder": "Please select collection template", + "selectTemplateRequired": "Please select collection template" + }, + "templateParams": { + "title": "Template Parameters" + }, + "sourceParams": { + "title": "Source Parameters" + }, + "targetParams": { + "title": "Target Parameters" + }, + "messages": { + "loadTemplatesFailed": "Failed to load collection templates", + "createSuccess": "Task created successfully", + "errorWithDetail": "{message}: {detail}", + "jsonObjectRequired": "Must be a JSON object", + "jsonObjectInvalid": "Please enter a valid JSON object", + "jsonFormatError": "Invalid JSON format", + "jsonFormatErrorWithMessage": "Invalid JSON format: {message}" + }, + "placeholders": { + "enter": "Please enter", + "select": "Please select", + "enterWithLabel": "Please enter {label}", + "selectWithLabel": "Please select {label}" + } + }, + "scheduler": { + "monthDaySuffix": " day", + "labels": { + "executionPeriod": "Execution Period", + "executionDate": "Execution Date", + "executionTime": "Execution Time", + "selectWeekday": "Select weekday", + "selectDate": "Select date", + "selectTime": "Select time" + }, + "periods": { + "once": "Once", + "daily": "Daily", + "weekly": "Weekly", + "monthly": "Monthly" + }, + "weekdays": { + "sunday": "Sunday", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday" + }, + "timePresets": { + "morning9": "9:00 AM", + "noon12": "12:00 PM", + "afternoon2": "2:00 PM", + "afternoon6": "6:00 PM", + "evening8": "8:00 PM", + "midnight0": "12:00 AM" + } + } + }, + "common": { + "actions": { + "createTask": "Create Task" + }, + "placeholders": { + "empty": "-" + }, + "status": { + "task": { + "running": "Running", + "stopped": "Stopped", + "failed": "Failed", + "completed": "Completed", + "draft": "Draft", + "pending": "Ready" + } + } + } +} diff --git a/frontend/src/i18n/locales/zh/common.json b/frontend/src/i18n/locales/zh/common.json new file mode 100644 index 000000000..a091f84b6 --- /dev/null +++ b/frontend/src/i18n/locales/zh/common.json @@ -0,0 +1,275 @@ +{ + "header": { + "simplifiedChinese": "简体中文", + "english": "English" + }, + "user": { + "actions": { + "login": "登录", + "register": "注册", + "logout": "退出登录" + }, + "messages": { + "loginSuccess": "登录成功", + "loginFailed": "登录失败,请稍后重试", + "signupSuccess": "注册成功,已自动登录", + "signupFailed": "注册失败,请稍后重试", + "logoutSuccess": "已退出登录", + "passwordMismatch": "两次输入的密码不一致" + }, + "loginDialog": { + "title": "登录", + "description": "请输入您的用户名和密码登录系统", + "submitButton": "登录", + "noAccount": "还没有账号?", + "registerNow": "立即注册" + }, + "signupDialog": { + "title": "注册", + "description": "创建您的账户以使用 DataMate 系统", + "submitButton": "注册", + "hasAccount": "已有账号?", + "loginNow": "立即登录" + }, + "fields": { + "username": "用户名", + "email": "邮箱", + "password": "密码", + "confirmPassword": "确认密码" + }, + "placeholders": { + "username": "请输入用户名", + "email": "请输入邮箱", + "password": "请输入密码", + "confirmPassword": "请再次输入密码" + }, + "validations": { + "usernameRequired": "请输入用户名", + "emailRequired": "请输入邮箱", + "emailInvalid": "请输入有效的邮箱地址", + "passwordRequired": "请输入密码", + "passwordTooShort": "密码至少需要6个字符", + "confirmPasswordRequired": "请确认密码", + "passwordMismatch": "两次输入的密码不一致" + } + }, + "dataCollection": { + "title": "数据归集", + "tabs": { + "taskManagement": "任务管理", + "taskExecution": "执行记录", + "taskTemplate": "模板管理" + }, + "taskManagement": { + "filters": { + "statusFilter": "状态筛选", + "allStatus": "全部状态", + "searchPlaceholder": "搜索任务名称..." + }, + "columns": { + "taskName": "任务名称", + "status": "状态", + "template": "所用模板", + "syncMode": "同步方式", + "cronExpression": "Cron调度表达式", + "timeout": "超时时间", + "description": "描述", + "createdAt": "创建时间", + "updatedAt": "更新时间", + "actions": "操作" + }, + "actions": { + "start": "启动", + "stop": "停止", + "executionRecords": "执行记录", + "delete": "删除" + }, + "messages": { + "startSuccess": "任务启动请求已发送", + "stopSuccess": "任务停止请求已发送", + "deleteSuccess": "任务已删除", + "deleteConfirm": "确定要删除该任务吗?此操作不可撤销。", + "confirmDelete": "删除", + "cancel": "取消" + } + }, + "templateManagement": { + "filters": { + "templateType": "模板类型", + "all": "全部", + "builtIn": "内置", + "custom": "自定义", + "searchPlaceholder": "搜索模板名称..." + }, + "columns": { + "templateName": "模板名称", + "templateType": "模板类型", + "source": "源端", + "target": "目标端", + "description": "描述", + "createdAt": "创建时间", + "updatedAt": "更新时间" + }, + "messages": { + "refreshFailed": "刷新失败" + } + }, + "execution": { + "filters": { + "statusFilter": "状态筛选", + "allStatus": "全部状态", + "running": "运行中", + "success": "成功", + "failed": "失败", + "stopped": "停止", + "searchPlaceholder": "搜索任务名称..." + }, + "duration": { + "minutesSuffix": "分钟", + "secondsSuffix": "秒" + }, + "messages": { + "loadLogFailed": "日志加载失败" + }, + "triggerType": { + "manual": "手动", + "scheduled": "定时", + "api": "API" + }, + "columns": { + "taskName": "任务名称", + "status": "状态", + "startTime": "开始时间", + "endTime": "结束时间", + "duration": "执行时长", + "errorMessage": "错误信息", + "actions": "操作" + }, + "actions": { + "viewLog": "查看日志" + }, + "modal": { + "title": "执行日志", + "downloadLog": "下载日志", + "close": "关闭", + "loading": "Loading...", + "empty": "(empty)" + }, + "datePicker": { + "startTime": "开始时间", + "endTime": "结束时间" + } + }, + "createTask": { + "title": "创建归集任务", + "back": "返回", + "cancel": "取消", + "submit": "创建任务", + "actions": { + "formatJson": "格式化JSON" + }, + "basicInfo": { + "title": "基本信息", + "name": "名称", + "namePlaceholder": "请输入任务名称", + "nameRequired": "请输入任务名称", + "timeout": "超时时间(秒)", + "timeoutPlaceholder": "默认 3600", + "timeoutRequired": "请输入超时时间", + "description": "描述", + "descriptionPlaceholder": "请输入任务描述" + }, + "syncConfig": { + "title": "同步配置", + "syncMode": { + "label": "同步方式", + "once": "立即执行", + "scheduled": "定时执行" + }, + "cronRequired": "请输入Cron表达式" + }, + "templateConfig": { + "title": "模板配置", + "selectTemplate": "选择模板", + "selectTemplatePlaceholder": "请选择归集模板", + "selectTemplateRequired": "请选择归集模板" + }, + "templateParams": { + "title": "模板参数" + }, + "sourceParams": { + "title": "源端参数" + }, + "targetParams": { + "title": "目标端参数" + }, + "messages": { + "loadTemplatesFailed": "加载归集模板失败", + "createSuccess": "任务创建成功", + "errorWithDetail": "{message}:{detail}", + "jsonObjectRequired": "必须是JSON对象", + "jsonObjectInvalid": "请输入合法的JSON对象", + "jsonFormatError": "JSON格式错误", + "jsonFormatErrorWithMessage": "JSON格式错误:{message}" + }, + "placeholders": { + "enter": "请输入", + "select": "请选择", + "enterWithLabel": "请输入{label}", + "selectWithLabel": "请选择{label}" + } + }, + "scheduler": { + "monthDaySuffix": "日", + "labels": { + "executionPeriod": "执行周期", + "executionDate": "执行日期", + "executionTime": "执行时间", + "selectWeekday": "选择周几", + "selectDate": "选择日期", + "selectTime": "选择时间" + }, + "periods": { + "once": "仅执行一次", + "daily": "每天执行", + "weekly": "每周执行", + "monthly": "每月执行" + }, + "weekdays": { + "sunday": "周日", + "monday": "周一", + "tuesday": "周二", + "wednesday": "周三", + "thursday": "周四", + "friday": "周五", + "saturday": "周六" + }, + "timePresets": { + "morning9": "上午 9:00", + "noon12": "中午 12:00", + "afternoon2": "下午 2:00", + "afternoon6": "下午 6:00", + "evening8": "晚上 8:00", + "midnight0": "午夜 0:00" + } + } + }, + "common": { + "actions": { + "createTask": "创建任务" + }, + "placeholders": { + "empty": "-" + }, + "status": { + "task": { + "running": "运行", + "stopped": "停止", + "failed": "错误", + "completed": "成功", + "draft": "草稿", + "pending": "就绪" + } + } + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2a96cbcf5..95de11ee2 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -8,18 +8,42 @@ import TopLoadingBar from "./components/TopLoadingBar"; import { store } from "./store"; import { Provider } from "react-redux"; import theme from "./theme"; +import {errorConfigStore} from "@/utils/errorConfigStore.ts"; +import "@/i18n"; -createRoot(document.getElementById("root")!).render( - - - - - }> - - - - - - - -); +async function bootstrap() { + const container = document.getElementById("root"); + if (!container) return; + + const root = createRoot(container); + + try { + // 2. 【关键步骤】在渲染前,等待配置文件加载完成 + // 这一步会发起 fetch 请求去拿 /config/error-code.json + await errorConfigStore.loadConfig(); + + } catch (e) { + // 容错处理:即使配置文件加载失败(比如404),也不应该导致整个 App 白屏崩溃 + // 此时 App 会使用代码里的默认兜底文案 + console.error('Error config load failed, using default messages.', e); + } finally { + // 3. 无论配置加载成功与否,最后都执行渲染 + root.render( + + + + + }> + + + + + + + + ); + } +} + +// 4. 执行启动 +bootstrap(); diff --git a/frontend/src/pages/DataCleansing/Create/CreateTask.tsx b/frontend/src/pages/DataCleansing/Create/CreateTask.tsx index 7a255704b..031cadbc4 100644 --- a/frontend/src/pages/DataCleansing/Create/CreateTask.tsx +++ b/frontend/src/pages/DataCleansing/Create/CreateTask.tsx @@ -43,9 +43,9 @@ export default function CleansingTaskCreate() { outputs: item.outputs, })), }; - navigate("/data/cleansing?view=task"); await createCleaningTaskUsingPost(task); message.success("任务已创建"); + navigate("/data/cleansing?view=task"); }; const canProceed = () => { diff --git a/frontend/src/pages/DataCleansing/Create/components/OperatorOrchestration.tsx b/frontend/src/pages/DataCleansing/Create/components/OperatorOrchestration.tsx index 75ec56e4c..64b66f0ea 100644 --- a/frontend/src/pages/DataCleansing/Create/components/OperatorOrchestration.tsx +++ b/frontend/src/pages/DataCleansing/Create/components/OperatorOrchestration.tsx @@ -181,7 +181,7 @@ const OperatorFlow: React.FC = ({ {operator?.categories?.map((categoryId) => { - return {categoryMap[categoryId].name} + return {categoryMap[categoryId]?.name} })} {/* 操作按钮 */} ([]); const [templatesLoading, setTemplatesLoading] = useState(false); @@ -72,7 +73,7 @@ export default function CollectionTaskCreate() { const list: CollectionTemplate[] = resp?.data?.content || []; setTemplates(list); } catch (e) { - message.error("加载归集模板失败"); + message.error(t("dataCollection.createTask.messages.loadTemplatesFailed")); } finally { setTemplatesLoading(false); } @@ -87,17 +88,17 @@ export default function CollectionTaskCreate() { if (!trimmed) return undefined; const parsed = JSON.parse(trimmed); if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") { - throw new Error("必须是JSON对象"); + throw new Error(t("dataCollection.createTask.messages.jsonObjectRequired")); } return parsed; } if (typeof value === "object") { if (Array.isArray(value) || value === null) { - throw new Error("必须是JSON对象"); + throw new Error(t("dataCollection.createTask.messages.jsonObjectRequired")); } return value; } - throw new Error("必须是JSON对象"); + throw new Error(t("dataCollection.createTask.messages.jsonObjectRequired")); }; const tryFormatJsonValue = (value: any) => { @@ -114,7 +115,7 @@ export default function CollectionTaskCreate() { form.setFieldValue(name, formatted); } } catch (error: any) { - message.error(error?.message || "JSON格式错误"); + message.error(error?.message || t("dataCollection.createTask.messages.jsonFormatError")); } }; @@ -171,7 +172,7 @@ export default function CollectionTaskCreate() { payload.scheduleExpression = scheduleExpression.cronExpression; } if (!payload.scheduleExpression) { - message.error("请输入Cron表达式"); + message.error(t("dataCollection.createTask.syncConfig.cronRequired")); return; } } else { @@ -195,10 +196,15 @@ export default function CollectionTaskCreate() { }; } await createTaskUsingPost(payload); - message.success("任务创建成功"); + message.success(t("dataCollection.createTask.messages.createSuccess")); navigate("/data/collection"); } catch (error) { - message.error(`${error?.data?.message}:${error?.data?.data}`); + message.error( + t("dataCollection.createTask.messages.errorWithDetail", { + message: error?.data?.message ?? "", + detail: error?.data?.data ?? "", + }) + ); } }; @@ -222,7 +228,10 @@ export default function CollectionTaskCreate() { const required = def?.required !== false; const rules: any[] = []; if (required) { - rules.push({ required: true, message: `请输入${label}` }); + rules.push({ + required: true, + message: t("dataCollection.createTask.placeholders.enterWithLabel", { label }), + }); } if (fieldType === "jsonobject") { rules.push({ @@ -240,7 +249,11 @@ export default function CollectionTaskCreate() { } catch (e) { return Promise.reject( new Error( - `JSON格式错误:${(e as Error)?.message || "请输入合法的JSON对象"}` + t("dataCollection.createTask.messages.jsonFormatErrorWithMessage", { + message: + (e as Error)?.message || + t("dataCollection.createTask.messages.jsonObjectInvalid"), + }) ) ); } @@ -259,7 +272,12 @@ export default function CollectionTaskCreate() { tooltip={description} rules={rules} > - + )); break; @@ -274,13 +292,16 @@ export default function CollectionTaskCreate() { extra={(
)} >