From 013a0fdc1ebc14a3f62a11ee2bc0ea64a50b353e Mon Sep 17 00:00:00 2001 From: Linzp Date: Mon, 23 Mar 2026 16:07:52 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +- doc/base.js | 6 +- package.json | 4 +- prompts/prompts-frontend-libs/README.md | 132 ++++++++ .../\345\233\275\351\231\205\345\214\226.md" | 217 ++++++++++++ ...14\345\205\263\351\224\256\350\257\215.md" | 142 ++++++++ ...73\345\236\213\345\243\260\346\230\216.md" | 103 ++++++ ...17\350\277\260\346\226\207\344\273\266.md" | 179 ++++++++++ ...37\346\210\220\346\226\207\346\241\243.md" | 20 ++ ...72\344\276\213\347\274\226\345\206\231.md" | 316 ++++++++++++++++++ prompts/prompts.json | 3 + ...\210\220README\347\264\242\345\274\225.md" | 65 ++++ src/assets/index.scss | 122 +++++++ src/fields/DatePicker.js | 45 +-- src/fields/DatePickerToday.js | 262 +++++++++++---- src/fields/Input.js | 22 +- src/fields/InputNumber.js | 12 +- src/fields/Select.js | 17 +- src/fields/TextArea.js | 12 +- src/fields/TimePicker.js | 22 +- src/fields/TreeSelect.js | 13 +- src/locale/en-US.js | 11 + src/locale/zh-CN.js | 11 + src/withLocale.js | 25 ++ 24 files changed, 1647 insertions(+), 126 deletions(-) create mode 100644 prompts/prompts-frontend-libs/README.md create mode 100644 "prompts/prompts-frontend-libs/\345\233\275\351\231\205\345\214\226.md" create mode 100644 "prompts/prompts-frontend-libs/\345\256\214\345\226\204package.json\346\217\217\350\277\260\345\222\214\345\205\263\351\224\256\350\257\215.md" create mode 100644 "prompts/prompts-frontend-libs/\346\267\273\345\212\240ts\347\261\273\345\236\213\345\243\260\346\230\216.md" create mode 100644 "prompts/prompts-frontend-libs/\347\224\237\346\210\220\345\214\205\345\212\237\350\203\275\346\217\217\350\277\260\346\226\207\344\273\266.md" create mode 100644 "prompts/prompts-frontend-libs/\347\224\237\346\210\220\346\226\207\346\241\243.md" create mode 100644 "prompts/prompts-frontend-libs/\347\273\204\344\273\266\347\244\272\344\276\213\347\274\226\345\206\231.md" create mode 100644 prompts/prompts.json create mode 100644 "prompts/\347\224\237\346\210\220README\347\264\242\345\274\225.md" create mode 100644 src/locale/en-US.js create mode 100644 src/locale/zh-CN.js create mode 100644 src/withLocale.js diff --git a/README.md b/README.md index cb5fbad..4142f60 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,15 @@ - # react-form-antd - ### 描述 把 @kne/react-form 表单校验逻辑应用到antd - ### 安装 ```shell npm i --save @kne/react-form-antd ``` - ### 概述 #### 特点 @@ -39,7 +35,7 @@ const {useRef} = React; const Example = () => { const addButton = useRef(); - return
+ return @@ -65,7 +61,9 @@ const Example = () => { ; }} - + { + console.log('value', value); + }}/> 提交 }; @@ -74,7 +72,5 @@ render(); ``` - ### API - diff --git a/doc/base.js b/doc/base.js index e9a172e..1da265b 100644 --- a/doc/base.js +++ b/doc/base.js @@ -4,7 +4,7 @@ const {useRef} = React; const Example = () => { const addButton = useRef(); - return
+ return @@ -30,7 +30,9 @@ const Example = () => { ; }} - + { + console.log('value', value); + }}/> 提交 }; diff --git a/package.json b/package.json index bad4318..6224a8f 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,9 @@ "react": ">=18.x" }, "dependencies": { - "@kne/react-form": "^3.1.2", + "@kne/react-form": "^3.1.6", "@kne/react-form-helper": "^3.0.2", + "@kne/react-intl": "^0.1.12", "@kne/use-control-value": "^0.1.1", "classnames": "^2.2.6", "dayjs": "^1.11.7", @@ -70,7 +71,6 @@ "devDependencies": { "@kne/microbundle": "^0.15.5", "@kne/modules-dev": "^2.1.2", - "@kne/react-form": "^3.1.0", "cross-env": "^7.0.3", "husky": "^9.0.11", "npm-run-all": "^4.1.5", diff --git a/prompts/prompts-frontend-libs/README.md b/prompts/prompts-frontend-libs/README.md new file mode 100644 index 0000000..33fe5be --- /dev/null +++ b/prompts/prompts-frontend-libs/README.md @@ -0,0 +1,132 @@ +# Prompts Frontend Libs 文档索引 + +本项目包含多个前端开发辅助提示词文档,用于指导 AI 完成常见的前端组件库开发任务。 + +## 文档列表 + +### 1. 组件国际化 + +**功能**: 指导将 React 组件完成国际化,支持多语言切换 + +**适用场景**: +- 组件需要支持多语言显示 +- 项目需要面向国际用户 +- 现有组件需要添加国际化支持 + +**核心内容**: +- useIntl Hook 使用方法 +- withLocale HOC 包裹模式 +- 语言包配置规范 +- 不同组件类型的国际化处理方式 + +**使用方式**: 阅读 [国际化](prompts/国际化.md) 了解详情 + +--- + +### 2. 添加 TypeScript 类型声明 + +**功能**: 为现有的 React 组件库添加完整的 TypeScript 类型定义 + +**适用场景**: +- JavaScript 组件库需要添加 TypeScript 支持 +- 提升代码可维护性和 IDE 智能提示 +- 便于团队协作和代码审查 + +**核心内容**: +- Props 接口定义规范 +- 类型声明文件结构 +- 复杂场景处理(嵌套对象、函数属性、联合类型) +- 组件导出声明模板 + +**使用方式**: 阅读 [添加ts类型声明](prompts/添加ts类型声明.md) 了解详情 + +--- + +### 3. 完善 package.json 描述和关键词 + +**功能**: 为项目完善 package.json 的 description 和 keywords 字段 + +**适用场景**: +- 新建项目需要完善 package.json 信息 +- 项目功能更新后需要调整描述 +- 提升 npm 包的搜索可发现性 + +**核心内容**: +- 描述撰写原则和模板 +- 关键词分类和提取来源 +- 信息收集和分析流程 +- 输出格式和检查清单 + +**使用方式**: 阅读 [完善package.json描述和关键词](prompts/完善package.json描述和关键词.md) 了解详情 + +--- + +### 4. 生成包功能描述文件 + +**功能**: 为 JavaScript/TypeScript 包生成 package-manifest.json 描述文件 + +**适用场景**: +- 自动化 API 文档生成 +- IDE 智能提示和自动补全 +- 包管理和分发平台的组件展示 +- 第三方工具的集成和分析 + +**核心内容**: +- 项目结构分析流程 +- 导出模块识别方法 +- JSON 结构构建规范 +- 类型定义标准和描述编写原则 + +**使用方式**: 阅读 [生成包功能描述文件](prompts/生成包功能描述文件.md) 了解详情 + +--- + +### 5. 生成文档 + +**功能**: 为项目生成标准化的 API 文档和概述文档 + +**适用场景**: +- 需要生成项目概述文档 (summary.md) +- 需要生成 API 文档 (api.md) +- 规范化项目文档结构 + +**核心内容**: +- 文档结构规范 +- 标题级别使用规范 +- 表格格式要求 +- 内容基于实际代码分析的原则 + +**使用方式**: 阅读 [生成文档](prompts/生成文档.md) 了解详情 + +--- + +### 6. 组件示例编写 + +**功能**: 编写遵循特定目录结构规范的 React 组件示例 + +**适用场景**: +- 为组件创建可运行的示例代码 +- 编写组件演示和文档 +- 构建组件库的示例展示系统 + +**核心内容**: +- 项目目录布局规范 +- example.json 配置结构 +- 示例代码编写模板 +- scope 依赖声明规则 +- 数据模拟方法 + +**使用方式**: 阅读 [组件示例编写](prompts/组件示例编写.md) 了解详情 + +--- + +## 如何选择 + +| 需求 | 使用文档 | +|------|----------| +| 组件需要支持多语言 | 组件国际化 | +| 为 JS 组件库添加 TS 类型支持 | 添加 TypeScript 类型声明 | +| 完善 package.json 描述和关键词 | 完善 package.json 描述和关键词 | +| 生成包的 API 描述文件 | 生成包功能描述文件 | +| 生成项目概述和 API 文档 | 生成文档 | +| 编写组件演示示例代码 | 组件示例编写 | diff --git "a/prompts/prompts-frontend-libs/\345\233\275\351\231\205\345\214\226.md" "b/prompts/prompts-frontend-libs/\345\233\275\351\231\205\345\214\226.md" new file mode 100644 index 0000000..9ec750e --- /dev/null +++ "b/prompts/prompts-frontend-libs/\345\233\275\351\231\205\345\214\226.md" @@ -0,0 +1,217 @@ +# 组件国际化指南 + +## 概述 + +本指南用于将组件完成国际化。 + +## 一、创建的文件 + +1. **`src/withLocale.js`** - 统一的国际化 HOC,所有组件共用 +2. **`src/locale/zh-CN.js`** - 中文语言包,所有组件共用 +3. **`src/locale/en-US.js`** - 英文语言包,所有组件共用 + +## 二、需要修改的文件类型 + +### 主组件文件 +- 添加 `useIntl` Hook +- 用 `withLocale` 包裹导出 + +### FormInner 表单组件 +- 添加 `useIntl` Hook +- 用 `withLocale` 包裹导出 +- 表单 label 国际化 + +### getColumns 等工具函数 +- 通过参数接收 `formatMessage` +- 移除内部的 `useIntl` 和 `withLocale` 引入 + +### Action 操作组件 +- 添加 `useIntl` Hook +- 用 `withLocale` 包裹导出 + +## 三、国际化的关键模式 + +### 1. useIntl Hook 使用 +```javascript +import { useIntl } from '@kne/react-intl'; + +const Component = () => { + const { formatMessage } = useIntl(); + return
{formatMessage({ id: 'ComponentName.Key' })}
; +}; +``` + +### 2. withLocale 包裹普通组件 +```javascript +import withLocale from '../withLocale'; +import { useIntl } from '@kne/react-intl'; + +const ComponentInner = ({ ...props }) => { + const { formatMessage } = useIntl(); + // 将所有中文替换为 formatMessage({ id: 'ComponentName.Key' }) + return
{formatMessage({ id: 'ComponentName.Key' })}
; +}; + +const Component = withLocale(ComponentInner); + +export default Component; +``` + +### 3. createWithRemoteLoader 组件(推荐格式) +```javascript +import withLocale from '../withLocale'; +import { useIntl } from '@kne/react-intl'; + +const ComponentInner = createWithRemoteLoader({...})(({ remoteModules, ...props }) => { + const { formatMessage } = useIntl(); + // ... +}); + +const Component = withLocale(ComponentInner); + +export default Component; +``` + +**注意:** 对于 `createWithRemoteLoader` 创建的组件,可以先用 `ComponentInner` 存储,再用 `withLocale` 包裹。 + +### 4. getColumns 等工具函数(formatMessage 从父组件传入) +```javascript +const getColumns = ({formatMessage}) => { + return [ + { + name: 'name', + title: formatMessage({ id: 'ComponentName.Key' }) + } + ]; +}; + +// 父组件中使用 +const columns = getColumns({formatMessage}); +``` + +### 5. 带参数的翻译 +```javascript +formatMessage({ id: 'ComponentName.KeyWithParam' }, { name: value }) +``` + +## 四、注意事项 + +1. **所有使用 `useIntl` 的组件必须用 `withLocale` 包裹** +2. **`getColumns` 等工具函数通过参数接收 `formatMessage`,不使用 `useIntl`** +3. **语言包中避免重复的 key**,命名规则:`组件名 + 功能名`,如 `PersonalCard.status.online` +4. **`createWithRemoteLoader` 创建的组件内部使用 useIntl 时,外层需要重命名并用 withLocale 包裹** +5. 注意检查 `withLocale`文件的引用地址,根目录组件使用 `../withLocale` + +--- + +# 组件国际化操作步骤 + +## 步骤 + +### 1. 更新语言包文件 +在 `src/locale/zh-CN.js` 和 `src/locale/en-US.js` 中添加对应的翻译文本: +```javascript +// zh-CN.js +const locale = { + ComponentName: { + status: { + online: '在线', + offline: '离线' + }, + summary: '简介' + } +}; + +// en-US.js +const locale = { + ComponentName: { + status: { + online: 'Online', + offline: 'Offline' + }, + summary: 'Summary' + } +}; +``` + +### 2. 修改组件文件 + +#### 主组件修改模式: +```javascript +import withLocale from '../withLocale'; +import { useIntl } from '@kne/react-intl'; + +const ComponentInner = ({ ...props }) => { + const { formatMessage } = useIntl(); + // 将所有中文替换为 formatMessage({ id: 'ComponentName.Key' }) + return ( + // ... + ); +}; + +const Component = withLocale(ComponentInner); + +export default Component; +``` + +#### FormInner 修改模式: +```javascript +import withLocale from '../withLocale'; +import { useIntl } from '@kne/react-intl'; + +const FormInnerInner = createWithRemoteLoader({...})(({ remoteModules, ...props }) => { + const { formatMessage } = useIntl(); + // label={formatMessage({ id: 'ComponentName.Key' })} + // ... +}); + +const FormInner = withLocale(FormInnerInner); + +export default FormInner; +``` + +#### Action 操作组件修改模式: +```javascript +import withLocale from '../withLocale'; +import { useIntl } from '@kne/react-intl'; + +const ActionInner = createWithRemoteLoader({...})(({ remoteModules, ...props }) => { + const { formatMessage } = useIntl(); + // ... +}); + +const ActionComponent = withLocale(ActionInner); + +export default ActionComponent; +``` + +#### getColumns 修改模式: +```javascript +// 移除 useIntl 和 withLocale 引入 +const getColumns = ({formatMessage}) => { + return [ + { + name: 'xxx', + title: formatMessage({ id: 'ComponentName.Key' }) + } + ]; +}; +``` + +父组件中调用:`getColumns({formatMessage})` + +### 3. 语言包 key 命名规范 +- 避免重复,使用 `组件名+功能名` 格式,如 `PersonalCard.status.online`、`UserName`、`UserRole`、`SettingType` +- 中文和英文语言包保持完全一致的 key 结构 + +### 4. 检查要点 +- [ ] 所有使用 `useIntl` 的组件都用 `withLocale` 包裹 +- [ ] `getColumns` 等工具函数通过参数接收 `formatMessage` +- [ ] 语言包中无重复 key +- [ ] `withLocale` 引用路径正确(组件在子目录使用 `../withLocale`) + +### 5. 最后检查 +运行命令找到所有使用 useIntl 的文件,确保都已正确包裹: +```bash +grep -r "useIntl" src/ --include="*.js" -l +``` diff --git "a/prompts/prompts-frontend-libs/\345\256\214\345\226\204package.json\346\217\217\350\277\260\345\222\214\345\205\263\351\224\256\350\257\215.md" "b/prompts/prompts-frontend-libs/\345\256\214\345\226\204package.json\346\217\217\350\277\260\345\222\214\345\205\263\351\224\256\350\257\215.md" new file mode 100644 index 0000000..b6438cc --- /dev/null +++ "b/prompts/prompts-frontend-libs/\345\256\214\345\226\204package.json\346\217\217\350\277\260\345\222\214\345\205\263\351\224\256\350\257\215.md" @@ -0,0 +1,142 @@ +# 完善 package.json 描述和关键词 + +## 任务描述 + +为 JavaScript/TypeScript 包项目的 `package.json` 文件完善 `description` 和 `keywords` 字段,使其准确反映项目的功能定位和核心特性。 + +## 实施步骤 + +### 1. 收集信息 + +#### 1.1 分析文档目录 +- 阅读 `doc/api.md` 了解组件 API 和功能列表 +- 阅读 `README.md` 了解项目概述和主要特性 +- 查看 `doc/example.json` 了解示例配置 + +#### 1.2 分析源码结构 +- 查看 `src/` 目录结构,识别导出的组件和模块 +- 分析主入口文件 `src/index.js` 的导出内容 +- 检查组件的 props 和功能特性 + +#### 1.3 分析依赖关系 +- 查看 `package.json` 中的 `peerDependencies` 了解技术栈 +- 查看 `dependencies` 了解核心依赖库 + +### 2. 撰写 description + +#### 2.1 描述结构 +``` +[核心定位] + [主要功能] + [适用场景] +``` + +#### 2.2 撰写原则 +- **简洁明了**:控制在 1-2 句话,不超过 100 个字符 +- **突出特色**:强调项目的独特价值 +- **用户视角**:从使用者角度描述功能 +- **避免技术细节**:不包含实现细节 + +#### 2.3 描述模板 + +**React 组件库模板**: +``` +一个用于 [场景] 的 React 组件库,提供 [核心组件/功能],支持 [特性列表]。 +``` + +**工具库模板**: +``` +用于 [场景] 的 [类型] 工具库,提供 [核心功能],支持 [特性列表]。 +``` + +### 3. 生成 keywords + +#### 3.1 关键词分类 + +| 分类 | 示例 | 说明 | +|------|------|------| +| 核心功能 | `select`, `picker`, `dropdown` | 描述主要功能 | +| 技术栈 | `react`, `antd`, `typescript` | 依赖的技术框架 | +| 组件类型 | `component`, `ui`, `form` | 组件分类 | +| 数据结构 | `tree`, `table`, `list` | 支持的数据展示形式 | +| 特性 | `multiselect`, `searchable`, `i18n` | 核心特性标签 | +| 场景 | `enterprise`, `admin`, `dashboard` | 适用场景 | + +#### 3.2 关键词数量 +- 建议数量:5-15 个 +- 按重要性排序 +- 包含英文和常见缩写 + +#### 3.3 关键词提取来源 + +1. **组件名称**:从导出的组件名称提取核心词汇 +2. **功能特性**:从支持的功能特性提取标签 +3. **技术依赖**:从 peerDependencies 和 dependencies 提取技术栈 +4. **使用场景**:从项目定位提取适用场景 + +### 4. 输出格式 + +#### 4.1 JSON 格式 +```json +{ + "description": "完善的描述内容", + "keywords": [ + "keyword1", + "keyword2", + "keyword3" + ] +} +``` + +#### 4.2 直接修改 package.json +将生成的内容更新到 `package.json` 的对应字段。 + +## 示例 + +### 示例分析流程 + +**步骤一:源码分析** +- 识别 `src/index.js` 导出的所有模块和组件 +- 分析组件的 props、功能特性 +- 确定技术栈(如 React、Vue、TypeScript 等) + +**步骤二:文档分析** +- 阅读 `README.md` 获取项目概述 +- 分析 `doc/api.md` 了解 API 功能列表 +- 查看示例代码理解使用场景 + +**步骤三:生成结果** +- 根据 分析结果撰写 description +- 从组件功能、技术栈、特性等维度提取 keywords +- 输出 JSON 格式的 description 和 keywords + +**示例输出格式**: +```json +{ + "description": "一个用于 [场景] 的 React 组件库,提供 [核心功能],支持 [特性列表]", + "keywords": [ + "react", + "component", + "功能关键词", + "技术栈关键词", + "特性关键词" + ] +} +``` + +## 检查清单 + +- [ ] description 长度适中(不超过 100 字符) +- [ ] description 包含核心功能和特色 +- [ ] keywords 包含技术栈标签 +- [ ] keywords 包含功能标签 +- [ ] keywords 按重要性排序 +- [ ] 没有重复或冗余的关键词 +- [ ] 关键词使用小写字母 +- [ ] 关键词使用连字符分隔单词(如 `tree-select`) + +## 注意事项 + +1. **避免过度宣传**:描述应客观准确,不使用夸张词汇 +2. **保持一致性**:关键词应与项目实际功能一致 +3. **考虑搜索优化**:使用 npm 搜索中常见的关键词 +4. **参考同类项目**:可参考 npm 上类似项目的关键词设置 +5. **定期更新**:当项目功能变化时,同步更新描述和关键词 diff --git "a/prompts/prompts-frontend-libs/\346\267\273\345\212\240ts\347\261\273\345\236\213\345\243\260\346\230\216.md" "b/prompts/prompts-frontend-libs/\346\267\273\345\212\240ts\347\261\273\345\236\213\345\243\260\346\230\216.md" new file mode 100644 index 0000000..f6117f6 --- /dev/null +++ "b/prompts/prompts-frontend-libs/\346\267\273\345\212\240ts\347\261\273\345\236\213\345\243\260\346\230\216.md" @@ -0,0 +1,103 @@ +# 为React组件库添加TypeScript类型定义的通用提示词 + +## 任务描述 + +为一个已有的React组件库添加完整的TypeScript类型定义,包括所有组件的Props接口、类型声明和导出。 + +## 实施步骤 + +### 1. 创建类型定义文件 + +在源代码目录创建`index.d.ts`文件,位置与主入口文件`index.js`同级。 + +### 2. 分析组件结构 + +- 查看所有组件文件的Props参数 +- 确定每个组件的输入属性类型 +- 识别回调函数的参数和返回值类型 + +### 3. 定义基础类型 + +```typescript +import { ReactNode, ComponentType, FC } from 'react'; +``` + +### 4. 为每个组件创建Props接口 + +- 组件名 + Props 作为接口名(如:FormInfoProps) +- 所有可选属性使用`?`标记 +- ReactNode类型用于支持字符串和JSX元素 +- 函数类型明确定义参数和返回值 +- 使用`[key: string]: any`支持动态属性 + +### 5. 处理复杂场景 + +- **嵌套对象属性**:定义内联对象类型 +- **函数属性**:明确参数类型和返回值类型 +- **联合类型**:使用`|`支持多种类型 +- **泛型组件**:使用ComponentType + +### 6. 导出组件类型声明 + +```typescript +export declare const ComponentName: FC; +``` + +## 类型定义模板 + +### 基础组件Props模板 + +```typescript +export interface ComponentNameProps { + className?: string; + children?: ReactNode; + + // 其他属性... + [key: string]: any; // 支持动态属性 +} +``` + +### 函数回调模板 + +```typescript +onEvent ? : (data: any, context: any, ...args: any[]) => Promise | any; +``` + +### 组件声明模板 + +```typescript +export declare const ComponentName: FC; +``` + +## 最佳实践 + +1. **类型完整性**:覆盖所有组件的公开API +2. **向后兼容**:保持现有JavaScript代码的正常运行 +3. **可选属性**:合理使用可选属性,提供良好的开发体验 +4. **文档注释**:为复杂类型添加JSDoc注释 +5. **版本控制**:在package.json中指定types字段指向类型文件 + +## 使用场景 + +- 为现有组件库添加TypeScript支持 +- 提升代码可维护性和开发体验 +- 支持IDE智能提示和类型检查 +- 便于团队协作和代码审查 + +## 示例项目结构 + +``` +src/ +├── index.js # 主入口文件 +├── index.d.ts # 类型定义文件(新增) +├── ComponentA.js # 组件A +├── ComponentB.js # 组件B +└── ... +``` + +## 注意事项 + +- 类型定义文件应与实际组件实现保持同步 +- 考虑使用更具体的类型替代`any`类型 +- 定期检查和更新类型定义以匹配组件API的变化 +- 对于复杂的组件,考虑拆分类型定义到多个文件中 \ No newline at end of file diff --git "a/prompts/prompts-frontend-libs/\347\224\237\346\210\220\345\214\205\345\212\237\350\203\275\346\217\217\350\277\260\346\226\207\344\273\266.md" "b/prompts/prompts-frontend-libs/\347\224\237\346\210\220\345\214\205\345\212\237\350\203\275\346\217\217\350\277\260\346\226\207\344\273\266.md" new file mode 100644 index 0000000..39dfcd3 --- /dev/null +++ "b/prompts/prompts-frontend-libs/\347\224\237\346\210\220\345\214\205\345\212\237\350\203\275\346\217\217\350\277\260\346\226\207\344\273\266.md" @@ -0,0 +1,179 @@ +# 生成包功能描述文件(package-manifest.json)的通用提示词 + +## 任务描述 + +为一个JavaScript/TypeScript包项目生成`package-manifest.json`文件,该文件用于描述包的主要功能、导出模块、组件API等详细信息。 + +## 实施步骤 + +### 1. 分析项目结构 + +- 查看`package.json`了解包的基本信息 +- 分析主入口文件(`index.js`/`index.ts`)的导出内容 +- 检查源代码目录结构,识别所有可导出的模块/组件 + +### 2. 识别导出模块 + +- 默认导出:通常标记为`default` +- 命名导出:所有具名导出的组件、函数、类等 + +### 3. 分析每个模块的功能 + +对于每个导出的模块,需要收集以下信息: + +#### React组件 +- **description**: 组件的主要功能描述 +- **type**: 通常为`ReactNode` +- **props**: 组件的所有属性,包括: + - 属性名 + - 属性描述 + - 属性类型(支持联合类型,如`string|number`,不能使用下面`类型定义规范`定义之外的类型) + +#### 函数/类 +- **description**: 功能描述 +- **type**: 返回值类型 +- **parameters**: 参数列表(如果有) +- **properties**: 类属性(如果是类) + +#### 常量/对象 +- **description**: 用途描述 +- **type**: 数据类型 +- **properties**: 对象属性结构 + +### 4. 构建JSON结构 + +```json +{ + "description": "包的主要导出模块入口", + "modules": { + "模块名": { + "description": "模块功能描述", + "type": "类型声明", + // 根据模块类型可能包含以下字段 + "props": {}, // React组件属性 + "parameters": [], // 函数参数 + "properties": {}, // 对象/类属性 + "returns": "返回值类型" // 函数返回值 + } + } +} +``` + +## 类型定义规范 + +### 基础类型 +- `string`: 字符串 +- `number`: 数字 +- `boolean`: 布尔值 +- `object`: 对象 +- `array`: 数组 +- `function`: 函数 +- `Date`: 日期对象 +- `Promise`: Promise对象 +- `RegExp`: 正则表达式 +- `Error`: 错误对象 +- `undefined`: 未定义 +- `null`: 空值 +- `any`: 任意类型 +- `void`: 无返回值 +- `unknown`: 未知类型 +- `ReactNode`: React节点(组件专用) + +### 联合类型 +使用`|`连接多种类型: +- `string|ReactNode`: 字符串或React节点 +- `number|object`: 数字或对象 +- `function|null`: 函数或null + +### 复杂类型 +- `object[]`: 对象数组 +- `ReactNode[]`: React节点数组 +- `string[]`: 字符串数组 + +## 优先级排序 + +1. **主要组件**: 包中最核心、最常用的组件 +2. **辅助组件**: 支持性组件 +3. **工具函数**: 纯函数、工具方法 +4. **类型定义**: TypeScript类型、接口 +5. **常量配置**: 配置对象、常量 + +## 属性命名约定 + +### 通用属性 +- `className`: CSS类名 +- `children`: 子元素 +- `style`: 样式对象 +- `title`: 标题 +- `description`: 描述 + +## 描述编写原则 + +1. **简洁明确**: 一句话说明核心功能 +2. **用户视角**: 从使用者的角度描述 +3. **突出价值**: 说明解决了什么问题 +4. **避免实现细节**: 不描述内部实现逻辑 + +## 示例格式 + +### React组件示例 +```json +{ + "FormModal": { + "description": "模态框表单组件,在弹窗中展示表单", + "type": "ReactNode", + "props": { + "open": { + "description": "是否打开模态框", + "type": "boolean" + }, + "title": { + "description": "模态框标题", + "type": "ReactNode" + }, + "onSubmit": { + "description": "提交回调函数", + "type": "function" + } + } + } +} +``` + +### 工具函数示例 +```json +{ + "formatDate": { + "description": "格式化日期字符串", + "type": "string", + "parameters": [ + { + "name": "format", + "description": "格式化模板", + "type": "string" + } + ] + } +} +``` + +## 文件位置 + +将生成的`package-manifest.json`文件放置在: +- `src/package-manifest.json` + +## 使用场景 + +- 自动化API文档生成 +- IDE智能提示和自动补全 +- 包管理和分发平台的组件展示 +- 团队协作时的API参考 +- 第三方工具的集成和分析 + +## 注意事项 + +- 避免包含内部实现细节 +- 不要包含私有方法和属性 +- 确保向后兼容性 +- 定期更新以匹配代码变更 +- 考虑国际化需求(如需要) \ No newline at end of file diff --git "a/prompts/prompts-frontend-libs/\347\224\237\346\210\220\346\226\207\346\241\243.md" "b/prompts/prompts-frontend-libs/\347\224\237\346\210\220\346\226\207\346\241\243.md" new file mode 100644 index 0000000..3476ca7 --- /dev/null +++ "b/prompts/prompts-frontend-libs/\347\224\237\346\210\220\346\226\207\346\241\243.md" @@ -0,0 +1,20 @@ +### 文档生成说明 + +本次文档生成遵循以下规范: + +#### 文档结构 + +1. **doc/summary.md** - 项目概述文档 + - 使用 h3 及以下标题级别 + - 包含:项目概述、主要特性、使用场景 + +2. **doc/api.md** - API 文档 + - 使用 h3 及以下标题级别 + - 使用表格格式展示命令和参数 + - 包含:命令行接口、程序化 API、输出结构说明 + +#### 文档要求 + +- 标题级别:仅使用 h3(###)及以下 +- 表格格式:属性名、类型/参数、默认值/返回值、说明 +- 内容基于实际代码分析,确保准确性 diff --git "a/prompts/prompts-frontend-libs/\347\273\204\344\273\266\347\244\272\344\276\213\347\274\226\345\206\231.md" "b/prompts/prompts-frontend-libs/\347\273\204\344\273\266\347\244\272\344\276\213\347\274\226\345\206\231.md" new file mode 100644 index 0000000..df906c5 --- /dev/null +++ "b/prompts/prompts-frontend-libs/\347\273\204\344\273\266\347\244\272\344\276\213\347\274\226\345\206\231.md" @@ -0,0 +1,316 @@ +# 组件示例编写提示词 + +本提示词用于编写遵循特定目录结构规范的 React 项目组件示例。 + +## 1. 目录结构规范 + +### 项目目录布局 + +``` +{PROJECT_ROOT}/ +├── src/ # 源代码目录 +│ └── components/ # 组件源代码 +│ ├── ComponentA/ # 单个组件目录 +│ ├── ComponentB/ # 带子组件的目录 +│ │ ├── Sub1/ +│ │ ├── Sub2/ +│ │ └── index.js +│ └── ... +├── doc/ # 文档及示例目录(所有组件共用) +│ ├── example.json # 示例配置文件(核心) +│ ├── api.md # 组件API文档 +│ ├── summary.md # 组件简要描述 +│ ├── base.js # 基础示例代码 +│ ├── component-a.js # ComponentA示例 +│ ├── component-b.js # ComponentB主组件示例 +│ ├── component-b-sub1.js # ComponentB子组件Sub1示例 +│ ├── component-b-sub2.js # ComponentB子组件Sub2示例 +│ └── style.scss # 示例样式(可选) +└── package.json # 项目配置文件 +``` + +### 示例文件规则 + +**重要**:所有组件的示例文件都统一放在项目根目录的 `doc/` 中,而不是在每个组件目录下创建单独的 `doc/` 目录。 + +- 单个组件:使用组件名的小写短横线形式命名示例文件,如 `component-a.js` +- 带子组件的组件:主组件示例使用组件名,子组件示例使用组件名加子组件名,如 `component-b.js`、`component-b-sub1.js` + +### 项目配置变量 + +在编写示例时需要替换以下变量: + +| 变量名 | 说明 | 示例值 | +|--------|------|--------| +| `{PACKAGE_NAME}` | 从 package.json 的 name 字段获取 | `@kne/react-file` | +| `{PROJECT_NAME}` | 项目名称,用于 getPublicPath | `react-file` | +| `{LIB_NAME}` | 组件库名称,通常是驼峰形式 | `ReactFile` | + +## 2. example.json 配置结构 + +所有组件的示例配置统一在 `doc/example.json` 中管理: + +```json +{ + "isFull": true, + "list": [ + { + "title": "ComponentA", + "description": "组件A的描述", + "code": "./component-a.js", + "scope": [ + { + "name": "_{LIB_NAME}", + "packageName": "{PACKAGE_NAME}", + "importStatement": "import * as _{LIB_NAME} from \"{PACKAGE_NAME}\"" + }, + { + "packageName": "{PACKAGE_NAME}/dist/index.css" + }, + { + "name": "antd", + "packageName": "antd" + }, + { + "name": "remoteLoader", + "packageName": "@kne/remote-loader" + } + ] + }, + { + "title": "ComponentB", + "description": "组件B主组件", + "code": "./component-b.js", + "scope": [] + }, + { + "title": "ComponentB-Sub1", + "description": "组件B子组件Sub1", + "code": "./component-b-sub1.js", + "scope": [] + } + ] +} +``` + +**重要说明**: +- `packageName` 可以使用当前组件包名的特殊格式:`@kne/current-lib_{PACKAGE_NAME_SUFFIX}` +- 例如:`@kne/react-file` 项目可以使用 `@kne/current-lib_react-file` 来引用当前正在开发的组件包 +- 这种特殊写法用于文档示例系统自动解析当前包名 + +## 3. 示例代码规范 + +### 示例编写原则 + +**重要原则**: + +1. **充分展示API功能**:示例代码要尽可能完整地展示组件的所有API功能和配置选项,包括: + - 组件的所有核心属性(props)及其不同取值 + - 事件回调函数的完整实现 + - 不同配置模式下的使用场景 + - 组件间的组合使用方式 + - 各种边界情况和特殊场景 + +2. **贴近业务场景的示例数据**:示例数据应尽量模拟真实的业务场景,避免过于简化的假数据: + - 使用真实的字段名称和数据结构 + - 包含合理的业务上下文信息 + - 体现实际应用中的复杂度和多样性 + - 模拟真实接口返回的数据格式 + +### 导入方式 + +- 使用 `scope` 中声明的变量名:`const { FilePreview } = _{LIB_NAME};` +- 工具包引用:`const { createWithRemoteLoader, getPublicPath } = remoteLoader;` + +### 标准示例模板(需要 PureGlobal 模拟数据) + +```javascript +const { YourComponent } = _{LIB_NAME}; +const { createWithRemoteLoader, getPublicPath } = remoteLoader; + +const BaseExample = createWithRemoteLoader({ + modules: ['components-core:Global@PureGlobal', 'components-core:InfoPage'] +})(({ remoteModules }) => { + const [PureGlobal, InfoPage] = remoteModules; + return ( + { + return { data: { code: 0, data: api.loader() } }; + }, + apis: { + file: { + staticUrl: getPublicPath('{PROJECT_NAME}') || window.PUBLIC_URL, + getUrl: { + loader: async ({ params }) => { + // 模拟数据 + return 'mock-url'; + } + } + } + } + }}> + + + + + + + ); +}); + +render(); +``` + +### 简化示例模板(无需远程加载) + +```javascript +const { YourComponent } = _{LIB_NAME}; +const { Flex, Switch } = antd; +const { useState } = React; + +const BaseExample = () => { + const [state, setState] = useState(false); + return ( + + + + ); +}; + +render(); +``` + +## 4. scope 依赖声明规则 + +| 场景 | name | packageName | importStatement(可选) | +|------|------|-------------|------------------------| +| 项目组件 | `_{LIB_NAME}` | `{PACKAGE_NAME}` 或 `@kne/current-lib_{PACKAGE_NAME_SUFFIX}` | `import * as _{LIB_NAME} from "{PACKAGE_NAME}"` | +| 样式文件 | - | `{PACKAGE_NAME}/dist/index.css` 或 `@kne/current-lib_{PACKAGE_NAME_SUFFIX}/dist/index.css` | - | +| 远程加载器 | `remoteLoader` | `@kne/remote-loader` | - | +| Antd组件 | `antd` | `antd` | - | +| React原生 | `React` | - | -(无需声明) | + +**当前包引用规则**: +- 在 `example.json` 的 `scope` 中引用当前正在开发的组件包时,使用 `@kne/current-lib_{PACKAGE_NAME_SUFFIX}` +- 例如:`@kne/info-page` 项目应使用 `@kne/current-lib_info-page` +- 这样可以让文档系统自动识别并加载当前开发中的组件版本 + +## 5. API文档编写 + +`doc/api.md` 中包含所有组件的API文档,使用表格格式: + +```markdown +### 组件名称 + +组件描述 + +#### 属性 + +| 属性 | 类型 | 默认值 | 描述 | +|------|------|-------|------| +| propName | string | - | 属性描述 | +``` + +## 6. 数据模拟 + +在示例中使用 PureGlobal 的 preset 模拟数据: + +```javascript +apis: { + file: { + staticUrl: getPublicPath('{PROJECT_NAME}') || window.PUBLIC_URL, + getUrl: { + loader: async ({ params }) => { + // 根据 params 返回对应的 mock 数据 + const urlMap = { + 1: '/mock/example.png', + 2: '/mock/example.pdf' + }; + return new Promise(resolve => { + setTimeout(() => { + resolve(urlMap[params.id]); + }, 500); + }); + } + } + } +} +``` + +## 7. 组件导出规范 + +在组件的主入口 `index.js` 中,需要导出所有子组件: + +```javascript +export { default } from './MainComponent'; +export { default as SubComponent1 } from './SubComponent1'; +export { default as SubComponent2 } from './SubComponent2'; +``` + +在示例中通过解构引用: + +```javascript +const { MainComponent, SubComponent1, SubComponent2 } = _{LIB_NAME}; +``` + +## 8. 使用说明 + +### 创建新组件示例步骤 + +1. 在 `src/components/` 下创建或编辑组件 +2. 在 `doc/example.json` 中添加示例配置 +3. 在 `doc/` 目录下创建对应的 `.js` 示例文件 +4. 在 `doc/api.md` 中添加组件API文档(如果是新组件) +5. 在组件的 `index.js` 中确保正确导出 + +### 变量替换示例 + +假设项目 `package.json` 中配置为: + +```json +{ + "name": "@your-org/your-project" +} +``` + +则变量值为: + +- `{PACKAGE_NAME}` → `@your-org/your-project` +- `{PROJECT_NAME}` → `your-project` +- `{LIB_NAME}` → `YourProject` +- `{PACKAGE_NAME_SUFFIX}` → `your-project`(用于当前包引用) + +实际使用时需要替换为: + +```json +{ + "name": "_YourProject", + "packageName": "@your-org/your-project", + "importStatement": "import * as _YourProject from \"@your-org/your-project\"" +} +``` + +**引用当前开发包时的特殊写法**: +```json +{ + "name": "_YourProject", + "packageName": "@kne/current-lib_your-project", + "importStatement": "import * as _YourProject from \"@your-org/your-project\"" +} +``` + +```javascript +const { YourComponent } = _YourProject; +const { createWithRemoteLoader, getPublicPath } = remoteLoader; + +// ... +staticUrl: getPublicPath('your-project') || window.PUBLIC_URL, +``` + +## 9. 项目参考 + +本项目的示例文件位于 `doc/` 目录: + +- `doc/example.json` - 所有组件的示例配置 +- `doc/api.md` - 所有组件的API文档 +- `doc/summary.md` - 项目概述和核心特性说明 \ No newline at end of file diff --git a/prompts/prompts.json b/prompts/prompts.json new file mode 100644 index 0000000..3d07349 --- /dev/null +++ b/prompts/prompts.json @@ -0,0 +1,3 @@ +{ + "@kne/prompts-frontend-libs": "1.0.1" +} diff --git "a/prompts/\347\224\237\346\210\220README\347\264\242\345\274\225.md" "b/prompts/\347\224\237\346\210\220README\347\264\242\345\274\225.md" new file mode 100644 index 0000000..b7f99c7 --- /dev/null +++ "b/prompts/\347\224\237\346\210\220README\347\264\242\345\274\225.md" @@ -0,0 +1,65 @@ +### 文档索引生成指南 + +#### 概述 + +本文档描述如何从包含多个子目录的文档目录生成结构清晰、便于查阅的索引文档。 + +#### 生成流程 + +##### 第一步:扫描源目录 + +识别当前文件所在目录中的所有子目录和文档文件中的所有README.md文件。 + +##### 第二步:分析子目录结构 + +对每个README.md进行内容分析: + +- 提取文档集合的核心用途和主要特性 +- 记录已安装的版本信息(如存在 prompts.json) + +##### 第三步:设计索引结构 + +采用统一的模板组织信息: + +```markdown +### 序号. 文档名称 + +**功能**: 简要描述 + +**适用场景**: +- 场景一 +- 场景二 + +**核心内容**: +- 内容点一 +- 内容点二 +``` + +##### 第四步:生成索引文档 + +按以下结构组织最终文档: + +1. **项目标题与简介**:说明文档集合的用途 +2. **文档集合列表**:按子目录分组展示各文档摘要 +3. **快速选择指南**:帮助用户根据需求快速定位 + +##### 第五步:添加导航辅助 + +在文末添加决策表格,帮助用户根据需求快速定位: + +| 需求 | 推荐文档 | 所属集合 | +|------|----------|----------| +| 具体需求描述 | 对应文档名称 | 所属子目录 | + +#### 设计原则 + +1. **信息层次清晰**:使用标题层级区分结构,使用加粗、列表等格式突出重点 +2. **内容适度抽象**:不直接复制原文内容,提炼核心要点 +3. **便于快速检索**:提供功能定位、明确适用场景、添加决策辅助表格 +4. **保持可维护性**:结构化模板便于更新,格式统一减少维护成本 + +#### 扩展建议 + +- **版本管理**:可在文档列表中添加版本号和更新日期 +- **标签系统**:为文档添加标签便于分类检索 +- **依赖关系**:说明文档之间的关联或前置依赖 diff --git a/src/assets/index.scss b/src/assets/index.scss index 309c237..baa57c1 100644 --- a/src/assets/index.scss +++ b/src/assets/index.scss @@ -242,3 +242,125 @@ } } } + +.svg_box { + display: flex; + align-items: center; + padding: 0 8px; + color: rgba(0, 0, 0, 0.25); +} + +.react-form__field-input { + .data_range_picker { + flex: 1; + } +} + +// 垂直布局下的特殊处理 +.react-form--inner { + .svg_box { + padding: 0 4px; + } + + .ant-picker { + flex: 1; + width: 100%; + } + + .date-picker-today-container { + width: 100%; + } +} + +// DatePickerToday 样式 +.date-picker-today-container { + position: relative; + display: inline-block; + vertical-align: top; + + .date-picker-today-inputs { + display: inline-flex; + align-items: center; + border: 1px solid #d9d9d9; + border-radius: 6px; + padding: 0 11px; + background: #fff; + transition: all 0.2s; + cursor: pointer; + + &:hover { + border-color: #4096ff; + } + + &:focus-within { + border-color: #4096ff; + box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1); + } + + .date-picker-today-start, + .date-picker-today-end { + display: flex; + align-items: center; + + .ant-input { + border: none; + padding: 0; + height: 30px; + cursor: pointer; + width: 110px; + outline: none; + background: transparent; + color: rgba(0, 0, 0, 0.85); + font-size: 14px; + + &::placeholder { + color: rgba(0, 0, 0, 0.25); + } + + &:focus { + box-shadow: none; + } + } + + .ant-input.so-far-active { + color: rgba(0, 0, 0, 0.85); + font-weight: 500; + } + } + + .date-picker-today-separator { + padding: 0 8px; + color: rgba(0, 0, 0, 0.25); + line-height: 30px; + } + + .date-picker-today-suffix { + display: flex; + align-items: center; + padding-left: 4px; + color: rgba(0, 0, 0, 0.25); + + .date-picker-today-clear { + color: rgba(0, 0, 0, 0.25); + font-size: 12px; + transition: color 0.2s; + cursor: pointer; + + &:hover { + color: rgba(0, 0, 0, 0.45); + } + } + } + } + + // 隐藏的 DatePicker 触发器 + .ant-picker { + position: absolute; + top: 0; + left: 0; + width: 0 !important; + height: 0 !important; + visibility: hidden; + pointer-events: none; + } +} diff --git a/src/fields/DatePicker.js b/src/fields/DatePicker.js index 83b24d2..a72aa64 100644 --- a/src/fields/DatePicker.js +++ b/src/fields/DatePicker.js @@ -1,53 +1,58 @@ import {DatePicker} from 'antd'; import {hooks} from '@kne/react-form-helper'; +import withLocale, {useIntl} from '../withLocale'; const {useOnChange} = hooks; const {MonthPicker, RangePicker, WeekPicker} = DatePicker; -const _DatePicker = (props) => { - props = Object.assign({}, { +const DatePickerInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'datePicker' }, props); - const render = useOnChange(Object.assign({placeholder: `请选择${props.label || ''}`}, props)); + const render = useOnChange(Object.assign({placeholder: formatMessage({id: 'PleaseSelect'}, {label: mergedProps.label || ''})}, mergedProps)); return render(DatePicker); }; -_DatePicker.Field = DatePicker; +DatePickerInner.Field = DatePicker; -const _MonthPicker = (props) => { - props = Object.assign({}, { +const MonthPickerInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'monthDatePicker' }, props); - const render = useOnChange(Object.assign({placeholder: ['开始时间', '结束时间']}, props)); + const render = useOnChange(Object.assign({placeholder: [formatMessage({id: 'StartTime'}), formatMessage({id: 'EndTime'})]}, mergedProps)); return render(MonthPicker); }; -_MonthPicker.Field = MonthPicker; +MonthPickerInner.Field = MonthPicker; -const _RangePicker = (props) => { - props = Object.assign({}, { +const RangePickerInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'rangeDatePicker' }, props); - const render = useOnChange(Object.assign({placeholder: ['开始时间', '结束时间']}, props)); + const render = useOnChange(Object.assign({placeholder: [formatMessage({id: 'StartTime'}), formatMessage({id: 'EndTime'})]}, mergedProps)); return render(RangePicker); }; -_RangePicker.Field = RangePicker; +RangePickerInner.Field = RangePicker; -const _WeekPicker = (props) => { - props = Object.assign({}, { +const WeekPickerInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'weekDatePicker' }, props); - const render = useOnChange(Object.assign({placeholder: ['开始时间', '结束时间']}, props)); + const render = useOnChange(Object.assign({placeholder: [formatMessage({id: 'StartTime'}), formatMessage({id: 'EndTime'})]}, mergedProps)); return render(WeekPicker); }; -_WeekPicker.Field = WeekPicker; - -_DatePicker.MonthPicker = _MonthPicker; -_DatePicker.RangePicker = _RangePicker; -_DatePicker.WeekPicker = _WeekPicker; +WeekPickerInner.Field = WeekPicker; +const _DatePicker = withLocale(DatePickerInner); +_DatePicker.MonthPicker = withLocale(MonthPickerInner); +_DatePicker.RangePicker = withLocale(RangePickerInner); +_DatePicker.WeekPicker = withLocale(WeekPickerInner); export default _DatePicker; diff --git a/src/fields/DatePickerToday.js b/src/fields/DatePickerToday.js index a4ce66b..39689e9 100644 --- a/src/fields/DatePickerToday.js +++ b/src/fields/DatePickerToday.js @@ -1,77 +1,223 @@ -import {Button, DatePicker} from 'antd'; -import React, {useRef, useMemo} from 'react' +import {Button, DatePicker, Input} from 'antd'; +import {CalendarOutlined, CloseCircleFilled} from '@ant-design/icons'; +import React, {useRef, useMemo, useCallback, useState} from 'react' import dayjs from 'dayjs' -import {get} from 'lodash' import useControlValue from '@kne/use-control-value' import {hooks} from '@kne/react-form-helper'; +import withLocale, {useIntl} from '../withLocale'; const {useOnChange} = hooks; -const PickerToday = ({soFarText, startProps, endProps, ...props}) => { +const PickerTodayInner = ({soFarText, ...props}) => { + const {formatMessage} = useIntl(); const [data, onChange] = useControlValue(props); - const ref_d = useRef(); - const newData = useMemo(() => { - // console.log('......', data); - const s = get(data, 0, ''); - const d = get(data, 1, ''); - const p = get(props, ['placeholder'], ['开始日期', '结束日期']); + const [openStart, setOpenStart] = useState(false); + const [openEnd, setOpenEnd] = useState(false); + const [tempStart, setTempStart] = useState(null); // 临时存储开始时间 + const [tempEnd, setTempEnd] = useState(null); // 临时存储结束时间 + const [hovering, setHovering] = useState(false); // hover 状态 + const containerRef = useRef(null); + const isSwitchingRef = useRef(false); // 标记是否正在切换到结束时间选择 + + const soFarLabel = soFarText || formatMessage({id: 'SoFar'}); + + // 判断是否有值 + const hasValue = useMemo(() => { + const [start, end] = data || []; + return !!(start || end); + }, [data]); + + // 判断是否为"至今" + const isSoFar = useMemo(() => { + const [, end] = data || []; + return end === 'soFar'; + }, [data]); + + // 解析数据 + const parsedValue = useMemo(() => { + const [start, end] = data || []; return { - start: s ? dayjs(s) : '', - end: d === '至今' ? null : (d ? dayjs(d) : ''), - showZj: d === '至今', - placeholder: p - } + start: start ? dayjs(start) : null, end: isSoFar ? null : (end ? dayjs(end) : null) + }; + }, [data, isSoFar]); + + // 显示文本 - 弹窗打开时显示临时值,关闭后显示实际值 + const displayText = useMemo(() => { + // 开始时间:有临时值时显示临时值 + const startText = tempStart + ? tempStart.format('YYYY-MM-DD') + : (parsedValue.start ? parsedValue.start.format('YYYY-MM-DD') : ''); + + // 结束时间:有临时值时显示临时值 + const endText = tempEnd + ? tempEnd.format('YYYY-MM-DD') + : (isSoFar ? soFarLabel : (parsedValue.end ? parsedValue.end.format('YYYY-MM-DD') : '')); + + return {start: startText, end: endText}; + }, [parsedValue, isSoFar, soFarLabel, tempStart, tempEnd]); + + // 点击整个Input框时,先弹出开始时间选择 + const handleInputClick = useCallback(() => { + const [start] = data || []; + setTempStart(start ? dayjs(start) : null); + setOpenStart(true); + setOpenEnd(false); }, [data]); - const startChange = (v) => { - // 比较日期大小 - if (!newData.showZj && newData.end && v && newData.end.isBefore(v)) { - onChange([newData.end, v]); - return + // 清除值 + const handleClear = useCallback((e) => { + e.stopPropagation(); + onChange([]); + setTempStart(null); + setTempEnd(null); + setOpenStart(false); + setOpenEnd(false); + }, [onChange]); + + // 处理开始时间变化 + const handleStartChange = useCallback((date) => { + setTempStart(date); + isSwitchingRef.current = true; // 标记正在切换 + setOpenStart(false); + setTimeout(() => { + setOpenEnd(true); + isSwitchingRef.current = false; + }, 100); + }, []); + + // 处理结束时间变化(选择日期时实时更新显示,选择后确认值) + const handleEndChange = useCallback((date) => { + if (date) { + // 选择了有效日期,确认值 + if (!tempStart) return; + onChange([tempStart.toISOString(), date.toISOString()]); + setOpenEnd(false); + setTempStart(null); + setTempEnd(null); + } else { + // 清空选择 + setTempEnd(null); } - onChange([v || '', newData.showZj ? '至今' : newData.end]); - }; + }, [tempStart, onChange]); - const endChange = (v) => { - if (newData.start && v && v.isBefore(newData.start)) { - onChange([v, newData.start]); - return + // 点击"至今"按钮 + const handleSoFarClick = useCallback(() => { + if (!tempStart) return; + onChange([tempStart.toISOString(), 'soFar']); + setOpenEnd(false); + setTempStart(null); + setTempEnd(null); + }, [tempStart, onChange]); + + // 处理开始时间弹窗关闭(未完成选择) + const handleStartOpenChange = useCallback((open) => { + if (!open) { + setOpenStart(false); + // 如果不是正在切换到结束时间,则清空临时状态 + if (!isSwitchingRef.current) { + setTempStart(null); + } } - onChange([newData.start, v || '']); - }; - - const foot = () => { - return () - } - return (
- -
- -
-
- {soFarText || '至今'} - + }, []); + + // 处理结束时间弹窗关闭(未完成选择) + const handleEndOpenChange = useCallback((open) => { + if (!open) { + setOpenEnd(false); + // 关闭结束时间弹窗时,不修改field值 + setTempStart(null); + setTempEnd(null); + } + }, []); + + // 结束时间面板变化时更新临时值(用于实时显示) + const handleEndPanelChange = useCallback((date) => { + setTempEnd(date); + }, []); + + // 空的 footer(用于开始时间,保持高度一致) + const renderEmptyFooter = useCallback(() => (
), []); + + // 带至今按钮的 footer + const renderExtraFooter = useCallback(() => { + const isCurrentSoFar = !tempStart && isSoFar; + return (
+ +
); + }, [isSoFar, tempStart, handleSoFarClick, soFarLabel]); + + // 结束时间的可选日期(不能早于开始时间) + const endDisabledDate = useCallback((current) => { + if (!tempStart) return false; + return current && current < tempStart.startOf('day'); + }, [tempStart]); + + return (
+
setHovering(true)} + onMouseLeave={() => setHovering(false)} + > +
+ +
+ ~ +
+ +
+ + {hasValue && hovering ? ( + + ) : ( + + )} +
+ + {/* 开始时间 DatePicker */} + containerRef.current || document.body} + style={{width: 0, height: 0, visibility: 'hidden', position: 'absolute'}} + placement="bottomLeft" + /> + + {/* 结束时间 DatePicker */} + containerRef.current || document.body} + style={{width: 0, height: 0, visibility: 'hidden', position: 'absolute'}} + placement="bottomLeft" + />
); -} +}; +const PickerToday = withLocale(PickerTodayInner); const RangePickerToday = (props) => { props = Object.assign({}, { diff --git a/src/fields/Input.js b/src/fields/Input.js index b746df4..3d32726 100644 --- a/src/fields/Input.js +++ b/src/fields/Input.js @@ -1,26 +1,32 @@ import {Input} from 'antd'; import {hooks} from '@kne/react-form-helper'; +import withLocale, {useIntl} from '../withLocale'; const {useDecorator} = hooks; -const InputField = (props) => { - props = Object.assign({}, { +const InputFieldInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'input', autoComplete: 'off' }, props); - const render = useDecorator(Object.assign({placeholder: `请输入${props.label}`}, props)); + const render = useDecorator(Object.assign({placeholder: formatMessage({id: 'PleaseInput'}, {label: mergedProps.label})}, mergedProps)); return render(Input); }; -InputField.Field = Input; +InputFieldInner.Field = Input; -InputField.Password = (props) => { - props = Object.assign({}, { +const PasswordInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'password', autoComplete: 'off' }, props); - const render = useDecorator(Object.assign({placeholder: `请输入${props.label}`}, props)); + const render = useDecorator(Object.assign({placeholder: formatMessage({id: 'PleaseInput'}, {label: mergedProps.label})}, mergedProps)); return render(Input.Password); }; -InputField.Password.Field = Input.Password; +PasswordInner.Field = Input.Password; + +const InputField = withLocale(InputFieldInner); +InputField.Password = withLocale(PasswordInner); export default InputField; diff --git a/src/fields/InputNumber.js b/src/fields/InputNumber.js index c07c184..49d288e 100644 --- a/src/fields/InputNumber.js +++ b/src/fields/InputNumber.js @@ -1,16 +1,20 @@ import {InputNumber} from 'antd'; import {hooks} from '@kne/react-form-helper'; +import withLocale, {useIntl} from '../withLocale'; const {useDecorator} = hooks; -const InputNumberField = (props) => { - props = Object.assign({}, { +const InputNumberFieldInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'inputNumber', autoComplete: 'off' }, props); - const render = useDecorator(Object.assign({placeholder: `请输入${props.label}`}, props)); + const render = useDecorator(Object.assign({placeholder: formatMessage({id: 'PleaseInput'}, {label: mergedProps.label})}, mergedProps)); return render(InputNumber); }; -InputNumberField.Field = InputNumber; +InputNumberFieldInner.Field = InputNumber; + +const InputNumberField = withLocale(InputNumberFieldInner); export default InputNumberField; diff --git a/src/fields/Select.js b/src/fields/Select.js index c7d697d..48bb225 100644 --- a/src/fields/Select.js +++ b/src/fields/Select.js @@ -1,17 +1,22 @@ import {Select} from 'antd'; import {hooks} from '@kne/react-form-helper'; +import withLocale, {useIntl} from '../withLocale'; const {useOnChange} = hooks; -const _Select = (props) => { - props = Object.assign({}, { +const SelectInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'select' }, props); - const render = useOnChange(Object.assign({placeholder: `请选择${props.label || ''}`}, props)); + const render = useOnChange(Object.assign({placeholder: formatMessage({id: 'PleaseSelect'}, {label: mergedProps.label || ''})}, mergedProps)); return render(Select); }; -_Select.Field = Select; -_Select.Option = Select.Option; -_Select.OptGroup = Select.OptGroup; + +SelectInner.Field = Select; +SelectInner.Option = Select.Option; +SelectInner.OptGroup = Select.OptGroup; + +const _Select = withLocale(SelectInner); export default _Select; diff --git a/src/fields/TextArea.js b/src/fields/TextArea.js index 1c7c9c0..f216ca4 100644 --- a/src/fields/TextArea.js +++ b/src/fields/TextArea.js @@ -1,16 +1,20 @@ import {Input} from 'antd'; import {hooks} from '@kne/react-form-helper'; +import withLocale, {useIntl} from '../withLocale'; const {useDecorator} = hooks; -const TextArea = (props) => { - props = Object.assign({}, { +const TextAreaInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'textArea' }, props); - const render = useDecorator(Object.assign({placeholder: `请输入${props.label}`}, props)); + const render = useDecorator(Object.assign({placeholder: formatMessage({id: 'PleaseInput'}, {label: mergedProps.label})}, mergedProps)); return render(Input.TextArea); }; -TextArea.Field = Input.TextArea; +TextAreaInner.Field = Input.TextArea; + +const TextArea = withLocale(TextAreaInner); export default TextArea; diff --git a/src/fields/TimePicker.js b/src/fields/TimePicker.js index 163b497..0a06d08 100644 --- a/src/fields/TimePicker.js +++ b/src/fields/TimePicker.js @@ -1,30 +1,34 @@ import {TimePicker} from 'antd'; import {hooks} from '@kne/react-form-helper'; +import withLocale, {useIntl} from '../withLocale'; const {useOnChange} = hooks; const {RangePicker} = TimePicker; -const _TimePicker = (props) => { - props = Object.assign({}, { +const TimePickerInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'timePicker' }, props); - const render = useOnChange(Object.assign({placeholder: `请选择${props.label || ''}`}, props)); + const render = useOnChange(Object.assign({placeholder: formatMessage({id: 'PleaseSelect'}, {label: mergedProps.label || ''})}, mergedProps)); return render(TimePicker); }; -_TimePicker.Field = TimePicker; +TimePickerInner.Field = TimePicker; -const _RangePicker = (props) => { - props = Object.assign({}, { +const RangePickerInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'rangeTimePicker' }, props); - const render = useOnChange(Object.assign({placeholder: `请选择${props.label || ''}`}, props)); + const render = useOnChange(Object.assign({placeholder: formatMessage({id: 'PleaseSelect'}, {label: mergedProps.label || ''})}, mergedProps)); return render(RangePicker); }; -_RangePicker.Field = RangePicker; +RangePickerInner.Field = RangePicker; -_TimePicker.RangePicker = _RangePicker; +const _TimePicker = withLocale(TimePickerInner); +_TimePicker.RangePicker = withLocale(RangePickerInner); export default _TimePicker; diff --git a/src/fields/TreeSelect.js b/src/fields/TreeSelect.js index 0f6d8ff..b958670 100644 --- a/src/fields/TreeSelect.js +++ b/src/fields/TreeSelect.js @@ -1,18 +1,21 @@ import {TreeSelect} from 'antd'; import {hooks} from '@kne/react-form-helper'; +import withLocale, {useIntl} from '../withLocale'; const {useOnChange} = hooks; -const _TreeSelect = (props) => { - props = Object.assign({}, { +const TreeSelectInner = (props) => { + const {formatMessage} = useIntl(); + const mergedProps = Object.assign({}, { fieldName: 'treeSelect' }, props); - const render = useOnChange(Object.assign({placeholder: `请选择${props.label || ''}`}, props)); + const render = useOnChange(Object.assign({placeholder: formatMessage({id: 'PleaseSelect'}, {label: mergedProps.label || ''})}, mergedProps)); return render(TreeSelect); }; -_TreeSelect.Field = TreeSelect; +TreeSelectInner.Field = TreeSelect; +TreeSelectInner.TreeNode = TreeSelect.TreeNode; -_TreeSelect.TreeNode = TreeSelect.TreeNode; +const _TreeSelect = withLocale(TreeSelectInner); export default _TreeSelect; diff --git a/src/locale/en-US.js b/src/locale/en-US.js new file mode 100644 index 0000000..cc27d74 --- /dev/null +++ b/src/locale/en-US.js @@ -0,0 +1,11 @@ +const locale = { + PleaseSelect: 'Please select {label}', + PleaseInput: 'Please enter {label}', + StartTime: 'Start Time', + EndTime: 'End Time', + StartDate: 'Start Date', + EndDate: 'End Date', + SoFar: 'Present' +}; + +export default locale; diff --git a/src/locale/zh-CN.js b/src/locale/zh-CN.js new file mode 100644 index 0000000..e48946a --- /dev/null +++ b/src/locale/zh-CN.js @@ -0,0 +1,11 @@ +const locale = { + PleaseSelect: '请选择{label}', + PleaseInput: '请输入{label}', + StartTime: '开始时间', + EndTime: '结束时间', + StartDate: '开始日期', + EndDate: '结束日期', + SoFar: '至今' +}; + +export default locale; diff --git a/src/withLocale.js b/src/withLocale.js new file mode 100644 index 0000000..4f51545 --- /dev/null +++ b/src/withLocale.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { IntlProvider, useIntl } from '@kne/react-intl'; +import zhCN from './locale/zh-CN'; +import enUS from './locale/en-US'; + +const localeMap = { + 'zh-CN': zhCN, + 'en-US': enUS +}; + +const withLocale = (WrappedComponent) => { + const WithLocaleComponent = ({ locale = 'zh-CN', ...props }) => { + const messages = localeMap[locale] || zhCN; + return ( + + + + ); + }; + WithLocaleComponent.displayName = `withLocale(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`; + return WithLocaleComponent; +}; + +export { useIntl }; +export default withLocale; From 19e2b22092c7c313d60ccc39913291bb4929ea5c Mon Sep 17 00:00:00 2001 From: Linzp Date: Mon, 23 Mar 2026 16:39:35 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1540 ++++++++++++++++++++++++++++++++- doc/api.md | 352 ++++++++ doc/base.js | 93 +- doc/buttons.js | 97 +++ doc/checkbox-radio.js | 113 +++ doc/date-picker.js | 74 ++ doc/example.json | 168 +++- doc/form-type.js | 52 ++ doc/group-list.js | 112 +++ doc/input.js | 63 ++ doc/rate-slider.js | 62 ++ doc/select.js | 207 +++++ doc/summary.md | 80 +- doc/validation.js | 155 ++++ package.json | 2 +- src/fields/DatePickerToday.js | 10 +- 16 files changed, 3083 insertions(+), 97 deletions(-) create mode 100644 doc/buttons.js create mode 100644 doc/checkbox-radio.js create mode 100644 doc/date-picker.js create mode 100644 doc/form-type.js create mode 100644 doc/group-list.js create mode 100644 doc/input.js create mode 100644 doc/rate-slider.js create mode 100644 doc/select.js create mode 100644 doc/validation.js diff --git a/README.md b/README.md index 4142f60..22e9eb2 100644 --- a/README.md +++ b/README.md @@ -12,65 +12,1513 @@ npm i --save @kne/react-form-antd ### 概述 -#### 特点 +基于 @kne/react-form 封装的 Ant Design 表单组件库,提供完整的表单校验功能和丰富的表单字段组件。 -* UI分离,支持自定义UI框架。提供了antd的组件封装 @kne/react-form-antd 和 taro的组件封装 @kne/react-form-taro -* 分级校验规则配置,校验规则支持异步校验 -* 事件驱动,方便灵活扩展。可以通过debug选项配置,通过触发事件顺序和参数轻松调试 -* 支持包含Group的复杂表单,子表单 +#### 核心特性 + +- **UI分离设计** - UI与校验逻辑分离,可适配不同UI框架 +- **分级校验规则** - 支持同步/异步校验,可配置校验规则优先级 +- **事件驱动架构** - 通过事件机制实现灵活扩展,支持调试模式 +- **复杂表单支持** - 支持 GroupList 动态表单、嵌套表单、子表单 +- **完整字段组件** - 覆盖所有常用 Ant Design 表单组件 +- **国际化支持** - 内置中英文提示信息 + +#### 支持的组件 + +**表单核心** +- Form - 表单容器组件 + +**基础字段** +- Input / Input.Password - 文本输入框 +- InputNumber - 数字输入框 +- TextArea - 多行文本 + +**选择器** +- Select - 下拉选择器 +- TreeSelect - 树形选择器 +- Cascader - 级联选择器 + +**日期时间** +- DatePicker - 日期选择器 +- TimePicker - 时间选择器 +- DatePickerToday - 快捷日期选择 + +**多选/单选** +- Checkbox - 复选框 +- CheckboxGroup - 复选框组 +- RadioGroup - 单选框组 + +**其他** +- Rate - 评分 +- Slider - 滑动条 +- Switch - 开关 + +**按钮** +- SubmitButton - 提交按钮 +- ResetButton - 重置按钮 +- CancelButton - 取消按钮 + +**高级功能** +- GroupList - 动态表单列表 +- preset - 全局配置 + +#### 快速开始 + +```bash +npm install @kne/react-form-antd antd +``` + +```jsx +import Form, { Input, SubmitButton } from '@kne/react-form-antd'; + +function MyForm() { + return ( +
console.log(data)}> + + 提交 +
+ ); +} +``` + +#### 校验规则 + +```jsx + // 必填 + // 长度2-10 + // 组合规则 +``` ### 示例 #### 示例代码 -- 这里填写示例标题 -- 这里填写示例说明 -- reactFormAntd(@kne/current-lib_react-form-antd),(@kne/current-lib_react-form-antd/dist/index.css),antd(antd) +- 基础表单 +- 展示表单的基本使用方法,包含各种表单字段和校验规则 +- _ReactFormAntd(@kne/current-lib_react-form-antd),(@kne/current-lib_react-form-antd/dist/index.css),antd(antd) + +```jsx +const {default: Form, Input, InputNumber, Select, TextArea, Switch, Checkbox, CheckboxGroup, RadioGroup, DatePicker, TimePicker, Rate, Slider, SubmitButton} = _ReactFormAntd; +const {Flex, Space, Divider} = antd; +const {useState} = React; + +const BaseExample = () => { + const [formData, setFormData] = useState(null); + return ( + +
{ + console.log('表单提交数据:', data); + setFormData(data); + }} + > + + + + +