From 055cac97393a26a0a1b192d3e5fc4f00a3b822af Mon Sep 17 00:00:00 2001 From: koushenhai <2413176044@qq.com> Date: Thu, 5 Mar 2026 18:48:04 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20UI=E5=A2=9E=E5=8A=A0=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/config/config.ts | 6 +++ ui/config/routes.ts | 45 ++++++++++----------- ui/src/app.tsx | 33 +++++++++++----- ui/src/pages/Login/index.tsx | 76 +++++++++++++++++++----------------- 4 files changed, 90 insertions(+), 70 deletions(-) diff --git a/ui/config/config.ts b/ui/config/config.ts index d07df62a90..ca07fa740d 100644 --- a/ui/config/config.ts +++ b/ui/config/config.ts @@ -36,6 +36,12 @@ export default defineConfig({ layout: { title: '老寇IoT云平台', }, + locale: { + // umi/max 内置 i18n + default: 'zh-CN', + antd: true, + baseNavigator: true, + }, /** * 路由的配置,不在路由中引入的文件不会编译. * @description 只支持 path,component,routes,redirect,wrappers,title 的配置 diff --git a/ui/config/routes.ts b/ui/config/routes.ts index 51c241d36e..d5268a21ef 100644 --- a/ui/config/routes.ts +++ b/ui/config/routes.ts @@ -17,91 +17,86 @@ export default [ redirect: '/home', }, { - name: '首页', + name: 'menu.home', path: '/home', component: './Home', icon: 'home' }, { - name: 'Login', + name: 'menu.login', path: '/login', component: './Login', layout: false, }, { - name: '系统管理', + name: 'menu.sys', path: '/sys', icon: 'setting', routes: [ { - name: '权限管理', + name: 'menu.sys.permission', path: '/sys/permission', routes: [ { - name: '菜单', - path: '/sys/permission/menu', - component: './Sys/Permission/menu' - }, - { - name: '部门', + name: 'menu.sys.permission.dept', path: '/sys/permission/dept', component: './Sys/Permission/dept' }, { - name: '角色', + name: 'menu.sys.permission.role', path: '/sys/permission/role', component: './Sys/Permission/role' }, { - name: '用户', + name: 'menu.sys.permission.user', path: '/sys/permission/user', component: './Sys/Permission/user' }, ] }, { - name: '日志管理', + name: 'menu.sys.log', path: '/sys/log', routes: [ { - name: '登录日志', + name: 'menu.sys.log.login', path: '/sys/log/login', component: './Sys/Log/login' }, { - name: '通知日志', + name: 'menu.sys.log.notice', path: '/sys/log/notice', component: './Sys/Log/notice' }, { - name: '操作日志', + name: 'menu.sys.log.operate', path: '/sys/log/operate', component: './Sys/Log/operate' } ] }, { - name: '对象存储', + name: 'menu.sys.oss', path: '/sys/oss', routes: [ { - name: '对象存储配置', + name: 'menu.sys.oss.config', path: '/sys/oss/config', component: './Sys/Oss/config' }, { - name: '对象存储日志', + name: 'menu.sys.oss.log', path: '/sys/oss/log', component: './Sys/Oss/log' } ] }, { - name: '系统配置', + name: 'menu.sys.config', path: '/sys/config', routes: [ { - name: '代码生成器', + name: 'menu.sys.config.generator', path: '/sys/config/generator', component: './Sys/Config/generator' } @@ -110,21 +105,21 @@ export default [ ] }, { - name: '物联管理', + name: 'menu.iot', path: '/iot', icon: 'robot', routes: [ { - name: '设备管理', + name: 'menu.iot.device', path: '/iot/device', routes: [ { - name: '物模型', + name: 'menu.iot.device.thingModel', path: '/iot/device/thingModel', component: './IoT/Device/thingModel' }, { - name: '产品类别', + name: 'menu.iot.device.productCategory', path: '/iot/device/productCategory', component: './IoT/Device/productCategory' }, diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 070b15eede..46723f64ac 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -3,7 +3,7 @@ // 全局初始化数据配置,用于 Layout 用户信息和权限初始化 // 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate import {Dropdown, message, theme} from "antd"; -import {history} from "@umijs/max"; +import {history, SelectLang, useIntl} from "@@/exports"; import {HomeOutlined, LogoutOutlined, RobotOutlined, SettingOutlined} from "@ant-design/icons"; import {ReactElement, ReactNode, ReactPortal} from "react"; import {logout, refresh} from '@/services/auth/auth'; @@ -28,7 +28,7 @@ const getIcon = (icon: string) => { const getRouters = (menus: any[]) => { const routers = [{ - name: '首页', + name: 'menu.home', path: '/home', icon: }] @@ -111,6 +111,9 @@ export async function getInitialState(): Promise<{ } export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { + // 新写法:使用 intl.formatMessage,替代 formatMessage() + const intl = useIntl(); + const t = (id: string, values?: Record) => intl.formatMessage({id}, values); return { // 面包屑配置 headerContentRender: () => , @@ -131,6 +134,10 @@ export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { colorPrimary: "#1677ff", fixedHeader: true, siderMenuType: "sub", + actionsRender: () => { + // Ant Design Pro 风格的语言切换组件(来自 umi plugin-locale) + return []; + }, avatarProps: { src: initialState?.avatar, size: 'small', @@ -143,7 +150,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { { key: 'logout', icon: , - label: '注销', + label: t('user.logout'), onClick: async () => { if (refreshTimeoutRef) { clearTimeout(refreshTimeoutRef); @@ -203,6 +210,14 @@ export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { }; }; +const t = (id: string, values?: Record) => { + // 新写法:在非 React 组件/Hook 环境下(如 request errorHandler)使用 getIntl() + // getIntl 来自 umi plugin-locale(@@/exports 导出) + // eslint-disable-next-line @typescript-eslint/no-var-requires + const {getIntl} = require('@@/exports'); + return getIntl().formatMessage({id}, values); +}; + export const request: { responseInterceptors: ((response: any) => any)[]; requestInterceptors: (((config: any) => any) | ((error: any) => any))[]; @@ -211,7 +226,7 @@ export const request: { } = { timeout: 60000, // other axios options you want - errorConfig: { + errorConfig: { errorHandler(error: any) { const {request, response, code} = error; let errorMessage; @@ -219,19 +234,19 @@ export const request: { errorMessage = response.data.error_description } if (response && response.status === 500) { - errorMessage = '服务器内部错误,无法完成请求' + errorMessage = t('error.serverInternal') } if (code === 'ERR_BAD_RESPONSE') { - errorMessage = '网络请求错误,请稍后再试' + errorMessage = t('error.network') } if (response && response.status === 400 && response.data.error === "invalid_grant") { - errorMessage = "令牌续期失败,请重新登录" + errorMessage = t('error.refreshTokenFailed') } if (response && response.status === 404) { - errorMessage = "无法找到 " + request.responseURL + " 请求的资源" + errorMessage = t('error.resourceNotFound', {url: request?.responseURL}) } if (response && response.status === 401 && response.data.error === "invalid_client") { - errorMessage = "无效客户端,请检查认证服务器配置" + errorMessage = t('error.invalidClient') } message.error(errorMessage).then(); }, diff --git a/ui/src/pages/Login/index.tsx b/ui/src/pages/Login/index.tsx index c540b50a9b..240146b241 100644 --- a/ui/src/pages/Login/index.tsx +++ b/ui/src/pages/Login/index.tsx @@ -20,6 +20,7 @@ import {JSEncrypt} from 'jsencrypt'; import {v7 as uuidV7} from 'uuid'; import {clearToken, setToken} from "@/access" import {history} from "@umijs/max"; +import {SelectLang, useIntl} from "@@/exports"; const USERNAME_PASSWORD = {key: 'username_password', label: '用户名密码登录'}; const MOBILE = {key: 'mobile', label: '手机号登录'}; @@ -34,6 +35,8 @@ const iconStyles: CSSProperties = { }; export default () => { + const intl = useIntl(); + const t = (id: string, values?: Record) => intl.formatMessage({id}, values); const items = [ USERNAME_PASSWORD, MOBILE, @@ -196,13 +199,16 @@ export default () => { width: '100vw', }} > +
+ +
} - title="老寇IoT云平台" - subTitle="企业级微服务架构云服务多租户IoT平台" + title={t('app.title')} + subTitle={t('login.subtitle')} loading={loading} actions={
{ }} > - - 其他登录方式 - + + {t('login.otherWays')} +
{ prefix: , autoComplete: 'new-password', }} - placeholder={'请输入租户编码'} + placeholder={t('login.tenantCode.placeholder')} rules={[ { required: true, - message: '请输入租户编码', + message: t('login.tenantCode.required'), }, ]} /> @@ -315,11 +319,11 @@ export default () => { prefix: , autoComplete: 'new-password', }} - placeholder={'请输入用户名'} + placeholder={t('login.username.placeholder')} rules={[ { required: true, - message: '请输入用户名', + message: t('login.username.required'), }, ]} /> @@ -331,11 +335,11 @@ export default () => { prefix: , autoComplete: 'new-password', }} - placeholder={'请输入密码'} + placeholder={t('login.password.placeholder')} rules={[ { required: true, - message: '请输入密码', + message: t('login.password.required'), }, ]} /> @@ -350,16 +354,16 @@ export default () => { autoComplete: 'new-password', }} name="captcha" - placeholder={'请输入验证码'} + placeholder={t('login.captcha.placeholder')} rules={[ { required: true, - message: '请输入验证码', + message: t('login.captcha.required'), }, { pattern: /^[A-Za-z0-9]{4}$/, - message: '请输入4位验证码(数字和字母)' - } + message: t('login.captcha.invalid'), + }, ]} /> @@ -388,16 +392,16 @@ export default () => { autoComplete: 'new-password', }} name="mobile" - placeholder={'请输入手机号'} + placeholder={t('login.mobile.placeholder')} rules={[ { required: true, - message: '请输入手机号', + message: t('login.mobile.required'), }, { pattern: /^1\d{10}$/, - message: '手机号格式错误', - } + message: t('login.mobile.invalid'), + }, ]} /> { captchaProps={{ size: 'large', }} - placeholder={'请输入验证码'} + placeholder={t('login.smsCaptcha.placeholder')} captchaTextRender={(timing, count) => { if (timing) { - return `${count} 获取验证码`; + return t('login.captcha.countdown', {count}); } - return '获取验证码'; + return t('login.captcha.get'); }} name="mobile_captcha" rules={[ { required: true, - message: '请输入验证码', + message: t('login.smsCaptcha.required'), }, { pattern: /^\d{6}$/, - message: '请输入6位数字的验证码' - } + message: t('login.smsCaptcha.invalid'), + }, ]} onGetCaptcha={sendMobileCaptcha} /> @@ -443,15 +447,15 @@ export default () => { autoComplete: 'new-password', }} name="mail" - placeholder={'请输入邮箱'} + placeholder={t('login.mail.placeholder')} rules={[ { required: true, - message: '请输入邮箱', + message: t('login.mail.required'), }, { pattern: /^\w+(-+.\w+)*@\w+(-.\w+)*.\w+(-.\w+)*$/, - message: '邮箱格式错误', + message: t('login.mail.invalid'), }, ]} /> @@ -466,23 +470,23 @@ export default () => { captchaProps={{ size: 'large', }} - placeholder={'请输入验证码'} + placeholder={t('login.mailCaptcha.placeholder')} captchaTextRender={(timing, count) => { if (timing) { - return `${count} 获取验证码`; + return t('login.captcha.countdown', {count}); } - return '获取验证码'; + return t('login.captcha.get'); }} name="mail_captcha" rules={[ { required: true, - message: '请输入验证码', + message: t('login.mailCaptcha.required'), }, { pattern: /^\d{6}$/, - message: '请输入6位数字的验证码' - } + message: t('login.mailCaptcha.invalid'), + }, ]} onGetCaptcha={sendMailCaptcha} /> From f237e46bf141cf2de896a8f3049cfe0f7ef904d4 Mon Sep 17 00:00:00 2001 From: koushenhai <2413176044@qq.com> Date: Thu, 5 Mar 2026 18:49:28 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20UI=E5=A2=9E=E5=8A=A0=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/locales/en-US.ts | 75 +++++++++++++++++++++++++++++++++++++++++ ui/src/locales/zh-CN.ts | 75 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 ui/src/locales/en-US.ts create mode 100644 ui/src/locales/zh-CN.ts diff --git a/ui/src/locales/en-US.ts b/ui/src/locales/en-US.ts new file mode 100644 index 0000000000..cb5a9ab8d0 --- /dev/null +++ b/ui/src/locales/en-US.ts @@ -0,0 +1,75 @@ +export default { + // app + 'app.title': 'KCloud IoT Platform', + + // common + 'common.ok': 'OK', + 'common.cancel': 'Cancel', + 'common.search': 'Search', + 'common.reset': 'Reset', + 'common.add': 'Add', + 'common.edit': 'Edit', + 'common.delete': 'Delete', + 'common.submit': 'Submit', + 'common.close': 'Close', + + // auth/user + 'user.logout': 'Logout', + + // error + 'error.serverInternal': 'Internal server error. Unable to complete the request.', + 'error.network': 'Network request error. Please try again later.', + 'error.refreshTokenFailed': 'Session refresh failed. Please log in again.', + 'error.resourceNotFound': 'Resource not found: {url}', + 'error.invalidClient': 'Invalid client. Please check the auth server configuration.', + + // login + 'login.subtitle': 'Enterprise-grade multi-tenant IoT platform based on microservice architecture', + 'login.otherWays': 'Other sign-in methods', + 'login.tenantCode.placeholder': 'Tenant code', + 'login.tenantCode.required': 'Please enter tenant code', + 'login.username.placeholder': 'Username', + 'login.username.required': 'Please enter username', + 'login.password.placeholder': 'Password', + 'login.password.required': 'Please enter password', + 'login.captcha.placeholder': 'Captcha', + 'login.captcha.required': 'Please enter captcha', + 'login.captcha.invalid': 'Captcha must be 4 characters (letters or numbers)', + 'login.mobile.placeholder': 'Mobile number', + 'login.mobile.required': 'Please enter mobile number', + 'login.mobile.invalid': 'Invalid mobile number', + 'login.smsCaptcha.placeholder': 'Verification code', + 'login.smsCaptcha.required': 'Please enter verification code', + 'login.smsCaptcha.invalid': 'Code must be 6 digits', + 'login.mail.placeholder': 'Email', + 'login.mail.required': 'Please enter email', + 'login.mail.invalid': 'Invalid email address', + 'login.mailCaptcha.placeholder': 'Verification code', + 'login.mailCaptcha.required': 'Please enter verification code', + 'login.mailCaptcha.invalid': 'Code must be 6 digits', + 'login.captcha.get': 'Get code', + 'login.captcha.countdown': '{count}s', + + // menu (routes) + 'menu.home': 'Home', + 'menu.login': 'Login', + 'menu.sys': 'System', + 'menu.sys.permission': 'Permission', + 'menu.sys.permission.menu': 'Menus', + 'menu.sys.permission.dept': 'Departments', + 'menu.sys.permission.role': 'Roles', + 'menu.sys.permission.user': 'Users', + 'menu.sys.log': 'Logs', + 'menu.sys.log.login': 'Login Logs', + 'menu.sys.log.notice': 'Notice Logs', + 'menu.sys.log.operate': 'Operation Logs', + 'menu.sys.oss': 'Object Storage', + 'menu.sys.oss.config': 'OSS Config', + 'menu.sys.oss.log': 'OSS Logs', + 'menu.sys.config': 'System Config', + 'menu.sys.config.generator': 'Code Generator', + 'menu.iot': 'IoT', + 'menu.iot.device': 'Devices', + 'menu.iot.device.thingModel': 'Thing Model', + 'menu.iot.device.productCategory': 'Product Category', +}; diff --git a/ui/src/locales/zh-CN.ts b/ui/src/locales/zh-CN.ts new file mode 100644 index 0000000000..d7b1b20501 --- /dev/null +++ b/ui/src/locales/zh-CN.ts @@ -0,0 +1,75 @@ +export default { + // app + 'app.title': '老寇IoT云平台', + + // common + 'common.ok': '确定', + 'common.cancel': '取消', + 'common.search': '查询', + 'common.reset': '重置', + 'common.add': '新增', + 'common.edit': '编辑', + 'common.delete': '删除', + 'common.submit': '提交', + 'common.close': '关闭', + + // auth/user + 'user.logout': '注销', + + // error + 'error.serverInternal': '服务器内部错误,无法完成请求', + 'error.network': '网络请求错误,请稍后再试', + 'error.refreshTokenFailed': '令牌续期失败,请重新登录', + 'error.resourceNotFound': '无法找到 {url} 请求的资源', + 'error.invalidClient': '无效客户端,请检查认证服务器配置', + + // login + 'login.subtitle': '企业级多租户IoT云平台', + 'login.otherWays': '其他登录方式', + 'login.tenantCode.placeholder': '请输入租户编码', + 'login.tenantCode.required': '请输入租户编码', + 'login.username.placeholder': '请输入用户名', + 'login.username.required': '请输入用户名', + 'login.password.placeholder': '请输入密码', + 'login.password.required': '请输入密码', + 'login.captcha.placeholder': '请输入验证码', + 'login.captcha.required': '请输入验证码', + 'login.captcha.invalid': '请输入4位验证码(数字和字母)', + 'login.mobile.placeholder': '请输入手机号', + 'login.mobile.required': '请输入手机号', + 'login.mobile.invalid': '手机号格式错误', + 'login.smsCaptcha.placeholder': '请输入验证码', + 'login.smsCaptcha.required': '请输入验证码', + 'login.smsCaptcha.invalid': '请输入6位数字的验证码', + 'login.mail.placeholder': '请输入邮箱', + 'login.mail.required': '请输入邮箱', + 'login.mail.invalid': '邮箱格式错误', + 'login.mailCaptcha.placeholder': '请输入验证码', + 'login.mailCaptcha.required': '请输入验证码', + 'login.mailCaptcha.invalid': '请输入6位数字的验证码', + 'login.captcha.get': '获取验证码', + 'login.captcha.countdown': '{count} 获取验证码', + + // menu (routes) + 'menu.home': '首页', + 'menu.login': '登录', + 'menu.sys': '系统管理', + 'menu.sys.permission': '权限管理', + 'menu.sys.permission.menu': '菜单', + 'menu.sys.permission.dept': '部门', + 'menu.sys.permission.role': '角色', + 'menu.sys.permission.user': '用户', + 'menu.sys.log': '日志管理', + 'menu.sys.log.login': '登录日志', + 'menu.sys.log.notice': '通知日志', + 'menu.sys.log.operate': '操作日志', + 'menu.sys.oss': '对象存储', + 'menu.sys.oss.config': '对象存储配置', + 'menu.sys.oss.log': '对象存储日志', + 'menu.sys.config': '系统配置', + 'menu.sys.config.generator': '代码生成器', + 'menu.iot': '物联管理', + 'menu.iot.device': '设备管理', + 'menu.iot.device.thingModel': '物模型', + 'menu.iot.device.productCategory': '产品类别', +}; From cea96c4ecec3ea189cbb692f6a6a7ce01534db8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?laokou=E3=80=90k=E2=86=91=E3=80=91?= <2413176044@qq.com> Date: Thu, 5 Mar 2026 22:25:04 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20ui=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/config/routes.ts | 45 +++++++++++++++++++--------------- ui/src/app.tsx | 2 +- ui/src/locales/en-US.ts | 27 +++++++++++--------- ui/src/locales/zh-CN.ts | 3 +++ ui/src/pages/Login/index.tsx | 14 +++++------ ui/src/pages/Sys/Log/login.tsx | 12 +++++---- 6 files changed, 58 insertions(+), 45 deletions(-) diff --git a/ui/config/routes.ts b/ui/config/routes.ts index d5268a21ef..51c241d36e 100644 --- a/ui/config/routes.ts +++ b/ui/config/routes.ts @@ -17,86 +17,91 @@ export default [ redirect: '/home', }, { - name: 'menu.home', + name: '首页', path: '/home', component: './Home', icon: 'home' }, { - name: 'menu.login', + name: 'Login', path: '/login', component: './Login', layout: false, }, { - name: 'menu.sys', + name: '系统管理', path: '/sys', icon: 'setting', routes: [ { - name: 'menu.sys.permission', + name: '权限管理', path: '/sys/permission', routes: [ { - name: 'menu.sys.permission.dept', + name: '菜单', + path: '/sys/permission/menu', + component: './Sys/Permission/menu' + }, + { + name: '部门', path: '/sys/permission/dept', component: './Sys/Permission/dept' }, { - name: 'menu.sys.permission.role', + name: '角色', path: '/sys/permission/role', component: './Sys/Permission/role' }, { - name: 'menu.sys.permission.user', + name: '用户', path: '/sys/permission/user', component: './Sys/Permission/user' }, ] }, { - name: 'menu.sys.log', + name: '日志管理', path: '/sys/log', routes: [ { - name: 'menu.sys.log.login', + name: '登录日志', path: '/sys/log/login', component: './Sys/Log/login' }, { - name: 'menu.sys.log.notice', + name: '通知日志', path: '/sys/log/notice', component: './Sys/Log/notice' }, { - name: 'menu.sys.log.operate', + name: '操作日志', path: '/sys/log/operate', component: './Sys/Log/operate' } ] }, { - name: 'menu.sys.oss', + name: '对象存储', path: '/sys/oss', routes: [ { - name: 'menu.sys.oss.config', + name: '对象存储配置', path: '/sys/oss/config', component: './Sys/Oss/config' }, { - name: 'menu.sys.oss.log', + name: '对象存储日志', path: '/sys/oss/log', component: './Sys/Oss/log' } ] }, { - name: 'menu.sys.config', + name: '系统配置', path: '/sys/config', routes: [ { - name: 'menu.sys.config.generator', + name: '代码生成器', path: '/sys/config/generator', component: './Sys/Config/generator' } @@ -105,21 +110,21 @@ export default [ ] }, { - name: 'menu.iot', + name: '物联管理', path: '/iot', icon: 'robot', routes: [ { - name: 'menu.iot.device', + name: '设备管理', path: '/iot/device', routes: [ { - name: 'menu.iot.device.thingModel', + name: '物模型', path: '/iot/device/thingModel', component: './IoT/Device/thingModel' }, { - name: 'menu.iot.device.productCategory', + name: '产品类别', path: '/iot/device/productCategory', component: './IoT/Device/productCategory' }, diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 46723f64ac..bb607c374e 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -28,7 +28,7 @@ const getIcon = (icon: string) => { const getRouters = (menus: any[]) => { const routers = [{ - name: 'menu.home', + name: '首页', path: '/home', icon: }] diff --git a/ui/src/locales/en-US.ts b/ui/src/locales/en-US.ts index cb5a9ab8d0..2a9fe86783 100644 --- a/ui/src/locales/en-US.ts +++ b/ui/src/locales/en-US.ts @@ -24,31 +24,34 @@ export default { 'error.invalidClient': 'Invalid client. Please check the auth server configuration.', // login - 'login.subtitle': 'Enterprise-grade multi-tenant IoT platform based on microservice architecture', + 'login.subtitle': 'Enterprise level multi tenant IoT cloud platform', + 'login.usernamePassword': 'User pwd login', + 'login.mobile': 'Mobile login', + 'login.mail': 'Mail login', 'login.otherWays': 'Other sign-in methods', - 'login.tenantCode.placeholder': 'Tenant code', + 'login.tenantCode.placeholder': 'Please enter tenant code', 'login.tenantCode.required': 'Please enter tenant code', - 'login.username.placeholder': 'Username', + 'login.username.placeholder': 'Please enter username', 'login.username.required': 'Please enter username', - 'login.password.placeholder': 'Password', + 'login.password.placeholder': 'Please enter password', 'login.password.required': 'Please enter password', - 'login.captcha.placeholder': 'Captcha', + 'login.captcha.placeholder': 'Please enter captcha', 'login.captcha.required': 'Please enter captcha', 'login.captcha.invalid': 'Captcha must be 4 characters (letters or numbers)', - 'login.mobile.placeholder': 'Mobile number', + 'login.mobile.placeholder': 'Please enter mobile number', 'login.mobile.required': 'Please enter mobile number', 'login.mobile.invalid': 'Invalid mobile number', - 'login.smsCaptcha.placeholder': 'Verification code', + 'login.smsCaptcha.placeholder': 'Please enter verification code', 'login.smsCaptcha.required': 'Please enter verification code', - 'login.smsCaptcha.invalid': 'Code must be 6 digits', - 'login.mail.placeholder': 'Email', + 'login.smsCaptcha.invalid': 'Please enter a 6-digit verification code', + 'login.mail.placeholder': 'Please enter email', 'login.mail.required': 'Please enter email', 'login.mail.invalid': 'Invalid email address', - 'login.mailCaptcha.placeholder': 'Verification code', + 'login.mailCaptcha.placeholder': 'Please enter verification code', 'login.mailCaptcha.required': 'Please enter verification code', - 'login.mailCaptcha.invalid': 'Code must be 6 digits', + 'login.mailCaptcha.invalid': 'Please enter a 6-digit verification code', 'login.captcha.get': 'Get code', - 'login.captcha.countdown': '{count}s', + 'login.captcha.countdown': '{count} Get code', // menu (routes) 'menu.home': 'Home', diff --git a/ui/src/locales/zh-CN.ts b/ui/src/locales/zh-CN.ts index d7b1b20501..f82e1930b8 100644 --- a/ui/src/locales/zh-CN.ts +++ b/ui/src/locales/zh-CN.ts @@ -25,6 +25,9 @@ export default { // login 'login.subtitle': '企业级多租户IoT云平台', + 'login.usernamePassword': '用户名密码登录', + 'login.mobile': '手机号登录', + 'login.mail': '邮箱登录', 'login.otherWays': '其他登录方式', 'login.tenantCode.placeholder': '请输入租户编码', 'login.tenantCode.required': '请输入租户编码', diff --git a/ui/src/pages/Login/index.tsx b/ui/src/pages/Login/index.tsx index 240146b241..e904bffca7 100644 --- a/ui/src/pages/Login/index.tsx +++ b/ui/src/pages/Login/index.tsx @@ -22,9 +22,9 @@ import {clearToken, setToken} from "@/access" import {history} from "@umijs/max"; import {SelectLang, useIntl} from "@@/exports"; -const USERNAME_PASSWORD = {key: 'username_password', label: '用户名密码登录'}; -const MOBILE = {key: 'mobile', label: '手机号登录'}; -const MAIL = {key: 'mail', label: '邮箱登录'}; +const USERNAME_PASSWORD = {key: 'username_password', label: 'login.usernamePassword'}; +const MOBILE = {key: 'mobile', label: 'login.mobile'}; +const MAIL = {key: 'mail', label: 'login.mail'}; type LoginType = 'username_password' | 'mobile' | 'mail'; const iconStyles: CSSProperties = { @@ -37,10 +37,10 @@ const iconStyles: CSSProperties = { export default () => { const intl = useIntl(); const t = (id: string, values?: Record) => intl.formatMessage({id}, values); - const items = [ - USERNAME_PASSWORD, - MOBILE, - MAIL +const items = [ + { ...USERNAME_PASSWORD, label: t(USERNAME_PASSWORD.label) }, + { ...MOBILE, label: t(MOBILE.label) }, + { ...MAIL, label: t(MAIL.label) }, ]; const [loading, setLoading] = useState(false); const [loginType, setLoginType] = useState('username_password'); diff --git a/ui/src/pages/Sys/Log/login.tsx b/ui/src/pages/Sys/Log/login.tsx index ca7fe006ea..aae450a8da 100644 --- a/ui/src/pages/Sys/Log/login.tsx +++ b/ui/src/pages/Sys/Log/login.tsx @@ -7,7 +7,7 @@ import {trim} from "@/utils/format"; import {ExportToExcel} from "@/utils/export"; import moment from "moment"; import {useRef, useState} from "react"; -import {useAccess} from "@@/exports"; +import {useAccess, useIntl} from "@@/exports"; export default () => { @@ -25,6 +25,8 @@ export default () => { }; const access = useAccess(); + const intl = useIntl(); + const t = (id: string, values?: Record) => intl.formatMessage({id}, values); const actionRef = useRef(null); const [list, setList] = useState([]); const [param, setParam] = useState({}); @@ -32,8 +34,8 @@ export default () => { const getLoginType = (type: string) => { return { - 'username_password': '用户名密码登录', - 'mobile': '手机号登录', + 'username_password': t('login.usernamePassword'), + 'mobile': t('login.mobile'), 'mail': '邮箱登录', 'authorization_code': '授权码登录', }[type] @@ -173,8 +175,8 @@ export default () => { mode: 'single', options: [ { - value: "username_password", - label: "用户名密码登录", + value: 'username_password', + label: t('login.usernamePassword'), }, { value: "mail", From aede9de550e1dae87b2c071662763121212d7418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?laokou=E3=80=90k=E2=86=91=E3=80=91?= <2413176044@qq.com> Date: Thu, 5 Mar 2026 23:10:57 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20ui=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/config/config.ts | 1 + ui/src/app.tsx | 8 ++++---- ui/src/pages/Login/index.tsx | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/config/config.ts b/ui/config/config.ts index ca07fa740d..33210b2964 100644 --- a/ui/config/config.ts +++ b/ui/config/config.ts @@ -40,6 +40,7 @@ export default defineConfig({ // umi/max 内置 i18n default: 'zh-CN', antd: true, + title: true, baseNavigator: true, }, /** diff --git a/ui/src/app.tsx b/ui/src/app.tsx index bb607c374e..583fe9dbba 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -26,9 +26,9 @@ const getIcon = (icon: string) => { } } -const getRouters = (menus: any[]) => { +const getRouters = (menus: any[], homeName: string) => { const routers = [{ - name: '首页', + name: homeName, path: '/home', icon: }] @@ -123,7 +123,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { params: initialState?.username, request: async () => { const result = await listUserTreeMenu({code: 0}).catch(console.log); - return getRouters(result?.data) + return getRouters(result?.data, t('menu.home')) } }, layout: 'mix', @@ -136,7 +136,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { siderMenuType: "sub", actionsRender: () => { // Ant Design Pro 风格的语言切换组件(来自 umi plugin-locale) - return []; + return []; }, avatarProps: { src: initialState?.avatar, diff --git a/ui/src/pages/Login/index.tsx b/ui/src/pages/Login/index.tsx index e904bffca7..5c01918330 100644 --- a/ui/src/pages/Login/index.tsx +++ b/ui/src/pages/Login/index.tsx @@ -37,7 +37,7 @@ const iconStyles: CSSProperties = { export default () => { const intl = useIntl(); const t = (id: string, values?: Record) => intl.formatMessage({id}, values); -const items = [ + const items = [ { ...USERNAME_PASSWORD, label: t(USERNAME_PASSWORD.label) }, { ...MOBILE, label: t(MOBILE.label) }, { ...MAIL, label: t(MAIL.label) }, From 99bc68d38846cc329cb770d0c10ee35642de24b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?laokou=E3=80=90k=E2=86=91=E3=80=91?= <2413176044@qq.com> Date: Thu, 5 Mar 2026 23:27:02 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20ui=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/config/routes.ts | 2 +- ui/src/app.tsx | 9 ++++++++- ui/src/locales/en-US.ts | 38 +++++++++----------------------------- ui/src/locales/zh-CN.ts | 26 +++----------------------- 4 files changed, 21 insertions(+), 54 deletions(-) diff --git a/ui/config/routes.ts b/ui/config/routes.ts index 51c241d36e..1427ee5ddc 100644 --- a/ui/config/routes.ts +++ b/ui/config/routes.ts @@ -23,7 +23,7 @@ export default [ icon: 'home' }, { - name: 'Login', + name: '登录', path: '/login', component: './Login', layout: false, diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 583fe9dbba..c454cc7673 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -254,9 +254,16 @@ export const request: { }, }, // 请求拦截 - requestInterceptors: [ + requestInterceptors: [ async (config: any) => { const headers = config.headers ? config.headers : []; + // 国际化:携带语言到后端(优先使用 umi plugin-locale 的 current locale) + const { getLocale } = require('@@/exports'); + const locale = getLocale?.() || 'zh-CN'; + if (locale) { + // 若后端使用自定义 header,也可以同时带上(按需保留/改名) + headers['Language'] = locale; + } const accessToken = getAccessToken() if (!headers['Skip-Token'] && accessToken) { headers['Authorization'] = `Bearer ${accessToken}` diff --git a/ui/src/locales/en-US.ts b/ui/src/locales/en-US.ts index 2a9fe86783..9c627f7f4f 100644 --- a/ui/src/locales/en-US.ts +++ b/ui/src/locales/en-US.ts @@ -37,42 +37,22 @@ export default { 'login.password.required': 'Please enter password', 'login.captcha.placeholder': 'Please enter captcha', 'login.captcha.required': 'Please enter captcha', - 'login.captcha.invalid': 'Captcha must be 4 characters (letters or numbers)', + 'login.captcha.invalid': 'Please enter 4 digits captcha', 'login.mobile.placeholder': 'Please enter mobile number', 'login.mobile.required': 'Please enter mobile number', 'login.mobile.invalid': 'Invalid mobile number', - 'login.smsCaptcha.placeholder': 'Please enter verification code', - 'login.smsCaptcha.required': 'Please enter verification code', - 'login.smsCaptcha.invalid': 'Please enter a 6-digit verification code', + 'login.smsCaptcha.placeholder': 'Please enter captcha', + 'login.smsCaptcha.required': 'Please enter captcha', + 'login.smsCaptcha.invalid': 'Please enter 6 digits captcha', 'login.mail.placeholder': 'Please enter email', 'login.mail.required': 'Please enter email', 'login.mail.invalid': 'Invalid email address', - 'login.mailCaptcha.placeholder': 'Please enter verification code', - 'login.mailCaptcha.required': 'Please enter verification code', - 'login.mailCaptcha.invalid': 'Please enter a 6-digit verification code', - 'login.captcha.get': 'Get code', - 'login.captcha.countdown': '{count} Get code', + 'login.mailCaptcha.placeholder': 'Please enter captcha', + 'login.mailCaptcha.required': 'Please enter captcha', + 'login.mailCaptcha.invalid': 'Please enter 6 digits captcha', + 'login.captcha.get': 'Get captcha', + 'login.captcha.countdown': '{count} Get captcha', // menu (routes) 'menu.home': 'Home', - 'menu.login': 'Login', - 'menu.sys': 'System', - 'menu.sys.permission': 'Permission', - 'menu.sys.permission.menu': 'Menus', - 'menu.sys.permission.dept': 'Departments', - 'menu.sys.permission.role': 'Roles', - 'menu.sys.permission.user': 'Users', - 'menu.sys.log': 'Logs', - 'menu.sys.log.login': 'Login Logs', - 'menu.sys.log.notice': 'Notice Logs', - 'menu.sys.log.operate': 'Operation Logs', - 'menu.sys.oss': 'Object Storage', - 'menu.sys.oss.config': 'OSS Config', - 'menu.sys.oss.log': 'OSS Logs', - 'menu.sys.config': 'System Config', - 'menu.sys.config.generator': 'Code Generator', - 'menu.iot': 'IoT', - 'menu.iot.device': 'Devices', - 'menu.iot.device.thingModel': 'Thing Model', - 'menu.iot.device.productCategory': 'Product Category', }; diff --git a/ui/src/locales/zh-CN.ts b/ui/src/locales/zh-CN.ts index f82e1930b8..5d26ba86c9 100644 --- a/ui/src/locales/zh-CN.ts +++ b/ui/src/locales/zh-CN.ts @@ -37,42 +37,22 @@ export default { 'login.password.required': '请输入密码', 'login.captcha.placeholder': '请输入验证码', 'login.captcha.required': '请输入验证码', - 'login.captcha.invalid': '请输入4位验证码(数字和字母)', + 'login.captcha.invalid': '请输入4位验证码', 'login.mobile.placeholder': '请输入手机号', 'login.mobile.required': '请输入手机号', 'login.mobile.invalid': '手机号格式错误', 'login.smsCaptcha.placeholder': '请输入验证码', 'login.smsCaptcha.required': '请输入验证码', - 'login.smsCaptcha.invalid': '请输入6位数字的验证码', + 'login.smsCaptcha.invalid': '请输入6位验证码', 'login.mail.placeholder': '请输入邮箱', 'login.mail.required': '请输入邮箱', 'login.mail.invalid': '邮箱格式错误', 'login.mailCaptcha.placeholder': '请输入验证码', 'login.mailCaptcha.required': '请输入验证码', - 'login.mailCaptcha.invalid': '请输入6位数字的验证码', + 'login.mailCaptcha.invalid': '请输入6位验证码', 'login.captcha.get': '获取验证码', 'login.captcha.countdown': '{count} 获取验证码', // menu (routes) 'menu.home': '首页', - 'menu.login': '登录', - 'menu.sys': '系统管理', - 'menu.sys.permission': '权限管理', - 'menu.sys.permission.menu': '菜单', - 'menu.sys.permission.dept': '部门', - 'menu.sys.permission.role': '角色', - 'menu.sys.permission.user': '用户', - 'menu.sys.log': '日志管理', - 'menu.sys.log.login': '登录日志', - 'menu.sys.log.notice': '通知日志', - 'menu.sys.log.operate': '操作日志', - 'menu.sys.oss': '对象存储', - 'menu.sys.oss.config': '对象存储配置', - 'menu.sys.oss.log': '对象存储日志', - 'menu.sys.config': '系统配置', - 'menu.sys.config.generator': '代码生成器', - 'menu.iot': '物联管理', - 'menu.iot.device': '设备管理', - 'menu.iot.device.thingModel': '物模型', - 'menu.iot.device.productCategory': '产品类别', }; From 986417f5a1a1bea5215d42b2689fd47cd8af1ad9 Mon Sep 17 00:00:00 2001 From: koushenhai <2413176044@qq.com> Date: Fri, 6 Mar 2026 12:29:39 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20UI=E5=A2=9E=E5=8A=A0=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/config/routes.ts | 63 +++++++++++++++++++++++++++-------------- ui/src/app.tsx | 11 ++++--- ui/src/locales/en-US.ts | 20 +++++++++++++ ui/src/locales/zh-CN.ts | 20 +++++++++++++ 4 files changed, 87 insertions(+), 27 deletions(-) diff --git a/ui/config/routes.ts b/ui/config/routes.ts index 1427ee5ddc..12d5879c6a 100644 --- a/ui/config/routes.ts +++ b/ui/config/routes.ts @@ -17,91 +17,108 @@ export default [ redirect: '/home', }, { - name: '首页', + name: 'menu.home', + title: 'menu.home', path: '/home', component: './Home', icon: 'home' }, { - name: '登录', + name: 'menu.login', + title: 'menu.login', path: '/login', component: './Login', layout: false, }, { - name: '系统管理', + name: 'menu.sys', + title: 'menu.sys', path: '/sys', icon: 'setting', routes: [ { - name: '权限管理', + name: 'menu.sys.permission', + title: 'menu.sys.permission', path: '/sys/permission', routes: [ { - name: '菜单', + name: 'menu.sys.permission.menu', + title: 'menu.sys.permission.menu', path: '/sys/permission/menu', component: './Sys/Permission/menu' }, { - name: '部门', + name: 'menu.sys.permission.dept', + title: 'menu.sys.permission.dept', path: '/sys/permission/dept', component: './Sys/Permission/dept' }, { - name: '角色', + name: 'menu.sys.permission.role', + title: 'menu.sys.permission.role', path: '/sys/permission/role', component: './Sys/Permission/role' }, { - name: '用户', + name: 'menu.sys.permission.user', + title: 'menu.sys.permission.user', path: '/sys/permission/user', component: './Sys/Permission/user' }, ] }, { - name: '日志管理', + name: 'menu.sys.log', + title: 'menu.sys.log', path: '/sys/log', routes: [ { - name: '登录日志', + name: 'menu.sys.log.login', + title: 'menu.sys.log.login', path: '/sys/log/login', component: './Sys/Log/login' }, { - name: '通知日志', + name: 'menu.sys.log.notice', + title: 'menu.sys.log.notice', path: '/sys/log/notice', component: './Sys/Log/notice' }, { - name: '操作日志', + name: 'menu.sys.log.operate', + title: 'menu.sys.log.operate', path: '/sys/log/operate', component: './Sys/Log/operate' } ] }, { - name: '对象存储', + name: 'menu.sys.oss', + title: 'menu.sys.oss', path: '/sys/oss', routes: [ { - name: '对象存储配置', + name: 'menu.sys.oss.config', + title: 'menu.sys.oss.config', path: '/sys/oss/config', component: './Sys/Oss/config' }, { - name: '对象存储日志', + name: 'menu.sys.oss.log', + title: 'menu.sys.oss.log', path: '/sys/oss/log', component: './Sys/Oss/log' } ] }, { - name: '系统配置', + name: 'menu.sys.config', + title: 'menu.sys.config', path: '/sys/config', routes: [ { - name: '代码生成器', + name: 'menu.sys.config.generator', + title: 'menu.sys.config.generator', path: '/sys/config/generator', component: './Sys/Config/generator' } @@ -110,21 +127,25 @@ export default [ ] }, { - name: '物联管理', + name: 'menu.iot', + title: 'menu.iot', path: '/iot', icon: 'robot', routes: [ { - name: '设备管理', + name: 'menu.iot.device', + title: 'menu.iot.device', path: '/iot/device', routes: [ { - name: '物模型', + name: 'menu.iot.device.thingModel', + title: 'menu.iot.device.thingModel', path: '/iot/device/thingModel', component: './IoT/Device/thingModel' }, { - name: '产品类别', + name: 'menu.iot.device.productCategory', + title: 'menu.iot.device.productCategory', path: '/iot/device/productCategory', component: './IoT/Device/productCategory' }, diff --git a/ui/src/app.tsx b/ui/src/app.tsx index c454cc7673..e3d4659fb8 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -3,7 +3,7 @@ // 全局初始化数据配置,用于 Layout 用户信息和权限初始化 // 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate import {Dropdown, message, theme} from "antd"; -import {history, SelectLang, useIntl} from "@@/exports"; +import {history, SelectLang} from "@@/exports"; import {HomeOutlined, LogoutOutlined, RobotOutlined, SettingOutlined} from "@ant-design/icons"; import {ReactElement, ReactNode, ReactPortal} from "react"; import {logout, refresh} from '@/services/auth/auth'; @@ -26,9 +26,10 @@ const getIcon = (icon: string) => { } } -const getRouters = (menus: any[], homeName: string) => { +const getRouters = (menus: any[]) => { const routers = [{ - name: homeName, + name: 'menu.home', + title: 'menu.home', path: '/home', icon: }] @@ -112,8 +113,6 @@ export async function getInitialState(): Promise<{ export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { // 新写法:使用 intl.formatMessage,替代 formatMessage() - const intl = useIntl(); - const t = (id: string, values?: Record) => intl.formatMessage({id}, values); return { // 面包屑配置 headerContentRender: () => , @@ -123,7 +122,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { params: initialState?.username, request: async () => { const result = await listUserTreeMenu({code: 0}).catch(console.log); - return getRouters(result?.data, t('menu.home')) + return getRouters(result?.data) } }, layout: 'mix', diff --git a/ui/src/locales/en-US.ts b/ui/src/locales/en-US.ts index 9c627f7f4f..0ad21447e0 100644 --- a/ui/src/locales/en-US.ts +++ b/ui/src/locales/en-US.ts @@ -55,4 +55,24 @@ export default { // menu (routes) 'menu.home': 'Home', + 'menu.login': 'Login', + 'menu.sys': 'System', + 'menu.sys.permission': 'Permission', + 'menu.sys.permission.menu': 'Menus', + 'menu.sys.permission.dept': 'Departments', + 'menu.sys.permission.role': 'Roles', + 'menu.sys.permission.user': 'Users', + 'menu.sys.log': 'Logs', + 'menu.sys.log.login': 'Login Logs', + 'menu.sys.log.notice': 'Notice Logs', + 'menu.sys.log.operate': 'Operation Logs', + 'menu.sys.oss': 'Object Storage', + 'menu.sys.oss.config': 'OSS Config', + 'menu.sys.oss.log': 'OSS Logs', + 'menu.sys.config': 'System Config', + 'menu.sys.config.generator': 'Code Generator', + 'menu.iot': 'IoT', + 'menu.iot.device': 'Devices', + 'menu.iot.device.thingModel': 'Thing Model', + 'menu.iot.device.productCategory': 'Product Category', }; diff --git a/ui/src/locales/zh-CN.ts b/ui/src/locales/zh-CN.ts index 5d26ba86c9..0b56bd5d39 100644 --- a/ui/src/locales/zh-CN.ts +++ b/ui/src/locales/zh-CN.ts @@ -55,4 +55,24 @@ export default { // menu (routes) 'menu.home': '首页', + 'menu.login': '登录', + 'menu.sys': '系统管理', + 'menu.sys.permission': '权限管理', + 'menu.sys.permission.menu': '菜单', + 'menu.sys.permission.dept': '部门', + 'menu.sys.permission.role': '角色', + 'menu.sys.permission.user': '用户', + 'menu.sys.log': '日志管理', + 'menu.sys.log.login': '登录日志', + 'menu.sys.log.notice': '通知日志', + 'menu.sys.log.operate': '操作日志', + 'menu.sys.oss': '对象存储', + 'menu.sys.oss.config': '对象存储配置', + 'menu.sys.oss.log': '对象存储日志', + 'menu.sys.config': '系统配置', + 'menu.sys.config.generator': '代码生成器', + 'menu.iot': '物联管理', + 'menu.iot.device': '设备管理', + 'menu.iot.device.thingModel': '物模型', + 'menu.iot.device.productCategory': '产品类别', }; From 475d1aa7c29551ec6e70403dc7700442e317d92e Mon Sep 17 00:00:00 2001 From: koushenhai <2413176044@qq.com> Date: Fri, 6 Mar 2026 12:38:24 +0800 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20UI=E5=A2=9E=E5=8A=A0=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/access.ts | 111 ++++++++++++++++++++-------------------- ui/src/app.tsx | 2 + ui/src/locales/en-US.ts | 2 +- 3 files changed, 59 insertions(+), 56 deletions(-) diff --git a/ui/src/access.ts b/ui/src/access.ts index 6982ff4c31..a5fd089d24 100644 --- a/ui/src/access.ts +++ b/ui/src/access.ts @@ -2,63 +2,64 @@ export default (initialState: any) => { // 在这里按照初始化数据定义项目中的权限,统一管理 // 参考文档 https://umijs.org/docs/max/access const permissions = initialState?.permissions || [] + const scopes = initialState?.scopes || [] return { - canMenuGetDetail: permissions?.includes('sys:menu:detail'), - canMenuModify: permissions?.includes('sys:menu:modify'), - canMenuRemove: permissions?.includes('sys:menu:remove'), - canMenuSave: permissions?.includes('sys:menu:save'), - - canDeptGetDetail: permissions?.includes('sys:dept:detail'), - canDeptModify: permissions?.includes('sys:dept:modify'), - canDeptRemove: permissions?.includes('sys:dept:remove'), - canDeptSave: permissions?.includes('sys:dept:save'), - - canRoleGetDetail: permissions?.includes('sys:role:detail'), - canRoleModify: permissions?.includes('sys:role:modify'), - canRoleRemove: permissions?.includes('sys:role:remove'), - canRoleSave: permissions?.includes('sys:role:save'), - - canUserGetDetail: permissions?.includes('sys:user:detail'), - canUserModify: permissions?.includes('sys:user:modify'), - canUserRemove: permissions?.includes('sys:user:remove'), - canUserSave: permissions?.includes('sys:user:save'), - - canOssUpload: permissions?.includes('sys:oss:upload'), - canOssGetDetail: permissions?.includes('sys:oss:detail'), - canOssModify: permissions?.includes('sys:oss:modify'), - canOssRemove: permissions?.includes('sys:oss:remove'), - canOssSave: permissions?.includes('sys:oss:save'), - - canOssLogExport: permissions?.includes('sys:oss-log:export'), - - canDeviceGetDetail: permissions?.includes('iot:device:detail'), - canDeviceModify: permissions?.includes('iot:device:modify'), - canDeviceRemove: permissions?.includes('iot:device:remove'), - canDeviceSave: permissions?.includes('iot:device:save'), - - canProductGetDetail: permissions?.includes('iot:product:detail'), - canProductModify: permissions?.includes('iot:product:modify'), - canProductRemove: permissions?.includes('iot:product:remove'), - canProductSave: permissions?.includes('iot:product:save'), - - canThingModelGetDetail: permissions?.includes('iot:thing-model:detail'), - canThingModelModify: permissions?.includes('iot:thing-model:modify'), - canThingModelRemove: permissions?.includes('iot:thing-model:remove'), - canThingModelSave: permissions?.includes('iot:thing-model:save'), - - canProductCategoryGetDetail: permissions?.includes('iot:product-category:detail'), - canProductCategoryModify: permissions?.includes('iot:product-category:modify'), - canProductCategoryRemove: permissions?.includes('iot:product-category:remove'), - canProductCategorySave: permissions?.includes('iot:product-category:save'), - - canOperateLogGetDetail: permissions?.includes('sys:operate-log:detail'), - canOperateLogExport: permissions?.includes('sys:operate-log:export'), - - canNoticeLogGetDetail: permissions?.includes('sys:notice-log:detail'), - canNoticeLogExport: permissions?.includes('sys:notice-log:export'), - - canLoginLogExport: permissions?.includes('sys:login-log:export'), + canMenuGetDetail: permissions?.includes('sys:menu:detail') && scopes?.includes('read'), + canMenuModify: permissions?.includes('sys:menu:modify') && scopes?.includes('write'), + canMenuRemove: permissions?.includes('sys:menu:remove') && scopes?.includes('write'), + canMenuSave: permissions?.includes('sys:menu:save') && scopes?.includes('write'), + + canDeptGetDetail: permissions?.includes('sys:dept:detail') && scopes?.includes('read'), + canDeptModify: permissions?.includes('sys:dept:modify') && scopes?.includes('write'), + canDeptRemove: permissions?.includes('sys:dept:remove') && scopes?.includes('write'), + canDeptSave: permissions?.includes('sys:dept:save') && scopes?.includes('write'), + + canRoleGetDetail: permissions?.includes('sys:role:detail') && scopes?.includes('read'), + canRoleModify: permissions?.includes('sys:role:modify') && scopes?.includes('write'), + canRoleRemove: permissions?.includes('sys:role:remove') && scopes?.includes('write'), + canRoleSave: permissions?.includes('sys:role:save') && scopes?.includes('write'), + + canUserGetDetail: permissions?.includes('sys:user:detail') && scopes?.includes('read'), + canUserModify: permissions?.includes('sys:user:modify') && scopes?.includes('write'), + canUserRemove: permissions?.includes('sys:user:remove') && scopes?.includes('write'), + canUserSave: permissions?.includes('sys:user:save') && scopes?.includes('write'), + + canOssUpload: permissions?.includes('sys:oss:upload') && scopes?.includes('write'), + canOssGetDetail: permissions?.includes('sys:oss:detail') && scopes?.includes('read'), + canOssModify: permissions?.includes('sys:oss:modify') && scopes?.includes('write'), + canOssRemove: permissions?.includes('sys:oss:remove') && scopes?.includes('write'), + canOssSave: permissions?.includes('sys:oss:save') && scopes?.includes('write'), + + canOssLogExport: permissions?.includes('sys:oss-log:export') && scopes?.includes('write'), + + canDeviceGetDetail: permissions?.includes('iot:device:detail') && scopes?.includes('read'), + canDeviceModify: permissions?.includes('iot:device:modify') && scopes?.includes('write'), + canDeviceRemove: permissions?.includes('iot:device:remove') && scopes?.includes('write'), + canDeviceSave: permissions?.includes('iot:device:save') && scopes?.includes('write'), + + canProductGetDetail: permissions?.includes('iot:product:detail') && scopes?.includes('read'), + canProductModify: permissions?.includes('iot:product:modify') && scopes?.includes('write'), + canProductRemove: permissions?.includes('iot:product:remove') && scopes?.includes('write'), + canProductSave: permissions?.includes('iot:product:save') && scopes?.includes('write'), + + canThingModelGetDetail: permissions?.includes('iot:thing-model:detail') && scopes?.includes('read'), + canThingModelModify: permissions?.includes('iot:thing-model:modify') && scopes?.includes('write'), + canThingModelRemove: permissions?.includes('iot:thing-model:remove') && scopes?.includes('write'), + canThingModelSave: permissions?.includes('iot:thing-model:save') && scopes?.includes('write'), + + canProductCategoryGetDetail: permissions?.includes('iot:product-category:detail') && scopes?.includes('read'), + canProductCategoryModify: permissions?.includes('iot:product-category:modify') && scopes?.includes('write'), + canProductCategoryRemove: permissions?.includes('iot:product-category:remove') && scopes?.includes('write'), + canProductCategorySave: permissions?.includes('iot:product-category:save') && scopes?.includes('write'), + + canOperateLogGetDetail: permissions?.includes('sys:operate-log:detail') && scopes?.includes('read'), + canOperateLogExport: permissions?.includes('sys:operate-log:export') && scopes?.includes('write'), + + canNoticeLogGetDetail: permissions?.includes('sys:notice-log:detail') && scopes?.includes('read'), + canNoticeLogExport: permissions?.includes('sys:notice-log:export') && scopes?.includes('write'), + + canLoginLogExport: permissions?.includes('sys:login-log:export') && scopes?.includes('write'), }; }; diff --git a/ui/src/app.tsx b/ui/src/app.tsx index e3d4659fb8..7886c6cbf6 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -101,6 +101,7 @@ export async function getInitialState(): Promise<{ username: string; avatar: string; permissions: string[] + scopes: string[] }> { const result = await getUserProfile().catch(console.log); return { @@ -108,6 +109,7 @@ export async function getInitialState(): Promise<{ username: result?.data?.username, avatar: result?.data?.avatar ? result?.data?.avatar : '/1.png', permissions: result?.data?.permissions, + scopes: result?.data?.scopes, }; } diff --git a/ui/src/locales/en-US.ts b/ui/src/locales/en-US.ts index 0ad21447e0..535a230c19 100644 --- a/ui/src/locales/en-US.ts +++ b/ui/src/locales/en-US.ts @@ -19,7 +19,7 @@ export default { // error 'error.serverInternal': 'Internal server error. Unable to complete the request.', 'error.network': 'Network request error. Please try again later.', - 'error.refreshTokenFailed': 'Session refresh failed. Please log in again.', + 'error.refreshTokenFailed': 'Token refresh failed. Please log in again.', 'error.resourceNotFound': 'Resource not found: {url}', 'error.invalidClient': 'Invalid client. Please check the auth server configuration.', From 82c697b1812458b15a4eae5ab7b177996380c616 Mon Sep 17 00:00:00 2001 From: koushenhai <2413176044@qq.com> Date: Fri, 6 Mar 2026 13:40:26 +0800 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20UI=E5=A2=9E=E5=8A=A0=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/config/config.ts | 2 +- ui/src/app.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/config/config.ts b/ui/config/config.ts index 33210b2964..c20bf9a39d 100644 --- a/ui/config/config.ts +++ b/ui/config/config.ts @@ -34,7 +34,7 @@ export default defineConfig({ */ proxy: proxy[REACT_APP_ENV as keyof typeof proxy], layout: { - title: '老寇IoT云平台', + // title 建议放到 src/app.tsx 的运行时 layout 中用 t('app.title') 动态返回 }, locale: { // umi/max 内置 i18n diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 7886c6cbf6..bf38c68942 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -114,8 +114,10 @@ export async function getInitialState(): Promise<{ } export const layout: RunTimeLayoutConfig = ({ initialState }: any) => { - // 新写法:使用 intl.formatMessage,替代 formatMessage() return { + // 浏览器 Tab 标题(可国际化) + title: t('app.title'), + // 面包屑配置 headerContentRender: () => , logo: '/logo.png',