diff --git a/package.json b/package.json index cd56c5c4..5d3d97dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne-components/components-core", - "version": "0.4.60", + "version": "0.4.61", "files": [ "build" ], @@ -22,9 +22,9 @@ "@kne/create-deferred": "^0.1.0", "@kne/ensure-slash": "^0.1.0", "@kne/flex-box": "^0.1.1", - "@kne/form-info": "^0.1.5", + "@kne/form-info": "^0.1.6", "@kne/global-context": "^1.3.2", - "@kne/info-page": "^0.2.5", + "@kne/info-page": "^0.2.7", "@kne/is-empty": "^1.0.1", "@kne/phone-number-input": "^0.1.5", "@kne/react-enum": "^0.1.12", @@ -98,7 +98,7 @@ "devDependencies": { "@craco/craco": "^7.1.0", "@kne/craco-module-federation": "^1.1.1", - "@kne/md-doc": "^0.1.15", + "@kne/md-doc": "^0.1.16", "@kne/modules-dev": "^2.0.3", "@kne/react-error-boundary": "^0.1.1", "antd": "6.0.0", diff --git "a/prompts/\347\273\204\344\273\266\347\244\272\344\276\213\347\274\226\345\206\231\346\217\220\347\244\272\350\257\215.md" "b/prompts/\347\273\204\344\273\266\347\244\272\344\276\213\347\274\226\345\206\231\346\217\220\347\244\272\350\257\215.md" index 6777145f..0b4105da 100644 --- "a/prompts/\347\273\204\344\273\266\347\244\272\344\276\213\347\274\226\345\206\231\346\217\220\347\244\272\350\257\215.md" +++ "b/prompts/\347\273\204\344\273\266\347\244\272\344\276\213\347\274\226\345\206\231\346\217\220\347\244\272\350\257\215.md" @@ -99,7 +99,7 @@ render(); ### 简化示例模板(无需远程加载) ```javascript const { default: YourComponent } = _YourComponent; -const { Flex, Switch } = antd; +const { Flex, Switch } = antd; // ⚠️ 使用了 antd 组件必须解构导入 const { useState } = React; const BaseExample = () => { @@ -114,6 +114,17 @@ const BaseExample = () => { render(); ``` +### 示例代码导入检查清单 +编写示例代码时,请务必检查: +- [ ] 是否使用了 antd 组件(如 Space、Card、Button、Typography 等)? + - 如果是,是否在代码中添加了 `const { 组件名 } = antd;` 解构导入? + - 如果是,是否在 example.json 的 scope 中添加了 `{"name": "antd", "packageName": "antd"}`? +- [ ] 是否使用了其他项目组件(如 Icon、Modal 等)? + - 如果是,是否在代码中添加了 `const { default: 组件名 } = _ComponentName;` 导入? + - 如果是,是否在 example.json 的 scope 中添加了对应的依赖声明? +- [ ] 是否使用了 React Hook? + - React Hook(如 useState、useEffect)无需导入,直接使用即可 + ## 4. scope 依赖声明规则 | 场景 | name | packageName | |------|------|-------------| @@ -129,6 +140,12 @@ render(); - 在示例代码中直接使用 `React.useState` 或 `useState` 即可,不需要额外的导入 - React 相关的包(如 `react-icons`)如果需要在示例中使用,建议使用简单的 emoji 或文本替代 +**⚠️ 特别注意:Antd 组件引入规范** +- 如果示例代码中使用了任何 antd 组件(如 `Space`, `Card`, `Button`, `Typography` 等),**必须**在 example.json 的 scope 中声明 `antd` 依赖 +- **必须**在示例代码文件顶部添加 `const { 组件名 } = antd;` 解构导入语句 +- 常见易错点:示例中使用了 antd 组件但忘记在 scope 中声明,或忘记在代码中解构导入,导致 "antd is not defined" 错误 +- 只要示例中有一个组件使用了 antd,就必须在 scope 中声明 antd,即使其他示例没有使用 + ## 5. 数据准备 - 在 `mockPreset/index.js` 中导出mock数据 - 示例中使用 `import` 引入:`import reportDetail from './report/detail.json'` @@ -141,11 +158,41 @@ render(); ## 7. 示例内容设计原则 +### 核心原则 + +**示例要根据API展示大部分功能,而且需要符合实际业务场景,示例数据也要尽量真实** + +这是示例编写的核心要求,所有示例必须满足以下三点: + +1. **API覆盖率**:示例必须覆盖组件API文档中的大部分功能,确保每个属性、方法都有对应的示例展示 +2. **真实业务场景**:示例数据和使用场景必须贴近真实业务,避免使用"内容1"、"测试数据"等无意义的占位符 +3. **数据真实性**:使用真实的人名、部门名称、业务状态等,让示例更具参考价值和可复制性 + ### 示例规划流程 1. **分析组件功能**:阅读组件源码,理解组件的核心功能和主要使用场景 -2. **确定示例类型**:根据组件特性规划不同场景的示例,确保覆盖主要使用方式 -3. **设计真实场景**:每个示例都应该模拟真实的业务场景,使用贴近用户的数据 -4. **避免通用占位**:避免使用"内容1"、"示例数据"等通用占位符 +2. **阅读 API 文档**:仔细阅读组件的 API 文档(`doc/api.md`),了解所有可用的属性、方法和配置项 +3. **确定示例类型**:根据组件特性和 API 文档规划不同场景的示例,确保覆盖主要使用方式 +4. **设计真实场景**:每个示例都应该模拟真实的业务场景,使用贴近用户的数据 +5. **避免通用占位**:避免使用"内容1"、"示例数据"等通用占位符 + +**重要:参考 API 文档设计示例** + +示例设计必须以 API 文档为依据,确保示例能够充分展示组件的所有可用 API。具体要求: + +- **属性覆盖**:每个常用的属性都应该有对应的示例展示其效果 +- **参数组合**:展示不同属性组合使用的效果,帮助用户理解属性之间的交互 +- **默认值对比**:对比使用默认值和自定义值的差异 +- **边界情况**:展示空值、无效值等边界情况的处理方式 +- **可选功能**:对于可选的功能(如链接、图标等),分别展示开启和关闭的效果 + +**示例与 API 的对应关系检查表**: + +在编写示例时,应该建立以下对应关系: +- 每个必填属性 → 至少有一个示例展示其必需性 +- 每个常用可选属性 → 至少有一个示例展示其效果 +- 每个特殊属性(如 className、style 等) → 有独立示例展示定制能力 +- 每个复杂属性(如对象、数组、函数等) → 有详细示例展示其用法 +- 每个枚举配置 → 有示例展示配置方式 ### 示例命名规范 - 使用语义化的文件名,清晰表达示例的核心内容 @@ -207,22 +254,26 @@ api={{ #### 必须覆盖的内容 1. **主要组件**:所有导出的主要组件都应该有对应的示例 2. **核心功能**:组件的核心功能点需要通过示例展示 -3. **常用属性**:常用的属性和配置项需要通过示例展示效果 -4. **典型场景**:至少包含 1-2 个真实的业务场景示例 +3. **所有 API 属性**:根据 API 文档,每个属性都应该在至少一个示例中展示其效果 +4. **常用属性**:常用的属性和配置项需要通过示例展示效果 +5. **典型场景**:至少包含 1-2 个真实的业务场景示例 #### 补充示例的原则 如果现有示例不足以完整演示 API,应该补充新的示例文件。判断标准: - 有子组件没有独立的示例 -- 有重要的 API 参数没有在示例中展示 +- 有 API 文档中列出的属性没有在示例中展示 +- 有重要的 API 参数没有在示例中展示其效果 - 有特殊使用场景(如自定义字段、组合使用)没有示例 - 示例代码过于复杂,一个文件展示了太多功能,需要拆分 +- **API 覆盖度不足**:对照 API 文档检查,有未展示的属性或功能 #### 补充示例的步骤 -1. 分析 API 文档,找出未覆盖的组件或功能点 -2. 为每个未覆盖的功能点创建独立的示例文件 -3. 示例文件命名应该清晰表达其展示的功能(如 `number-range.js`、`cascader.js`) -4. 更新 `example.json`,添加新的示例条目 -5. 确保示例标题和描述准确表达示例内容 +1. **分析 API 文档**:仔细阅读 `doc/api.md`,列出所有属性、方法和配置项 +2. **对照现有示例**:检查每个 API 项是否已有示例展示,标记未覆盖的内容 +3. **设计新示例**:为每个未覆盖的 API 功能点设计独立的示例文件 +4. **示例文件命名**:应该清晰表达其展示的功能(如 `link.js`、`custom-style.js`) +5. **更新配置**:更新 `example.json`,添加新的示例条目 +6. **验证完整性**:确保示例标题和描述准确表达示例内容,且能展示对应的 API #### 示例文件数量建议 - **简单组件**:1-3 个示例文件即可 @@ -233,7 +284,9 @@ api={{ 在完成示例编写后,使用以下清单进行检查: - [ ] 所有导出的主要组件都有对应的示例 -- [ ] 所有常用的 API 参数都在示例中展示 +- [ ] **所有 API 文档中列出的属性都有示例展示** +- [ ] **每个常用属性都有独立示例展示其效果** +- [ ] **每个可选配置(如链接、样式等)都有对比示例** - [ ] 至少有一个基础用法示例 - [ ] 至少有一个真实业务场景的示例 - [ ] 有组合使用或高级用法的示例(如果适用) @@ -241,6 +294,7 @@ api={{ - [ ] 示例标题和描述准确表达示例内容 - [ ] `example.json` 中的配置正确且完整 - [ ] 所有示例代码都能正常运行 +- [ ] **对照 API 文档,确保没有遗漏的重要功能** ## 9. FormInfo 组件示例特殊规则 diff --git a/src/common/components/CascaderField/createTreeUtils.js b/src/common/components/CascaderField/createTreeUtils.js index 0f43615c..453eb57a 100644 --- a/src/common/components/CascaderField/createTreeUtils.js +++ b/src/common/components/CascaderField/createTreeUtils.js @@ -2,231 +2,214 @@ import get from "lodash/get"; import isNil from "lodash/isNil"; const createTreeUtils = (mapping) => { - const mappingList = Array.from(mapping.values()); - const rootNodes = mappingList.filter((item) => item.parentId === null); - - const delItem = (array, item) => { - const index = array.indexOf(item); - if (index > -1) { - array.splice(index, 1); - } - }; - - /** - * 通过一个nodeId获取所有父级和所有子级的第一个元素,组成一个从最顶级到最子级的数组 - * */ - const getSelectedQueue = (id) => { - const currentNode = mapping.get(id); - if (!currentNode) { - return []; - } - const getFirstChild = (target) => { - if (target.children && target.children.length > 0) { - return getFirstChild( - target.children.find( - (item) => item.children && target.children.length > 0 - ) || get(target.children, "0") - ); - } - - const hasChildrenItem = getAllChildren(target.parentId).find( - (item) => item.children && item.children.length > 0 - ); - - if (hasChildrenItem) { - return getFirstChild(hasChildrenItem); - } - - return target; - }; + const mappingList = Array.from(mapping.values()); + const rootNodes = mappingList.filter((item) => item.parentId === null); - const getParentNodeIdList = (targetId, parents = []) => { - const output = [targetId, ...parents]; - const node = mapping.get(targetId); - if (!node || isNil(node.parentId)) { - return output; - } - return getParentNodeIdList(node.parentId, output); - }; - const lastLevelNode = getFirstChild(currentNode); - - return getParentNodeIdList(get(lastLevelNode, "id")); - }; - - /** - * 通过一个nodeId获得该节点的所有子级后代的id数组 - * */ - const getAllChildren = (id) => { - const output = []; - const core = (id) => { - const node = mapping.get(id); - if (!node) { - return; - } - if (Array.isArray(node.children) && node.children.length > 0) { - output.push(...node.children); - node.children.forEach((item) => { - core(item.id); - }); - } + const delItem = (array, item) => { + const index = array.indexOf(item); + if (index > -1) { + array.splice(index, 1); + } }; - core(id); - return output; - }; - - /** - * 从自身节点开始向父级查找callback返回为true的node,找到第一个返回结果 - * */ - const findInParents = (id, callback) => { - const core = (id) => { - const currentNode = mapping.get(id); - if (!currentNode) { - return null; - } - if (callback(currentNode)) { - return currentNode; - } - if (!isNil(currentNode.parentId)) { - return core(currentNode.parentId); - } - return null; + + /** + * 通过一个nodeId获取所有父级和所有子级的第一个元素,组成一个从最顶级到最子级的数组 + * */ + const getSelectedQueue = (id) => { + const currentNode = mapping.get(id); + if (!currentNode) { + return []; + } + const getFirstChild = (target) => { + if (target.children && target.children.length > 0) { + return getFirstChild(target.children.find((item) => item.children && target.children.length > 0) || get(target.children, "0")); + } + + const hasChildrenItem = getAllChildren(target.parentId).find((item) => item.children && item.children.length > 0); + + if (hasChildrenItem) { + return getFirstChild(hasChildrenItem); + } + + return target; + }; + + const getParentNodeIdList = (targetId, parents = []) => { + const output = [targetId, ...parents]; + const node = mapping.get(targetId); + if (!node || isNil(node.parentId)) { + return output; + } + return getParentNodeIdList(node.parentId, output); + }; + const lastLevelNode = getFirstChild(currentNode); + + return getParentNodeIdList(get(lastLevelNode, "id")); }; - return core(id); - }; - - const getSiblingNode = (id) => { - const currentNode = mapping.get(id); - if (!currentNode) { - return []; - } - const parentId = currentNode.parentId; - if (isNil(parentId)) { - return rootNodes; - } - const parentNode = mapping.get(parentId); - return parentNode.children; - }; - - const setNodeChecked = (id, value = []) => { - const newValue = value.slice(0); - const core = (id) => { - const node = mapping.get(id); - if (!node) { - return; - } - newValue.push(id); - getAllChildren(id).forEach((node) => { - delItem(newValue, node.id); - }); - const siblingNode = getSiblingNode(id); - const siblingNodeIsAllChecked = siblingNode.every( - (node) => newValue.indexOf(node.id) > -1 - ); - if (node.parentId && siblingNodeIsAllChecked) { - siblingNode.forEach((node) => { - delItem(newValue, node.id); - }); - core(node.parentId); - } + + /** + * 通过一个nodeId获得该节点的所有子级后代的id数组 + * */ + const getAllChildren = (id) => { + const output = []; + const core = (id) => { + const node = mapping.get(id); + if (!node) { + return; + } + if (Array.isArray(node.children) && node.children.length > 0) { + output.push(...node.children); + node.children.forEach((item) => { + core(item.id); + }); + } + }; + core(id); + return output; }; - core(id); - return newValue; - }; - - const setNodeUnchecked = (id, value = []) => { - const newValue = value.slice(0); - const core = (id) => { - const node = mapping.get(id); - if (!node) { - return; - } - if (newValue.indexOf(node.id) > -1) { - delItem(newValue, node.id); - return; - } - const siblingNode = getSiblingNode(id); - if (node.parentId) { - siblingNode.forEach((node) => { - if (id !== node.id) { - newValue.push(node.id); - } - }); - core(node.parentId); - } + + /** + * 从自身节点开始向父级查找callback返回为true的node,找到第一个返回结果 + * */ + const findInParents = (id, callback) => { + const core = (id) => { + const currentNode = mapping.get(id); + if (!currentNode) { + return null; + } + if (callback(currentNode)) { + return currentNode; + } + if (!isNil(currentNode.parentId)) { + return core(currentNode.parentId); + } + return null; + }; + return core(id); }; - core(id); - return newValue; - }; - - const computedCheckboxStatus = (id, value = []) => { - if ( - !!findInParents(id, (node) => { - return value.indexOf(node.id) > -1; - }) - ) { - return { - checked: true, - indeterminate: false, - }; - } - const indeterminate = ((id, callback) => { - const core = (id) => { + + const getSiblingNode = (id) => { const currentNode = mapping.get(id); if (!currentNode) { - return null; + return []; } - if (callback(currentNode)) { - return currentNode; + const parentId = currentNode.parentId; + if (isNil(parentId)) { + return rootNodes; } - if ( - Array.isArray(currentNode.children) && - currentNode.children.length > 0 - ) { - return currentNode.children.find((item) => !!core(item.id)); + const parentNode = mapping.get(parentId); + return parentNode.children; + }; + + const setNodeChecked = (id, value = []) => { + const newValue = value.slice(0); + const core = (id) => { + const node = mapping.get(id); + if (!node) { + return; + } + newValue.push(id); + getAllChildren(id).forEach((node) => { + delItem(newValue, node.id); + }); + const siblingNode = getSiblingNode(id); + const siblingNodeIsAllChecked = siblingNode.every((node) => newValue.indexOf(node.id) > -1); + if (node.parentId && siblingNodeIsAllChecked) { + siblingNode.forEach((node) => { + delItem(newValue, node.id); + }); + core(node.parentId); + } + }; + core(id); + return newValue; + }; + + const setNodeUnchecked = (id, value = []) => { + const newValue = value.slice(0); + const core = (id) => { + const node = mapping.get(id); + if (!node) { + return; + } + if (newValue.indexOf(node.id) > -1) { + delItem(newValue, node.id); + return; + } + const siblingNode = getSiblingNode(id); + if (node.parentId) { + siblingNode.forEach((node) => { + if (id !== node.id) { + newValue.push(node.id); + } + }); + core(node.parentId); + } + }; + core(id); + return newValue; + }; + + const computedCheckboxStatus = (id, value = []) => { + if (!!findInParents(id, (node) => { + return value.indexOf(node.id) > -1; + })) { + return { + checked: true, indeterminate: false, + }; } - }; - - return !!core(id); - })(id, (node) => value.indexOf(node.id) > -1); - if (indeterminate) { - return { - checked: false, - indeterminate: true, - }; - } - - return { checked: false, indeterminate: false }; - }; - - const transformToTreeData = () => { - const core = (nodeList) => { - if (Array.isArray(nodeList) && nodeList.length > 0) { - return nodeList.map((node) => { - const children = mappingList.filter( - (item) => item.parentId === node.id - ); - return Object.assign({}, node, { children: core(children) }); - }); - } - return null; + const indeterminate = ((id, callback) => { + const core = (id) => { + const currentNode = mapping.get(id); + if (!currentNode) { + return null; + } + if (callback(currentNode)) { + return currentNode; + } + if (Array.isArray(currentNode.children) && currentNode.children.length > 0) { + return currentNode.children.find((item) => !!core(item.id)); + } + }; + + return !!core(id); + })(id, (node) => value.indexOf(node.id) > -1); + if (indeterminate) { + return { + checked: false, indeterminate: true, + }; + } + + return {checked: false, indeterminate: false}; + }; + + const transformToTreeData = () => { + const core = (nodeList) => { + if (Array.isArray(nodeList) && nodeList.length > 0) { + return nodeList.map((node) => { + const children = mappingList.filter((item) => item.parentId === node.id); + return Object.assign({}, node, {children: core(children)}); + }); + } + return null; + }; + return core(rootNodes); + }; + + const treeData = transformToTreeData(); + + return { + mapping, + getSelectedQueue, + getAllChildren, + computedCheckboxStatus, + getSiblingNode, + findInParents, + setNodeChecked, + setNodeUnchecked, + treeData, + treeMapping: new Map((treeData || []).map((item) => [item.id, item])), }; - return core(rootNodes); - }; - - const treeData = transformToTreeData(); - - return { - mapping, - getSelectedQueue, - getAllChildren, - computedCheckboxStatus, - getSiblingNode, - findInParents, - setNodeChecked, - setNodeUnchecked, - treeData, - treeMapping: new Map(treeData.map((item) => [item.id, item])), - }; }; export default createTreeUtils; diff --git a/src/common/components/SuperSelectField/index.js b/src/common/components/SuperSelectField/index.js index e0c959df..c4f5b251 100644 --- a/src/common/components/SuperSelectField/index.js +++ b/src/common/components/SuperSelectField/index.js @@ -79,7 +79,8 @@ export const SuperSelectUserField = forwardRef((p, ref) => {
{item[labelKey]}
{item[descriptionKey || 'description'] && (
+ className={classnames(style["select-list-item-description"], "select-list-item-description")} + title={item[descriptionKey || 'description']}> {item[descriptionKey || 'description']}
)} diff --git a/src/components/Enum/index.js b/src/components/Enum/index.js index 5ec7e6fb..bf6e8b5a 100644 --- a/src/components/Enum/index.js +++ b/src/components/Enum/index.js @@ -1,31 +1,7 @@ import {preset} from "@kne/react-enum"; import transform from "lodash/transform"; -const baseLoaders = [['commonStatus', () => [{value: 'open', description: '开启', type: 'success',}, { - value: 'close', description: '关闭', type: 'danger' -}]], ["gender", () => [{value: "M", description: "男"}, { - value: "F", description: "女", -},],], ["marital", () => [{description: "已婚", value: "Y"}, { - description: "未婚", value: "N", -},],], ["confirm", () => [{description: "是", value: "Y"}, { - description: "否", value: "N", -},],], ["political", () => [{description: "中共党员", value: "中共党员"}, { - description: "共青团员", value: "共青团员", -}, {description: "群众", value: "群众"}, { - description: "其他党派", value: "其他党派", -},],], ["phoneStateEnum", () => [{ - value: 0, description: "空号", -}, { - value: 1, description: "实号", -}, { - value: 2, description: "停机", -}, { - value: 3, description: "库无", -}, { - value: 4, description: "沉默号", -}, { - value: 5, description: "风险号", -},],], ["degreeEnum", () => [{ +const degree = [{ description: "初中", value: 10, }, { description: "中专", value: 20, @@ -43,7 +19,41 @@ const baseLoaders = [['commonStatus', () => [{value: 'open', description: '开 description: "博士后", value: 75, }, { description: "学历不限", value: 999, -},],],]; +}]; + +const phoneState = [{ + value: 0, description: "空号", +}, { + value: 1, description: "实号", +}, { + value: 2, description: "停机", +}, { + value: 3, description: "库无", +}, { + value: 4, description: "沉默号", +}, { + value: 5, description: "风险号", +}]; + +const openStatus = [{value: 'open', description: '开启', type: 'success',}, { + value: 'closed', description: '关闭', type: 'danger' +}]; + +const baseLoaders = [['openStatus', openStatus], ['commonStatus', () => [{ + value: 'open', description: '开启', type: 'success', +}, { + value: 'close', description: '关闭', type: 'danger' +}]], ["gender", () => [{value: "M", description: "男"}, { + value: "F", description: "女", +},],], ["marital", () => [{description: "已婚", value: "Y"}, { + description: "未婚", value: "N", +},],], ["confirm", () => [{description: "是", value: "Y"}, { + description: "否", value: "N", +},],], ["political", () => [{description: "中共党员", value: "中共党员"}, { + description: "共青团员", value: "共青团员", +}, {description: "群众", value: "群众"}, { + description: "其他党派", value: "其他党派", +},],], ["phoneStateEnum", phoneState], ["phoneState", phoneState], ["degreeEnum", degree], ["degree", degree]]; preset({ base: transform(baseLoaders, (result, value) => { diff --git a/src/components/Filter/README.md b/src/components/Filter/README.md index 02749e76..26542006 100644 --- a/src/components/Filter/README.md +++ b/src/components/Filter/README.md @@ -167,6 +167,34 @@ render(); ``` +- 展示和Enum一起使用 +- +- _Filter(@components/Filter),_Enum(@components/Enum) + +```jsx +const { + default: Filter, SuperSelectFilterItem, getFilterValue +} = _Filter; +const {default: Enum} = _Enum; +const {useState} = React; +const BaseExample = () => { + const [value, onChange] = useState([]); + return ( { + console.log(getFilterValue(value)); + onChange(value); + }} + list={[[ { + return {(options) => children({options})} + }}/>]]} + />); +}; + +render(); + +``` + - 高级筛选 - 展示 AdvancedFilter 组件的高级筛选功能,适用于复杂筛选场景 - _Filter(@components/Filter) @@ -565,85 +593,53 @@ render(); ```jsx const { - default: Filter, - CascaderFilterItem, - getFilterValue, + default: Filter, CascaderFilterItem, getFilterValue, } = _Filter; -const { useState } = React; +const {useState} = React; -const options = [ - { - label: '浙江', - value: 'zhejiang', - children: [ - { +const options = [{ + label: '浙江', value: 'zhejiang', children: [{ label: '杭州', value: 'hangzhou', - children: [ - { label: '西湖区', value: 'xihu' }, - { label: '滨江区', value: 'binjiang' }, - { label: '余杭区', value: 'yuhang' }, - ], - }, - { + children: [{label: '西湖区', value: 'xihu'}, {label: '滨江区', value: 'binjiang'}, { + label: '余杭区', value: 'yuhang' + },], + }, { label: '宁波', value: 'ningbo', - children: [ - { label: '海曙区', value: 'haishu' }, - { label: '江北区', value: 'jiangbei' }, - ], - }, - ], - }, - { - label: '江苏', - value: 'jiangsu', - children: [ - { + children: [{label: '海曙区', value: 'haishu'}, {label: '江北区', value: 'jiangbei'},], + },], +}, { + label: '江苏', value: 'jiangsu', children: [{ label: '南京', value: 'nanjing', - children: [ - { label: '玄武区', value: 'xuanwu' }, - { label: '秦淮区', value: 'qinhuai' }, - ], - }, - { + children: [{label: '玄武区', value: 'xuanwu'}, {label: '秦淮区', value: 'qinhuai'},], + }, { label: '苏州', value: 'suzhou', - children: [ - { label: '姑苏区', value: 'gusu' }, - { label: '吴中区', value: 'wuzhong' }, - ], - }, - ], - }, -]; + children: [{label: '姑苏区', value: 'gusu'}, {label: '吴中区', value: 'wuzhong'},], + },], +},]; const BaseExample = () => { - const [value, onChange] = useState([]); - - return ( - { - console.log('筛选值:', getFilterValue(value)); - onChange(value); - }} - list={[ - [ - { + console.log('筛选值:', getFilterValue(value)); + onChange(value); + }} + list={[[, - ], - ]} - /> - ); + />,],]} + />); }; -render(); +render(); ``` diff --git a/src/components/Filter/doc/cascader.js b/src/components/Filter/doc/cascader.js index 37e80e5b..5a9483eb 100644 --- a/src/components/Filter/doc/cascader.js +++ b/src/components/Filter/doc/cascader.js @@ -1,80 +1,48 @@ const { - default: Filter, - CascaderFilterItem, - getFilterValue, + default: Filter, CascaderFilterItem, getFilterValue, } = _Filter; -const { useState } = React; +const {useState} = React; -const options = [ - { - label: '浙江', - value: 'zhejiang', - children: [ - { +const options = [{ + label: '浙江', value: 'zhejiang', children: [{ label: '杭州', value: 'hangzhou', - children: [ - { label: '西湖区', value: 'xihu' }, - { label: '滨江区', value: 'binjiang' }, - { label: '余杭区', value: 'yuhang' }, - ], - }, - { + children: [{label: '西湖区', value: 'xihu'}, {label: '滨江区', value: 'binjiang'}, { + label: '余杭区', value: 'yuhang' + },], + }, { label: '宁波', value: 'ningbo', - children: [ - { label: '海曙区', value: 'haishu' }, - { label: '江北区', value: 'jiangbei' }, - ], - }, - ], - }, - { - label: '江苏', - value: 'jiangsu', - children: [ - { + children: [{label: '海曙区', value: 'haishu'}, {label: '江北区', value: 'jiangbei'},], + },], +}, { + label: '江苏', value: 'jiangsu', children: [{ label: '南京', value: 'nanjing', - children: [ - { label: '玄武区', value: 'xuanwu' }, - { label: '秦淮区', value: 'qinhuai' }, - ], - }, - { + children: [{label: '玄武区', value: 'xuanwu'}, {label: '秦淮区', value: 'qinhuai'},], + }, { label: '苏州', value: 'suzhou', - children: [ - { label: '姑苏区', value: 'gusu' }, - { label: '吴中区', value: 'wuzhong' }, - ], - }, - ], - }, -]; + children: [{label: '姑苏区', value: 'gusu'}, {label: '吴中区', value: 'wuzhong'},], + },], +},]; const BaseExample = () => { - const [value, onChange] = useState([]); + const [value, onChange] = useState([]); - return ( - { - console.log('筛选值:', getFilterValue(value)); - onChange(value); - }} - list={[ - [ - { + console.log('筛选值:', getFilterValue(value)); + onChange(value); + }} + list={[[, - ], - ]} - /> - ); + />,],]} + />); }; -render(); +render(); diff --git a/src/components/Filter/doc/example.json b/src/components/Filter/doc/example.json index 5b411549..77b76b6b 100644 --- a/src/components/Filter/doc/example.json +++ b/src/components/Filter/doc/example.json @@ -12,6 +12,21 @@ } ] }, + { + "title": "展示和Enum一起使用", + "description": "", + "code": "./use-enum.js", + "scope": [ + { + "name": "_Filter", + "packageName": "@components/Filter" + }, + { + "name": "_Enum", + "packageName": "@components/Enum" + } + ] + }, { "title": "高级筛选", "description": "展示 AdvancedFilter 组件的高级筛选功能,适用于复杂筛选场景", diff --git a/src/components/Filter/doc/use-enum.js b/src/components/Filter/doc/use-enum.js new file mode 100644 index 00000000..5b8b08d4 --- /dev/null +++ b/src/components/Filter/doc/use-enum.js @@ -0,0 +1,20 @@ +const { + default: Filter, SuperSelectFilterItem, getFilterValue +} = _Filter; +const {default: Enum} = _Enum; +const {useState} = React; +const BaseExample = () => { + const [value, onChange] = useState([]); + return ( { + console.log(getFilterValue(value)); + onChange(value); + }} + list={[[ { + return {(options) => children({options})} + }}/>]]} + />); +}; + +render(); diff --git a/src/components/Filter/fields/index.js b/src/components/Filter/fields/index.js index a1365a84..caa98c21 100644 --- a/src/components/Filter/fields/index.js +++ b/src/components/Filter/fields/index.js @@ -1,11 +1,10 @@ -import { FormattedMessage } from "@components/Intl"; +import {FormattedMessage} from "@components/Intl"; import withFieldItem from "../withFieldItem"; import AdvancedSelectField, { - UserField, + UserField, } from "@common/components/AdvancedSelectField"; import SuperSelectField, { - SuperSelectTableListField, - SuperSelectUserField, + SuperSelectTableListField, SuperSelectUserField, } from "@common/components/SuperSelectField"; import FunctionSelectField from "@common/components/FunctionSelectField"; import AddressSelectField from "@common/components/AddressSelectField"; @@ -15,32 +14,23 @@ import TreeField from "@common/components/TreeField"; import InputFilterItemField from "./InputFilterItem"; import NumberRangeFilterItemField from "./NumberRangeFilterItem"; -const withInputDefaultPlaceholder = - (WrappedComponent) => - ({ placeholder, label, ...props }) => - ( - - {(text) => { - return ( - - ); - }} - - ); +const withInputDefaultPlaceholder = (WrappedComponent) => ({placeholder, label, ...props}) => ( + {(text) => { + return (); + }} +); export const AdvancedSelectFilterItem = withFieldItem(AdvancedSelectField); export const SuperSelectFilterItem = withFieldItem(SuperSelectField); -export const SuperSelectTableListFilterItem = withFieldItem( - SuperSelectTableListField -); +export const SuperSelectTableListFilterItem = withFieldItem(SuperSelectTableListField); export const SuperSelectUserFilterItem = withFieldItem(SuperSelectUserField); export const UserFilterItem = withFieldItem(UserField); export const FunctionSelectFilterItem = withFieldItem(FunctionSelectField); @@ -51,12 +41,9 @@ export const CityFilterItem = withFieldItem(AddressSelectField); export const CascaderFilterItem = withFieldItem(CascaderField); export const TreeFilterItem = withFieldItem(TreeField); -export const InputFilterItem = - withInputDefaultPlaceholder(InputFilterItemField); -export const NumberRangeFilterItem = withInputDefaultPlaceholder( - NumberRangeFilterItemField -); +export const InputFilterItem = withInputDefaultPlaceholder(InputFilterItemField); +export const NumberRangeFilterItem = withInputDefaultPlaceholder(NumberRangeFilterItemField); -export { default as DatePickerFilterItem } from "./DatePickerFilterItem"; -export { default as DateRangePickerFilterItem } from "./DateRangePickerFilterItem"; -export { default as TypeDateRangePickerFilterItem } from "./TypeDateRangePickerFilterItem"; +export {default as DatePickerFilterItem} from "./DatePickerFilterItem"; +export {default as DateRangePickerFilterItem} from "./DateRangePickerFilterItem"; +export {default as TypeDateRangePickerFilterItem} from "./TypeDateRangePickerFilterItem"; diff --git a/src/components/Filter/withFieldItem.js b/src/components/Filter/withFieldItem.js index 6b04ecce..7e6744d0 100644 --- a/src/components/Filter/withFieldItem.js +++ b/src/components/Filter/withFieldItem.js @@ -1,33 +1,24 @@ -import { useState } from "react"; +import {useState} from "react"; import isNotEmpty from "@common/utils/isNotEmpty"; import FilterItem from "./FilterItem"; import style from "./style.module.scss"; -const withFieldItem = - (WrappedComponent) => - ({ value, onChange, interceptor, label, ...props }) => { +const withFieldItem = (WrappedComponent) => ({value, onChange, interceptor, label, render, ...props}) => { const [open, setOpen] = useState(false); - return ( - - onChange(interceptor.output(...args)) - : onChange - } - valueType="all" - onOpenChange={setOpen} - /> - - ); - }; + const renderChildren = (otherProps) => onChange(interceptor.output(...args)) : onChange} + valueType="all" + onOpenChange={setOpen} + />; + return ( + {typeof render === "function" ? render({ + children: renderChildren + }) : renderChildren()} + ); +}; export default withFieldItem; diff --git a/src/components/Global/README.md b/src/components/Global/README.md index e74d2ba9..9cc698dc 100644 --- a/src/components/Global/README.md +++ b/src/components/Global/README.md @@ -2,11 +2,24 @@ ### 概述 -### 何时使用 +Global 是 components-core 组件库的全局配置组件,负责为整个应用提供统一的上下文环境、样式主题和全局配置。它集成了 Antd ConfigProvider、国际化支持、字体加载、主题定制等功能,是使用 components-core 组件库时必须包含的最外层组件。 -在使用components-core的任何组件的业务系统,需要将该组件放置于最外层,并且按照要求正确设置preset。 +**核心特性** -以下是components-core组件系统中需要设置的preset值,及使用这些值的组件 +- **统一的主题管理**:支持自定义主题色,自动生成主题色透明度渐变,提供丰富的 CSS 变量用于全局样式控制 +- **国际化支持**:内置中文和英文两种语言,支持 Antd 组件库的国际化以及第三方组件的本地化 +- **全局上下文管理**:通过 preset 参数统一配置权限、API、枚举等全局资源,所有子组件都可以通过 usePreset Hook 访问 +- **错误边界处理**:自动捕获页面错误并展示友好的错误提示,提升用户体验 +- **字体资源管理**:自动加载图标字体,支持自定义字体配置 +- **响应式设计**:提供多种尺寸的文字、颜色和行高变量,适配不同场景 + +**适用场景** + +在使用 components-core 组件库的任何业务系统中,都需要将 Global 组件放置在应用根位置,并按照要求配置 preset 参数。这样所有 components-core 的组件才能正确获取全局配置并正常工作。 + +**Preset 配置说明** + +preset 是一个对象,包含 components-core 组件系统所需的全局配置,以下是常用的配置项: | 名称 | 说明 | 类型 | 使用组件 | |-------------------|-------------------------------------------------------------|----------|---------------------------------| @@ -23,18 +36,20 @@ | formInfo | 表单配置 | object | FormInfo.formModule | | formInfo.rules | 表单规则配置 | object | FormInfo.formModule | -全局context管理设置及默认样式 +**样式管理** + +Global 组件提供了全局样式管理功能,所有全局覆盖性的样式、Antd 的样式覆盖都应放置在此组件中。组件内置了丰富的 CSS 变量,包括字体大小、颜色、行高、圆角、背景色等,开发者可以通过这些变量快速定制应用风格。 -* 请将全局覆盖性的样式放在此组件中 -* 请将字体文件的引用放在此组件中 -* 请将antd的覆盖性样式放在此组件中 -* 该组件需要放置在应用根位置 +**字体配置** -更新字体文件: +如需自定义图标字体,请按照以下步骤操作: +1. 将 iconfont 上下载的字体包解压后放在 public 文件夹下 +2. 更新 src/common/params.js 中的变量 iconfontBase +3. 修改后构建项目并发布到对应环境 -* 将iconfont上下载的字体包解压后放在public文件夹下面 -* 更新src/common/params.js 中的变量 iconfontBase -* 修改后构建该项目发布到对应环境 +**组件位置** + +Global 组件必须放置在应用的最外层,包裹所有其他组件,确保全局配置能够正确传递到所有子组件。 ### 示例 @@ -90,6 +105,373 @@ render(); ``` +- Preset 配置 +- 展示了 preset 全局配置的使用方法 +- _Global(@components/Global),antd(antd) + +```jsx +const { PureGlobal, usePreset } = _Global; +const { Button, Space, Typography, Card } = antd; + +const { Text } = Typography; + +// 模拟的 preset 配置 +const mockPreset = { + locale: 'zh-CN', + permissions: ['user:view', 'user:edit', 'user:delete'], + apis: { + getUserList: '/api/users', + updateUser: '/api/user/update' + }, + enums: { + status: { + active: '启用', + inactive: '停用' + } + }, + features: { + debug: true, + profile: 'production' + } +}; + +const PresetExample = () => { + const preset = usePreset(); + + return ( + + + +
+ 语言设置: + {preset.locale || '未设置'} +
+
+ 权限列表: + {preset.permissions?.join(', ') || '未设置'} +
+
+ API 接口: + {preset.apis?.getUserList || '未设置'} +
+
+ 状态枚举: + {JSON.stringify(preset.enums?.status) || '未设置'} +
+
+ 特性配置: + debug: {preset.features?.debug?.toString() || '未设置'}, profile: {preset.features?.profile || '未设置'} +
+
+
+ + + preset 是通过 Global 组件传入的全局配置,所有子组件都可以通过 usePreset Hook 访问。 + 在实际业务中,preset 通常包含权限列表、API 接口、枚举值等全局配置信息。 + + +
+ ); +}; + +const BaseExample = () => { + return ( + + + + ); +}; + +render(); + +``` + +- 主题配置 +- 展示了主题色的自定义和预览效果 +- _Global(@components/Global),antd(antd) + +```jsx +const { PureGlobal } = _Global; +const { Space, Button, Card, ColorPicker, Typography, Divider } = antd; + +const { Text, Title } = Typography; + +const ThemeExample = ({ themeToken }) => { + const primaryColor = themeToken?.colorPrimary || '#4096ff'; + + return ( + + + +
+ 当前主题色: + + {primaryColor} +
+ +
+ 主色按钮: + +
+
+ 链接文字: + + 链接文字 + +
+ +
+ Alert 组件(使用主题色): + + + + +
+
+
+ + + 通过 themeToken 属性可以自定义主题色。Global 组件会自动根据主题色生成透明度渐变, + 并应用到所有使用主题色的组件上,包括按钮、链接、输入框等。 + + +
+ ); +}; + +const BaseExample = () => { + const [color, setColor] = React.useState('#4096ff'); + + return ( + + + + 选择主题色: + setColor(color.toHexString())} + showText + /> + + + + + + + ); +}; + +render(); + +``` + +- 全局状态管理 +- 展示了 useGlobalContext、SetGlobal、GetGlobal 的使用 +- _Global(@components/Global),antd(antd) + +```jsx +const { PureGlobal, useGlobalContext, SetGlobal, GetGlobal } = _Global; +const { Space, Button, Input, Card, Typography, Divider } = antd; + +const { Text } = Typography; + +const GlobalContextExample = () => { + const { global: userName, setGlobal: setUserName } = useGlobalContext('userName'); + const { global: userCount, setGlobal: setUserCount } = useGlobalContext('userCount'); + + return ( + + + +
+ 用户名: + {userName || '未设置'} +
+
+ 用户数量: + {userCount || 0} +
+ + + + + + + + + + +
+
+ + + {({ global: appName }) => ( + + 应用名称:{appName} + + )} + + + + {({ value }) => ( + + 当前用户名:{value || '未设置'} + + )} + + + + + + useGlobalContext Hook 提供了全局状态管理功能,状态保存在 Global 组件一级, + 不会随着组件销毁而销毁。适合用于需要在多个组件间共享的状态。 + + + SetGlobal 和 GetGlobal 组件提供了更声明式的方式来设置和获取全局值, + 特别适合在 JSX 中直接使用。 + + + +
+ ); +}; + +const BaseExample = () => { + return ( + + + + ); +}; + +render(); + +``` + +- 初始化加载 +- 展示了 init 方法的使用,用于系统首次加载时的异步操作 +- _Global(@components/Global),antd(antd) + +```jsx +const { PureGlobal } = _Global; +const { Space, Card, Typography, Alert, Spin, Button } = antd; + +const { Title, Text } = Typography; + +const InitExample = () => { + return ( + + + + + 点击下方按钮查看初始化加载效果。init 方法会在系统首次加载时执行, + 可以返回 Promise 来处理异步操作,在加载完成前不会显示页面内容。 + + + + + + + + + +
+ 用户信息: +
+
用户ID:10001
+
用户名:张三
+
部门:技术部
+
+
+
+ 系统配置: +
+
主题色:#4096ff
+
语言:zh-CN
+
环境:production
+
+
+
+
+ + + + + init 方法会在应用初始化时执行,通常用于加载用户信息、系统配置、权限数据等。 + 在 init 方法返回的 Promise resolve 之前,页面会显示加载状态,不会渲染子组件。 + + + 这样可以确保在页面显示前,所有必要的全局数据都已经加载完成, + 避免页面出现闪烁或需要数据时的加载等待状态。 + + + + +
+ ); +}; + +// 模拟的 init 方法 +const mockInit = () => { + return new Promise((resolve) => { + setTimeout(() => { + console.log('初始化完成:加载用户数据和系统配置'); + resolve(); + }, 1500); + }); +}; + +const BaseExample = () => { + return ( + + + + ); +}; + +render(); + +``` + - 警告提示 - 展示了警告提示的覆盖样式 - _Global(@components/Global),antd(antd),icon(@components/Icon) @@ -346,37 +728,128 @@ render(); ### API -| 属性名 | 说明 | 类型 | 默认值 | -|------------|----------------------------------------------|----------|-----| -| preset | 全局预设参数,可以通过usePreset获取,由业务系统设置 | object | {} | -| themeToken | 设置主题,参看antd的themeToken,一般只需要设置{colorPrimary} | object | {} | -| init | 初始化方法,在系统首次加载时执行,可以返回Promise。用来放置系统显示之前的异步操作 | function | - | +### Global + +Global 组件是 components-core 的全局配置组件,必须放置在应用最外层。它提供了全局上下文、主题配置、国际化支持、错误边界等功能。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| preset | object | 否 | {} | 全局预设参数,可通过 usePreset 获取,由业务系统设置 | +| themeToken | object | 否 | {} | 主题配置,参考 Antd 的 themeToken,一般只需设置 {colorPrimary} | +| init | function | 否 | - | 初始化方法,在系统首次加载时执行,可返回 Promise,用于放置系统显示前的异步操作 | +| children | ReactNode | 是 | - | 子组件 | +| className | string | 否 | - | 自定义类名 | ### PureGlobal -api同Global,但是少了页面错误捕获和className:container-body带来的默认最小宽度等样式设置,主要用在组件库的演示环境和弹窗中 +纯全局组件,API 与 Global 相同。去除了页面错误捕获和 container-body 类名带来的默认最小宽度等样式设置,主要用于组件库的演示环境和弹窗中。 + +### GlobalProvider + +全局上下文提供者组件,是 Global 和 PureGlobal 的底层实现,一般不直接使用。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| preset | object | 否 | {locale: "zh-CN", apis: {}} | 全局预设参数 | +| themeToken | object | 否 | - | 主题配置 | +| init | function | 否 | - | 初始化方法 | +| children | ReactNode | 是 | - | 子组件 | ### usePreset -获取预设的preset,已经确定为系统需要使用的key值:permissions,apis,formOptions,modalOptions +获取预设的 preset 参数 Hook。已确定的系统需要使用的 key 值包括:permissions、apis、formOptions、modalOptions。 + +#### 返回值 + +返回 preset 对象,包含所有通过 Global 组件传入的全局配置。 ### useGlobalContext -获取和设置全局状态,该状态保存在Global组件一级,不会随着内部组件本身的销毁而销毁。 -主要给组件内部使用,业务应该避免使用该api设置新的global变量。业务如果有需要应当自行在顶级组件中设置context。 +获取和设置全局状态的 Hook。该状态保存在 Global 组件一级,不会随着内部组件的销毁而销毁。主要用于组件内部,业务应避免使用该 API 设置新的 global 变量。业务如有需要应自行在顶级组件中设置 context。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 否 | - | 全局参数的 key。当存在 globalKey 时,获取和设置的是 global[key],否则获取和设置的是整个 global 对象。除非存在多个 key-value,否则不推荐直接使用不存在 globalKey 的情况 | + +#### 返回值 + +返回包含 global 和 setGlobal 的对象: + +| 属性名 | 类型 | 说明 | +|--------|------|------| +| global | any | 当前的 global 值 | +| setGlobal | function | 设置当前的 global 值 | + +### useGlobalValue + +获取指定 key 的全局值的 Hook,类似 useGlobalContext 的简化版本。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 是 | - | 要获取的全局参数的 key | + +#### 返回值 + +返回指定 key 对应的 global 值。 + +### GlobalValue + +通过 render props 模式获取指定 global 值的组件。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 是 | - | 要获取的全局参数的 key | +| children | function | 是 | - | 渲染函数,接收 {value} 参数 | + +### containerClassName + +Global 组件容器的 CSS 类名常量。当需要使用 CSS 选择器选中 Global 组件容器时,可以使用该常量确保选择器的准确性。 + +该值是 Global 组件内部使用的 CSS 类名的转义版本,用于处理类名中的特殊字符(如 + 和 /),确保在 CSS 选择器中能够正确匹配。 + +### GlobalSetting + +设置全局值的组件(文档中未详细说明具体用法)。 + +### SetGlobal + +设置全局值的组件,支持条件渲染和函数作为 children。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 是 | - | 要设置的全局参数的 key | +| value | any | 是 | - | 要设置的值 | +| needReady | boolean | 否 | false | 是否需要等待 global 有值后再渲染 children | +| children | ReactNode \| function | 是 | - | 子组件,当为函数时会接收 {global, setGlobal} 参数 | + +### GetGlobal + +获取全局值的组件,通过 render props 模式访问。 + +#### 属性说明 -#### params:useGlobalContext(globalKey) +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 是 | - | 要获取的全局参数的 key | +| children | function | 是 | - | 渲染函数,接收 {value} 参数 | -| 属性名 | 说明 | 类型 | 默认值 | -|-----------|------------------------------------------------------------------------------------------------------------------------------|--------|-----| -| globalKey | 全局参数的key,当存在globalKey时,默认获取和设置都是global[key],当不存在globalKey获取和设置的都是global,除非存在多个获取和设置global的key-value,否则不推荐直接使用不存在globalKey的情况 | string | - | +### containerClassName -#### return:{global,setGlobal} +Global 组件容器的 CSS 类名常量。当需要使用 CSS 选择器选中 Global 组件容器时,可以使用该常量确保选择器的准确性。 -| 属性名 | 说明 | 类型 | -|-----------|--------------|----------| -| global | 当前的global值 | any | -| setGlobal | 设置当前的global值 | function | +该值是 Global 组件内部使用的 CSS 类名的转义版本,用于处理类名中的特殊字符(如 + 和 /),确保在 CSS 选择器中能够正确匹配。 diff --git a/src/components/Global/doc/api.md b/src/components/Global/doc/api.md index 3d1715d4..ccd5b4d4 100644 --- a/src/components/Global/doc/api.md +++ b/src/components/Global/doc/api.md @@ -1,34 +1,125 @@ -| 属性名 | 说明 | 类型 | 默认值 | -|------------|----------------------------------------------|----------|-----| -| preset | 全局预设参数,可以通过usePreset获取,由业务系统设置 | object | {} | -| themeToken | 设置主题,参看antd的themeToken,一般只需要设置{colorPrimary} | object | {} | -| init | 初始化方法,在系统首次加载时执行,可以返回Promise。用来放置系统显示之前的异步操作 | function | - | +### Global + +Global 组件是 components-core 的全局配置组件,必须放置在应用最外层。它提供了全局上下文、主题配置、国际化支持、错误边界等功能。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| preset | object | 否 | {} | 全局预设参数,可通过 usePreset 获取,由业务系统设置 | +| themeToken | object | 否 | {} | 主题配置,参考 Antd 的 themeToken,一般只需设置 {colorPrimary} | +| init | function | 否 | - | 初始化方法,在系统首次加载时执行,可返回 Promise,用于放置系统显示前的异步操作 | +| children | ReactNode | 是 | - | 子组件 | +| className | string | 否 | - | 自定义类名 | ### PureGlobal -api同Global,但是少了页面错误捕获和className:container-body带来的默认最小宽度等样式设置,主要用在组件库的演示环境和弹窗中 +纯全局组件,API 与 Global 相同。去除了页面错误捕获和 container-body 类名带来的默认最小宽度等样式设置,主要用于组件库的演示环境和弹窗中。 + +### GlobalProvider + +全局上下文提供者组件,是 Global 和 PureGlobal 的底层实现,一般不直接使用。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| preset | object | 否 | {locale: "zh-CN", apis: {}} | 全局预设参数 | +| themeToken | object | 否 | - | 主题配置 | +| init | function | 否 | - | 初始化方法 | +| children | ReactNode | 是 | - | 子组件 | ### usePreset -获取预设的preset,已经确定为系统需要使用的key值:permissions,apis,formOptions,modalOptions +获取预设的 preset 参数 Hook。已确定的系统需要使用的 key 值包括:permissions、apis、formOptions、modalOptions。 + +#### 返回值 + +返回 preset 对象,包含所有通过 Global 组件传入的全局配置。 ### useGlobalContext -获取和设置全局状态,该状态保存在Global组件一级,不会随着内部组件本身的销毁而销毁。 -主要给组件内部使用,业务应该避免使用该api设置新的global变量。业务如果有需要应当自行在顶级组件中设置context。 +获取和设置全局状态的 Hook。该状态保存在 Global 组件一级,不会随着内部组件的销毁而销毁。主要用于组件内部,业务应避免使用该 API 设置新的 global 变量。业务如有需要应自行在顶级组件中设置 context。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 否 | - | 全局参数的 key。当存在 globalKey 时,获取和设置的是 global[key],否则获取和设置的是整个 global 对象。除非存在多个 key-value,否则不推荐直接使用不存在 globalKey 的情况 | + +#### 返回值 + +返回包含 global 和 setGlobal 的对象: + +| 属性名 | 类型 | 说明 | +|--------|------|------| +| global | any | 当前的 global 值 | +| setGlobal | function | 设置当前的 global 值 | + +### useGlobalValue + +获取指定 key 的全局值的 Hook,类似 useGlobalContext 的简化版本。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 是 | - | 要获取的全局参数的 key | + +#### 返回值 + +返回指定 key 对应的 global 值。 + +### GlobalValue + +通过 render props 模式获取指定 global 值的组件。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 是 | - | 要获取的全局参数的 key | +| children | function | 是 | - | 渲染函数,接收 {value} 参数 | + +### containerClassName + +Global 组件容器的 CSS 类名常量。当需要使用 CSS 选择器选中 Global 组件容器时,可以使用该常量确保选择器的准确性。 + +该值是 Global 组件内部使用的 CSS 类名的转义版本,用于处理类名中的特殊字符(如 + 和 /),确保在 CSS 选择器中能够正确匹配。 + +### GlobalSetting + +设置全局值的组件(文档中未详细说明具体用法)。 + +### SetGlobal + +设置全局值的组件,支持条件渲染和函数作为 children。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 是 | - | 要设置的全局参数的 key | +| value | any | 是 | - | 要设置的值 | +| needReady | boolean | 否 | false | 是否需要等待 global 有值后再渲染 children | +| children | ReactNode \| function | 是 | - | 子组件,当为函数时会接收 {global, setGlobal} 参数 | + +### GetGlobal + +获取全局值的组件,通过 render props 模式访问。 + +#### 属性说明 -#### params:useGlobalContext(globalKey) +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| globalKey | string | 是 | - | 要获取的全局参数的 key | +| children | function | 是 | - | 渲染函数,接收 {value} 参数 | -| 属性名 | 说明 | 类型 | 默认值 | -|-----------|------------------------------------------------------------------------------------------------------------------------------|--------|-----| -| globalKey | 全局参数的key,当存在globalKey时,默认获取和设置都是global[key],当不存在globalKey获取和设置的都是global,除非存在多个获取和设置global的key-value,否则不推荐直接使用不存在globalKey的情况 | string | - | +### containerClassName -#### return:{global,setGlobal} +Global 组件容器的 CSS 类名常量。当需要使用 CSS 选择器选中 Global 组件容器时,可以使用该常量确保选择器的准确性。 -| 属性名 | 说明 | 类型 | -|-----------|--------------|----------| -| global | 当前的global值 | any | -| setGlobal | 设置当前的global值 | function | +该值是 Global 组件内部使用的 CSS 类名的转义版本,用于处理类名中的特殊字符(如 + 和 /),确保在 CSS 选择器中能够正确匹配。 diff --git a/src/components/Global/doc/example.json b/src/components/Global/doc/example.json index f0d3bfc4..8ed3639a 100644 --- a/src/components/Global/doc/example.json +++ b/src/components/Global/doc/example.json @@ -16,6 +16,66 @@ } ] }, + { + "title": "Preset 配置", + "description": "展示了 preset 全局配置的使用方法", + "code": "./preset.js", + "scope": [ + { + "name": "_Global", + "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, + { + "title": "主题配置", + "description": "展示了主题色的自定义和预览效果", + "code": "./theme.js", + "scope": [ + { + "name": "_Global", + "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, + { + "title": "全局状态管理", + "description": "展示了 useGlobalContext、SetGlobal、GetGlobal 的使用", + "code": "./global-state.js", + "scope": [ + { + "name": "_Global", + "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, + { + "title": "初始化加载", + "description": "展示了 init 方法的使用,用于系统首次加载时的异步操作", + "code": "./init.js", + "scope": [ + { + "name": "_Global", + "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, { "title": "警告提示", "description": "展示了警告提示的覆盖样式", diff --git a/src/components/Global/doc/global-state.js b/src/components/Global/doc/global-state.js new file mode 100644 index 00000000..4770b760 --- /dev/null +++ b/src/components/Global/doc/global-state.js @@ -0,0 +1,98 @@ +const { PureGlobal, useGlobalContext, SetGlobal, GetGlobal } = _Global; +const { Space, Button, Input, Card, Typography, Divider } = antd; + +const { Text } = Typography; + +const GlobalContextExample = () => { + const { global: userName, setGlobal: setUserName } = useGlobalContext('userName'); + const { global: userCount, setGlobal: setUserCount } = useGlobalContext('userCount'); + + return ( + + + +
+ 用户名: + {userName || '未设置'} +
+
+ 用户数量: + {userCount || 0} +
+ + + + + + + + + + +
+
+ + + {({ global: appName }) => ( + + 应用名称:{appName} + + )} + + + + {({ value }) => ( + + 当前用户名:{value || '未设置'} + + )} + + + + + + useGlobalContext Hook 提供了全局状态管理功能,状态保存在 Global 组件一级, + 不会随着组件销毁而销毁。适合用于需要在多个组件间共享的状态。 + + + SetGlobal 和 GetGlobal 组件提供了更声明式的方式来设置和获取全局值, + 特别适合在 JSX 中直接使用。 + + + +
+ ); +}; + +const BaseExample = () => { + return ( + + + + ); +}; + +render(); diff --git a/src/components/Global/doc/init.js b/src/components/Global/doc/init.js new file mode 100644 index 00000000..55672ce1 --- /dev/null +++ b/src/components/Global/doc/init.js @@ -0,0 +1,83 @@ +const { PureGlobal } = _Global; +const { Space, Card, Typography, Alert, Spin, Button } = antd; + +const { Title, Text } = Typography; + +const InitExample = () => { + return ( + + + + + 点击下方按钮查看初始化加载效果。init 方法会在系统首次加载时执行, + 可以返回 Promise 来处理异步操作,在加载完成前不会显示页面内容。 + + + + + + + + + +
+ 用户信息: +
+
用户ID:10001
+
用户名:张三
+
部门:技术部
+
+
+
+ 系统配置: +
+
主题色:#4096ff
+
语言:zh-CN
+
环境:production
+
+
+
+
+ + + + + init 方法会在应用初始化时执行,通常用于加载用户信息、系统配置、权限数据等。 + 在 init 方法返回的 Promise resolve 之前,页面会显示加载状态,不会渲染子组件。 + + + 这样可以确保在页面显示前,所有必要的全局数据都已经加载完成, + 避免页面出现闪烁或需要数据时的加载等待状态。 + + + + +
+ ); +}; + +// 模拟的 init 方法 +const mockInit = () => { + return new Promise((resolve) => { + setTimeout(() => { + console.log('初始化完成:加载用户数据和系统配置'); + resolve(); + }, 1500); + }); +}; + +const BaseExample = () => { + return ( + + + + ); +}; + +render(); diff --git a/src/components/Global/doc/preset.js b/src/components/Global/doc/preset.js new file mode 100644 index 00000000..a71f9ec8 --- /dev/null +++ b/src/components/Global/doc/preset.js @@ -0,0 +1,73 @@ +const { PureGlobal, usePreset } = _Global; +const { Button, Space, Typography, Card } = antd; + +const { Text } = Typography; + +// 模拟的 preset 配置 +const mockPreset = { + locale: 'zh-CN', + permissions: ['user:view', 'user:edit', 'user:delete'], + apis: { + getUserList: '/api/users', + updateUser: '/api/user/update' + }, + enums: { + status: { + active: '启用', + inactive: '停用' + } + }, + features: { + debug: true, + profile: 'production' + } +}; + +const PresetExample = () => { + const preset = usePreset(); + + return ( + + + +
+ 语言设置: + {preset.locale || '未设置'} +
+
+ 权限列表: + {preset.permissions?.join(', ') || '未设置'} +
+
+ API 接口: + {preset.apis?.getUserList || '未设置'} +
+
+ 状态枚举: + {JSON.stringify(preset.enums?.status) || '未设置'} +
+
+ 特性配置: + debug: {preset.features?.debug?.toString() || '未设置'}, profile: {preset.features?.profile || '未设置'} +
+
+
+ + + preset 是通过 Global 组件传入的全局配置,所有子组件都可以通过 usePreset Hook 访问。 + 在实际业务中,preset 通常包含权限列表、API 接口、枚举值等全局配置信息。 + + +
+ ); +}; + +const BaseExample = () => { + return ( + + + + ); +}; + +render(); diff --git a/src/components/Global/doc/summary.md b/src/components/Global/doc/summary.md index b6634bb9..c8d2657f 100644 --- a/src/components/Global/doc/summary.md +++ b/src/components/Global/doc/summary.md @@ -1,8 +1,21 @@ -### 何时使用 +Global 是 components-core 组件库的全局配置组件,负责为整个应用提供统一的上下文环境、样式主题和全局配置。它集成了 Antd ConfigProvider、国际化支持、字体加载、主题定制等功能,是使用 components-core 组件库时必须包含的最外层组件。 -在使用components-core的任何组件的业务系统,需要将该组件放置于最外层,并且按照要求正确设置preset。 +**核心特性** -以下是components-core组件系统中需要设置的preset值,及使用这些值的组件 +- **统一的主题管理**:支持自定义主题色,自动生成主题色透明度渐变,提供丰富的 CSS 变量用于全局样式控制 +- **国际化支持**:内置中文和英文两种语言,支持 Antd 组件库的国际化以及第三方组件的本地化 +- **全局上下文管理**:通过 preset 参数统一配置权限、API、枚举等全局资源,所有子组件都可以通过 usePreset Hook 访问 +- **错误边界处理**:自动捕获页面错误并展示友好的错误提示,提升用户体验 +- **字体资源管理**:自动加载图标字体,支持自定义字体配置 +- **响应式设计**:提供多种尺寸的文字、颜色和行高变量,适配不同场景 + +**适用场景** + +在使用 components-core 组件库的任何业务系统中,都需要将 Global 组件放置在应用根位置,并按照要求配置 preset 参数。这样所有 components-core 的组件才能正确获取全局配置并正常工作。 + +**Preset 配置说明** + +preset 是一个对象,包含 components-core 组件系统所需的全局配置,以下是常用的配置项: | 名称 | 说明 | 类型 | 使用组件 | |-------------------|-------------------------------------------------------------|----------|---------------------------------| @@ -19,15 +32,17 @@ | formInfo | 表单配置 | object | FormInfo.formModule | | formInfo.rules | 表单规则配置 | object | FormInfo.formModule | -全局context管理设置及默认样式 +**样式管理** + +Global 组件提供了全局样式管理功能,所有全局覆盖性的样式、Antd 的样式覆盖都应放置在此组件中。组件内置了丰富的 CSS 变量,包括字体大小、颜色、行高、圆角、背景色等,开发者可以通过这些变量快速定制应用风格。 + +**字体配置** -* 请将全局覆盖性的样式放在此组件中 -* 请将字体文件的引用放在此组件中 -* 请将antd的覆盖性样式放在此组件中 -* 该组件需要放置在应用根位置 +如需自定义图标字体,请按照以下步骤操作: +1. 将 iconfont 上下载的字体包解压后放在 public 文件夹下 +2. 更新 src/common/params.js 中的变量 iconfontBase +3. 修改后构建项目并发布到对应环境 -更新字体文件: +**组件位置** -* 将iconfont上下载的字体包解压后放在public文件夹下面 -* 更新src/common/params.js 中的变量 iconfontBase -* 修改后构建该项目发布到对应环境 +Global 组件必须放置在应用的最外层,包裹所有其他组件,确保全局配置能够正确传递到所有子组件。 diff --git a/src/components/Global/doc/theme.js b/src/components/Global/doc/theme.js new file mode 100644 index 00000000..d3b52598 --- /dev/null +++ b/src/components/Global/doc/theme.js @@ -0,0 +1,81 @@ +const { PureGlobal } = _Global; +const { Space, Button, Card, ColorPicker, Typography, Divider } = antd; + +const { Text, Title } = Typography; + +const ThemeExample = ({ themeToken }) => { + const primaryColor = themeToken?.colorPrimary || '#4096ff'; + + return ( + + + +
+ 当前主题色: + + {primaryColor} +
+ +
+ 主色按钮: + +
+
+ 链接文字: + + 链接文字 + +
+ +
+ Alert 组件(使用主题色): + + + + +
+
+
+ + + 通过 themeToken 属性可以自定义主题色。Global 组件会自动根据主题色生成透明度渐变, + 并应用到所有使用主题色的组件上,包括按钮、链接、输入框等。 + + +
+ ); +}; + +const BaseExample = () => { + const [color, setColor] = React.useState('#4096ff'); + + return ( + + + + 选择主题色: + setColor(color.toHexString())} + showText + /> + + + + + + + ); +}; + +render(); diff --git a/src/components/HelperGuide/README.md b/src/components/HelperGuide/README.md index 7f3ee5c5..389e89af 100644 --- a/src/components/HelperGuide/README.md +++ b/src/components/HelperGuide/README.md @@ -2,20 +2,73 @@ ### 概述 -给用户提供帮助文档 +HelperGuide 是一个轻量级的帮助文档提示组件,用于在页面上显示帮助说明和可选的帮助链接。它采用图标+文字的形式,样式简洁,适用于在表单、配置页面等场景中为用户提供操作指引或功能说明。 + +**核心特性** + +- **简洁设计**:采用图标+文字的展示形式,占用空间小,不干扰主要内容 +- **可配置性**:通过全局枚举配置帮助内容,支持多语言 +- **灵活展示**:支持仅显示帮助内容,或显示帮助内容+链接 +- **样式可定制**:支持自定义类名,方便调整样式 + +**适用场景** + +- 表单字段说明:在复杂表单中为特定字段提供帮助提示 +- 功能指引:在配置页面或设置页面提供操作说明 +- 文档链接:提供相关文档的快速访问入口 +- 提示信息:显示注意事项、使用建议等提示信息 ### 示例 +#### 示例样式 + +```scss +.helper-guide-custom { + background: #f0f5ff; + border: 1px solid #adc6ff; + + .inner { + background: transparent; + color: #2f54eb; + } +} + +// 添加一些其他可能的样式示例 +.helper-guide-warning { + background: #fffbe6; + border: 1px solid #ffe58f; + + .inner { + background: transparent; + color: #faad14; + } +} + +.helper-guide-error { + background: #fff2f0; + border: 1px solid #ffccc7; + + .inner { + background: transparent; + color: #ff4d4f; + } +} +``` + #### 示例代码 -- 这里填写示例标题 -- 这里填写示例说明 -- _HelperGuide(@components/HelperGuide),Global(@components/Global) +- 基础用法 +- 展示不带链接的简单帮助提示 +- _HelperGuide(@components/HelperGuide),Global(@components/Global),antd(antd) ```jsx const { default: HelperGuide } = _HelperGuide; const { PureGlobal } = Global; +const { Space, Typography, Card } = antd; + +const { Title, Text } = Typography; + const BaseExample = () => { return ( { enums: { helperGuide: () => [ { - value: "test", - content: - "哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈", - url: "/xxxx", + value: "username", + content: "请输入有效的用户名,长度为4-20个字符" }, - ], - }, + { + value: "password", + content: "密码必须包含字母、数字和特殊字符,长度为8-30个字符" + } + ] + } }} > - + + + +
+ 用户名: + +
+
+ 密码: + +
+
+
+ + + 基础用法:只显示帮助内容,不显示链接。适用于简单的提示信息。 + + +
); }; @@ -41,7 +114,376 @@ render(); ``` +- 带帮助链接 +- 展示带帮助链接的提示,可跳转到文档页面 +- _HelperGuide(@components/HelperGuide),Global(@components/Global),antd(antd) + +```jsx +const { default: HelperGuide } = _HelperGuide; +const { PureGlobal } = Global; +const { Space, Typography, Card } = antd; + +const { Text } = Typography; + +const LinkExample = () => { + return ( + [ + { + value: "api-doc", + content: "查看 API 接口文档,了解详细的接口定义和使用说明", + url: "https://example.com/api-docs" + }, + { + value: "quick-start", + content: "快速开始指南,帮助您快速上手使用系统", + url: "https://example.com/quick-start" + } + ] + } + }} + > + + + +
+ API 文档: + +
+
+ 快速开始: + +
+
+
+ + + 当配置中包含 url 字段时,HelperGuide 会显示"查看帮助"链接, + 点击后可以在新窗口打开对应的帮助文档。 + + +
+
+ ); +}; + +render(); + +``` + +- 多个帮助提示 +- 展示在同一页面中使用多个 HelperGuide 组件 +- _HelperGuide(@components/HelperGuide),Global(@components/Global),antd(antd) + +```jsx +const { default: HelperGuide } = _HelperGuide; +const { PureGlobal } = Global; +const { Space, Typography, Card, Divider } = antd; + +const { Title, Text } = Typography; + +const MultipleExample = () => { + return ( + [ + { + value: "user-profile", + content: "用户个人信息配置,包括基本资料和联系方式", + url: "https://example.com/docs/user-profile" + }, + { + value: "security-settings", + content: "安全设置包括密码修改、两步验证等安全功能配置" + }, + { + value: "notification-preferences", + content: "通知偏好设置,控制接收哪些类型的通知消息" + }, + { + value: "data-privacy", + content: "数据隐私设置,管理个人数据的访问权限和使用方式", + url: "https://example.com/docs/privacy" + } + ] + } + }} + > + + + +
+ 个人信息 + +
+ +
+ 安全设置 + +
+ +
+ 通知设置 + +
+ +
+ 隐私设置 + +
+
+
+ + + 可以在同一个页面中使用多个 HelperGuide 组件,每个组件通过 name 属性 + 引用不同的帮助内容。这种方式特别适合在配置页面、设置页面等多字段场景中使用。 + + +
+
+ ); +}; + +render(); + +``` + +- 自定义样式 +- 展示通过 className 属性自定义组件样式 +- _HelperGuide(@components/HelperGuide),Global(@components/Global),antd(antd) + +```jsx +const { default: HelperGuide } = _HelperGuide; +const { PureGlobal } = Global; +const { Space, Typography, Card } = antd; + +const { Text } = Typography; + +const CustomStyleExample = () => { + return ( + [ + { + value: "normal-style", + content: "默认样式的帮助提示" + }, + { + value: "custom-color", + content: "蓝色背景的自定义帮助提示" + }, + { + value: "custom-warning", + content: "黄色警告样式的帮助提示" + }, + { + value: "custom-error", + content: "红色错误样式的帮助提示" + }, + { + value: "custom-spacing", + content: "自定义间距的帮助提示" + } + ] + } + }} + > + + + +
+ 默认样式: + +
+
+ 蓝色自定义样式: + +
+
+ 警告样式: + +
+
+ 错误样式: + +
+
+ 自定义间距: + + (通过 style 属性添加边距) +
+
+
+ + +
1. 通过 className 属性可以自定义 HelperGuide 的样式,样式应用在外层容器上。
+
2. 通过 style 属性可以添加行内样式,如调整间距等。
+
3. 自定义样式可以覆盖组件的默认背景色、边框、文字颜色等。
+
+
+
+
+ ); +}; + +render(); + +``` + +- 真实业务场景 +- 展示在员工信息录入表单中的实际应用 +- _HelperGuide(@components/HelperGuide),_FormInfo(@components/FormInfo),_Modal(@components/Modal),Global(@components/Global),antd(antd) + +```jsx +const { default: HelperGuide } = _HelperGuide; +const { default: FormInfo, Form, SubmitButton, fields } = _FormInfo; +const { useModal } = _Modal; +const { PureGlobal } = Global; +const { Space, Card, Typography } = antd; + +const { Input, Select } = fields; + +const RealScenarioExample = () => { + const modal = useModal(); + + return ( + [ + { + value: "employee-form-employeeId", + content: "员工ID是员工的唯一标识,由系统自动生成,不可修改" + }, + { + value: "employee-form-department", + content: "请选择员工所属部门,部门决定了员工的权限范围", + url: "https://example.com/docs/departments" + }, + { + value: "employee-form-email", + content: "邮箱地址用于系统通知和密码找回,请确保邮箱地址有效" + }, + { + value: "employee-form-phone", + content: "手机号码用于接收短信验证码和紧急通知" + }, + { + value: "employee-form-hireDate", + content: "入职日期决定了员工的年假计算和试用期时长" + } + ] + } + }} + > + + +
{ + modal({ + title: "员工信息提交成功", + children:
{JSON.stringify(data, null, 2)}
+ }); + }} + > + , + , + , + + ]} + /> + + 保存 + + +
+ + + 真实业务场景示例:在员工信息录入表单中,为每个字段提供相应的帮助提示, + 帮助用户理解字段含义和要求。这样可以提高表单填写的准确性和效率。 + + +
+
+ ); +}; + +render(); + +``` + ### API -|属性名|说明|类型|默认值| -| --- | --- | --- | --- | +### HelperGuide + +HelperGuide 组件用于给用户提供帮助文档提示,显示帮助内容和可选的帮助链接。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| name | string | 是 | - | 帮助文档的标识符,用于从枚举中获取对应的帮助信息 | +| className | string | 否 | - | 自定义类名 | + +#### 枚举配置 + +HelperGuide 组件通过 preset.enums.helperGuide 配置帮助文档内容,该配置应该是一个函数,返回帮助文档数组。 + +| 字段名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| value | string | 是 | 帮助文档的标识符,对应 HelperGuide 组件的 name 属性 | +| content | string | 是 | 帮助文档的内容文字 | +| url | string | 否 | 帮助文档的链接地址,如果提供则显示"查看帮助"链接 | diff --git a/src/components/HelperGuide/doc/api.md b/src/components/HelperGuide/doc/api.md index 0a387583..48076f1e 100644 --- a/src/components/HelperGuide/doc/api.md +++ b/src/components/HelperGuide/doc/api.md @@ -1,2 +1,20 @@ -|属性名|说明|类型|默认值| -| --- | --- | --- | --- | +### HelperGuide + +HelperGuide 组件用于给用户提供帮助文档提示,显示帮助内容和可选的帮助链接。 + +#### 属性说明 + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| name | string | 是 | - | 帮助文档的标识符,用于从枚举中获取对应的帮助信息 | +| className | string | 否 | - | 自定义类名 | + +#### 枚举配置 + +HelperGuide 组件通过 preset.enums.helperGuide 配置帮助文档内容,该配置应该是一个函数,返回帮助文档数组。 + +| 字段名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| value | string | 是 | 帮助文档的标识符,对应 HelperGuide 组件的 name 属性 | +| content | string | 是 | 帮助文档的内容文字 | +| url | string | 否 | 帮助文档的链接地址,如果提供则显示"查看帮助"链接 | diff --git a/src/components/HelperGuide/doc/base.js b/src/components/HelperGuide/doc/base.js index 7489763d..993b3732 100644 --- a/src/components/HelperGuide/doc/base.js +++ b/src/components/HelperGuide/doc/base.js @@ -1,5 +1,9 @@ const { default: HelperGuide } = _HelperGuide; const { PureGlobal } = Global; +const { Space, Typography, Card } = antd; + +const { Title, Text } = Typography; + const BaseExample = () => { return ( { enums: { helperGuide: () => [ { - value: "test", - content: - "哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈哈哈啊哈哈哈", - url: "/xxxx", + value: "username", + content: "请输入有效的用户名,长度为4-20个字符" }, - ], - }, + { + value: "password", + content: "密码必须包含字母、数字和特殊字符,长度为8-30个字符" + } + ] + } }} > - + + + +
+ 用户名: + +
+
+ 密码: + +
+
+
+ + + 基础用法:只显示帮助内容,不显示链接。适用于简单的提示信息。 + + +
); }; diff --git a/src/components/HelperGuide/doc/custom-style.js b/src/components/HelperGuide/doc/custom-style.js new file mode 100644 index 00000000..81ae6e0e --- /dev/null +++ b/src/components/HelperGuide/doc/custom-style.js @@ -0,0 +1,87 @@ +const { default: HelperGuide } = _HelperGuide; +const { PureGlobal } = Global; +const { Space, Typography, Card } = antd; + +const { Text } = Typography; + +const CustomStyleExample = () => { + return ( + [ + { + value: "normal-style", + content: "默认样式的帮助提示" + }, + { + value: "custom-color", + content: "蓝色背景的自定义帮助提示" + }, + { + value: "custom-warning", + content: "黄色警告样式的帮助提示" + }, + { + value: "custom-error", + content: "红色错误样式的帮助提示" + }, + { + value: "custom-spacing", + content: "自定义间距的帮助提示" + } + ] + } + }} + > + + + +
+ 默认样式: + +
+
+ 蓝色自定义样式: + +
+
+ 警告样式: + +
+
+ 错误样式: + +
+
+ 自定义间距: + + (通过 style 属性添加边距) +
+
+
+ + +
1. 通过 className 属性可以自定义 HelperGuide 的样式,样式应用在外层容器上。
+
2. 通过 style 属性可以添加行内样式,如调整间距等。
+
3. 自定义样式可以覆盖组件的默认背景色、边框、文字颜色等。
+
+
+
+
+ ); +}; + +render(); diff --git a/src/components/HelperGuide/doc/example.json b/src/components/HelperGuide/doc/example.json index 33a0c130..620b9b19 100644 --- a/src/components/HelperGuide/doc/example.json +++ b/src/components/HelperGuide/doc/example.json @@ -2,8 +2,8 @@ "isFull": false, "list": [ { - "title": "这里填写示例标题", - "description": "这里填写示例说明", + "title": "基础用法", + "description": "展示不带链接的简单帮助提示", "code": "./base.js", "scope": [ { @@ -13,6 +13,94 @@ { "name": "Global", "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, + { + "title": "带帮助链接", + "description": "展示带帮助链接的提示,可跳转到文档页面", + "code": "./link.js", + "scope": [ + { + "name": "_HelperGuide", + "packageName": "@components/HelperGuide" + }, + { + "name": "Global", + "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, + { + "title": "多个帮助提示", + "description": "展示在同一页面中使用多个 HelperGuide 组件", + "code": "./multiple.js", + "scope": [ + { + "name": "_HelperGuide", + "packageName": "@components/HelperGuide" + }, + { + "name": "Global", + "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, + { + "title": "自定义样式", + "description": "展示通过 className 属性自定义组件样式", + "code": "./custom-style.js", + "scope": [ + { + "name": "_HelperGuide", + "packageName": "@components/HelperGuide" + }, + { + "name": "Global", + "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, + { + "title": "真实业务场景", + "description": "展示在员工信息录入表单中的实际应用", + "code": "./real-scenario.js", + "scope": [ + { + "name": "_HelperGuide", + "packageName": "@components/HelperGuide" + }, + { + "name": "_FormInfo", + "packageName": "@components/FormInfo" + }, + { + "name": "_Modal", + "packageName": "@components/Modal" + }, + { + "name": "Global", + "packageName": "@components/Global" + }, + { + "name": "antd", + "packageName": "antd" } ] } diff --git a/src/components/HelperGuide/doc/link.js b/src/components/HelperGuide/doc/link.js new file mode 100644 index 00000000..a9b14847 --- /dev/null +++ b/src/components/HelperGuide/doc/link.js @@ -0,0 +1,51 @@ +const { default: HelperGuide } = _HelperGuide; +const { PureGlobal } = Global; +const { Space, Typography, Card } = antd; + +const { Text } = Typography; + +const LinkExample = () => { + return ( + [ + { + value: "api-doc", + content: "查看 API 接口文档,了解详细的接口定义和使用说明", + url: "https://example.com/api-docs" + }, + { + value: "quick-start", + content: "快速开始指南,帮助您快速上手使用系统", + url: "https://example.com/quick-start" + } + ] + } + }} + > + + + +
+ API 文档: + +
+
+ 快速开始: + +
+
+
+ + + 当配置中包含 url 字段时,HelperGuide 会显示"查看帮助"链接, + 点击后可以在新窗口打开对应的帮助文档。 + + +
+
+ ); +}; + +render(); diff --git a/src/components/HelperGuide/doc/multiple.js b/src/components/HelperGuide/doc/multiple.js new file mode 100644 index 00000000..e357e058 --- /dev/null +++ b/src/components/HelperGuide/doc/multiple.js @@ -0,0 +1,70 @@ +const { default: HelperGuide } = _HelperGuide; +const { PureGlobal } = Global; +const { Space, Typography, Card, Divider } = antd; + +const { Title, Text } = Typography; + +const MultipleExample = () => { + return ( + [ + { + value: "user-profile", + content: "用户个人信息配置,包括基本资料和联系方式", + url: "https://example.com/docs/user-profile" + }, + { + value: "security-settings", + content: "安全设置包括密码修改、两步验证等安全功能配置" + }, + { + value: "notification-preferences", + content: "通知偏好设置,控制接收哪些类型的通知消息" + }, + { + value: "data-privacy", + content: "数据隐私设置,管理个人数据的访问权限和使用方式", + url: "https://example.com/docs/privacy" + } + ] + } + }} + > + + + +
+ 个人信息 + +
+ +
+ 安全设置 + +
+ +
+ 通知设置 + +
+ +
+ 隐私设置 + +
+
+
+ + + 可以在同一个页面中使用多个 HelperGuide 组件,每个组件通过 name 属性 + 引用不同的帮助内容。这种方式特别适合在配置页面、设置页面等多字段场景中使用。 + + +
+
+ ); +}; + +render(); diff --git a/src/components/HelperGuide/doc/real-scenario.js b/src/components/HelperGuide/doc/real-scenario.js new file mode 100644 index 00000000..5ee28b15 --- /dev/null +++ b/src/components/HelperGuide/doc/real-scenario.js @@ -0,0 +1,111 @@ +const { default: HelperGuide } = _HelperGuide; +const { default: FormInfo, Form, SubmitButton, fields } = _FormInfo; +const { useModal } = _Modal; +const { PureGlobal } = Global; +const { Space, Card, Typography } = antd; + +const { Input, Select } = fields; + +const RealScenarioExample = () => { + const modal = useModal(); + + return ( + [ + { + value: "employee-form-employeeId", + content: "员工ID是员工的唯一标识,由系统自动生成,不可修改" + }, + { + value: "employee-form-department", + content: "请选择员工所属部门,部门决定了员工的权限范围", + url: "https://example.com/docs/departments" + }, + { + value: "employee-form-email", + content: "邮箱地址用于系统通知和密码找回,请确保邮箱地址有效" + }, + { + value: "employee-form-phone", + content: "手机号码用于接收短信验证码和紧急通知" + }, + { + value: "employee-form-hireDate", + content: "入职日期决定了员工的年假计算和试用期时长" + } + ] + } + }} + > + + +
{ + modal({ + title: "员工信息提交成功", + children:
{JSON.stringify(data, null, 2)}
+ }); + }} + > + , + , + , + + ]} + /> + + 保存 + + +
+ + + 真实业务场景示例:在员工信息录入表单中,为每个字段提供相应的帮助提示, + 帮助用户理解字段含义和要求。这样可以提高表单填写的准确性和效率。 + + +
+
+ ); +}; + +render(); diff --git a/src/components/HelperGuide/doc/style.scss b/src/components/HelperGuide/doc/style.scss index e69de29b..a76a5fb5 100644 --- a/src/components/HelperGuide/doc/style.scss +++ b/src/components/HelperGuide/doc/style.scss @@ -0,0 +1,30 @@ +.helper-guide-custom { + background: #f0f5ff; + border: 1px solid #adc6ff; + + .inner { + background: transparent; + color: #2f54eb; + } +} + +// 添加一些其他可能的样式示例 +.helper-guide-warning { + background: #fffbe6; + border: 1px solid #ffe58f; + + .inner { + background: transparent; + color: #faad14; + } +} + +.helper-guide-error { + background: #fff2f0; + border: 1px solid #ffccc7; + + .inner { + background: transparent; + color: #ff4d4f; + } +} \ No newline at end of file diff --git a/src/components/HelperGuide/doc/summary.md b/src/components/HelperGuide/doc/summary.md index c5106529..d61c9d86 100644 --- a/src/components/HelperGuide/doc/summary.md +++ b/src/components/HelperGuide/doc/summary.md @@ -1 +1,15 @@ -给用户提供帮助文档 +HelperGuide 是一个轻量级的帮助文档提示组件,用于在页面上显示帮助说明和可选的帮助链接。它采用图标+文字的形式,样式简洁,适用于在表单、配置页面等场景中为用户提供操作指引或功能说明。 + +**核心特性** + +- **简洁设计**:采用图标+文字的展示形式,占用空间小,不干扰主要内容 +- **可配置性**:通过全局枚举配置帮助内容,支持多语言 +- **灵活展示**:支持仅显示帮助内容,或显示帮助内容+链接 +- **样式可定制**:支持自定义类名,方便调整样式 + +**适用场景** + +- 表单字段说明:在复杂表单中为特定字段提供帮助提示 +- 功能指引:在配置页面或设置页面提供操作说明 +- 文档链接:提供相关文档的快速访问入口 +- 提示信息:显示注意事项、使用建议等提示信息 diff --git a/src/components/HelperGuide/index.js b/src/components/HelperGuide/index.js index 7193b40a..f9397a5f 100644 --- a/src/components/HelperGuide/index.js +++ b/src/components/HelperGuide/index.js @@ -15,7 +15,7 @@ const HelperGuide = ({ name, className }) => { data && data.value && (
- + {data.content} {data.url && ( diff --git a/src/components/HelperGuide/style.module.scss b/src/components/HelperGuide/style.module.scss index 13a88ccb..be8a4dde 100644 --- a/src/components/HelperGuide/style.module.scss +++ b/src/components/HelperGuide/style.module.scss @@ -1,21 +1,44 @@ .helper-guide { color: var(--font-color-grey); line-height: 20px; - height: 20px; + min-height: 20px; + width: 100%; + max-width: 100%; + display: block; } .inner { background: var(--primary-color-1); border-radius: var(--radius-default, 2px); padding: 0 8px; - + width: 100%; + display: flex; + align-items: center; + min-height: 20px; + :global { + .ant-space { + width: 100%; + display: flex; + } + .ant-space-item { height: 20px; line-height: 20px; font-size: 12px; } + /* 确保内容项能够收缩 */ + .ant-space-item:last-child { + flex-shrink: 0; /* 链接项不收缩 */ + } + + .ant-space-item:nth-child(2) { + flex: 1; + min-width: 0; + overflow: hidden; + } + .iconfont--color { display: block; margin-top: 2px; @@ -25,13 +48,25 @@ } .content { - max-width: 216px; + display: block; + max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - display: block; } .link { font-size: 12px; + white-space: nowrap; + flex-shrink: 0; /* 防止链接收缩 */ +} + +/* 在表单项中的 HelperGuide 样式调整 */ +:global { + .ant-form-item { + .helper-guide { + margin-top: 4px; + max-width: 100%; + } + } } diff --git a/src/components/HistoryStore/README.md b/src/components/HistoryStore/README.md index 1851c45c..a5f2a9e8 100644 --- a/src/components/HistoryStore/README.md +++ b/src/components/HistoryStore/README.md @@ -2,45 +2,75 @@ ### 概述 -历史记录提示 +HistoryStore 是一个历史记录管理组件,用于保存和展示用户的操作历史记录(如搜索记录、选择记录等)。它利用 localStorage 持久化存储数据,在用户再次访问时可以快速选择历史记录,提升用户体验。 + +**核心特性** + +- **持久化存储**:基于 localStorage 实现数据持久化,刷新页面后数据不丢失 +- **多场景支持**:通过 storeName 属性区分不同场景的历史记录,互不干扰 +- **智能去重**:自动去除重复的历史记录,相同值只会保留最新的一次 +- **数量限制**:可配置最大保存数量,避免占用过多存储空间 +- **灵活触发**:支持通过焦点、点击等多种方式触发历史记录展示 +- **Render Props**:通过 render props 模式提供完整的控制能力,可自定义触发逻辑 + +**适用场景** + +- 搜索框历史记录:保存用户的搜索关键词,方便快速重新搜索 +- 下拉框选择历史:保存用户选择过的选项,提供快捷选择入口 +- 过滤器历史:保存用户设置过的过滤条件,一键应用历史配置 +- 其他需要记录用户操作历史的场景 ### 示例 #### 示例代码 -- 这里填写示例标题 -- 这里填写示例说明 +- 搜索框历史记录 +- 展示基础用法,搜索框获取焦点时显示历史记录 - _HistoryStore(@components/HistoryStore),antd(antd) ```jsx const { default: HistoryStore } = _HistoryStore; -const { Input } = antd; -const { useState } = React; +const { Input, Space, Card, Typography } = antd; + +const { Text } = Typography; + const BaseExample = () => { - const [value, setValue] = useState(""); return ( - { - setValue(value); - }} - > - {({ appendHistory, openHistory }) => ( - { - setValue(e.target.value); + + + { + console.log('选中历史记录:', value, item); }} - onFocus={openHistory} - onSearch={(value) => { - appendHistory({ - value, - label: value, - }); - }} - /> - )} - + > + {({ appendHistory, openHistory }) => ( + { + if (value) { + appendHistory({ value, label: value }); + } + }} + /> + )} + + + + + + + 基础用法:搜索框获取焦点时显示历史记录,点击历史记录标签或回车搜索后, + 该记录会被保存到历史记录中。 + + + 历史记录使用 localStorage 持久化存储,刷新页面后仍然可用。 + + + + ); }; @@ -48,7 +78,609 @@ render(); ``` +- 自定义配置 +- 展示 maxLength、label 等配置属性的用法 +- _HistoryStore(@components/HistoryStore),antd(antd) + +```jsx +const { default: HistoryStore } = _HistoryStore; +const { Input, Space, Card, Typography, Divider } = antd; + +const { Text } = Typography; + +const CustomConfigExample = () => { + return ( + + + +
+ 默认配置(最多5条,标题"最近搜索"): +
+ + {({ appendHistory, openHistory }) => ( + { + if (value) { + appendHistory({ value, label: value }); + } + }} + /> + )} + +
+
+ + + +
+ 自定义最大数量(最多10条): +
+ + {({ appendHistory, openHistory }) => ( + { + if (value) { + appendHistory({ value, label: value }); + } + }} + /> + )} + +
+
+ + + +
+ 自定义标题("搜索历史"): +
+ + {({ appendHistory, openHistory }) => ( + { + if (value) { + appendHistory({ value, label: value }); + } + }} + /> + )} + +
+
+ + + +
+ 不限制数量(maxLength={0}): +
+ + {({ appendHistory, openHistory }) => ( + { + if (value) { + appendHistory({ value, label: value }); + } + }} + /> + )} + +
+
+
+
+ + + + 通过 maxLength、label 等属性可以自定义历史记录的配置。 + maxLength 为 0 时不限制保存数量,但建议设置合理的最大值以避免占用过多存储空间。 + + +
+ ); +}; + +render(); + +``` + +- Select 组件历史记录 +- 展示与 Select 组件结合使用,记录选择历史 +- _HistoryStore(@components/HistoryStore),antd(antd) + +```jsx +const { default: HistoryStore } = _HistoryStore; +const { Select, Space, Card, Typography, Input } = antd; + +const { Text } = Typography; + +const SelectExample = () => { + const departmentOptions = [ + { label: '技术部', value: 'tech' }, + { label: '产品部', value: 'product' }, + { label: '运营部', value: 'operation' }, + { label: '市场部', value: 'marketing' }, + { label: '人力资源部', value: 'hr' }, + { label: '财务部', value: 'finance' } + ]; + + return ( + + + +
+ 部门选择: +
+ + {({ appendHistory, openHistory, close, open }) => ( + { + if (visible) { + openHistory(); + } else { + close(); + } + }} + onChange={(values) => { + if (values.length > 0) { + const lastValue = values[values.length - 1]; + const option = [ + { label: '北京', value: 'beijing' }, + { label: '上海', value: 'shanghai' }, + { label: '广州', value: 'guangzhou' }, + { label: '深圳', value: 'shenzhen' } + ].find(opt => opt.value === lastValue); + if (option) { + appendHistory({ + value: lastValue, + label: option.label + }); + } + } + }} + /> + )} + +
+
+
+
+ + + + HistoryStore 可以与 Select 组件结合使用,记录用户的选择历史。 + 通过 storeName 区分不同的历史记录场景,互不干扰。 + 使用 onDropdownVisibleChange 控制下拉框的打开状态,实现历史记录和选项列表的切换。 + + +
+ ); +}; + +render(); + +``` + +- 多个独立存储 +- 展示通过不同 storeName 创建多个独立的历史记录 +- _HistoryStore(@components/HistoryStore),antd(antd) + +```jsx +const { default: HistoryStore } = _HistoryStore; +const { Input, Select, Space, Card, Typography, Divider } = antd; + +const { Text } = Typography; + +const MultipleStoresExample = () => { + return ( + + + +
+ 用户搜索(storeName: user_search): +
+ + {({ appendHistory, openHistory }) => ( + { + if (value) { + appendHistory({ value, label: value }); + } + }} + /> + )} + +
+
+ + + +
+ 订单搜索(storeName: order_search): +
+ + {({ appendHistory, openHistory }) => ( + { + if (value) { + appendHistory({ value, label: value }); + } + }} + /> + )} + +
+
+ + + +
+ 部门筛选(storeName: department_filter): +
+ + {({ appendHistory, openHistory, close, open }) => ( + { + if (visible) { + openHistory(); + } else { + close(); + } + }} + onSelect={(value, option) => { + appendHistory({ value, label: option.label }); + }} + /> + )} + +
+
+
+
+ + + + 通过不同的 storeName 可以创建多个独立的历史记录存储,每个存储互不干扰。 + 这样可以在同一个页面中使用多个 HistoryStore 组件,分别记录不同操作的历史记录。 + 常用于多个搜索框、多个筛选器等场景。 + + +
+ ); +}; + +render(); + +``` + +- 真实业务场景 +- 展示在订单管理页面中的实际应用 +- _HistoryStore(@components/HistoryStore),antd(antd) + +```jsx +const { default: HistoryStore } = _HistoryStore; +const { Input, Select, Button, Space, Table, Card, Typography, Tag } = antd; + +const { Text } = Typography; + +const RealScenarioExample = () => { + const [filters, setFilters] = React.useState({}); + + const columns = [ + { + title: '订单号', + dataIndex: 'orderNo', + key: 'orderNo' + }, + { + title: '客户姓名', + dataIndex: 'customerName', + key: 'customerName' + }, + { + title: '金额', + dataIndex: 'amount', + key: 'amount', + render: (amount) => `¥${amount}` + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status) => { + const statusMap = { + pending: 待处理, + processing: 处理中, + completed: 已完成, + cancelled: 已取消 + }; + return statusMap[status] || status; + } + }, + { + title: '创建时间', + dataIndex: 'createTime', + key: 'createTime' + } + ]; + + const mockData = [ + { + key: '1', + orderNo: 'ORD202401001', + customerName: '张三', + amount: 1200.00, + status: 'completed', + createTime: '2024-01-15 10:30:00' + }, + { + key: '2', + orderNo: 'ORD202401002', + customerName: '李四', + amount: 3500.00, + status: 'processing', + createTime: '2024-01-15 11:20:00' + }, + { + key: '3', + orderNo: 'ORD202401003', + customerName: '王五', + amount: 890.00, + status: 'pending', + createTime: '2024-01-15 14:45:00' + } + ]; + + return ( + + + + +
+ 订单搜索: + + {({ appendHistory, openHistory }) => ( + { + if (value) { + setFilters({ ...filters, keyword: value }); + appendHistory({ value, label: value }); + } + }} + /> + )} + +
+ +
+ 状态: + + {({ appendHistory, openHistory, close, open }) => ( + { + if (visible) { + openHistory(); + } else { + close(); + } + }} + onSelect={(value, option) => { + appendHistory({ value, label: option.label }); + }} + /> + )} + +
+
+ + + +
+ 状态筛选(storeName: status_filter): +
+ + {({ appendHistory, openHistory, close, open }) => ( + { + if (visible) { + openHistory(); + } else { + close(); + } + }} + onSelect={(value, option) => { + setFilters({ ...filters, status: value }); + appendHistory({ value, label: option.label }); + }} + onClear={() => { + setFilters({ ...filters, status: undefined }); + }} + /> + )} + +
+ + + + + +
+ + 当前筛选条件:{Object.keys(filters).length > 0 ? JSON.stringify(filters) : '无'} + +
+ + + + + + + + 真实业务场景示例:在订单管理页面中,使用两个独立的 HistoryStore 组件, + 分别记录订单搜索历史和状态筛选历史。这样用户可以快速选择之前的搜索条件, + 提高操作效率。通过不同的 storeName 确保两个历史记录互不干扰。 + + + + ); +}; + +render(); diff --git a/src/components/HistoryStore/doc/select.js b/src/components/HistoryStore/doc/select.js new file mode 100644 index 00000000..edc22843 --- /dev/null +++ b/src/components/HistoryStore/doc/select.js @@ -0,0 +1,114 @@ +const { default: HistoryStore } = _HistoryStore; +const { Select, Space, Card, Typography, Input } = antd; + +const { Text } = Typography; + +const SelectExample = () => { + const departmentOptions = [ + { label: '技术部', value: 'tech' }, + { label: '产品部', value: 'product' }, + { label: '运营部', value: 'operation' }, + { label: '市场部', value: 'marketing' }, + { label: '人力资源部', value: 'hr' }, + { label: '财务部', value: 'finance' } + ]; + + return ( + + + +
+ 部门选择: +
+ + {({ appendHistory, openHistory, close, open }) => ( + { + if (visible) { + openHistory(); + } else { + close(); + } + }} + onChange={(values) => { + if (values.length > 0) { + const lastValue = values[values.length - 1]; + const option = [ + { label: '北京', value: 'beijing' }, + { label: '上海', value: 'shanghai' }, + { label: '广州', value: 'guangzhou' }, + { label: '深圳', value: 'shenzhen' } + ].find(opt => opt.value === lastValue); + if (option) { + appendHistory({ + value: lastValue, + label: option.label + }); + } + } + }} + /> + )} + +
+
+
+
+ + + + HistoryStore 可以与 Select 组件结合使用,记录用户的选择历史。 + 通过 storeName 区分不同的历史记录场景,互不干扰。 + 使用 onDropdownVisibleChange 控制下拉框的打开状态,实现历史记录和选项列表的切换。 + + +
+ ); +}; + +render(); diff --git a/src/components/HistoryStore/doc/summary.md b/src/components/HistoryStore/doc/summary.md index dd859a8d..50653997 100644 --- a/src/components/HistoryStore/doc/summary.md +++ b/src/components/HistoryStore/doc/summary.md @@ -1 +1,17 @@ -历史记录提示 +HistoryStore 是一个历史记录管理组件,用于保存和展示用户的操作历史记录(如搜索记录、选择记录等)。它利用 localStorage 持久化存储数据,在用户再次访问时可以快速选择历史记录,提升用户体验。 + +**核心特性** + +- **持久化存储**:基于 localStorage 实现数据持久化,刷新页面后数据不丢失 +- **多场景支持**:通过 storeName 属性区分不同场景的历史记录,互不干扰 +- **智能去重**:自动去除重复的历史记录,相同值只会保留最新的一次 +- **数量限制**:可配置最大保存数量,避免占用过多存储空间 +- **灵活触发**:支持通过焦点、点击等多种方式触发历史记录展示 +- **Render Props**:通过 render props 模式提供完整的控制能力,可自定义触发逻辑 + +**适用场景** + +- 搜索框历史记录:保存用户的搜索关键词,方便快速重新搜索 +- 下拉框选择历史:保存用户选择过的选项,提供快捷选择入口 +- 过滤器历史:保存用户设置过的过滤条件,一键应用历史配置 +- 其他需要记录用户操作历史的场景 diff --git a/src/components/Icon/README.md b/src/components/Icon/README.md index fb7c8faf..b617a676 100644 --- a/src/components/Icon/README.md +++ b/src/components/Icon/README.md @@ -1,12 +1,119 @@ -# Icon + +# react-icon + + +### 描述 + +用于将一个font或svg展示为一个图标组件. + + +### 安装 + +```shell +npm i --save @kne/react-icon +``` + ### 概述 -可以显示一个图标,图标必须在字体文件中被定义过 +### Iconfont + +`Iconfont` 是一个基于字体图标的 React 组件,支持两种模式: + +- **单色模式**:使用传统字体图标渲染 +- **多彩模式**:通过 SVG 方式渲染彩色图标 + +#### 基础图标 +```jsx + +``` + +#### 指定尺寸 +```jsx + +``` + +#### 多彩图标模式 +```jsx + +``` + +#### 注意事项 + +1. 需要预先引入对应的字体文件/CSS +2. 多彩模式需要确保 SVG 资源可用 +3. 组件会自动处理 `icon-` 前缀(无需手动添加) + +**以上资源可以通过`FontLoader`进行加载** + +### FontLoader + +`FontLoader` 是一个用于动态加载/卸载字体资源的 React 组件,具有以下特性: + +- 按需加载字体文件 +- 自动卸载机制(组件卸载时) +- 纯逻辑组件(无UI渲染) + +#### 加载本地字体 +```jsx + +``` + +#### 加载CDN字体 +```jsx + +``` + +#### 注意事项 + +1. 需要配合 `@font-face` CSS 规则使用 +2. 字体名称(`name`)需与CSS定义保持一致 +3. 建议在应用根组件或路由组件中使用 +4. 多次加载同名字体时会自动去重 + +### loadFont + +该函数提供了动态加载字体资源的功能,主要包含两个实用函数: + +1. **路径处理函数** - `getLastFolderName` + - 从文件路径中提取最后一个非空文件夹名 + - 自动处理路径末尾的冗余斜杠 + +2. **字体加载函数** - `loadFont` + - 智能避免重复加载相同字体 + - 支持通过JS脚本方式加载字体资源 + - 自动使用路径最后一段作为默认字体名称 +#### 基本用法 +```javascript +import { loadFont } from './loadFont'; + +// 加载字体(自动使用路径最后一段作为名称) +await loadFont('/assets/fonts/roboto/roboto.js'); + +// 指定字体名称 +await loadFont('/assets/fonts/roboto/main.js', 'Roboto'); +``` + +#### 实现特点 +1. **防重复加载**:通过检查head中是否已存在相同href的script标签 +2. **路径标准化**:自动处理路径末尾的冗余斜杠 +3. **容错处理**:过滤路径中的空字符串部分 + +#### 注意事项 +1. 当前仅支持通过.js文件加载字体 +2. 需要确保字体JS文件符合标准格式 +3. 在浏览器环境中使用,依赖document对象 ### 示例(全屏) + #### 示例样式 ```scss @@ -33,81 +140,80 @@ - 这里填写示例标题 - 这里填写示例说明 -- _Icon(@components/Icon),antd(antd),ReactFetch(@kne/react-fetch),Global(@components/Global),_axios(axios),remoteLoader(@kne/remote-loader) +- _Icon(@kne/react-icon),antd(antd),ReactFetch(@kne/react-fetch),_axios(axios),remoteLoader(@kne/remote-loader) ```jsx -const { default: Icon } = _Icon; -const { Slider, Space, Typography } = antd; -const { useState } = React; -const { createWithFetch } = ReactFetch; -const { loadFont } = Global; -const { default: axios } = _axios; -const { createWithRemoteLoader } = remoteLoader; +const {default: Icon} = _Icon; +const {Slider, Space, Typography} = antd; +const {useState} = React; +const {createWithFetch} = ReactFetch; +const {default: axios} = _axios; +const {createWithRemoteLoader} = remoteLoader; const BaseExample = createWithRemoteLoader({ - modules: ["components-iconfont:Font"], -})(({ remoteModules }) => { - const [Font] = remoteModules; - const [value, setValue] = useState(30); - return ( - - -
调整大小:
- -
{value}px
-
- { - - {({ list }) => { - return ( - - {list.map(({ name, font_class }) => { - return ( - - - ", - }} - > - {font_class} - -
{name}
-
- ); - })} -
- ); - }} -
- } -
- ); + modules: ["components-iconfont:Font"], +})(({remoteModules}) => { + const [Font] = remoteModules; + const [value, setValue] = useState(30); + return ( + + +
调整大小:
+ +
{value}px
+
+ { + + {({list}) => { + return ( + + {list.map(({name, font_class}) => { + return ( + + + ", + }} + > + {font_class} + +
{name}
+
+ ); + })} +
+ ); + }} +
+ } +
+ ); }); -render(); +render(); ``` - 这里填写示例标题 - 这里填写示例说明 -- _Icon(@components/Icon),antd(antd),ReactFetch(@kne/react-fetch),Global(@components/Global),_axios(axios),remoteLoader(@kne/remote-loader) +- _Icon(@kne/react-icon),antd(antd),ReactFetch(@kne/react-fetch),_axios(axios),remoteLoader(@kne/remote-loader) ```jsx const { default: Icon } = _Icon; @@ -173,11 +279,43 @@ render(); ``` + ### API -|属性名| 说明 |类型| 默认值 | -| --- |---------------| --- |-------| -|type| 图标类型,参考示例下的字符串 |string | - | -| colorful | 是否是彩色图标 | boolean| false | -| prefix| 图标前缀 |string| "" | -|size| 图标大小 |number| - | +### Iconfont + +| 属性 | 类型 | 默认值 | 说明 | +|-----------------|---------------|--------------|------------------------| +| `type` | string | **必填** | 图标名称(如 `'home'`) | +| `colorful` | boolean | `false` | 是否启用多彩模式 | +| `className` | string | - | 自定义 CSS 类名 | +| `fontClassName` | string | `'iconfont'` | 字体图标基础类名 | +| `size` | number/string | - | 图标尺寸(如 `20` 或 `'2em'`) | +| `style` | object | - | 行内样式对象 | +| `prefix` | string | `''` | 图标名前缀(自动处理 `icon-` 前缀) | +| `...other` | any | - | 其他透传的 DOM 属性 | + +### FontLoader + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `path` | string | 是 | 字体文件路径(支持相对/绝对路径) | +| `name` | string | 是 | 注册的字体名称(用于CSS引用) | + +### `getLastFolderName(path)` +```javascript +/** + * 从文件路径中提取最后一个文件夹名 + * @param {string} path - 文件路径 + * @return {string} 最后一个非空文件夹名 + */ +``` + +### `loadFont(path, name)` +```javascript +/** + * 动态加载字体资源 + * @param {string} path - 字体资源路径(.js) + * @param {string} [name] - 可选字体名称,未提供时使用路径最后一段 + */ +``` diff --git a/src/components/Icon/doc/api.md b/src/components/Icon/doc/api.md deleted file mode 100644 index 07a7b6b3..00000000 --- a/src/components/Icon/doc/api.md +++ /dev/null @@ -1,6 +0,0 @@ -|属性名| 说明 |类型| 默认值 | -| --- |---------------| --- |-------| -|type| 图标类型,参考示例下的字符串 |string | - | -| colorful | 是否是彩色图标 | boolean| false | -| prefix| 图标前缀 |string| "" | -|size| 图标大小 |number| - | diff --git a/src/components/Icon/doc/base.js b/src/components/Icon/doc/base.js deleted file mode 100644 index c887e341..00000000 --- a/src/components/Icon/doc/base.js +++ /dev/null @@ -1,66 +0,0 @@ -const { default: Icon } = _Icon; -const { Slider, Space, Typography } = antd; -const { useState } = React; -const { createWithFetch } = ReactFetch; -const { loadFont } = Global; -const { default: axios } = _axios; -const { createWithRemoteLoader } = remoteLoader; - -const BaseExample = createWithRemoteLoader({ - modules: ["components-iconfont:Font"], -})(({ remoteModules }) => { - const [Font] = remoteModules; - const [value, setValue] = useState(30); - return ( - - -
调整大小:
- -
{value}px
-
- { - - {({ list }) => { - return ( - - {list.map(({ name, font_class }) => { - return ( - - - ", - }} - > - {font_class} - -
{name}
-
- ); - })} -
- ); - }} -
- } -
- ); -}); - -render(); diff --git a/src/components/Icon/doc/colorful.js b/src/components/Icon/doc/colorful.js deleted file mode 100644 index 4eb8f75e..00000000 --- a/src/components/Icon/doc/colorful.js +++ /dev/null @@ -1,60 +0,0 @@ -const { default: Icon } = _Icon; -const { Space, Slider, Typography } = antd; -const { useState } = React; -const { createWithFetch } = ReactFetch; -const { createWithRemoteLoader } = remoteLoader; -const { default: axios } = _axios; - -const BaseExample = createWithRemoteLoader({ - modules: ["components-iconfont:ColorfulFont"], -})(({ remoteModules }) => { - const [ColorfulFont] = remoteModules; - const [value, setValue] = useState(30); - return ( - - -
调整大小:
- -
{value}px
-
- - {({ list }) => ( - - {list.map(({ name }) => { - return ( - - - ", - }} - > - {name} - - - ); - })} - - )} - -
- ); -}); - -render(); diff --git a/src/components/Icon/doc/example.json b/src/components/Icon/doc/example.json index 438113ae..c3bc3b2a 100644 --- a/src/components/Icon/doc/example.json +++ b/src/components/Icon/doc/example.json @@ -1,67 +1,3 @@ { - "isFull": true, - "list": [ - { - "title": "这里填写示例标题", - "description": "这里填写示例说明", - "code": "./base.js", - "scope": [ - { - "name": "_Icon", - "packageName": "@components/Icon" - }, - { - "name": "antd", - "packageName": "antd" - }, - { - "name": "ReactFetch", - "packageName": "@kne/react-fetch" - }, - { - "name": "Global", - "packageName": "@components/Global" - }, - { - "name": "_axios", - "packageName": "axios" - }, - { - "name": "remoteLoader", - "packageName": "@kne/remote-loader" - } - ] - }, - { - "title": "这里填写示例标题", - "description": "这里填写示例说明", - "code": "./colorful.js", - "scope": [ - { - "name": "_Icon", - "packageName": "@components/Icon" - }, - { - "name": "antd", - "packageName": "antd" - }, - { - "name": "ReactFetch", - "packageName": "@kne/react-fetch" - }, - { - "name": "Global", - "packageName": "@components/Global" - }, - { - "name": "_axios", - "packageName": "axios" - }, - { - "name": "remoteLoader", - "packageName": "@kne/remote-loader" - } - ] - } - ] + "reference": "@kne/react-icon" } diff --git a/src/components/Icon/doc/style.scss b/src/components/Icon/doc/style.scss deleted file mode 100644 index fc0b418c..00000000 --- a/src/components/Icon/doc/style.scss +++ /dev/null @@ -1,17 +0,0 @@ -.item { - width: 150px; - word-break: break-all; - .ant-typography { - position: relative; - } - .ant-typography-copy { - visibility: hidden; - position: absolute; - right: -20px; - } - &:hover { - .ant-typography-copy { - visibility: visible; - } - } -} diff --git a/src/components/Icon/style.module.scss b/src/components/Icon/style.module.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/Image/README.md b/src/components/Image/README.md index e0bc052e..d35991b3 100644 --- a/src/components/Image/README.md +++ b/src/components/Image/README.md @@ -2,29 +2,105 @@ ### 概述 -用于展示一张图片,和img标签不同的是,可以展示一张普通图片,也可以通过id加载一张oss图片,在加载oss地址和图片数据的时候会显示loading状态 +Image 组件是一个增强的图片显示组件,支持两种加载方式: + +1. 通过 src 属性直接加载图片URL +2. 通过 id 属性从 OSS 服务器加载图片 + +组件特性: +- 自动加载状态显示和错误处理 +- 支持 Avatar 头像模式,可显示默认性别图标 +- 支持自定义加载状态和错误状态组件 +- 完全兼容原生 img 标签的基本属性 + +主要应用场景: +- 用户头像展示 +- 商品图片展示 +- 文档预览 +- 需要加载状态的图片显示 ### 示例 +#### 示例样式 + +```scss +/* Image 组件示例样式 */ +.product-card { + .ant-card-cover { + height: 180px; + overflow: hidden; + } +} + +.user-avatar { + &:hover { + transform: scale(1.05); + transition: transform 0.3s; + } +} + +.image-preview { + text-align: center; + + img { + max-width: 100%; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + } +} +``` + #### 示例代码 -- 通过src加载一个普通图片 -- 通过src加载一个普通图片 -- _Image(@components/Image) +- 基础图片加载 +- 通过src属性直接加载图片 +- _Image(@components/Image),antd(antd) ```jsx -const {default: Image} = _Image; +const { default: Image } = _Image; +const { Space, Card, Typography } = antd; + +const { Text } = Typography; + const BaseExample = () => { - return ; + return ( + + + +
+ 通过 src 属性加载图片: +
+ +
+
+
+ 通过 URL 加载网络图片: +
+ +
+
+
+
+ + + +
1. Image 组件通过 src 属性直接加载图片URL。
+
2. 支持本地图片和网络图片加载。
+
3. 可以通过 style 属性设置图片大小。
+
4. 自动处理加载状态和错误状态。
+
+
+
+ ); }; -render(); +render(); ``` -- 通过id加载一个oss图片 -- 图片一加载成功,图片二加载中,图片三加载失败 +- OSS图片加载 +- 通过id从OSS服务器加载图片,展示加载中和失败状态 - _Image(@components/Image),global(@components/Global),antd(antd) ```jsx @@ -63,8 +139,8 @@ render(); ``` -- 显示一个头像 -- 显示图片头像和默认头像 +- 头像组件 +- 展示Image.Avatar头像组件的各种用法 - _Image(@components/Image),antd(antd) ```jsx @@ -97,19 +173,393 @@ render(); ``` +- 自定义状态组件 +- 自定义加载中和错误状态的显示组件 +- _Image(@components/Image),antd(antd),icon(@components/Icon) + +```jsx +const { default: Image } = _Image; +const { Space, Card, Spin, Alert } = antd; +const { default: Icon } = icon; + +const CustomStatesExample = () => { + const customLoading = ( +
+ +
+ ); + + const customError = ( +
+ +
+ ); + + return ( + + + +
+
默认加载状态:
+ +
+
+
自定义加载状态:
+ +
+
+
+ + + +
+
默认错误状态:
+ +
+
+
自定义错误状态:
+ +
+
+
+ + + + +
+ ); +}; + +render(); +``` + +- 图片交互 +- 展示图片点击事件和图片预览功能 +- _Image(@components/Image),antd(antd) + +```jsx +const { default: Image } = _Image; +const { Space, Card, Modal, Typography, Button } = antd; +const { useState } = React; + +const { Text } = Typography; + +const ImageInteractionExample = () => { + const [previewVisible, setPreviewVisible] = useState(false); + const [previewImage, setPreviewImage] = useState(''); + + const handleImageClick = (src) => { + setPreviewImage(src); + setPreviewVisible(true); + }; + + const handlePreviewClose = () => { + setPreviewVisible(false); + }; + + return ( + + + + 点击图片查看大图: + + handleImageClick('https://picsum.photos/seed/product1/600/600.jpg')} + alt="产品图片1" + /> + handleImageClick('https://picsum.photos/seed/product2/600/600.jpg')} + alt="产品图片2" + /> + handleImageClick('https://picsum.photos/seed/product3/600/600.jpg')} + alt="产品图片3" + /> + + + + + + +
+ console.log('点击了用户头像')} + style={{ cursor: 'pointer' }} + alt="用户头像" + /> +
用户头像
+
+
+ console.log('点击了默认女性头像')} + style={{ cursor: 'pointer' }} + /> +
默认女性头像
+
+
+
+ + + +
1. Image 和 Image.Avatar 组件都支持 onClick 事件,可以添加交互功能。
+
2. 结合 Modal 组件可以实现图片预览功能。
+
3. 通过设置 cursor: 'pointer' 样式可以提示用户图片是可点击的。
+
+
+ + + 关闭 + + ]} + width={700} + onCancel={handlePreviewClose} + > +
+ 预览 +
+
+
+ ); +}; + +render(); +``` + +- 真实业务场景 +- 用户信息卡片中的头像和商品列表中的图片展示 +- _Image(@components/Image),antd(antd) + +```jsx +const { default: Image } = _Image; +const { Space, Card, Typography, List, Avatar, Tag } = antd; + +const { Title, Text, Paragraph } = Typography; + +const RealScenarioExample = () => { + // 模拟用户数据 + const users = [ + { + id: 1, + name: '张三', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhangsan', + position: '前端开发工程师', + department: '技术部' + }, + { + id: 2, + name: '李四', + avatar: '', + gender: 'male', + position: '产品经理', + department: '产品部' + }, + { + id: 3, + name: '王五', + avatar: '', + gender: 'female', + position: 'UI设计师', + department: '设计部' + } + ]; + + // 模拟商品数据 + const products = [ + { + id: 1, + name: '高端笔记本电脑', + image: 'https://picsum.photos/seed/laptop/200/200.jpg', + price: 8999, + status: '在售' + }, + { + id: 2, + name: '无线蓝牙耳机', + image: 'https://picsum.photos/seed/earphone/200/200.jpg', + price: 499, + status: '在售' + }, + { + id: 3, + name: '智能手表', + image: 'https://picsum.photos/seed/watch/200/200.jpg', + price: 1299, + status: '缺货' + } + ]; + + return ( + + + ( + + + ) : ( + + ) + } + title={ +
+ {user.name} + + {user.department} + +
+ } + description={user.position} + /> +
+ )} + /> +
+ + + ( + + + } + > + + + ¥{product.price} + + + {product.status} + + + } + /> + + + )} + /> + + + + + 本示例展示了 Image 组件在真实业务场景中的应用: + +
    +
  • 用户信息卡片中的头像展示,支持图片头像和默认性别图标头像
  • +
  • 商品展示列表中的图片展示,支持加载状态和错误处理
  • +
  • 结合其他组件(List、Card、Tag等)实现完整的功能页面
  • +
