From 446028c074437b70ac9c13b20de4081d94cd1553 Mon Sep 17 00:00:00 2001 From: Linzp Date: Thu, 4 Jun 2026 15:59:38 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +++- .../Tenant/CompanyInfo/DevelopmentHistory/index.js | 7 +++---- src/components/Tenant/Tenant.js | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 34098d3..8c868b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne-components/components-admin", - "version": "1.1.45", + "version": "1.1.46", "description": "用于实现一个后台管理系统的必要组件", "scripts": { "init": "husky", @@ -88,6 +88,7 @@ "@kne/axios-fetch": "^1.2.0", "@kne/column-split": "^1.0.5", "@kne/count-down": "^0.2.2", + "@kne/ensure-slash": "^0.1.0", "@kne/is-empty": "^1.0.1", "@kne/json-view": "^0.1.1", "@kne/react-box": "^0.1.9", @@ -99,6 +100,7 @@ "@kne/use-control-value": "^0.1.9", "@kne/use-ref-callback": "^0.1.2", "@kne/use-refer-navigate": "^1.0.0", + "dayjs": "^1.11.21", "md5": "^2.3.0", "xlsx": "^0.18.5" } diff --git a/src/components/Tenant/CompanyInfo/DevelopmentHistory/index.js b/src/components/Tenant/CompanyInfo/DevelopmentHistory/index.js index 79549e0..f58131c 100644 --- a/src/components/Tenant/CompanyInfo/DevelopmentHistory/index.js +++ b/src/components/Tenant/CompanyInfo/DevelopmentHistory/index.js @@ -2,6 +2,7 @@ import { createWithRemoteLoader } from '@kne/remote-loader'; import { Empty } from 'antd'; import dayjs from 'dayjs'; import Timeline from '@kne/timeline'; +import ensureSlash from '@kne/ensure-slash'; import withLocale from '../../withLocale'; import style from '../style.module.scss'; @@ -16,7 +17,7 @@ const resolveImageSrc = (id, origin) => { if (typeof id === 'string' && /^https?:\/\//i.test(id)) { return id; } - return `${origin}/api/v1/static/file-id/${id}`; + return `${ensureSlash(origin) || window.location.origin}/api/v1/static/file-id/${id}`; }; const DevelopmentHistory = createWithRemoteLoader({ @@ -31,8 +32,6 @@ const DevelopmentHistory = createWithRemoteLoader({ return ; } - const origin = (typeof staticUrl === 'string' && staticUrl) || (typeof window !== 'undefined' ? window.location.origin : ''); - const timelineData = list.map(item => { const row = { title: formatTime(item.time), @@ -40,7 +39,7 @@ const DevelopmentHistory = createWithRemoteLoader({ }; if (item.images && item.images.length > 0) { row.images = item.images.map(id => ({ - src: resolveImageSrc(id, origin) + src: resolveImageSrc(id, staticUrl) })); } if (item.extra) { diff --git a/src/components/Tenant/Tenant.js b/src/components/Tenant/Tenant.js index 7bed20c..0812ee7 100644 --- a/src/components/Tenant/Tenant.js +++ b/src/components/Tenant/Tenant.js @@ -24,6 +24,7 @@ const Tenant = createWithRemoteLoader({ navigation={{ base: `${baseUrl}/tenant`, showIndex: false, + defaultTitle: navigation.defaultTitle, list: [ ...(navigation.list || []), { From de82f868d67aaa52b2e77c468b48bb957ff748ed Mon Sep 17 00:00:00 2001 From: Linzp Date: Thu, 4 Jun 2026 18:19:14 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8c868b6..9aaaf43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne-components/components-admin", - "version": "1.1.46", + "version": "1.1.47", "description": "用于实现一个后台管理系统的必要组件", "scripts": { "init": "husky", @@ -64,7 +64,7 @@ } }, "devDependencies": { - "@kne/modules-dev": "^2.2.1", + "@kne/modules-dev": "^2.3.4", "@kne/react-fetch": "^1.4.3", "@kne/react-scripts": "^5.1.5", "@kne/remote-loader": "^1.2.3", From 95830fadcce0ec876d88ea8fdfa377b7ad6e6a2a Mon Sep 17 00:00:00 2001 From: Linzp Date: Thu, 4 Jun 2026 18:37:36 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\345\233\275\351\231\205\345\214\226.md" | 129 ++++++++++++++++++ src/components/Task/enums.js | 31 ++--- src/components/Task/withLocale.js | 14 +- 3 files changed, 156 insertions(+), 18 deletions(-) diff --git "a/prompts/prompts-remote-components/\345\233\275\351\231\205\345\214\226.md" "b/prompts/prompts-remote-components/\345\233\275\351\231\205\345\214\226.md" index 30401db..b7fc66f 100644 --- "a/prompts/prompts-remote-components/\345\233\275\351\231\205\345\214\226.md" +++ "b/prompts/prompts-remote-components/\345\233\275\351\231\205\345\214\226.md" @@ -90,12 +90,139 @@ const columns = getColumns({formatMessage}); formatMessage({ id: 'KeyWithParam' }, { name: value }) ``` +### 6. 两种可复用国际化模式 + +#### 模式一:提供 `locale` 的 function(常用于枚举国际化) + +适用场景:枚举、静态配置、非 React 组件函数等不能直接使用 `useIntl` 的地方。参考 `EnumLoader`。 + +核心做法是在 `withLocale.js` 中额外导出 `createFormatMessage(locale)`,由普通函数接收 `locale` 后创建当前语言的 `formatMessage`: + +```javascript +import { createWithIntlProvider, createIntl } from '@kne/react-intl'; +import zhCN from './locale/zh-CN'; +import enUS from './locale/en-US'; + +const withLocale = createWithIntlProvider({ + defaultLocale: 'zh-CN', + messages: { + 'zh-CN': zhCN, + 'en-US': enUS + }, + namespace: 'ai-talent-saas' +}); + +export const createFormatMessage = locale => { + const { formatMessage } = createIntl({ + locale, + messages: { + 'zh-CN': zhCN, + 'en-US': enUS + }, + namespace: 'ai-talent-saas' + }); + return formatMessage; +}; + +export default withLocale; +``` + +枚举函数只负责接收 `locale` 并返回国际化后的枚举项,`value` 保持业务值稳定,只翻译 `description`: + +```javascript +import { createFormatMessage } from './withLocale'; + +const employeeStatus = ({ locale }) => { + const formatMessage = createFormatMessage(locale); + return [ + { description: formatMessage({ id: 'enumLoader.employeeStatusActive' }), value: 'ACTIVE' }, + { description: formatMessage({ id: 'enumLoader.employeeStatusResign' }), value: 'RESIGN' } + ]; +}; + +export default employeeStatus; +``` + +使用时由调用方传入当前语言: + +```javascript +const statusList = enums.employeeStatus({ locale }); +``` + +#### 模式二:将国际化内容作为 JSX(常用于配置项、列、卡片内容) + +适用场景:`label`、`title`、`content` 等字段支持 `ReactNode`,并且这些配置可能在普通函数中返回,不能直接调用 Hook。参考 `TenantUserPlugin`。 + +核心做法是在 `withLocale.js` 中额外导出一个被 `withLocale` 包裹的 `FormatMessage` 组件: + +```javascript +import { createWithIntlProvider, useIntl } from '@kne/react-intl'; +import zhCN from './locale/zh-CN'; +import enUS from './locale/en-US'; + +const withLocale = createWithIntlProvider({ + defaultLocale: 'zh-CN', + messages: { + 'zh-CN': zhCN, + 'en-US': enUS + }, + namespace: 'ai-talent-saas' +}); + +export const FormatMessage = withLocale(props => { + const { formatMessage } = useIntl(); + return formatMessage(props); +}); + +export default withLocale; +``` + +普通配置函数中直接把翻译内容作为 JSX 返回: + +```javascript +import withLocale, { FormatMessage } from './withLocale'; + +export const personalCard = ({ moreInfo }) => { + return [ + ...moreInfo, + { + key: 'position', + label: , + content: 'xxx' + } + ]; +}; + +export const getUserListColumns = ({ columns }) => { + return [ + ...columns, + { + title: , + name: 'options.position' + } + ]; +}; +``` + +如果组件内部本身需要立即得到字符串(如表单 `label`、按钮文本、错误信息、modal 标题),仍然在被 `withLocale` 包裹的组件内使用 `useIntl`: + +```javascript +const TenantUserPlugin = createWithRemoteLoader({...})( + withLocale(({ remoteModules, ...props }) => { + const { formatMessage } = useIntl(); + return ; + }) +); +``` + ## 四、注意事项 1. **所有使用 `useIntl` 的组件必须用 `withLocale` 包裹** 2. **`getColumns` 等工具函数通过参数接收 `formatMessage`,不使用 `useIntl`** 3. **语言包中避免重复的 key**,命名规则:`模块名 + 功能名`,如 `UserName`、`UserRole` 4. **`createWithRemoteLoader` 创建的组件内部使用 useIntl 时,外层需要重命名并用 withLocale 包裹** +5. **枚举/普通函数国际化优先使用 `createFormatMessage(locale)` 模式**,函数入参显式接收 `locale`,返回值中只翻译展示字段 +6. **配置项需要返回 ReactNode 时使用 `` 模式**,不要在普通函数中直接调用 `useIntl` --- @@ -177,6 +304,8 @@ const getColumns = ({formatMessage}) => { - [ ] `getColumns` 等工具函数通过参数接收 `formatMessage` - [ ] 语言包中无重复 key - [ ] `createWithRemoteLoader` 组件必须使用 `createWithRemoteLoader({...})(withLocale(...))` 链式调用格式,**禁止**先定义中间变量再包裹 +- [ ] 枚举/普通函数如需国际化,使用 `createFormatMessage(locale)`,不要在函数中使用 Hook +- [ ] `label`、`title`、`content` 等支持 JSX 的配置项可使用 `` ### 5. 最后检查 运行命令找到所有使用 useIntl 的文件,确保都已正确包裹: diff --git a/src/components/Task/enums.js b/src/components/Task/enums.js index 7e57ae0..8f6d3cd 100644 --- a/src/components/Task/enums.js +++ b/src/components/Task/enums.js @@ -1,20 +1,17 @@ -const TASK_STATUS_ENUM = [ - { value: 'pending', description: '等待执行', type: 'info' }, - { - value: 'running', - description: '执行中', - type: 'progress' - }, - { value: 'waiting', description: '等待操作', type: 'info' }, - { - value: 'success', - description: '成功', - type: 'success' - }, - { value: 'failed', description: '失败', type: 'danger' }, - { value: 'canceled', description: '取消' } -]; +import { createFormatMessage } from './withLocale'; -const enums = { taskStatus: TASK_STATUS_ENUM }; +const taskStatus = ({ locale }) => { + const formatMessage = createFormatMessage(locale); + return [ + { description: formatMessage({ id: 'Pending' }), value: 'pending', type: 'info' }, + { description: formatMessage({ id: 'Running' }), value: 'running', type: 'progress' }, + { description: formatMessage({ id: 'Waiting' }), value: 'waiting', type: 'info' }, + { description: formatMessage({ id: 'Success' }), value: 'success', type: 'success' }, + { description: formatMessage({ id: 'Failed' }), value: 'failed', type: 'danger' }, + { description: formatMessage({ id: 'Canceled' }), value: 'canceled' } + ]; +}; + +const enums = { taskStatus }; export default enums; diff --git a/src/components/Task/withLocale.js b/src/components/Task/withLocale.js index c932c37..6663bef 100644 --- a/src/components/Task/withLocale.js +++ b/src/components/Task/withLocale.js @@ -1,4 +1,4 @@ -import { createWithIntlProvider } from '@kne/react-intl'; +import { createWithIntlProvider, createIntl } from '@kne/react-intl'; import zhCN from './locale/zh-CN'; import enUS from './locale/en-US'; @@ -11,4 +11,16 @@ const withLocale = createWithIntlProvider({ namespace: 'components-admin:Task' }); +export const createFormatMessage = locale => { + const { formatMessage } = createIntl({ + locale, + messages: { + 'zh-CN': zhCN, + 'en-US': enUS + }, + namespace: 'components-admin:Task' + }); + return formatMessage; +}; + export default withLocale;