+
+
+ ); +}; + +render(); +``` + ### API +### Image 基础图片组件 + | 属性名 | 说明 | 类型 | 默认值 | |---------|------------|--------|-----| | src | 图片的src地址 | string | - | | id | oss的id | string | - | -| loading | 加载时显示的组件 | jsx | - | -| error | 加载错误时显示的组件 | jsx | - | +| loading | 加载时显示的组件 | ReactNode | `` | +| error | 加载错误时显示的组件 | ReactNode | `` | +| alt | 图片的alt属性 | string | - | +| className | 自定义类名 | string | - | +| onClick | 点击图片的回调函数 | function | - | +| apis | API配置,用于加载OSS图片 | object | - | -### Image.Avatar +### Image.Avatar 头像组件 -用antd的Avatar来显示图片,可以显示默认的男女头像,其他参数参考antd的Avatar组件 +基于Antd的Avatar组件,支持图片头像和默认性别图标头像,其他参数参考Antd的Avatar组件 | 属性名 | 说明 | 类型 | 默认值 | |--------|---------------------|--------|-----| -| gender | 性别 F,female,f为女其他为男 | string | - | +| src | 图片的src地址 | string | - | +| id | oss的id | string | - | +| gender | 性别 F,female,f为女其他为男 | string | - | +| size | 头像大小 | number | 100 | +| width | 头像宽度 | number | - | +| height | 头像高度 | number | - | +| shape | 头像形状,可选 'circle' | string | - | +| gap | 头像与图标之间的间距 | number | - | +| icon | 自定义图标 | ReactNode | - | +| defaultAvatar | 默认头像 | string | 默认头像SVG | +| className | 自定义类名 | string | - | +| apis | API配置,用于加载OSS图片 | object | - | diff --git a/src/components/Image/doc/api.md b/src/components/Image/doc/api.md index 2ef5a103..bb6180f7 100644 --- a/src/components/Image/doc/api.md +++ b/src/components/Image/doc/api.md @@ -1,14 +1,31 @@ +### Image 基础图片组件 + | 属性名 | 说明 | 类型 | 默认值 | |---------|------------|--------|-----| | src | 图片的src地址 | string | - | | id | oss的id | string | - | -| loading | 加载时显示的组件 | jsx | - | -| error | 加载错误时显示的组件 | jsx | - | +| loading | 加载时显示的组件 | ReactNode | `` | +| error | 加载错误时显示的组件 | ReactNode | `` | +| alt | 图片的alt属性 | string | - | +| className | 自定义类名 | string | - | +| onClick | 点击图片的回调函数 | function | - | +| apis | API配置,用于加载OSS图片 | object | - | -### Image.Avatar +### Image.Avatar 头像组件 -用antd的Avatar来显示图片,可以显示默认的男女头像,其他参数参考antd的Avatar组件 +基于Antd的Avatar组件,支持图片头像和默认性别图标头像,其他参数参考Antd的Avatar组件 | 属性名 | 说明 | 类型 | 默认值 | |--------|---------------------|--------|-----| -| gender | 性别 F,female,f为女其他为男 | string | - | +| src | 图片的src地址 | string | - | +| id | oss的id | string | - | +| gender | 性别 F,female,f为女其他为男 | string | - | +| size | 头像大小 | number | 100 | +| width | 头像宽度 | number | - | +| height | 头像高度 | number | - | +| shape | 头像形状,可选 'circle' | string | - | +| gap | 头像与图标之间的间距 | number | - | +| icon | 自定义图标 | ReactNode | - | +| defaultAvatar | 默认头像 | string | 默认头像SVG | +| className | 自定义类名 | string | - | +| apis | API配置,用于加载OSS图片 | object | - | diff --git a/src/components/Image/doc/base.js b/src/components/Image/doc/base.js index 5d815f0f..c8c96774 100644 --- a/src/components/Image/doc/base.js +++ b/src/components/Image/doc/base.js @@ -1,6 +1,38 @@ -const {default: Image} = _Image; +const { default: Image } = _Image; +const { Space, Card, Typography } = antd; + +const { Text } = Typography; + const BaseExample = () => { - return ; + return ( + + + +
+ 通过 src 属性加载图片: +
+ +
+
+
+ 通过 URL 加载网络图片: +
+ +
+
+
+
+ + + +
1. Image 组件通过 src 属性直接加载图片URL。
+
2. 支持本地图片和网络图片加载。
+
3. 可以通过 style 属性设置图片大小。
+
4. 自动处理加载状态和错误状态。
+
+
+
+ ); }; -render(); +render(); diff --git a/src/components/Image/doc/custom-states.js b/src/components/Image/doc/custom-states.js new file mode 100644 index 00000000..135ab5ec --- /dev/null +++ b/src/components/Image/doc/custom-states.js @@ -0,0 +1,65 @@ +const { default: Image } = _Image; +const { Space, Card, Spin, Alert } = antd; +const { default: Icon } = icon; + +const CustomStatesExample = () => { + const customLoading = ( +
+ +
+ ); + + const customError = ( +
+ +
+ ); + + return ( + + + +
+
默认加载状态:
+ +
+
+
自定义加载状态:
+ +
+
+
+ + + +
+
默认错误状态:
+ +
+
+
自定义错误状态:
+ +
+
+
+ + + + +
+ ); +}; + +render(); \ No newline at end of file diff --git a/src/components/Image/doc/example.json b/src/components/Image/doc/example.json index 94bb99d2..27775bb1 100644 --- a/src/components/Image/doc/example.json +++ b/src/components/Image/doc/example.json @@ -2,19 +2,23 @@ "isFull": false, "list": [ { - "title": "通过src加载一个普通图片", - "description": "通过src加载一个普通图片", + "title": "基础图片加载", + "description": "通过src属性直接加载图片", "code": "./base.js", "scope": [ { "name": "_Image", "packageName": "@components/Image" + }, + { + "name": "antd", + "packageName": "antd" } ] }, { - "title": "通过id加载一个oss图片", - "description": "图片一加载成功,图片二加载中,图片三加载失败", + "title": "OSS图片加载", + "description": "通过id从OSS服务器加载图片,展示加载中和失败状态", "code": "./id-loader.js", "scope": [ { @@ -32,8 +36,8 @@ ] }, { - "title": "显示一个头像", - "description": "显示图片头像和默认头像", + "title": "头像组件", + "description": "展示Image.Avatar头像组件的各种用法", "code": "./avatar.js", "scope": [ { @@ -45,6 +49,55 @@ "packageName": "antd" } ] + }, + { + "title": "自定义状态组件", + "description": "自定义加载中和错误状态的显示组件", + "code": "./custom-states.js", + "scope": [ + { + "name": "_Image", + "packageName": "@components/Image" + }, + { + "name": "antd", + "packageName": "antd" + }, + { + "name": "icon", + "packageName": "@components/Icon" + } + ] + }, + { + "title": "图片交互", + "description": "展示图片点击事件和图片预览功能", + "code": "./image-interaction.js", + "scope": [ + { + "name": "_Image", + "packageName": "@components/Image" + }, + { + "name": "antd", + "packageName": "antd" + } + ] + }, + { + "title": "真实业务场景", + "description": "用户信息卡片中的头像和商品列表中的图片展示", + "code": "./real-scenario.js", + "scope": [ + { + "name": "_Image", + "packageName": "@components/Image" + }, + { + "name": "antd", + "packageName": "antd" + } + ] } ] } diff --git a/src/components/Image/doc/image-interaction.js b/src/components/Image/doc/image-interaction.js new file mode 100644 index 00000000..a1121eb0 --- /dev/null +++ b/src/components/Image/doc/image-interaction.js @@ -0,0 +1,119 @@ +const { default: Image } = _Image; +const { Space, Card, Modal, Typography, Button } = antd; +const { useState } = React; + +const { Text } = Typography; + +const ImageInteractionExample = () => { + const [previewVisible, setPreviewVisible] = useState(false); + const [previewImage, setPreviewImage] = useState(''); + + const handleImageClick = (src) => { + setPreviewImage(src); + setPreviewVisible(true); + }; + + const handlePreviewClose = () => { + setPreviewVisible(false); + }; + + return ( + + + + 点击图片查看大图: + + handleImageClick('https://picsum.photos/seed/product1/600/600.jpg')} + alt="产品图片1" + /> + handleImageClick('https://picsum.photos/seed/product2/600/600.jpg')} + alt="产品图片2" + /> + handleImageClick('https://picsum.photos/seed/product3/600/600.jpg')} + alt="产品图片3" + /> + + + + + + +
+ console.log('点击了用户头像')} + style={{ cursor: 'pointer' }} + alt="用户头像" + /> +
用户头像
+
+
+ console.log('点击了默认女性头像')} + style={{ cursor: 'pointer' }} + /> +
默认女性头像
+
+
+
+ + + +
1. Image 和 Image.Avatar 组件都支持 onClick 事件,可以添加交互功能。
+
2. 结合 Modal 组件可以实现图片预览功能。
+
3. 通过设置 cursor: 'pointer' 样式可以提示用户图片是可点击的。
+
+
+ + + 关闭 + + ]} + width={700} + onCancel={handlePreviewClose} + > +
+ 预览 +
+
+
+ ); +}; + +render(); \ No newline at end of file diff --git a/src/components/Image/doc/real-scenario.js b/src/components/Image/doc/real-scenario.js new file mode 100644 index 00000000..2964b94f --- /dev/null +++ b/src/components/Image/doc/real-scenario.js @@ -0,0 +1,152 @@ +const { default: Image } = _Image; +const { Space, Card, Typography, List, Avatar, Tag } = antd; + +const { Title, Text, Paragraph } = Typography; + +const RealScenarioExample = () => { + // 模拟用户数据 + const users = [ + { + id: 1, + name: '张三', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhangsan', + position: '前端开发工程师', + department: '技术部' + }, + { + id: 2, + name: '李四', + avatar: '', + gender: 'male', + position: '产品经理', + department: '产品部' + }, + { + id: 3, + name: '王五', + avatar: '', + gender: 'female', + position: 'UI设计师', + department: '设计部' + } + ]; + + // 模拟商品数据 + const products = [ + { + id: 1, + name: '高端笔记本电脑', + image: 'https://picsum.photos/seed/laptop/200/200.jpg', + price: 8999, + status: '在售' + }, + { + id: 2, + name: '无线蓝牙耳机', + image: 'https://picsum.photos/seed/earphone/200/200.jpg', + price: 499, + status: '在售' + }, + { + id: 3, + name: '智能手表', + image: 'https://picsum.photos/seed/watch/200/200.jpg', + price: 1299, + status: '缺货' + } + ]; + + return ( + + + ( + + + ) : ( + + ) + } + title={ +
+ {user.name} + + {user.department} + +
+ } + description={user.position} + /> +
+ )} + /> +
+ + + ( + + + } + > + + + ¥{product.price} + + + {product.status} + + + } + /> + + + )} + /> + + + + + 本示例展示了 Image 组件在真实业务场景中的应用: + +
    +
  • 用户信息卡片中的头像展示,支持图片头像和默认性别图标头像
  • +
  • 商品展示列表中的图片展示,支持加载状态和错误处理
  • +
  • 结合其他组件(List、Card、Tag等)实现完整的功能页面
  • +
+
+
+ ); +}; + +render(); \ No newline at end of file diff --git a/src/components/Image/doc/style.scss b/src/components/Image/doc/style.scss index e69de29b..981b495e 100644 --- a/src/components/Image/doc/style.scss +++ b/src/components/Image/doc/style.scss @@ -0,0 +1,24 @@ +/* Image 组件示例样式 */ +.product-card { + .ant-card-cover { + height: 180px; + overflow: hidden; + } +} + +.user-avatar { + &:hover { + transform: scale(1.05); + transition: transform 0.3s; + } +} + +.image-preview { + text-align: center; + + img { + max-width: 100%; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + } +} \ No newline at end of file diff --git a/src/components/Image/doc/summary.md b/src/components/Image/doc/summary.md index e0633a93..13ceef50 100644 --- a/src/components/Image/doc/summary.md +++ b/src/components/Image/doc/summary.md @@ -1 +1,16 @@ -用于展示一张图片,和img标签不同的是,可以展示一张普通图片,也可以通过id加载一张oss图片,在加载oss地址和图片数据的时候会显示loading状态 +Image 组件是一个增强的图片显示组件,支持两种加载方式: + +1. 通过 src 属性直接加载图片URL +2. 通过 id 属性从 OSS 服务器加载图片 + +组件特性: +- 自动加载状态显示和错误处理 +- 支持 Avatar 头像模式,可显示默认性别图标 +- 支持自定义加载状态和错误状态组件 +- 完全兼容原生 img 标签的基本属性 + +主要应用场景: +- 用户头像展示 +- 商品图片展示 +- 文档预览 +- 需要加载状态的图片显示 diff --git a/src/components/Intl/doc/api.md b/src/components/Intl/doc/api.md deleted file mode 100644 index 0a387583..00000000 --- a/src/components/Intl/doc/api.md +++ /dev/null @@ -1,2 +0,0 @@ -|属性名|说明|类型|默认值| -| --- | --- | --- | --- | diff --git a/src/components/Intl/doc/base.js b/src/components/Intl/doc/base.js deleted file mode 100644 index fcce13d1..00000000 --- a/src/components/Intl/doc/base.js +++ /dev/null @@ -1,33 +0,0 @@ -const {FormattedMessage, IntlProvider} = _Intl; -const {PureGlobal} = global; -const {Select, Space} = antd; -const {default: en} = localeEN; -const {default: cn} = localeCN; -const {useState} = React; -const BaseExample = () => { - const [locale, setLocale] = useState('zh-CN'); - return ( -
+ ) : ( +
+ 暂无数据,请点击"加载数据"按钮 +
+ )} + + ), + footerButtons: [ + { + children: '刷新', + onClick: () => { + if (data) { + setData([...data]); + message.success("数据已刷新"); + } + }, + }, + { + children: '关闭', + onClick: () => { + message.info("已关闭"); + }, + }, + ], + }); + modalApiRef.current = api; + }, [modal, data]); + + return ( + + + + ); +}; + +// TabsModal 示例 +const TabsModalExample = () => { + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [activeTab, setActiveTab] = useState('basic'); + + // 模拟数据 + const userData = { + name: '张三', + email: 'zhangsan@example.com', + avatar: 'https://randomuser.me/api/portraits/men/1.jpg', + department: '技术部', + joinDate: '2020-05-15', + projects: ['项目A', '项目B', '项目C'], + skills: ['JavaScript', 'React', 'Node.js'], + achievements: ['优秀员工', '技术创新奖', '团队协作奖'], + }; + + const items = [ + { + key: 'basic', + label: '基本信息', + withDecorator: (render) => { + return ( +
+

这是基本信息标签的装饰内容

+ {render()} +
+ ); + }, + children: () => ( +
+ +
+ +
+

{userData.name}

+

{userData.email}

+
+
+ + {userData.department} + {userData.joinDate} + +
+
+ ), + }, + { + key: 'projects', + label: '项目经验', + children: () => ( +
+ 参与项目
} + bordered + dataSource={userData.projects} + renderItem={(item, index) => ( + + {index + 1}} + title={item} + description={`这是${item}的描述信息,展示了项目的主要内容和成果。`} + /> + + )} + /> + + ), + }, + { + key: 'skills', + label: '技能特长', + children: () => ( +
+ + {userData.skills.map((skill, index) => ( + + {skill} + + ))} + + +

技能掌握程度评估:

+ + {userData.skills.map((skill, index) => ( +
+
{skill}:
+
+
+
+ {90 - index * 10}% +
+ ))} + +
+ ), + }, + { + key: 'achievements', + label: '成就荣誉', + children: () => ( +
+ + {userData.achievements.map((achievement, index) => ( + +

{achievement}

+

+ {index === 0 ? '2023年度评选' : index === 1 ? '2022年度评选' : '2021年度评选'} +

+
+ ))} +
+
+ ), + }, + ]; + + return ( + + + + setOpen(false)} + activeKey={activeTab} + onChange={setActiveTab} + items={items} + onConfirm={() => { + return new Promise((resolve) => { + setLoading(true); + setTimeout(() => { + setLoading(false); + message.success("操作成功"); + resolve(); + }, 1500); + }); }} - optionType="button" - buttonStyle="solid" /> - - { - setOpen(false); - }} - onConfirm={() => { + + ); +}; + +// ModalButton 示例 +const ModalButtonExample = () => { + return ( + +

ModalButton 可以在点击后加载数据,然后弹出弹窗

+ + { return new Promise((resolve) => { - message.success("弹窗1s后关闭"); setTimeout(() => { - message.success("弹窗关闭"); - resolve(); - }, 1000); + resolve({ + userInfo: { + name: '张三', + email: 'zhangsan@example.com', + department: '技术部', + position: '高级工程师', + joinDate: '2020-05-15', + } + }); + }, 1500); }); - }} - > -
弹窗弹窗弹窗弹窗弹窗弹窗弹窗
- - - -
+ ) : ( +
+

加载员工列表失败

- ), + )} + + ), + })} + > + 查看员工列表 + + + ); +}; + +// TabsModalButton 示例 +const TabsModalButtonExample = () => { + const formRef1 = useRef(null); + const formRef2 = useRef(null); + + return ( + +

TabsModalButton 结合了 TabsModal 和数据加载功能

+ + { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + profile: { + name: '李四', + email: 'lisi@example.com', + phone: '13800138000', + }, + settings: { + theme: 'dark', + language: 'zh-CN', + notifications: true, + }, + }); + }, 1500); }); - }} - > - 展示超高弹窗 - - -
+ }, + }} + modalProps={({ data }) => ({ + title: "用户设置", + items: [ + { + key: 'profile', + label: '个人信息', + children: () => ( + , + , + , + ]} + /> ), + }, + { + key: 'settings', + label: '系统设置', + children: () => ( + , + , + , + ]} + /> + ), + }, + ], + onConfirm: async () => { + try { + // 获取表单数据 + const form1Data = formRef1.current ? await formRef1.current.getData() : {}; + const form2Data = formRef2.current ? await formRef2.current.getData() : {}; + + console.log('表单数据:', { ...form1Data, ...form2Data }); + return Promise.resolve(); + } catch (error) { + console.error('表单验证失败:', error); + throw error; + } + }, + })} + > + 打开用户设置 + + + ); +}; + +const AdvancedModalExamples = () => { + return ( + + + + + + + + + ); +}; + +render(); +``` + +- FormModal表单弹窗 +- 展示FormModal组件的用法,在弹窗中展示表单,适合数据录入、编辑等场景 +- _Modal(@components/Modal),_FormInfo(@components/FormInfo),global(@components/Global),antd(antd) + +```jsx +const { FormModal, useFormModal } = _FormInfo; +const { Space, Button } = antd; +const { PureGlobal } = global; +const { default: FormInfo, List, Input, TextArea } = _FormInfo; +const { useState } = React; + +const BaseExample = () => { + const [open, setOpen] = useState(false); + const formModal = useFormModal(); + return ( + + { + setOpen(false); + }} + formProps={{ + data: { + productName: "示例产品", + }, + onSubmit: async (data) => { + console.log(data); + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 1000); }); - }} - > - 展示自定义footer弹框 - - - - -