diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 81f40ca44..72aa4e3fc 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -2,7 +2,7 @@ name: Build & Deploy on: push: branches: - - dev + - dev2 env: SKIP_PREFLIGHT_CHECK: true diff --git a/examples/website2/.dumirc.ts b/examples/website2/.dumirc.ts new file mode 100644 index 000000000..95333365a --- /dev/null +++ b/examples/website2/.dumirc.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'dumi'; + +export default defineConfig({ + outputPath: 'dist', + themeConfig: { + name: 'antdp', + logo:'/logo.svg', + editLink:true, + socialLinks: { + github: 'https://github.com/antdpro/antdp', + }, + footer: 'Copyright © 2023 antdp. All rights reserved.' , + nav:[{title:'实例预览',link:'https://stackblitz.com/github/antdpro/antdp/tree/master/examples/antdp-base?embed=1&hideNavigation=0&hidedevtools=0'},{title:'教程',link:'/guide/quick-start'},{title:'组件',link:'/component/user-login'}] + }, +}); diff --git a/examples/website2/.editorconfig b/examples/website2/.editorconfig new file mode 100644 index 000000000..e717f5eb6 --- /dev/null +++ b/examples/website2/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/examples/website2/.gitignore b/examples/website2/.gitignore new file mode 100644 index 000000000..5a239de45 --- /dev/null +++ b/examples/website2/.gitignore @@ -0,0 +1,5 @@ +node_modules +/dist +.dumi/tmp +.dumi/tmp-production +.DS_Store diff --git a/examples/website2/.prettierignore b/examples/website2/.prettierignore new file mode 100644 index 000000000..22b6303b6 --- /dev/null +++ b/examples/website2/.prettierignore @@ -0,0 +1,3 @@ +.dumi/tmp +.dumi/tmp-production +*.yaml diff --git a/examples/website2/.prettierrc.js b/examples/website2/.prettierrc.js new file mode 100644 index 000000000..e048a9d54 --- /dev/null +++ b/examples/website2/.prettierrc.js @@ -0,0 +1,14 @@ +module.exports = { + printWidth: 80, + proseWrap: 'never', + singleQuote: true, + trailingComma: 'all', + overrides: [ + { + files: '*.md', + options: { + proseWrap: 'preserve', + }, + }, + ], +}; diff --git a/examples/website2/LICENSE b/examples/website2/LICENSE new file mode 100644 index 000000000..b0c71392e --- /dev/null +++ b/examples/website2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/website2/README.md b/examples/website2/README.md new file mode 100644 index 000000000..2aa73e9aa --- /dev/null +++ b/examples/website2/README.md @@ -0,0 +1,20 @@ +# + +A static site base on [dumi](https://d.umijs.org). + +## Development + +```bash +# install dependencies +$ yarn install + +# start dev server +$ yarn start + +# build docs +$ yarn run build +``` + +## LICENSE + +MIT diff --git a/examples/website2/docs/component/authorized.md b/examples/website2/docs/component/authorized.md new file mode 100644 index 000000000..d9cc16e1a --- /dev/null +++ b/examples/website2/docs/component/authorized.md @@ -0,0 +1,181 @@ +--- +nav: 组件 +group: 依赖 +order: 6 +--- + +## @antdp/authorized + +[![npm](https://img.shields.io/npm/v/@antdp/authorized.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/authorized) +[![npm download](https://img.shields.io/npm/dm/@antdp/authorized.svg?style=flat)](https://www.npmjs.com/package/@antdp/authorized) + +权限判断组件或方法,通过判断是否进入主界面还是登录界面。 + +## 下载依赖 + +```bash +$ npm i @antdp/authorized # yarn add @antdp/authorized +``` + +## 启用方式 + +配置开启。同时需要 config/config.ts 提供权限配置。 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_AUTH_CONF: { ++ auth_menu: 'authMenu', ++ auth_btn: 'authBtn', ++ auth_check_url: true, + } +}); +``` + +### `ANTD_AUTH_CONF` 权限配置参数 + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | +| auth_menu | 储存菜单路由权限---本地 keys | `string` | `authMenu` | +| auth_btn | 储存按钮路径权限---本地 keys | `string` | `authBtn` | +| auth_check_url | 判断路径是否有权限的字段 默认值`menuUrl`,如果字段设置为`undefined`则`auth_menu`和`auth_btn`储存形式为 `["/web"]`,反之储存形式为`[{menuUrl:"/web"}]` | `string` | `menuUrl` | + +## 路由菜单权限 + +这是你的路由菜单(config/router.json) + +```json +[ + { + "path": "/login", + "component": "@/layouts/UserLayout" + }, + { + "path": "/", + "component": "@/layouts/BasicLayout", + "routes": [ + { + "path": "/", + "redirectTo": "/welcome" + }, + { + "path": "/welcome", + "name": "首页", + "icon": "welcome", + "locale": "welcome", + "component": "@/pages/Home/index" + }, + { + "path": "/404", + "name": "404", + "hideInMenu": true, + "icon": "file-protect", + "component": "@/pages/404" + }, + { + "path": "/403", + "name": "403", + "hideInMenu": true, + "icon": "file-protect", + "component": "@/pages/403" + } + ] + } +] +``` + +登陆后后端返回的菜单列表可能如下 + +```js +const menus = ['/', '/welcome', '/404', '/403']; +``` + +### 路由匹配过程 + +- 1.当你登陆成功后,你需将其保存于你的 sessionStorage 中,储存的字段为你`ANTD_AUTH_CONF`中配的`auth_menu`字段,并在登陆后存储在`sessionStorage`中,如`sessionStorage.setItem('authMenu', JSON.stringify([]))` +- 2.当你跳转至页面时,会根据 sessionStorage 中`authMenu`进行权限匹配,如果没有权限则会跳往 404 或 403 页面 + +请保证 403 和 404 页面存在 + +## 页面权限重定向 + +如果你想根据 `token`判断是否重定向回登陆页,可在 layouts/BasicLayout.ts 中添加`Authorized` + +```ts | pure +import Authorized from '@antdp/authorized'; +import BasicLayouts from '@antdp/basic-layouts'; + +const Layout = () => { + const token = ''; + return ( + + + + ); +}; + +export default Layout; +``` + +## 按钮权限 + +很多大型项目中,也会对按钮权限进行管理,请提前配置好`ANTD_AUTH_CONF`中配的`auth_btn`字段,并在登陆后存储在`sessionStorage`中,如`sessionStorage.setItem("authBtn",JSON.stringify(['/api/select']))` + +```tsx | pure +// 为了渲染设置的本地权限数 +import { AuthorizedBtn } from '@antdp/authorized'; +const Demo = () => { + return ( + + + + ); +}; +export default Demo; +``` + +### AuthorizedBtn 参数 + +| 参数 | 说明 | 类型 | 默认值 | +| -------- | -------- | ----------------- | ------ | +| path | 权限路径 | `string` | - | +| children | 展示内容 | `React.ReactNode` | - | + +## 使用 AuthorizedConfigProvider 设置按钮权限配置 + +使用 `AuthorizedConfigProvider`可以自己进行重新设置组件包裹内的所有按钮的权限参数,不使用默认配置的按钮权限配置 + +```tsx | pure +import { AuthorizedBtn, AuthorizedConfigProvider } from '@antdp/authorized'; +const Page = () => { + useEffect(() => { + sessionStorage.setItem('btn', JSON.stringify([{ menuUrl: '/api/select' }])); + }, []); + return ( + + + + + + ); +}; +export default Page; +``` + +### AuthorizedConfigProvider 参数 + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ---------- | +| auth_menu | 储存菜单路由权限---本地 keys | `string` | `authMenu` | +| auth_btn | 储存按钮路径权限---本地 keys | `string` | `authBtn` | +| auth_check_url | 判断路径是否有权限的字段 默认值`menuUrl`,如果字段设置为`undefined`则`auth_menu`和`auth_btn`储存形式为 `["/web"]`,反之储存形式为`[{menuUrl:"/web"}]` | `string` | `menuUrl` | +| isCheckAuth | 是否检查权限 | `boolean` | `false` | +| children | 子内容 | `string` | - | + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/component/basic-layouts.md b/examples/website2/docs/component/basic-layouts.md new file mode 100644 index 000000000..bdcee19a4 --- /dev/null +++ b/examples/website2/docs/component/basic-layouts.md @@ -0,0 +1,422 @@ +--- +nav: 组件 +order: 0 +group: + title: 依赖 + order: 1 +--- + +BasicLayouts +高级布局 + +--- + +[![npm](https://img.shields.io/npm/v/@antdp/basic-layouts.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/basic-layouts) +[![npm download](https://img.shields.io/npm/dm/@antdp/basic-layouts.svg?style=flat)](https://www.npmjs.com/package/@antdp/basic-layouts) + +入口公共界面 + +## 安装 + +```bash +$ npm i @antdp/basic-layouts # yarn add @antdp/basic-layouts +``` + +## 基本使用 + +```jsx | pure +import React from 'react'; +import BasicLayouts from '@antdp/basic-layouts'; +const Demo = () => { + return ; +}; + +export default Demo; +``` + +## 导航菜单模式 + +### slider + +```jsx | pure +import React from 'react'; +import BasicLayouts from '@antdp/basic-layouts'; +const Demo = () => { + return ; +}; +``` + +[![Ant Design Project](https://user-images.githubusercontent.com/59959718/262203847-d1612c3c-a37e-47fa-8282-dba85a8366be.png)](https://stackblitz.com/github/antdpro/antdp/tree/master/examples/antdp-base?embed=1&hideNavigation=0&hidedevtools=0) + +### mix + +```jsx | pure +import React from 'react'; +import BasicLayouts from '@antdp/basic-layouts'; +const Demo = () => { + return ; +}; +``` + +[![Ant Design Project](https://user-images.githubusercontent.com/59959718/262203911-58268a5b-a948-4909-87cc-bd6b4a6b8d1f.png)](https://stackblitz.com/github/antdpro/antdp/tree/master/examples/antdp-base?embed=1&hideNavigation=0&hidedevtools=0) + +### topleft + +```jsx | pure +import React from 'react'; +import BasicLayouts from '@antdp/basic-layouts'; +const Demo = () => { + return ; +}; +``` + +[![Ant Design Project](https://user-images.githubusercontent.com/59959718/262203891-ba31a1c0-84ad-42ae-8e0f-447b81ab9439.png)](https://stackblitz.com/github/antdpro/antdp/tree/master/examples/antdp-base?embed=1&hideNavigation=0&hidedevtools=0) + +## 配置明亮主题 + +### 亮主题 light + +```jsx | pure +import BasicLayouts from '@antdp/basic-layouts'; + +export default () => { + return ; +}; +``` + +### 暗主题 dark + +```jsx | pure +import BasicLayouts from '@antdp/basic-layouts'; + +export default () => { + return ; +}; +``` + +### 默认样式 + +```js +const defaultThemeColors = (layout) => { + if (layout === 'slider') { + return { + light: { + '--primary-slider-bg': 'rgb(36, 37, 37)', + '--primary-header-bg': '#fff', + '--primary-shadow': 'rgba(29,35,41,.05)', + '--primary-slider-trigger-border': '#fff', + '--primary-sider-trigger-text-color': '#fff', + '--primary-header-text-color': 'rgb(36, 37, 37)', + '--primary-title-text-color': '#fff', + '--primary-content-bg': '#f5f5f5', + itemSelectedBg: 'rgb(24, 144, 255)', + colorItemBgSelected: '#fff', + itemActiveBg: '#fff', + horizontalItemSelectedBg: '#fff', + itemColor: 'rgba(229, 224, 216, 0.85)', + itemHoverColor: 'rgba(229, 224, 216, 0.85)', + itemSelectedColor: '#fff', + colorBgElevated: 'rgba(229, 224, 216, 0.85)', + }, + dark: { + '--primary-slider-bg': 'rgb(36, 37, 37)', + '--primary-header-bg': 'rgb(36, 37, 37)', + '--primary-shadow': 'rgba(13, 13, 13, 0.65)', + '--primary-slider-trigger-border': '#fff', + '--primary-sider-trigger-text-color': '#fff', + '--primary-header-text-color': '#fff', + '--primary-title-text-color': '#fff', + '--primary-content-bg': '#1d1d1d', + itemSelectedBg: 'rgb(24, 144, 255)', + colorItemBgSelected: '#fff', + itemActiveBg: '#fff', + horizontalItemSelectedBg: '#fff', + itemColor: 'rgba(229, 224, 216, 0.85)', + itemHoverColor: 'rgba(229, 224, 216, 0.85)', + itemSelectedColor: '#fff', + colorBgElevated: 'rgba(229, 224, 216, 0.85)', + }, + }; + } + if (layout === 'topleft') { + return { + light: { + '--primary-slider-bg': '#fff', + '--primary-header-bg': '#fff', + '--primary-shadow': 'rgba(0,21,41,.12)', + '--primary-slider-trigger-border': 'rgba(0,0,0,.06)', + '--primary-sider-trigger-text-color': '#1d1d1d', + '--primary-header-text-color': '#1d1d1d', + '--primary-title-text-color': '#1d1d1d', + '--primary-content-bg': '#f5f5f5', + itemSelectedBg: '#e6f7ff', + colorItemBgSelected: 'rgba(0, 0, 0, 0.06)', + itemActiveBg: '#e6f7ff', + horizontalItemSelectedBg: 'rgba(0, 0, 0, 0.06)', + itemColor: 'rgba(0, 0, 0, 0.65)', + itemHoverColor: 'rgba(0, 0, 0, 0.85)', + itemSelectedColor: 'rgb(24, 144, 255)', + colorBgElevated: '#fff', + }, + dark: { + '--primary-slider-bg': 'rgb(36, 37, 37)', + '--primary-header-bg': 'rgb(36, 37, 37)', + '--primary-shadow': 'rgba(13, 13, 13, 0.65)', + '--primary-slider-trigger-border': '#fff', + '--primary-sider-trigger-text-color': '#fff', + '--primary-header-text-color': '#fff', + '--primary-title-text-color': '#fff', + '--primary-content-bg': '#1d1d1d', + itemSelectedBg: 'rgb(24, 144, 255)', + colorItemBgSelected: 'rgb(36, 37, 37)', + itemActiveBg: '#fff', + horizontalItemSelectedBg: 'rgb(36, 37, 37)', + itemColor: 'rgba(229, 224, 216, 0.85)', + itemHoverColor: 'rgba(229, 224, 216, 0.85)', + itemSelectedColor: '#fff', + colorBgElevated: 'rgba(229, 224, 216, 0.85)', + }, + }; + } + if (layout === 'mix') { + return { + light: { + '--primary-slider-bg': '#fff', + '--primary-header-bg': 'rgb(36, 37, 37)', + '--primary-shadow': 'rgba(0,21,41,.08)', + '--primary-slider-trigger-border': 'rgba(0,0,0,.06)', + '--primary-sider-trigger-text-color': 'rgb(36, 37, 37)', + '--primary-header-text-color': '#fff', + '--primary-title-text-color': '#fff', + '--primary-content-bg': '#f5f5f5', + itemSelectedBg: '#e6f7ff', + colorItemBgSelected: 'rgba(0, 0, 0, 0.06)', + itemActiveBg: '#e6f7ff', + horizontalItemSelectedBg: 'rgba(0, 0, 0, 0.06)', + itemColor: 'rgba(0, 0, 0, 0.65)', + itemHoverColor: 'rgba(0, 0, 0, 0.85)', + itemSelectedColor: 'rgb(24, 144, 255)', + colorBgElevated: '#fff', + }, + dark: { + '--primary-slider-bg': 'rgb(36, 37, 37)', + '--primary-header-bg': 'rgb(15, 28, 41)', + '--primary-shadow': 'rgba(13, 13, 13, 0.65)', + '--primary-slider-trigger-border': '#fff', + '--primary-sider-trigger-text-color': '#fff', + '--primary-header-text-color': '#fff', + '--primary-title-text-color': '#fff', + '--primary-content-bg': '#1d1d1d', + itemSelectedBg: 'rgb(24, 144, 255)', + colorItemBgSelected: '#fff', + itemActiveBg: '#fff', + horizontalItemSelectedBg: '#fff', + itemColor: 'rgba(229, 224, 216, 0.85)', + itemHoverColor: 'rgba(229, 224, 216, 0.85)', + itemSelectedColor: '#fff', + colorBgElevated: 'rgba(229, 224, 216, 0.85)', + }, + }; + } +}; +``` + +### token 自定义 BasicLayouts 颜色 + +```jsx | pure +import BasicLayouts from '@antdp/basic-layouts'; +import './index.css'; + +export default () => { + return ( + + ); +}; +``` + +![](https://user-images.githubusercontent.com/59959718/245099199-77d4288b-08e9-49cf-bab8-532d2d2d9f2c.png) + +## 菜单国际化设置 + +```jsx | pure +import BasicLayouts from '@antdp/basic-layouts'; +import { useIntl, SelectLang } from '@umijs/max'; + +const Demo = () => { + return ( + } + /> + ); +}; +export default Demo; +``` + +## 配置头部右侧菜单 + +```jsx | pure +import BasicLayouts from '@antdp/basic-layouts'; + +export default () => { + return ( + , + onClick: () => {}, + }, + { + title: '个人设置', + link: '/setting/property', + icon: , + }, + { + divider: true, + }, + { + title: '退出登录', + icon: , + onClick: () => { + logout(); + }, + }, + ]} + /> + ); +}; +``` + +## Message + +由于 antd 5.x 需全局包裹 App,引用 message 组件。我们在 basic-layouts 下也进行了注册。 + +### 方法一 + +```jsx | pure +import { App } from 'antd'; +import React from 'react'; + +const MyPage = () => { + const { message, notification, modal } = App.useApp(); + message.success('Good!'); + notification.info({ message: 'Good' }); + modal.warning({ title: 'Good' }); + // .... + // other message, notification, modal static function + return
Hello word
; +}; +export default MyPage; +``` + +### 方法二 + +```jsx | pure +import { Button, Space } from 'antd'; +import React from 'react'; +import { message } from '@antdp/basic-layouts'; + +export default () => { + const showMessage = () => { + message.success('Success!'); + }; + + return ( + + + + ); +}; +``` + +## API + +| 参数 | 说明 | 类型 | 默认值 | +| ---------------- | ------------------------------------------------------------------- | --------------------------------- | ------- | +| logo | 项目 logo | string | - | +| projectName | 项目名称 | `React.ReactNode` | - | +| children | 自定义内容 | `React.ReactNode` | - | +| intlLanguage | 国际化语言转换方法 | `IntlShape` | +| topRightMenu | 头像下拉菜单 | `TopRightMenuProps[]` | - | +| bodyPadding | 设置内容区域补白,默认 14px | `number` | - | +| topRightLanguage | 顶部右方 | `React.ReactNode` | - | +| siderWidth | 置最左边菜单宽度 | `number` | `180` | +| profile | 用户信息显示 | `{avatar?: string;name?: string}` | - | +| theme | 明暗主题 | `dark \| light` | `light` | +| className | 样式 | `string` | - | +| layout | 导航菜单模式,slider:右侧导航,topleft:顶部左侧导航,mix:混合导航 | `LayoutModel` | `mix` | +| token | 导航和头部样式集合 | `TokenProps` | - | + +## TopRightMenuProps + +| 参数 | 说明 | 类型 | 默认值 | +| ------- | ------------ | ----------------- | ------ | +| icon | 图标 | `React.ReactNode` | - | +| title | 标题 | `React.ReactNode` | - | +| link | 链接 | `string` | - | +| divider | 是否有下划线 | `boolean` | - | +| onClick | 点击事件 | `IntlShape` | + +## TokenProps + +```ts +export interface TokenProps { + menu?: { + colorMenuBackground?: string; + colorBgMenuItemHover?: string; + colorBgMenuItemSelected?: string; + colorTextMenu?: string; + colorTextMenuActive?: string; + colorTextMenuSelected?: string; + colorBgMenuItemCollapsedElevated?: string; + triggerColor?: string; + triggerTextColor?: string; + }; + header?: { + colorHeaderBackground?: string; + headerTextColor?: string; + }; + titleColor?: string; + shadowColor?: string; + contentBackground?: string; +} +``` diff --git a/examples/website2/docs/component/buttongroup-pro.md b/examples/website2/docs/component/buttongroup-pro.md new file mode 100644 index 000000000..a47dcefd6 --- /dev/null +++ b/examples/website2/docs/component/buttongroup-pro.md @@ -0,0 +1,80 @@ +--- +nav: 组件 +order: 0 +group: + title: 业务组件 + order: 2 +--- + +# ButtonGroupPro + +## 案例 + +```tsx mdx:preview +import { ButtonGroupPro } from '@antdp/antdp-ui'; + +const Demo = () => ( + {}, + }, + { + label: 'Menu', + type: 'primary', + menu: [ + { + key: '1', + label: '新增2', + onClick: () => {}, + }, + { + key: '2', + label: '新增3', + onClick: () => {}, + disabled: true, + }, + ], + }, + ]} + /> +); +export default Demo; +``` + +## Props + +组件继承 antd 的 [`Button`](https://ant.design/components/button-cn/#header) + +| 参数 | 说明 | 类型 | 默认值 | +| ----------------- | -------------------------- | ------------------------- | ------ | +| path | 权限路径 | string | - | +| label | 按钮展示名称 | string | React.ReactNode | - | +| option | 按钮组数据 | `Array` | - | +| menu | 按钮组名称 | `Array` | - | +| key | 按钮组唯一值 | number | - | +| ButtonandDropdown | 是否能点击按钮组的下拉菜单 | string | number | - | +| type | 按钮组类型 | string | number | - | +| render | 按钮组渲染 | ButtonType | - | +| badge | 按钮组徽标 | number | - | + +```ts +interface MenusOptionProps extends Omit, ButtonGroupProps { + path?: string; + label?: string | React.ReactNode; + option?: Array; + menu?: Array; + key?: number; + ButtonandDropdown?: string | number; + type?: ButtonType; + render?: (...arg: any) => React.ReactNode; + badge?: number | string; +} + +export interface ButtonGroupProProps { + button: Array; + className?: string; +} +``` diff --git a/examples/website2/docs/component/config.md b/examples/website2/docs/component/config.md new file mode 100644 index 000000000..ffdb589ed --- /dev/null +++ b/examples/website2/docs/component/config.md @@ -0,0 +1,308 @@ +--- +nav: 组件 +group: 依赖 +order: 7 +--- + +## @antdp/config + +[![npm](https://img.shields.io/npm/v/@antdp/config.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/config) +[![npm download](https://img.shields.io/npm/dm/@antdp/config.svg?style=flat)](https://www.npmjs.com/package/@antdp/config) + +我们已将 umi 配置包裹了一层, 如果需要 antdp 中能使用自定义配置,你可以使用项目根目录的 `config/config.ts`/`config/config.js`进行配置 + +## Installation + +```bash +npm i @antdp/config --save-dev +``` + +## Basic Usage + +```js +// config/config.{js|ts} + +//二次封装的umi配置 +import config from '@antdp/config'; +// 路由数据 +import router from './router.json'; +/**开发代理配置*/ +import proxy from './proxy'; + +export default config(router, { + proxy, +}); +``` + +## Interface + +```typescript +import { IConfig, IRoute } from '@umijs/max'; + +export interface Options extends Omit { + routes: IRoute; +} + +export interface Config { + (routes?: IRoute, optiosn?: Options): IConfig; +} + +declare var config: Config; +export default config; +``` + +## define 配置 + +### ANTD_IS_TABS + +是否显示 Tab 选项卡,默认值`true`显示 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_IS_TABS: true + }, +); +``` + +### ANTD_IS_IFRAME_RENDER + +是否使用 iframe 展示内容,默认值`true`开启 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_IS_IFRAME_RENDER: true + }, +); +``` + +### ANTD_IS_BREADCRUMB + +是否展示面包屑, Tab 选项卡优先级大于面包屑 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_IS_BREADCRUMB: true + }, +); +``` + +### ANTD_AUTH_CONF + +是否开启权限验证 默认值`false`不启用 + +- `auth_menu` 储存菜单路由权限---本地 keys 默认值`authMenu` +- `auth_btn` 储存按钮路径权限---本地 keys 默认值 `authBtn` +- `auth_check_url` 判断路径是否有权限的字段 默认值`menuUrl`,如果字段设置为`undefined`则`auth_menu`和`auth_btn`储存形式为 `["/web"]`,反之储存形式为`[{menuUrl:"/web"}]`, + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_AUTH_CONF: { ++ auth_menu:"authMenu", ++ auth_btn:"authBtn", ++ auth_check_url:undefined + } + }, +); + +``` + +### ANTD_MENU_IS_SHOW + +是否显示 左侧菜单,默认值`true` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_MENU_IS_SHOW: true + }, +); +``` + +### ANTD_HEAD_IS_SHOW + +是否显示 head 头部,默认值`true` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_HEAD_IS_SHOW: true + }, +); +``` + +### ANTD_MENU_SEARCH_IS_SHOW + +是否开启菜单栏搜索,默认值`false` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_MENU_SEARCH_IS_SHOW: true + }, +); +``` + +## locale 配置 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, + locale: { + // 默认使用 src/locales/zh-CN.ts 作为多语言文件 ++ default: 'zh-CN', ++ antd: true, + // default true, when it is true, will use `navigator.language` overwrite default + // baseNavigator: true, + /** + * [国际化] 控制台提示 Warning: The current popular language does not exist, please check the locales folder! 警告信息 + * https://github.com/umijs/umi/issues/4363#issuecomment-616134434 + * 警用 `baseNavigator` 和 `title` 两个配置项 可以解决国际化警告问题 + */ ++ baseNavigator: false, ++ title: false, + }, + }, +); +``` + +## @umijs/max 配置 + +### request + +开启 `useRequest` 和 `request`,默认`未开启` + +- `dataField` 该配置的默认值是 data。该配置的主要目的是方便 useRequest 直接消费数据。如果你想要在消费数据时拿到后端的原始数据,需要在这里配置 `dataField` 为 '' + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ request:{} +); +``` + +### react-query + +开启`react-query`,默认`未开启` + +- `devtool`: boolean,是否开启 react query 官方 devtool 工具,默认 `true` +- `queryClient`: boolean, 是否注册全局的 QueryClient 和 QueryClientProvier,默认 `true` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ reactQuery: { + // 是否开启 react query 官方 devtool 工具 ++ devtool: false, ++ queryClient: true, + }, +); +``` + +### useModel + +开启`useModel`,默认`未开启` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ model: {}, +); +``` + +### dva + +开启`dva`,默认`未开启` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ dva: {}, +); +``` + +### styled-components + +[styled-components](https://styled-components.com/) 样式方案 + +- `babelPlugin`: Object,开启 styled-components 的 babel 插件,仅 dev 模式有效 + 比如: + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ styledComponents: {}, +); +``` + +### valtio + +`valtio` 数据流方案 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ valtio: {}, +); +``` + +[更多配置参考 umi](https://umijs.org/docs/api/config) diff --git a/examples/website2/docs/component/document-title.md b/examples/website2/docs/component/document-title.md new file mode 100644 index 000000000..1679d8425 --- /dev/null +++ b/examples/website2/docs/component/document-title.md @@ -0,0 +1,39 @@ +--- +nav: 组件 +group: 依赖 +order: 4 +--- + +## @antdp/document-title + +[![npm](https://img.shields.io/npm/v/@antdp/document-title.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/document-title) +[![npm download](https://img.shields.io/npm/dm/@antdp/document-title.svg?style=flat)](https://www.npmjs.com/package/@antdp/document-title) + +设置页面标题。 + +如果需要通过当前组件方式渲染 title,配 `title: false` 可禁用内置的 `title` 渲染机制,`@antdp/config` 默认配置 `title: false`。 + +> - https://github.com/umijs/umi/pull/4345/files +> - https://umijs.org/zh-CN/config#title + +## Installation + +```bash +npm i @antdp/document-title --save +``` + +## Basic Usage + + + +```tsx mdx:preview +import DocumentTitle from '@antdp/document-title'; + +const Demo = () => ( + +

Home, sweet home.

+
+); + +export default Demo; +``` diff --git a/examples/website2/docs/component/edit-table.md b/examples/website2/docs/component/edit-table.md new file mode 100644 index 000000000..6950a6aa3 --- /dev/null +++ b/examples/website2/docs/component/edit-table.md @@ -0,0 +1,495 @@ +--- +nav: 组件 +group: 业务组件 +order: 3 +--- + +# EditTable + +## 依赖安装 + +```bash + npm i @antdp/edit-table +``` + +## 基本使用 + +```tsx mdx:preview +import EditTable from '@antdp/edit-table'; +import { Button, Form, Input, InputNumber } from 'antd'; +import 'antd/dist/reset.css'; +import React from 'react'; +const originData = []; + +for (let i = 0; i < 5; i++) { + originData.push({ + key: i.toString(), + name: `Edrward ${i}`, + age: 32, + address: `London Park no. ${i}`, + }); +} + +const EditableTable = () => { + const [data, setData] = React.useState(originData); + const [tableProps, setTableProps] = React.useState({ + isAdd: true, + isOpt: true, + optIsFirst: false, + }); + const columns = [ + { + title: 'name', + dataIndex: 'name', + width: '20%', + editable: true, + type: 'Input', + }, + { + title: 'age', + dataIndex: 'age', + width: '15%', + editable: true, + type: 'Custom', + rules: [{ required: true, message: '请输入' }], + inputNode: , + }, + { + title: 'isList', + dataIndex: 'list', + width: '15%', + editable: true, + type: 'Custom', + isList: true, + render: (text) => { + return ( + text && + (text || []) + .filter((it) => it) + .map((ite) => ite.first) + .join(',') + ); + }, + inputNode: (fields, { add, remove }, { errors }) => ( + <> + {fields.map(({ key, name, fieldKey, ...restField }, index) => ( +
+ + + +
+ ))} + + + + + + ), + }, + { + title: 'address', + dataIndex: 'address', + width: '30%', + editable: true, + type: 'Input', + }, + ]; + return ( +
+ setData(list)} + rowKey="key" + dataSource={data} + columns={columns} + onSave={(list) => setData(list)} + {...tableProps} + /> +
+ ); +}; +export default EditableTable; +``` + +## 操作列在第一列 + +```tsx mdx:preview +import EditTable from '@antdp/edit-table'; +import { Input } from 'antd'; +import 'antd/dist/reset.css'; +import React from 'react'; +const originData = []; + +for (let i = 0; i < 5; i++) { + originData.push({ + key: i.toString(), + name: `Edrward ${i}`, + age: 32, + // address: `London Park no. ${i}`, + }); +} + +const EditableTable = () => { + const [data, setData] = React.useState(originData); + const [tableProps, setTableProps] = React.useState({ + isAdd: true, + isOpt: true, + optIsFirst: true, + }); + const columns = [ + { + title: 'name', + dataIndex: 'name', + width: '20%', + editable: true, + type: 'Custom', + inputNode: (edit) => { + return ; + }, + }, + { + title: 'age', + dataIndex: 'age', + width: '15%', + editable: true, + type: 'Input', + // rules: [{ required: true, message: '请输入' }], + inputNode: (edit) => { + return ; + }, + }, + { + title: 'address', + dataIndex: 'address', + width: '30%', + editable: true, + type: 'Input', + }, + ]; + return ( +
+ setData(list)} + rowKey="key" + optIsFirst={true} + dataSource={data} + columns={columns} + onSave={(list) => setData(list)} + isAdd={true} + {...tableProps} + /> +
+ ); +}; +export default EditableTable; +``` + +## 显示删除按钮 + +```tsx mdx:preview +import EditTable from '@antdp/edit-table'; +import { Input } from 'antd'; +import 'antd/dist/reset.css'; +import React from 'react'; +const originData = []; + +for (let i = 0; i < 5; i++) { + originData.push({ + key: i.toString(), + name: `Edrward ${i}`, + age: 32, + // address: `London Park no. ${i}`, + }); +} + +const EditableTable = () => { + const [data, setData] = React.useState(originData); + const [tableProps, setTableProps] = React.useState({ + isAdd: true, + isOpt: true, + isOptDelete: true, + optIsFirst: false, + }); + const columns = [ + { + title: 'name', + dataIndex: 'name', + width: '20%', + editable: true, + type: 'Custom', + inputNode: (edit) => { + return ; + }, + }, + { + title: 'age', + dataIndex: 'age', + width: '15%', + editable: true, + type: 'Custom', + rules: [{ required: true, message: '请输入' }], + inputNode: (edit) => { + return ; + }, + }, + { + title: 'address', + dataIndex: 'address', + width: '30%', + editable: true, + type: 'Input', + }, + ]; + return ( +
+ setData(list)} + rowKey="key" + dataSource={data} + columns={columns} + onSave={(list) => setData(list)} + isAdd={true} + {...tableProps} + /> +
+ ); +}; +export default EditableTable; +``` + +## 允许同时编辑多行 + +```tsx mdx:preview +import EditTable from '@antdp/edit-table'; +import { Input } from 'antd'; +import 'antd/dist/reset.css'; +import React from 'react'; +const originData = []; + +for (let i = 0; i < 5; i++) { + originData.push({ + key: i.toString(), + name: `Edrward ${i}`, + age: 32, + // address: `London Park no. ${i}`, + }); +} + +const EditableTable = () => { + const [data, setData] = React.useState(originData); + const [tableProps, setTableProps] = React.useState({ + isAdd: true, + isOpt: true, + isOptDelete: true, + optIsFirst: false, + multiple: true, + }); + const columns = [ + { + title: 'name', + dataIndex: 'name', + width: '20%', + editable: true, + type: 'Custom', + inputNode: (edit) => { + return ; + }, + }, + { + title: 'age', + dataIndex: 'age', + width: '15%', + editable: true, + type: 'Custom', + rules: [{ required: true, message: '请输入' }], + inputNode: (edit) => { + return ; + }, + }, + { + title: 'address', + dataIndex: 'address', + width: '30%', + editable: true, + type: 'Input', + }, + ]; + return ( +
+ setData(list)} + rowKey="key" + dataSource={data} + columns={columns} + onSave={(list) => setData(list)} + isAdd={true} + {...tableProps} + /> +
+ ); +}; +export default EditableTable; +``` + +## 无操作和新增 + +```tsx mdx:preview +import EditTable from '@antdp/edit-table'; +import { Input } from 'antd'; +import 'antd/dist/reset.css'; +import React from 'react'; +const originData = []; + +for (let i = 0; i < 5; i++) { + originData.push({ + key: i.toString(), + name: `Edrward ${i}`, + age: 32, + // address: `London Park no. ${i}`, + }); +} + +const EditableTable = () => { + const [data, setData] = React.useState(originData); + const [tableProps, setTableProps] = React.useState({ + isOpt: false, + isAdd: false, + }); + const columns = [ + { + title: 'name', + dataIndex: 'name', + width: '20%', + editable: true, + type: 'Custom', + inputNode: (edit) => { + return ; + }, + }, + { + title: 'age', + dataIndex: 'age', + width: '15%', + editable: true, + type: 'Custom', + rules: [{ required: true, message: '请输入' }], + inputNode: (edit) => { + return ; + }, + }, + { + title: 'address', + dataIndex: 'address', + width: '30%', + editable: true, + type: 'Input', + }, + ]; + return ( +
+ setData(list)} + rowKey="key" + dataSource={data} + columns={columns} + onSave={(list) => setData(list)} + isAdd={true} + {...tableProps} + /> +
+ ); +}; +export default EditableTable; +``` + +## API + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | ------------------------ | -------------------------------------------------------------- | ------ | +| columns | 列 | `ColumnsProps[]` | - | +| onSave | 保存数据 | `(data: any[], row: any, record?: any, indx?: number) => void` | - | +| onBeforeSave | 保存数据之前校验 | ` (item: any, record: any, index: number) => boolean` | - | +| rowKey | 主键 | `string` | - | +| optIsFirst | 操作列是放在首位还是最后 | `boolean` | - | +| isOpt | 是否需要操作列 | `boolean` | - | +| optConfig | 操作配置 | `ColumnsProps` | - | +| isOptDelete | 操作是否需要 删除 按钮 | `boolean` | - | +| isAdd | 是否存在新增按钮 | `boolean` | - | +| initValue | 新增初始值 | `any` | - | +| onBeforeAdd | 新增之前判断 | `() => boolean` | - | +| onErr | 行报错信息 | `(err: ValidateErrorEntity) => void` | - | +| onValuesChange | 表单值更新事件 | `(list:any) => void` | - | +| multiple | 是否可以多行编辑 | `boolean` | - | +| addBtnProps | 新增按钮配置 | `ButtonProps` | - | +| store | form 存储表单 | `Store` | - | + +## ColumnsProps + +| 参数 | 说明 | 类型 | 默认值 | +| --------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | ------ | +| editable | 是否编辑 | `boolean` | - | +| inputNode | 自定义 渲染编辑组件 | `(...arg: any[]) => React.ReactNode \| React.ReactNode` | - | +| rules | 规则 | `Rule[]` | - | +| itemAttr | formItem 表单 其他属性值 | `Omit` | - | +| attr | formItem 表单 children 中组件参数 | `Partial>` | - | +| type | 组件类型 | `ItemChildType` | - | +| tip | 错误提示 | `(errs: string) => React.ReactNode` | - | +| tipAttr | Tooltip 组件属性 | `TooltipProps` | - | +| isList | 是否是 List | `boolean` | - | +| listAttr | List 组件参数 | `Omit` | - | +| render | 自定义 渲染(列原始默认的自定义渲染,加了个 other 参数,不是编辑状态下的表格渲染) , other 参数 只有操作列才有 | `(value: any,record: any,index: number,other?: OtherProps) => React.ReactNode \| RenderedCell` | - | + +## OtherProps + +| 参数 | 说明 | 类型 | 默认值 | +| ---------- | ------------------------------------------------------- | ------------------------------------------------------------- | ------ | +| editingKey | 编辑中字段 | `any[]` | - | +| editable | 编辑中字段 | `boolean` | - | +| newAdd | 当前行 是否编辑 | `any[]` | - | +| isNewAdd | 是否新增的 | `boolean` | - | +| save | 保存 ,`key:主键` ,`record:行数据`,`index:下标` | `(key: string \| number, record: any, indx: number) => void` | - | +| cancel | 取消 , `id:主键` | `(id: string \| number) => void` | - | +| onDelete | 删除 ,`id:主键`, `rowItem 当前行数据` ,`index:下标` | `(id: string \| number, rowItem: any, index: number) => void` | - | +| edit | 编辑 按钮 ,`record 当前行数` | `(record: any) => void` | - | + +## ref 返回值 + +| 参数 | 说明 | 类型 | 默认值 | +| ---------- | ------------------- | ------------------------------------------------------------- | ------ | +| save | 保存 | `(key: string, record: any, indx: number) => void` | - | +| onDelete | 删除 | `(id: string \| number, rowItem: any, index: number) => void` | - | +| edit | 编辑 | `(record: any) => void` | - | +| cancel | 取消编辑 | `(key: string \| number) => void` | - | +| add | 新增 | `() => void` | - | +| isEditing | 是否编辑中 | `(record: any) => boolean` | - | +| editingKey | 编辑 id | `(string \| number)[]` | - | +| newAdd | 是否编辑 新增的数据 | `(string \| number)[]` | - | +| forms | 收集 所有 表单 | `Store` | - | + +## Item 组件参数 + +| 参数 | 说明 | 类型 | 默认值 | +| --------- | --------------------------------------- | ----------------------------------- | ------ | +| preName | 当前行数据存储父级的 name list 时不用传 | `string` | - | +| itemValue | 当前行的所有数据 | `any` | - | +| tipAttr | Tooltip 组件属性 | `TooltipProps` | - | +| tip | 错误提示 | `(errs: string) => React.ReactNode` | - | +| children | 进行覆写 方法时 新增一个 行参数 v | `React.ReactNode` | - | diff --git a/examples/website2/docs/component/fullscreen.md b/examples/website2/docs/component/fullscreen.md new file mode 100644 index 000000000..3085ee363 --- /dev/null +++ b/examples/website2/docs/component/fullscreen.md @@ -0,0 +1,34 @@ +--- +nav: 组件 +group: 依赖 +order: 3 +--- + +## @antdp/fullscreen + +[![npm version](https://img.shields.io/npm/v/@antdp/fullscreen.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/fullscreen) +[![npm download](https://img.shields.io/npm/dm/@antdp/fullscreen.svg?style=flat)](https://www.npmjs.com/package/@antdp/fullscreen) + +设置页面全屏。 + +## 安装使用 + +```bash +npm i @antdp/fullscreen --save +``` + +## 基础示例预览 + + + +```tsx mdx:preview +import Fullscreen from '@antdp/fullscreen'; + +const Demo = () => ( +
+ +
+); + +export default Demo; +``` diff --git a/examples/website2/docs/component/fuzzy-query.md b/examples/website2/docs/component/fuzzy-query.md new file mode 100644 index 000000000..76e3dea36 --- /dev/null +++ b/examples/website2/docs/component/fuzzy-query.md @@ -0,0 +1,102 @@ +--- +nav: 组件 +group: 业务组件 +order: 4 +--- + +# FuzzyQuery + +## 依赖安装 + +```bash + npm i @antdp/fuzzy-query +``` + +## 基本使用 + + + +```tsx mdx:preview +import FuzzyQuery from '@antdp/fuzzy-query'; +import 'antd/dist/reset.css'; +import React from 'react'; + +const Query = () => { + const [value, setValue] = React.useState([]); + // 根据key模糊查询组织 + const selectLike = async () => { + return Array.from({ length: 20 }).map((_, index) => { + return { + label: `名称---${index}`, + phone: index, + }; + }); + }; + return ( +
+ +
+ ); +}; +export default Query; +``` + +## 延迟时间 5s + +```tsx mdx:preview +import FuzzyQuery from '@antdp/fuzzy-query'; +import 'antd/dist/reset.css'; +import React from 'react'; + +const Query = () => { + const [value, setValue] = React.useState([]); + // 根据key模糊查询组织 + const selectLike = async () => { + return Array.from({ length: 20 }).map((_, index) => { + return { + label: `名称---${index}`, + phone: index, + }; + }); + }; + return ( +
+ +
+ ); +}; +export default Query; +``` + +## API + +[更多参数参考 antd5 Select 组件](https://ant.design/components/select-cn#api) + +| 参数 | 说明 | 类型 | 默认值 | +| --------------- | -------- | -------------------------------------------------------------------------- | ------ | +| columns | 表格标题 | `TablesProps["columns"]` | - | +| request | 请求 | `(params: any) => Promise<{ label: any, value: any, [s: string]: any }[]>` | - | +| debounceTimeout | 延迟时间 | `number` | - | diff --git a/examples/website2/docs/component/layout-tabs.md b/examples/website2/docs/component/layout-tabs.md new file mode 100644 index 000000000..ad0140d8e --- /dev/null +++ b/examples/website2/docs/component/layout-tabs.md @@ -0,0 +1,68 @@ +--- +nav: 组件 +group: 依赖 +order: 5 +--- + +## @antdp/layout-tabs + +[![npm version](https://img.shields.io/npm/v/@antdp/layout-tabs.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/layout-tabs) +[![npm download](https://img.shields.io/npm/dm/@antdp/layout-tabs.svg?style=flat)](https://www.npmjs.com/package/@antdp/layout-tabs) + +用于主框架选项卡组件。解决 `antd` 组件 `Tabs` 切换性能慢的问题。 + +## Tab 选项卡技术实现 + +| 测试项 | 页面切换重新渲染 | 页面切换 “隐藏” | iframe src 嵌入页面 | iframe 组件生成 | +| ---------- | ---------------- | --------------- | ---------------------------- | -------------------------- | +| 性能 | ✅💯 | ⚠️(还需优化) | ✅💯 | ✅💯 | +| 页面状态 | ⚠️(有代码量) | ✅ | ✅ | ✅ | +| 路由使用 | ✅ | ✅ | ❌(浏览器地址栏无变化) | ✅ | +| antd 组件 | ✅ | ✅ | ✅ | ❌(大量弹出类组件位置错乱) | +| 主框架交互 | ✅ | ✅ | ⚠️(局限以内,父页面交互复杂) | ✅ | +| 样式加载 | ✅ | ✅ | ✅ | ⚠️(还需优化) | + +## Installation + +```bash +npm i @antdp/layout-tabs --save # yarn add @antdp/layout-tabs +``` + +## 基本使用 + +```tsx | pure +import LayoutTabs from '@antdp/layout-tabs'; + +const Demo = () => { + return ( + + ); +}; + +export default Demo; +``` + +## API + +| 参数 | 说明 | 类型 | 默认值 | +| ----------- | ------------ | ------------------------- | ------- | --- | +| dataSource | 菜单路由数据 | `LayoutTabsRouterProps[]` | - | +| bodyPadding | 内容边距 | `string | number` | - | + +### LayoutTabsRouterProps + +| 参数 | 说明 | 类型 | 默认值 | +| -------- | ------------ | ----------------- | ------ | +| icon | logo | `string` | - | +| key | key 健 | `string` | - | +| name | 名称 | `string` | - | +| path | 路径 | `string` | - | +| exact | 是否匹配路由 | `boolean` | - | +| location | 路由信息 | `any` | - | +| match | 匹配信息 | `any` | - | +| element | 渲染内容 | `React.ReactNode` | - | + +通过配置 [`@antdp/config`](https://www.npmjs.com/package/@antdp/config),来解决是否重新渲染或者 `iframe` 展示页面等功能 diff --git a/examples/website2/docs/component/page-loading.md b/examples/website2/docs/component/page-loading.md new file mode 100644 index 000000000..902d5c372 --- /dev/null +++ b/examples/website2/docs/component/page-loading.md @@ -0,0 +1,35 @@ +--- +nav: 组件 +group: 依赖 +order: 2 +--- + +## @antdp/page-loading + +[![npm](https://img.shields.io/npm/v/@antdp/page-loading.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/page-loading) +[![npm download](https://img.shields.io/npm/dm/@antdp/page-loading.svg?style=flat)](https://www.npmjs.com/package/@antdp/page-loading) + +页面过渡动画组件。 + +## Installation + +```bash +npm i @antdp/page-loading --save +``` + +## Basic Usage + + + +```jsx mdx:preview +import React from 'react'; +import PageLoading from '@antdp/page-loading'; + +const Demo = () => ( +
+ +
+); + +export default Demo; +``` diff --git a/examples/website2/docs/component/quickform.md b/examples/website2/docs/component/quickform.md new file mode 100644 index 000000000..d746d7da9 --- /dev/null +++ b/examples/website2/docs/component/quickform.md @@ -0,0 +1,340 @@ +--- +nav: 组件 +group: 业务组件 +order: 1 +--- + +# QuickForm + +快速生成 Form 表单。 + +## 基础示例 + + + +```jsx mdx:preview +import React, { useRef } from 'react'; +import { QuickForm, CardPro } from '@antdp/antdp-ui'; + +const QuickFormDemo = (props) => { + const baseRef = useRef(); + return ( + + + + ); +}; +export default QuickFormDemo; +``` + +## 各种类型表单 + + + +```jsx mdx:preview +import React, { useRef } from 'react'; +import { QuickForm } from '@antdp/antdp-ui'; + +const QuickFormDemo = (props) => { + const baseRef = useRef(); + return ( +
+ + + +
+ ); +}; +export default QuickFormDemo; +``` + +## 表单排列 + + + +```jsx mdx:preview +import React, { useRef } from 'react'; +import { QuickForm, CardPro } from '@antdp/antdp-ui'; + +const QuickFormDemo = (props) => { + const baseRef = useRef(); + return ( + + + + ); +}; +export default QuickFormDemo; +``` + +## 表单 size + + + +```jsx mdx:preview +import React, { useRef } from 'react'; +import { QuickForm, CardPro } from '@antdp/antdp-ui'; + +const QuickFormDemo = (props) => { + const baseRef = useRef(); + return ( + + + + ); +}; +export default QuickFormDemo; +``` + +## 折叠表单时每个面板右上角的内容 + + + +```jsx mdx:preview +import React, { useRef } from 'react'; +import { QuickForm, CardPro } from '@antdp/antdp-ui'; + +const QuickFormDemo = (props) => { + const baseRef = useRef(); + return ( + + 右上角内容} + formDatas={[ + { + label: '备注', + name: 'remark', + type: 'input', + }, + { + label: '水果', + name: 'fruit', + type: 'select', + options: [{ label: 'apple', value: 1 }], + }, + ]} + /> + + ); +}; +export default QuickFormDemo; +``` + +## 初始隐藏表单项 + + + +```jsx mdx:preview +import React, { useRef } from 'react'; +import { QuickForm, CardPro } from '@antdp/antdp-ui'; + +const QuickFormDemo = (props) => { + const baseRef = useRef(); + // 获取form表单隐藏方法 + const [hide] = QuickForm.useFormItemHide(); + + // 表单变更 + const onValuesChange = ({ remark }) => { + if (remark === '1234') { + // 组件显示 + hide.updateValue('fruit', false); + } else { + // 组件隐藏 + hide.updateValue('fruit', true); + } + }; + return ( + + + + ); +}; +export default QuickFormDemo; +``` + +## 表单提交验证 + + + +```jsx mdx:preview +import React, { useRef, useState } from 'react'; +import { QuickForm, CardPro, ButtonGroupPro } from '@antdp/antdp-ui'; + +const QuickFormDemo = (props) => { + const [state, setState] = useState({}); + const baseRef = useRef(); + return ( + + + { + const value = await baseRef?.current?.validateFields(); + setState({ ...value }); + }, + }, + ]} + /> +
{JSON.stringify(state)}
+
+ ); +}; +export default QuickFormDemo; +``` + + + +## Props + +组件继承[antd Form](https://ant.design/components/form-cn) + +| 参数 | 说明 | 类型 | 默认值 | +| --------------------- | -------------------------------------------------- | -------------------------- | ------ | +| formDatas | 表单集合 | `Array` | - | +| collapseAttributes | antd collapse 组件属性集合 | Object | - | +| panelAttributes | antd collapse.panel 组件属性集合 | Object | - | +| visible | 折叠表单下是否初始化选中面板 | boolean | false | +| colspan | 表单单行排列 | number | - | +| header | 组件头部标题 | `React.ReactNode` | - | +| defaultFormItemLayout | 自定义表单栅格宽度占比,参照 antd 栅格布局 | Object | - | +| defaultFormLayout | 自定义表单排列方式 | FormLayout | - | +| size | 尺寸 | number | - | +| type | 表单类型:modal | cardform | CardPro | string | - | +| extra | antd collapse.panel 自定义渲染每个面板右上角的内容 | `React.ReactNode` | - | +| formHide | Form.useFormItemHide 返回值 | Object | - | +| initialHide | 初始值 隐藏显示 字段对应的值 | `{ [x: string]: boolean }` | - | +| initialHide | 初始值 隐藏显示 字段对应的值 | `{ [x: string]: boolean }` | - | + +## itemProps + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | ------------------------------------------------------- | --------------------------------------------------- | ------ | +| defaultcolspan | 表单默认列 span | number | - | +| label | 表单元素标题 | string | - | +| name | 表单名称 antd from 组件 getFieldDecorator 第一个参数 | string | - | +| initialValue | 表单初始值 | string | any | - | +| full | 表单是否独占一行 | boolean | - | +| hideInForm | 表单隐藏 | boolean | - | +| attributes | input select 等表单组件属性集合 | T | any | - | +| type | 列表需要展示的其他组件,如:(Checkbox,Radio,Select 等) | string | undefined | - | +| options | ?? | `Array<{ label: string, value: string | number }>` | - | +| span | 设置列表的占位格数 | number | - | +| render | 自定义渲染 | JSX.Element | - | diff --git a/examples/website2/docs/component/user-login.md b/examples/website2/docs/component/user-login.md new file mode 100644 index 000000000..4e604015c --- /dev/null +++ b/examples/website2/docs/component/user-login.md @@ -0,0 +1,156 @@ +--- +nav: 组件 +group: 依赖 +order: 1 +--- + +# User Login + +## Installation + +```bash +$ npm i @antdp/user-login --save +``` + +## 基本使用 + +```tsx mdx:preview +import UserLogin from '@antdp/user-login'; + +const Demo = (props) => { + return ( + { + console.log('打印保存参数---->', value, submitType); + }} + onSend={() => console.log('短信验证回调')} + formBtns={[ + { + label: '登录', + attr: { + type: 'primary', + htmlType: 'submit', + style: { + marginRight: 20, + }, + }, + }, + { + label: '重置', + attr: { + type: 'primary', + }, + }, + ]} + /> + ); +}; +export default Demo; +``` + +## 账号登录 + +```tsx mdx:preview +import UserLogin from '@antdp/user-login'; + +const Demo = (props) => { + return ( + { + console.log('打印保存参数---->', values); + }} + type="account" + formBtns={[ + { + label: '登录', + attr: { + type: 'primary', + htmlType: 'submit', + style: { + marginRight: 20, + }, + }, + }, + { + label: '重置', + attr: { + type: 'primary', + }, + }, + ]} + /> + ); +}; +export default Demo; +``` + +## 手机号登录 + +```tsx mdx:preview +import UserLogin from '@antdp/user-login'; + +const Demo = (props) => { + return ( + { + console.log('打印保存参数---->', values); + }} + type="phone" + onSend={() => console.log('短信验证回调')} + formBtns={[ + { + label: '登录', + attr: { + type: 'primary', + htmlType: 'submit', + style: { + marginRight: 20, + }, + }, + }, + { + label: '重置', + attr: { + type: 'primary', + }, + }, + ]} + /> + ); +}; +export default Demo; +``` + +## API + +### 该组件依赖 + +- [antd Button](https://ant.design/components/button-cn) +- [antd Radio](https://ant.design/components/radio-cn) +- [antd Form](https://ant.design/components/form-cn) + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | ---------------------------- | --------------------------------------------------------------------------- | ------ | +| logo | logo | string | - | +| projectName | 项目名称 | `string` | - | +| className | 登陆样式 | `string` | - | +| type | 登录类型 | `account \| phone \| account` | +| children | 自定义内容 | `React.ReactNode` | - | +| formItems | 账号登录设置的 formItem | `({ render?: React.ReactNode, inputProps?: InputProps } & FormItemProps)[]` | - | +| formBtns | 表单操作按钮 | `{ label?: React.ReactNode, attr?: ButtonProps }[]` | - | +| loading | 加载状态 | `boolean` | - | +| onFinish | 表单提交 | `(value: any, submitType: "account" \| "phone") => void` | - | +| phoneFormItems | 手机号登录设置的 formItem 项 | `UserLoginProps["formItems"]` | - | +| phoneCodeProps | 手机号 FormItem 属性 | `FormItemProps` | - | +| onSend | 自定义 form 表单内渲染 | `() => void` | - | +| warpStyle | 外层样式 | `React.CSSProperties` | - | +| titleStyle | 标题样式 | `React.CSSProperties` | - | diff --git a/examples/website2/docs/guide/auth.md b/examples/website2/docs/guide/auth.md new file mode 100644 index 000000000..d32bb4c2a --- /dev/null +++ b/examples/website2/docs/guide/auth.md @@ -0,0 +1,183 @@ +--- +nav: 教程 +group: 数据管理 +order: 2 +--- + +## @antdp/authorized + +[![npm](https://img.shields.io/npm/v/@antdp/authorized.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/authorized) +[![npm download](https://img.shields.io/npm/dm/@antdp/authorized.svg?style=flat)](https://www.npmjs.com/package/@antdp/authorized) + +权限判断组件或方法,通过判断是否进入主界面还是登录界面。 + +# 权限 + +## 下载依赖 + +```bash +$ npm i @antdp/authorized # yarn add @antdp/authorized +``` + +## 启用方式 + +配置开启。同时需要 config/config.ts 提供权限配置。 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_AUTH_CONF: { ++ auth_menu: 'authMenu', ++ auth_btn: 'authBtn', ++ auth_check_url: true, + } +}); +``` + +### `ANTD_AUTH_CONF` 权限配置参数 + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | +| auth_menu | 储存菜单路由权限---本地 keys | `string` | `authMenu` | +| auth_btn | 储存按钮路径权限---本地 keys | `string` | `authBtn` | +| auth_check_url | 判断路径是否有权限的字段 默认值`menuUrl`,如果字段设置为`undefined`则`auth_menu`和`auth_btn`储存形式为 `["/web"]`,反之储存形式为`[{menuUrl:"/web"}]` | `string` | `menuUrl` | + +## 路由菜单权限 + +这是你的路由菜单(config/router.json) + +```json +[ + { + "path": "/login", + "component": "@/layouts/UserLayout" + }, + { + "path": "/", + "component": "@/layouts/BasicLayout", + "routes": [ + { + "path": "/", + "redirectTo": "/welcome" + }, + { + "path": "/welcome", + "name": "首页", + "icon": "welcome", + "locale": "welcome", + "component": "@/pages/Home/index" + }, + { + "path": "/404", + "name": "404", + "hideInMenu": true, + "icon": "file-protect", + "component": "@/pages/404" + }, + { + "path": "/403", + "name": "403", + "hideInMenu": true, + "icon": "file-protect", + "component": "@/pages/403" + } + ] + } +] +``` + +登陆后后端返回的菜单列表可能如下 + +```js +const menus = ['/', '/welcome', '/404', '/403']; +``` + +### 路由匹配过程 + +- 1.当你登陆成功后,你需将其保存于你的 sessionStorage 中,储存的字段为你`ANTD_AUTH_CONF`中配的`auth_menu`字段,并在登陆后存储在`sessionStorage`中,如`sessionStorage.setItem('authMenu', JSON.stringify([]))` +- 2.当你跳转至页面时,会根据 sessionStorage 中`authMenu`进行权限匹配,如果没有权限则会跳往 404 或 403 页面 + +请保证 403 和 404 页面存在 + +## 页面权限重定向 + +如果你想根据 `token`判断是否重定向回登陆页,可在 layouts/BasicLayout.ts 中添加`Authorized` + +```ts | pure +import Authorized from '@antdp/authorized'; +import BasicLayouts from '@antdp/basic-layouts'; + +const Layout = () => { + const token = ''; + return ( + + + + ); +}; + +export default Layout; +``` + +## 按钮权限 + +很多大型项目中,也会对按钮权限进行管理,请提前配置好`ANTD_AUTH_CONF`中配的`auth_btn`字段,并在登陆后存储在`sessionStorage`中,如`sessionStorage.setItem("authBtn",JSON.stringify(['/api/select']))` + +```tsx | pure +// 为了渲染设置的本地权限数 +import { AuthorizedBtn } from '@antdp/authorized'; +const Demo = () => { + return ( + + + + ); +}; +export default Demo; +``` + +### AuthorizedBtn 参数 + +| 参数 | 说明 | 类型 | 默认值 | +| -------- | -------- | ----------------- | ------ | +| path | 权限路径 | `string` | - | +| children | 展示内容 | `React.ReactNode` | - | + +## 使用 AuthorizedConfigProvider 设置按钮权限配置 + +使用 `AuthorizedConfigProvider`可以自己进行重新设置组件包裹内的所有按钮的权限参数,不使用默认配置的按钮权限配置 + +```tsx | pure +import { AuthorizedBtn, AuthorizedConfigProvider } from '@antdp/authorized'; +const Page = () => { + useEffect(() => { + sessionStorage.setItem('btn', JSON.stringify([{ menuUrl: '/api/select' }])); + }, []); + return ( + + + + + + ); +}; +export default Page; +``` + +### AuthorizedConfigProvider 参数 + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ---------- | +| auth_menu | 储存菜单路由权限---本地 keys | `string` | `authMenu` | +| auth_btn | 储存按钮路径权限---本地 keys | `string` | `authBtn` | +| auth_check_url | 判断路径是否有权限的字段 默认值`menuUrl`,如果字段设置为`undefined`则`auth_menu`和`auth_btn`储存形式为 `["/web"]`,反之储存形式为`[{menuUrl:"/web"}]` | `string` | `menuUrl` | +| isCheckAuth | 是否检查权限 | `boolean` | `false` | +| children | 子内容 | `string` | - | + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/change-log.md b/examples/website2/docs/guide/change-log.md new file mode 100644 index 000000000..c6b8d15bd --- /dev/null +++ b/examples/website2/docs/guide/change-log.md @@ -0,0 +1,157 @@ +--- +nav: 教程 +group: 入门 +order: 2 +--- + +# 更新日志 + +`antdp` 遵循 [Semantic Versioning 2.0.0](http://semver.org/lang/zh-CN/) 语义化版本规范。 + +#### 发布周期 + +- 修订版本号:每周末会进行日常 bugfix 更新。(如果有紧急的 bugfix,则任何时候都可发布) +- 次版本号:每月发布一个带有新特性的向下兼容的版本。 +- 主版本号:含有破坏性更新和新特性,不在发布周期内。 + +## 2.0.4 + +`2023-05-08` + +- 🐞 fix: ButtonGroupPro menu 菜单修改 antd 去除的 api & 兼容按钮权限 [98f85f0](https://github.com/antdpro/antdp/commit/98f85f0eec26c07f70c2e49af6d3247bb994ca32) + +## 2.0.3 + +`2023-05-07` + +- 📖 doc: 在线文档首页 [d7e652a](https://github.com/antdpro/antdp/commit/e4856ff1484cbb7a91886c0082973735fba316b1) +- 📖 fix: antdp-base 固定 antd 版本 [9791d21](https://github.com/antdpro/antdp/commit/9791d21e42173f87fe29382418492f97d214754b) +- 🐞 fix: ButtongroupPro 增加权限功能 [5dbf87c](https://github.com/antdpro/antdp/commit/5dbf87c0af1e1baa4a54d21c1e37537fbd0120e1) + +## 2.0.2 + +`2023-04-24` + +- 📖 doc: 处理预实例报错 [6e2223d](https://github.com/antdpro/antdp/commit/6e2223d27c0c77af2f3da92467bdc02ec31615fb) +- 📖 doc: 调整文档顶部菜单高度和边距 [ddaa53c](https://github.com/antdpro/antdp/commit/ddaa53cd0fe6ae60f42d76e0ff01ac8f4ba34c80) +- 📄 deps(@antdp/hooks): 导出 fetchFn [30ac11a](https://github.com/antdpro/antdp/commit/30ac11a9c8ebe321bc470f6fdb55ba798d43b127) +- 📄 deps(basic-layouts): 增加菜单搜索功能 [404741c](https://github.com/antdpro/antdp/commit/404741cecb575545d50b680de19246ba0f465448) +- 📖 doc: 增加 react-query 请求相应文档 [a0475f4](https://github.com/antdpro/antdp/commit/a0475f4681ef34964aa5e7e81da547a8f4dc84ac) +- 📖 doc: 增加样式文档 [eda3e67](https://github.com/antdpro/antdp/commit/eda3e6744bca621b534aa705eb8089e45977cfb8) +- 📖 doc: 修改快速开始文档 [c9efe29](https://github.com/antdpro/antdp/commit/c9efe291029297515d0c55f4b68d2fb4175a1a41) +- 📖 doc: 修改新增页面文档 [13fb0b8](https://github.com/antdpro/antdp/commit/13fb0b8ca8e239a23f884a5250cb33b2f5a01da2) +- 📖 doc: 修改请求和样式文档 [88fd076](https://github.com/antdpro/antdp/commit/88fd0760370756d52c94fc3b0ac71b48f6e60a24) +- 📖 doc: 增加 mock 文档 [4ea040c](https://github.com/antdpro/antdp/commit/4ea040cd1cd8221151861bbef2d86d13c61a1069) +- 📖 doc: 增加数据流文档 [b5f5119](https://github.com/antdpro/antdp/commit/b5f51194c149be84058b2b25e97d8784dd41c7bf) +- 📖 doc: 实例预览删除多余路由页面 [9319b83](https://github.com/antdpro/antdp/commit/9319b838069cafc5c91ca51f3e69cee1408ab399) +- 🐞 fix: 修复实例路由报错 & basic yarn 报错 [51d645c](https://github.com/antdpro/antdp/commit/51d645cf2a4dda38678e0f447fc5a7d29271a5e0) + +## 2.0.1 + +`2023-04-20` + +- 📖 doc(User-Login): API 文档 [`e94ab97`](https://github.com/antdpro/antdp/commit/e94ab974f7490122154ef1b28dc96e69aeb5ba48) +- 📖 doc(Basic-Layouts): api 文档修改 [`9a260b6`](https://github.com/antdpro/antdp/commit/9a260b60138191977c8ee701744ec2a8eec1e37a) +- 📖 doc(antdp-ui): 组件 api 文档 [`512c265`](https://github.com/antdpro/antdp/commit/512c265a00b38ac31de34e5ab49ddc37658de027) +- 🐞 fix(doc): 调整 Edit-table api 文档 [`214ed5c`](https://github.com/antdpro/antdp/commit/512c265a00b38ac31de34e5ab49ddc37658de027) +- 📖 doc(EditTable): 增加使用案例 [`b423efd`](https://github.com/antdpro/antdp/commit/b423efd8c4c4db3e3648759931415e048ed10506) +- 🐞 fix: 修改文档菜单以及路由 [`7c6c1e4`](https://github.com/antdpro/antdp/commit/7c6c1e46bd348a1c10195ce2d82af1cea2be8923) +- 📖 doc: 增加升级文档 & 更新日志文档 [`1cf66e1`](https://github.com/antdpro/antdp/commit/1cf66e1c937fb26c3f03ccee67518972bcddcd88) +- 🐞 fix: 修复文档控件明暗主题色切换 [`c53abd0`](https://github.com/antdpro/antdp/commit/c53abd08b36376e19bc76ed9236871c2301594b6) +- 📖 doc: 增加更新日志 [`1e38adb`](https://github.com/antdpro/antdp/commit/1e38adb97a0efdbd6732714cadc1daff79053dc8) +- 🐞 fix(website): 调整文档 header 组件样式 [`05eecda`](https://github.com/antdpro/antdp/commit/05eecda448d96ad1f37bcf44cc9c8d3c5cdaff89) +- 🐞 fix: 删除 layout header 没有使用的组件 [`ecd7c69`](https://github.com/antdpro/antdp/commit/ecd7c69e939515b1104a4ebe09dde0935b51de75) +- 📖 doc: 增加新增页面文档 [`f1006c8`](https://github.com/antdpro/antdp/commit/f1006c884ea97656652ff6c0994b2b20d33fffb7) +- 📖 doc: 新增网络请求文档 [`82fe63b`](https://github.com/antdpro/antdp/commit/82fe63b2cb4cd96525102e4acd46acb962ab13ae) +- 🐞 fix(basic-layouts): 修复 Breadcrumb 中 Breadcrumb.Item 替换为 items [`f3159c8`](https://github.com/antdpro/antdp/commit/f3159c87e703c344ebffcb245cac92f77c21a2b6) +- 📖 doc: 增加 proxy 文档 [`d1e5332`](https://github.com/antdpro/antdp/commit/d1e533253b20cc72350e27c70a3894bce59f68b4) +- 📖 doc: 更新请求文档 [`f5fcbb0`](https://github.com/antdpro/antdp/commit/f5fcbb0a5fc3271f066a816e084ca407adf0ee9b) +- 📖 doc: 多级时路由不再跳转 [`62db8bc`](https://github.com/antdpro/antdp/commit/62db8bce7a38ecf9c6570fe624bdd6d9354958e4) +- 📖 doc: 增加 umi 文档菜单 [`f7b690a`](https://github.com/antdpro/antdp/commit/f7b690a3ddbe782cfbcaf3fbd027bb1e10ca6a5d) +- 📄 dep(@antdp/hooks): 新增 react-query [`efcfd5e`](https://github.com/antdpro/antdp/commit/efcfd5e2395e2a8beba09846368c8c8510af6451) + +## 2.0.0 + +`2023-04-09` + +- 🐞 fix:修复文档和权限取值判断 + +## 2.0.0-bate6 + +`2023-04-02` +) + +- 🐞 fix:修复预览标签 [`#5968554`](https://github.com/antdpro/antdp/commit/5968554197f09bd5d8b1f75331f2102bf38e4ec2) +- 🌟 feat:添加 basic 案例 [`#1ecae70`](https://github.com/antdpro/antdp/commit/1ecae70f30734df8e07ba275d06a52291299ca86) +- 🐞 fix:添加布局案例 [`#5434316`](https://github.com/antdpro/antdp/commit/54343162b49834100419a216e0fd9213b6a61d3a) +- 🐞 fix:修复 umi setup [`#a128746`](https://github.com/antdpro/antdp/commit/a128746362ad5804d0e94c9e9be0daff1a1b5cf3) +- 🐞 fix:修复案例包版本 [`#f45300b`](https://github.com/antdpro/antdp/commit/f45300b90841b2435745c9a3460fd74c2131383b) +- 📖 doc:更新文档 @SunLxy[`#009bba3`](https://github.com/antdpro/antdp/commit/009bba365f3900a207a0567a0985ed114f7a2ecd) +- 🐞 fix(user-login):修改登录组件 [`#f888620`](https://github.com/antdpro/antdp/commit/f88862027deb36ffa3baa8c197cf3ecc1fc53195) +- 🆎 type:修复类型 [`#29eeb92`](https://github.com/antdpro/antdp/commit/29eeb926c64a1958d7e8723462b75d28bddb1c90) +- 🆎 type:修改类型 [`#08c5ed7`](https://github.com/antdpro/antdp/commit/08c5ed72c7ff180c2e8cd88447b1655d35efee93) +- 📖 doc:更新文档 [`#4a97ef1`](https://github.com/antdpro/antdp/commit/4a97ef11675383051a509721938d58dc3ed36bdf) +- 🐞 fix:修改案例依赖 [`#7698c2e`](https://github.com/antdpro/antdp/commit/7698c2e4e8599fe5bf019f75f8aaf537b877cb85) +- 🐞 fix:修复表单设置初始隐藏表单项 [`#86ac78f`](https://github.com/antdpro/antdp/commit/86ac78f4af5c7409c981501f633b60989d5c97b0) +- 🐞 fix:修复本地文档预览网站 [`#869d72b`](https://github.com/antdpro/antdp/commit/869d72bc69132fd5b4f2faa4044ffd923e8f16ce) +- 📖 doc:更新文档 [`d32e801`](https://github.com/antdpro/antdp/commit/d32e801dc69d4aed926f709d790beb9681c6db7d) +- 🐞 fix:更新文档预览 [`#8de7bc6`](https://github.com/antdpro/antdp/commit/8de7bc6eee32ed72ee67ad41755a70bbb8bcce6a) +- 📖 doc(user-login):更新文档预览 [`#d5822e7`](https://github.com/antdpro/antdp/commit/d5822e776909bdedb75e342460b6257a61087913) +- 📖 doc:更新文档 [`#8466a3f`](https://github.com/antdpro/antdp/commit/8466a3fce00525e6a1c288c01fd2851b8e955651) +- 🐞 fix:使用异步加载文档 [`#904263c`](https://github.com/antdpro/antdp/commit/904263cce0fa4f92d0eeb9f59acb323e5557178f) +- 📖 doc:更新文档 [`#a2f6434`](https://github.com/antdpro/antdp/commit/a2f64342fa1f76bf6a01953a13ecf760cf9029bd) +- 🐞 fix:修改文档预览主题色 [`#9809cc7`](https://github.com/antdpro/antdp/commit/9809cc7e9467c1eab10d215109546f4828842013) +- 🌟 feat(authorized):添加 AuthorizedConfigProvider 和 useAuthorizedonfig [`#cf75f09`](https://github.com/antdpro/antdp/commit/cf75f096ad0646a1e831f45141cc7c84c1442c2d) +- 🆎 type:修复类型 [`#2b68319`](https://github.com/antdpro/antdp/commit/2b683192c1f3af1fed393c6329e8789ad09b986a) + +## v2.0.0-bate-4.1 + +- 🐞 fix:修复弹框参数 [`#78baff2`](https://github.com/antdpro/antdp/commit/78baff20178cabe2ef2f23b26d83fce597ba1aa6) +- 🐞 fix:修复 ts 类型引入 [`#f2a6091`](https://github.com/antdpro/antdp/commit/f2a609160e8969baac8014a6866cd0756995db77) +- 📖 doc:更新文档 bd4ed80[`#f2a6091`](https://github.com/antdpro/antdp/commit/f2a609160e8969baac8014a6866cd0756995db77) +- 🌍 website: fix menu error. [`#786ed76`](https://github.com/antdpro/antdp/commit/786ed76d4397b9b2a5a45ee278e30eee04d0458d) +- 🌍 website: fix dark theme issue. [`#379b965`](https://github.com/antdpro/antdp/commit/379b965da411db80282f8db4b3a769cbff16f7a7) +- 🌍 website: add version & add theme button. [`#9f9f2b4`](https://github.com/antdpro/antdp/commit/9f9f2b4a6241065a2fbbc665febd8e7959cc3089) +- 🐞 fix:修复布局参数 [`#022c3af`](https://github.com/antdpro/antdp/commit/022c3af706eb6d25ecc4726fc21bec419dc8bf90) +- 🐞 fix:修复 message.warn 提示方法不存在 [`#12d02b1`](https://github.com/antdpro/antdp/commit/12d02b179661a31b608228c758238379190f9953) + +## v2.0.0-bate-2 + +- 💄 chore:升级 umi 版本 [`#5f3aaaa`](https://github.com/antdpro/antdp/commit/5f3aaaa821f514b3c5eba0e5150e029b629fc07d) +- 🌟 feat:添加路由图标处理 [`#c3c8f97`](https://github.com/antdpro/antdp/commit/c3c8f97def3dad5bda4cac53dedcfa5753db5c65) +- 🐞 fix:修改配置,最新版本不支持兼容 ie11 [`#edf50ec`](https://github.com/antdpro/antdp/commit/edf50ec2334cc88009b073d1a4b775a36038b34c) +- 🐞 fix:文档 [`#783191a`](https://github.com/antdpro/antdp/commit/783191a6bbb7c0ca1749a747fe5ac122891da489) +- 🎨 style:修改文档页面布局样式 [`#867edd5`](https://github.com/antdpro/antdp/commit/867edd5c5eefc6d0995e164b0a09ac7aee0b3be2) +- 🐞 fix:移出图标 [`#d092b35`](https://github.com/antdpro/antdp/commit/d092b35105d79b5b49fc08173ddbb341b27e77ce) +- 💄 chore:替换 react-sortable-hoc 拖拽包 [`#4ddcf16`](https://github.com/antdpro/antdp/commit/4ddcf163af5345219524931f6ca102211767f952) + +## v2.0.0-bate-1 + +- 💄 chore:升级 umi 版本 [`#70e2d33`](https://github.com/antdpro/antdp/commit/70e2d330e91d388114f31591ab8d617b7677e9cd) +- 🌟 feat:添加路由图标处理 [`#97e0d0f`](https://github.com/antdpro/antdp/commit/97e0d0fe32a55dfcafd6c3ec0046c2fe40af87df) +- 🐞 fix:修改配置,最新版本不支持兼容 ie11 [`#6c37f14`](https://github.com/antdpro/antdp/commit/6c37f149e819a46197fe50713bc90672bc211faf) + +## v2.0.0-bate-0 + +- 🐞 fix:修改值 [`#a7dad34`](https://github.com/antdpro/antdp/commit/a7dad3457ec01066cbba4402aeed05a6e23b8846) +- 📖 doc:增加文档说明 [`#3cfe92c`](https://github.com/antdpro/antdp/commit/3cfe92c195ebffe3c372b39a0adf7c30455f195e) +- 🐝 refactor:重构 @antdp/config 包 [`#76638d0`](https://github.com/antdpro/antdp/commit/76638d0e06c1675dbe75a8e212374fdf0e414cfd) +- 🐝 refactor(@antdp/authorized):重构成 ts 版本 [`#1f5a19e`](https://github.com/antdpro/antdp/commit/1f5a19e9460730333bf91387e8c55e9246609ca6) +- 🐞 fix:修复菜单隐藏问题 [`#9bc0070`](https://github.com/antdpro/antdp/commit/9bc00702e76eb8548dc7f0f9022afffa804f85cf) +- 🐝 refactor(@antdp/document-title):重构 ts 版本 [(#189)](https://github.com/antdpro/antdp/pull/189) [`#bef3c6e`](https://github.com/antdpro/antdp/commit/bef3c6e1b199b4bee1364b87efec85ba68f22a1d) +- 💄 chore:升级依赖包 [`#88cff2a`](https://github.com/antdpro/antdp/commit/88cff2a192b2e24b5fe0d11a3c64ee3308e18621) +- 🐞 fix: ts 重写 user-login 组件和 layout-tabs 组件 [(#194)](https://github.com/antdpro/antdp/pull/194) [`#c0b6460`](https://github.com/antdpro/antdp/commit/c0b6460eb90e51977e1c150cd29b49ef1f920d88) +- 💄 chore:修复 website 依赖问题 [`#7ce8e30`](https://github.com/antdpro/antdp/commit/7ce8e301a0880b36d9ef923f3c4e4477663dafe7) +- 🌟 feat:替换文档预览工具 [`#53e6433`](https://github.com/antdpro/antdp/commit/53e6433a3b82bbb6f762412cae4c82876eb2041d) +- 🌟 feat:添加文档底部内容 [`#41f3200`](https://github.com/antdpro/antdp/commit/41f3200fb34228ed97b3b0d98d83139e9dc1993c) +- 📖 doc(edit-table/fuzzy-query):更新文档 [`#4ff97c6`](https://github.com/antdpro/antdp/commit/4ff97c6eca3c4ae06341e989075c6585a20d28d2) +- 🐝 refactor(@antdp/config):重构 ts 版本 [(#196)](https://github.com/antdpro/antdp/pull/196) [`#8f0bec4`](https://github.com/antdpro/antdp/commit/8f0bec42cbd64cbf34c0aa4df7509c2638b92b13) +- 🐝 refactor(@antdp/fullscreen):重构 ts 版本 [(#197)](https://github.com/antdpro/antdp/pull/197) [`#ebac4b9`](https://github.com/antdpro/antdp/commit/ebac4b959f8f46316e9edf916e5811942ac35b3c) +- 📄 remove:移除多余文件 [`#6c9450d`](https://github.com/antdpro/antdp/commit/6c9450ddda30c4298101ee88238fefe74e7df7eb) +- 📄 添加 antd 组件示例预览 [(#198)](https://github.com/antdpro/antdp/pull/198) [`#5723817`](https://github.com/antdpro/antdp/commit/572381798c1168422fc3d120271b96f973a4404b) +- 🐝 refactor:重写 basic-layouts 组件 [`#2b3ad38`](https://github.com/antdpro/antdp/commit/2b3ad38deca0b31b9f575980bf1239249ae738b5) +- 🐞 fix:修复渲染判断 [`#c5e96df`](https://github.com/antdpro/antdp/commit/c5e96df0d50922ce08beef55844a0efe76735bbc) +- 🐞 fix:完善全局变了控制 [`#253234a`](https://github.com/antdpro/antdp/commit/253234a4d4e8f7e7304bb0bdcf69b8f9ddcb9055) +- 🐞 fix:修复全屏按钮 [`#4f0c851`](https://github.com/antdpro/antdp/commit/4f0c8515a5467e776bc243b33f8ac67fec6c5523) + +[Github 更新日志](https://github.com/antdpro/antdp/releases) diff --git a/examples/website2/docs/guide/config.md b/examples/website2/docs/guide/config.md new file mode 100644 index 000000000..b4e2791c5 --- /dev/null +++ b/examples/website2/docs/guide/config.md @@ -0,0 +1,310 @@ +--- +nav: 教程 +order: 1 +group: + title: 基础配置 + order: 4 +--- + +## @antdp/config + +[![npm](https://img.shields.io/npm/v/@antdp/config.svg?maxAge=3600)](https://www.npmjs.com/package/@antdp/config) +[![npm download](https://img.shields.io/npm/dm/@antdp/config.svg?style=flat)](https://www.npmjs.com/package/@antdp/config) + +我们已将 umi 配置包裹了一层, 如果需要 antdp 中能使用自定义配置,你可以使用项目根目录的 `config/config.ts`/`config/config.js`进行配置 + +## Installation + +```bash +npm i @antdp/config --save-dev +``` + +## Basic Usage + +```js +// config/config.{js|ts} + +//二次封装的umi配置 +import config from '@antdp/config'; +// 路由数据 +import router from './router.json'; +/**开发代理配置*/ +import proxy from './proxy'; + +export default config(router, { + proxy, +}); +``` + +## Interface + +```typescript +import { IConfig, IRoute } from '@umijs/max'; + +export interface Options extends Omit { + routes: IRoute; +} + +export interface Config { + (routes?: IRoute, optiosn?: Options): IConfig; +} + +declare var config: Config; +export default config; +``` + +## define 配置 + +### ANTD_IS_TABS + +是否显示 Tab 选项卡,默认值`true`显示 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_IS_TABS: true + }, +); +``` + +### ANTD_IS_IFRAME_RENDER + +是否使用 iframe 展示内容,默认值`true`开启 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_IS_IFRAME_RENDER: true + }, +); +``` + +### ANTD_IS_BREADCRUMB + +是否展示面包屑, Tab 选项卡优先级大于面包屑 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_IS_BREADCRUMB: true + }, +); +``` + +### ANTD_AUTH_CONF + +是否开启权限验证 默认值`false`不启用 + +- `auth_menu` 储存菜单路由权限---本地 keys 默认值`authMenu` +- `auth_btn` 储存按钮路径权限---本地 keys 默认值 `authBtn` +- `auth_check_url` 判断路径是否有权限的字段 默认值`menuUrl`,如果字段设置为`undefined`则`auth_menu`和`auth_btn`储存形式为 `["/web"]`,反之储存形式为`[{menuUrl:"/web"}]`, + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_AUTH_CONF: { ++ auth_menu:"authMenu", ++ auth_btn:"authBtn", ++ auth_check_url:undefined + } + }, +); + +``` + +### ANTD_MENU_IS_SHOW + +是否显示 左侧菜单,默认值`true` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_MENU_IS_SHOW: true + }, +); +``` + +### ANTD_HEAD_IS_SHOW + +是否显示 head 头部,默认值`true` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_HEAD_IS_SHOW: true + }, +); +``` + +### ANTD_MENU_SEARCH_IS_SHOW + +是否开启菜单栏搜索,默认值`false` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_MENU_SEARCH_IS_SHOW: true + }, +); +``` + +## locale 配置 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, + locale: { + // 默认使用 src/locales/zh-CN.ts 作为多语言文件 ++ default: 'zh-CN', ++ antd: true, + // default true, when it is true, will use `navigator.language` overwrite default + // baseNavigator: true, + /** + * [国际化] 控制台提示 Warning: The current popular language does not exist, please check the locales folder! 警告信息 + * https://github.com/umijs/umi/issues/4363#issuecomment-616134434 + * 警用 `baseNavigator` 和 `title` 两个配置项 可以解决国际化警告问题 + */ ++ baseNavigator: false, ++ title: false, + }, + }, +); +``` + +## @umijs/max 配置 + +### request + +开启 `useRequest` 和 `request`,默认`未开启` + +- `dataField` 该配置的默认值是 data。该配置的主要目的是方便 useRequest 直接消费数据。如果你想要在消费数据时拿到后端的原始数据,需要在这里配置 `dataField` 为 '' + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ request:{} +); +``` + +### react-query + +开启`react-query`,默认`未开启` + +- `devtool`: boolean,是否开启 react query 官方 devtool 工具,默认 `true` +- `queryClient`: boolean, 是否注册全局的 QueryClient 和 QueryClientProvier,默认 `true` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ reactQuery: { + // 是否开启 react query 官方 devtool 工具 ++ devtool: false, ++ queryClient: true, + }, +); +``` + +### useModel + +开启`useModel`,默认`未开启` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ model: {}, +); +``` + +### dva + +开启`dva`,默认`未开启` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ dva: {}, +); +``` + +### styled-components + +[styled-components](https://styled-components.com/) 样式方案 + +- `babelPlugin`: Object,开启 styled-components 的 babel 插件,仅 dev 模式有效 + 比如: + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ styledComponents: {}, +); +``` + +### valtio + +`valtio` 数据流方案 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ valtio: {}, +); +``` + +[更多配置参考 umi](https://umijs.org/docs/api/config) diff --git a/examples/website2/docs/guide/css.md b/examples/website2/docs/guide/css.md new file mode 100644 index 000000000..00cb376d0 --- /dev/null +++ b/examples/website2/docs/guide/css.md @@ -0,0 +1,87 @@ +--- +nav: 教程 +group: 页面开发 +order: 2 +--- + +# 样式 + +本文介绍各种在 antdp 项目中使用样式的方式。 + +## 使用 CSS 样式 + +你可以在 Umi 项目中使用 `.css` 文件声明各种样式,然后在 `.ts` 文件中引入即可生效。 + +例如,在 `src/pages/index.css` 文件按照以下代码声明 `.title` 类的样式为红色: + +```css +.title { + color: red; +} +``` + +然后在 `src/pages/index.tsx` 文件中引入即可生效。 + +```bash +// src/pages/index.tsx + +import './index.css'; + +export default function () { + return
Hello World
; +} +``` + +按照此种引入方式的样式会在整个 Umi 项目中生效,即无论你从哪个 `.ts` 文件引入,他声明的样式可以在任何页面和组件中使用。如果你想要避免这种情况,可以使用 `CSS Modules` 的功能来限制样式的作用域。 + +## 使用 CSS Modules + +在 `ts` 文件中引入样式时,如果赋予他一个变量名,就可以将样式以 CSS Module 的形式引入。 + +```bash +// src/pages/index.tsx + +import styles from './index.css'; + +export default function () { + return
+ Hello World +
; +} +``` + +上面的示例中,`index.css` 文件中声明的样式不会对全局样式造成影响,只会对从 `styles` 变量中使用的样式生效。 + +## 使用 CSS 预处理器 + +Umi 默认支持 LESS (推荐),你可以直接按照引入 CSS 文件的方式引入并使用这些由 CSS 预处理器处理的样式。 + +```bash +// src/pages/index.tsx + +import './index.less'; + +export default function () { + return
Hello World
; +} +``` + +同样也支持 CSS Module 的用法: + +```bash +// src/pages/index.tsx + +import lessStyles from './index.less'; + +export default function () { + return
+ Hello World +

I am blue

+

I am red

+
; +} +``` + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/curd.md b/examples/website2/docs/guide/curd.md new file mode 100644 index 000000000..793458778 --- /dev/null +++ b/examples/website2/docs/guide/curd.md @@ -0,0 +1,79 @@ +--- +nav: 教程 +group: 页面开发 +order: 4 +--- + +# ProTable 快速搭建 CRUD 的利器 + +大部分中后台页面都是非常同质化的 CRUD 组成的,很多时候都是一个 Table,然后提供一些操作按钮,并且有一个新增表单。看起来就像这样: + +![0BAFCFEF-9EA2-4DB7-A36D-4D5A092BCC30.png](https://gw.alipayobjects.com/zos/antfincdn/w6XCWacQH6/1582038656687-065b40ef-5029-4bf7-8941-6e843570e4e0.png) + +## 🤷‍♂️ 为什么要做 ProTable + +antd 作为服务于企业级产品的设计体系的组件库,已经提供了强大的 Table,但是业务的不同导致我们仍有需要进行一些定制,不同的单元格有很多不同的数据格式,金额,日期,数字等,包括一些常用的操作,页码切换时的重新请求,刷新数据等,这些都是很简单的重复劳动,但是却不可避免。 + +ProTable 就是为了解决这些问题,在 Table 的层面上提供了一些预设,你可以通过 [`valueType` ](https://procomponents.ant.design/components/tablevalue-type)来支持各种类型的数据,预设了 金额,日期,序号,进度条 等多种类型,并且支持通过 `valueEnum`  来映射枚举,解决非常烦人的各种枚举配置,可以大大的简化代码。 + +```tsx +const columns = [ + { + title: '状态', + dataIndex: 'status', + initialValue: 'all', + width: 100, + valueEnum: { + close: { text: '关闭', status: 'Default' }, + running: { text: '运行中', status: 'Processing' }, + online: { text: '已上线', status: 'Success' }, + error: { text: '异常', status: 'Error' }, + }, + }, +]; +``` + +ProTable 接管了翻页,页码改变等事件,理论上你只要有配置列和 request 属性,就可以生成一个全功能的表格,完成分页查询,刷新,列属性修改等功能。 + +在很多项目中 Table 的操作按钮与标题的位置都会不一致,即使是一个项目中也可能有一些不同,ProTable 提供了相应的规范,toolBarRender 与 headerTitle 实现了规范,toolBarRender 支持返回一个 ReactNode 数组,会自动地增加间隔等样式,toolBarRender 提供 action 与当前选中的列等数据,方便进行一些快捷的操作。代码看起来是这样的 + +```tsx | pure +toolBarRender = (_, { selectedRowKeys }) => [ + , + selectedRowKeys && selectedRowKeys.length && ( + + ), +]; +``` + +## 🦄 更多的功能 + +一个完整的页面除了 Table 之外,还需要一个查询表单,查询表单很大程度上也是根据列来生成的,有些表单几乎和列是一一对应的。为了减少这部分的工作量,ProTable 会通过列的配置来自动生成查询表单。 + +![image.png](https://gw.alipayobjects.com/zos/antfincdn/aIkGYS0KvN/1582127528798-704c4833-955e-4020-9f41-5206c42f2389.png) + +根据不同的值类型,表单会生成不同的输入框,查询成功之后的数据会通过 request 的 params 参数自动发起查询,无需进行任何的数据绑定。 + +如果你的表单比较简单,没有过多的特殊组件,或者你自行封装了很多符合 antd 表单的组件(指拥有受控的 value 和 onChange 方法),你可以通过  renderFormItem 来自行生成表单元素,然后配置  `type=Form`,就可以生成一个添加表单。 + +![image.png](https://gw.alipayobjects.com/zos/antfincdn/p3YxxMOlwz/1582130440043-71722655-42e6-4698-a37a-14d69f6008b8%252520%281%29.png) + +这样就可以用极低成本来实现一个 完整的 CRUD 界面,早日完成需求,早点下班。 + +更多:[https://procomponents.ant.design/components/table](https://procomponents.ant.design/components/table) + +Ant Design Table [https://ant.design/components/table](https://ant.design/components/table-cn/) + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/dva.md b/examples/website2/docs/guide/dva.md new file mode 100644 index 000000000..6c44daf6f --- /dev/null +++ b/examples/website2/docs/guide/dva.md @@ -0,0 +1,356 @@ +--- +nav: 教程 +group: 数据管理 +order: 3 +--- + +# dva + +本文介绍各种在 antdp 项目中使用样式的方式。 + +## 为什么需要状态管理 + +React 的组件只是通过 jsx 以及样式按照 state 构建最终的 UI,真正将页面动态化的实际上是 state 的变化实现的。对于简单的前端应用,在组件中通过组件自身的 state 加上父组件通过 props 状态的传递就能够满足应用数据管理的需求。但是当应用膨胀到一定程度后就会导致组件内维护的状态非常的复杂,加上组件之间状态的传递,很容易导致数据管理混乱。很小的修改都可能导致难以预料的副作用。 + +所以我们需要纯净的 UI 组件,除了渲染逻辑,不再杂糅其他(比如网络请求)。这样我们就要想办法把与渲染无关的业务逻辑抽离出来,形成独立的层(在 antdp 中就是 `src/models` 文件夹中所管理的 model )去管理。让所有组件降级为`无状态组件`,仅仅依赖 props 渲染。这样 UI 层面就不需关心渲染无关的逻辑,专注做 UI 渲染。(注:这里说的组件主要是指 page 下面的页面组件,对于 component 下的组件本身就应该是比较通用的组件,更应该仅仅依赖 props 渲染,它们也不应该有 model,数据应该通过在页面组件中通过 props 传递过去)。 + +## 简单的数据共享 + +对于简单的应用,不需要复杂的数据流,只需要一些简单的数据共享。我们推荐使用 中台最佳实践简易数据流`useModel`。 + +## antdp 如何管理状态 + +如下图所示,Umi 内置了 [Dva](https://dvajs.com) 提供了一套状态管理方案: + +![undefined](https://gw.alipayobjects.com/zos/skylark/48f9ff5f-ab11-4896-9fb6-65cdd83340de/2018/png/dcb7073b-fc0c-4e2c-aa39-93ac249d715c.png) + +数据统一在 `src/models` 中的 model 管理,组件内尽可能的不去维护数据,而是通过 connect 去关联 model 中的数据。页面有操作的时候则触发一个 action 去请求后端接口以及修改 model 中的数据,将业务逻辑分离到一个环形的闭环中,使得数据在其中单向流动。让应用更好维护。这样的思想最初来源于 Facebook 的 [flux](http://facebook.github.io/flux/)。接下来我们来具体看看如何在 antdp 中实现这样的逻辑。 + +### 配置 dva + +首先你需要在 config.ts 配置 `dva: {}` + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {} ++ dva: { }, +); +``` + +antdp 会默认将 `src/models` 下的 model 定义自动挂载,你只需要在 model 文件夹中新建文件即可新增一个 model 用来管理组件状态。 + +在 2.0 后,为了更好的支持移动端的 H5 项目的按需加载和大型项目的 model 组织,对于某个 page 文件夹下面的 model 我们也会默认挂载。但是需要注意的是 model 的 namespace 是全局的,你仍然需要保证你的 namesapce 唯一(默认是文件名)。对于大部分的项目,我们推荐统一放到 model 中进行管理即可,不需要使用该功能。 + +model 的写法参考如下示例: + +```jsx | pure +import { queryUsers, queryUser } from '../../services/user'; + +export default { + state: { + user: {}, + }, + + effects: { + *queryUser({ payload }, { call, put }) { + const { data } = yield call(queryUser, payload); + yield put({ type: 'queryUserSuccess', payload: data }); + }, + }, + + reducers: { + queryUserSuccess(state, { payload }) { + return { + ...state, + user: payload, + }; + }, + }, + + test(state) { + console.log('test'); + return state; + }, +}; +``` + +新建完成 model 之后你就可以在组件中通过 ES6 的 [Decorator](http://es6.ruanyifeng.com/#docs/decorator) 方便的把 model 和组件 connect 到一起。然后你就可以在组件中通过`this.props.[modelName]`的方式来访问 model 中的数据了。(在对应的 model 中,默认 namespace 即为文件名) + +组件如下示例: + +```jsx | pure +import React, { Component } from 'react'; +import { connect } from '@umijs/max'; + +@connect(({ user }) => ({ + user, +})) +class UserInfo extends Component { + constructor(props) { + super(props); + } + render() { + return
{this.props.user.name}
; + } +} + +export default UserInfo; +``` + +### 在组件中 dispatch 事件 + +connect 方法同时也会添加 `dispatch` 到 `this.props` 上,你可以在用户触发某个事件的时候调用它来触发 model 中的 effects 或者 reducer 来修改 model 中的数据。如下所示: + +```jsx | pure +import React, { Component } from 'react'; +import { connect } from '@umijs/max'; + +@connect(({ user }) => ({ + user, +})) +class UserInfo extends Component { + constructor(props) { + super(props); + } + render() { + return ( +
{ + this.props.dispatch({ + type: 'user/test', + }); + }} + > + {this.props.user.name} +
+ ); + } +} + +export default UserInfo; +``` + +### 修改数据 + +dispatch 一个 action 之后会按照 action 中的 type 找到定义在 model 中的一个 effect 或者 reducer。 + +如果是 effect,那么可以去请求后端数据,然后再触发一个 reducer 来修改数据。通过 reducer 修改数据之后组件便会按照最新的数据更新,至此,一次数据的流动就结束了。 + +## 文档 + +### model 定义 + +一个 model 中可以定义如下几个部分: + +- namespace # model 的命名空间,唯一标识一个 model,如果与文件名相同可以省略不写 +- state # model 中的数据 +- effects # 异步 action,用来发送异步请求 +- reducers # 同步 action,用来修改 state + +### connect + +`connect` 的是用来将 model 和组件关联在一起的,它会将相关数据和 `dispatch` 添加到组件的 `props` 中。如下所示: + +```jsx | pure +import React, { Component } from 'react'; +import { connect } from '@umijs/max'; + +const mapModelToProps = (allModels) => { + return { + test: 'hello world', + // props you want connect to Component + }; +}; + +@connect(mapModelToProps) +class UserInfo extends Component { + render() { + return
{this.props.test}
; + } +} + +export default UserInfo; +``` + +推荐通过注解的方式调用 connect,它等同于 `export default connect(mapModelToProps)(UserInfo);`。connect 接收一个参数,是一个方法,在该方法中你接收到所有的 model 信息,需要返回要添加到 props 上的对象。在上面的例子中你就可以通过 `this.props.test` 得到 `hello world` 的字符串了。 + +### dispatch + +在使用 `connect` 将组件和 model 关联在一起的同时框架也会添加一个 `this.props.dispatch` 的方法,通过该方法你可以触发一个 action 到 model 中。如下所示: + +```jsx | pure +render () { + return
{ + this.props.dispacth({ + type: 'modelnamespace/actionname', + sometestdata: 'xxx', + othertestata: {}, + }).then(() => { + // it will return a promise + // action success + }); + }}>test
+} +``` + +通过 `this.props.dispatch` 触发的 action 分为 effect 和 reducer 两类,下面是对他们的更多细节说明。 + +### Reducer + +reducer 是一个函数,用来处理修改数据的逻辑(同步,不能请求后端)。接受 state 和 action,返回老的或新的 state 。即:`(state, action) => state`。 + +#### 增删改 + +以 todos 为例。 + +```javascript +exports default { + namespace: 'todos', + state: [], + reducers: { + add(state, { payload: todo }) { + return state.concat(todo); + }, + remove(state, { payload: id }) { + return state.filter(todo => todo.id !== id); + }, + update(state, { payload: updatedTodo }) { + return state.map(todo => { + if (todo.id === updatedTodo.id) { + return { ...todo, ...updatedTodo }; + } else { + return todo; + } + }); + }, + }, +}; +``` + +#### 嵌套数据的增删改 + +建议最多一层嵌套,以保持 state 的扁平化,深层嵌套会让 reducer 很难写和难以维护。 + +```javascript +app.model({ + namespace: 'app', + state: { + todos: [], + loading: false, + }, + reducers: { + add(state, { payload: todo }) { + const todos = state.todos.concat(todo); + return { ...state, todos }; + }, + }, +}); +``` + +下面是深层嵌套的例子,应尽量避免。 + +```javascript +app.model({ + namespace: 'app', + state: { + a: { + b: { + todos: [], + loading: false, + }, + }, + }, + reducers: { + add(state, { payload: todo }) { + const todos = state.a.b.todos.concat(todo); + const b = { ...state.a.b, todos }; + const a = { ...state.a, b }; + return { ...state, a }; + }, + }, +}); +``` + +### Effect + +effects 是定义在 model 中的。它也是一种类型的 action,主要用于和后端的异步通讯。通过 effects 请求后端发送和接收必要的数据之后可以通过 put 方法再次发送一个 reducer 来修改数据。 + +effect 通过 ES6 中 [Generator 函数](http://es6.ruanyifeng.com/#docs/generator) 来支持通过顺序的代码实现异步的请求,示例如下: + +```javascript +export default { + namespace: 'todos', + effects: { + *addRemote({ payload: todo }, { put, call }) { + yield call(addTodo, todo); + yield put({ type: 'add', payload: todo }); + }, + }, +}; +``` + +effects 中定义的 action 都必须是通过 `*` 定义的 Generator 函数,然后在函数中通过关键字 `yield` 来触发异步逻辑。 + +#### Effects + +##### put + +用于触发 action 。 + +```javascript +yield put({ type: 'todos/add', payload: 'Learn Dva' }); +``` + +##### call + +用于调用异步逻辑,支持 promise 。 + +```javascript +const result = yield call(fetch, '/todos'); +``` + +##### select + +用于从 state 里获取数据。 + +```javascript +const todos = yield select(state => state.todos); +``` + +### loading + +框架会默认添加一个命名空间为 loading 的 model,该 model 包含 effects 异步加载 loading 的相关信息,它的 state 格式如下: + +```js +{ + global: Boolean, // 是否真正有异步请求发送中 + models: { + [modelnamespace]: Boolean, // 具体每个 model 的加载情况 + }, + effects: { + [modelnamespace/effectname]: Boolean, // 具体每个 effect 的加载情况 + }, +} +``` + +你可以使用该 model 实现在组件中添加 loading 动画。 + +## 调试 + +### redux + +dva 的底层是基于 redux,所以你可以安装 redux 的[开发者工具](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=zh-CN)用来查看 model 中的数据和变化的记录。 + +![reduxdevtool](https://lh3.googleusercontent.com/wfhSnnYEQc3TCXbRTpTloa-XZesgDt0xAogzGoLF1BUCU04aYhdwAjueJYTtDxfRiqjUfC539g=w640-h400-e365) + +## 参考文章 + +- [dva 官网](https://dvajs.com/) + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/internationalization.md b/examples/website2/docs/guide/internationalization.md new file mode 100644 index 000000000..b8e8fdf92 --- /dev/null +++ b/examples/website2/docs/guide/internationalization.md @@ -0,0 +1,503 @@ +--- +nav: 教程 +order: 2 +group: + title: 基础配置 + order: 4 +--- + +# 国际化 + +`@umi/max` 内置了[国际化插件](https://github.com/umijs/umi/blob/master/packages/plugins/src/locale.ts),它可以轻松地将国际化功能集成到你的 Umi 应用程序之中。 + +## 开始使用 + +国际化插件采用约定式目录结构,我们约定在 `src/locales` 目录下引入多语言文件。 + +多语言文件的命名需遵循此规范:`.(js|json|ts)`。其中,`` 为分隔符,默认为 `-`,可以通过 `baseSeparator` 项配置。 + +例如,如果您需要在项目中引入简体中文和英文的多语言支持,可以在 `src/locales` 目录下创建 `zh-CN.ts` 和 `en-US.ts` 两个文件: + +```diff +src + + locales + + zh-CN.ts + + en-US.ts + pages +``` + +在 `config.ts` 中配置国际化插件: + +```bash +locale: { + // 默认使用 src/locales/zh-CN.ts 作为多语言文件 + default: 'zh-CN', + baseSeparator: '-', +} +``` + +在 `layouts` 中添加`SelectLang` 和 `useIntl`: + +```diff +import BasicLayouts from '@antdp/basic-layouts'; ++ import { SelectLang, useIntl } from '@umijs/max'; + +const Layout = (props) => { + return ( + } ++ intlLanguage={useIntl()} + ... + /> + ); +}; + +export default Layout; + +``` + +现在,添加您的第一条多语言内容: + +```ts | pure +// src/locales/zh-CN.ts +export default { + welcome: '欢迎光临 Umi 的世界!', +}; +``` + +```ts | pure +// src/locales/en-US.ts +export default { + welcome: "Welcome to Umi's world!", +}; +``` + +您也可以使用 `.json` 文件来存放多语言的内容: + +```json +// src/locales/zh-CN.json +{ + "welcome": "欢迎光临 Umi 的世界!", +} + +// src/locales/en-US.json +{ + "welcome": "Welcome to Umi's world!", +} +``` + +一切就绪,现在您可以在 Umi 中使用多语言内容。交给我们的 `` 组件吧,只需要将前面的 `welcome` 作为参数 `id` 的值传入即可: + +```ts | purex +import { FormattedMessage } from 'umijs/max'; + +export default function Page() { + return ( +
+ +
+ ); +} +``` + +渲染的结果如下: + +```html + +
欢迎光临 Umi 的世界!
+ + +
Welcome to Umi's world!
+``` + +## 在组件的参数中使用 + +在某些情况下,您需要将多语言内容作为参数传递给某个组件。可以通过 `intl` 对象来实现: + +```ts | purex +import { Alert } from 'antd'; +import { useIntl } from 'umi'; + +export default function Page() { + const intl = useIntl(); + const msg = intl.formatMessage({ + id: 'welcome', + }); + + return ; +} +``` + +在底层,国际化插件基于 [`react-intl`](https://github.com/formatjs/formatjs/tree/main/packages/react-intl) 封装,并支持它的所有接口,详情可见[此文档](https://github.com/formatjs/formatjs/blob/main/website/docs/react-intl/api.md)。 + +在上面的代码中,我们就运用到了 `react-intl` 提供的 `useIntl()` 接口来初始化 `intl` 对象,并调用此对象的 [`formatMessage()`](https://github.com/formatjs/formatjs/blob/main/website/docs/react-intl/api.md#formatmessage) 方法来格式化字符串。 + +## 格式化字符串 + +您可能想要在多语言翻译中动态插值,那么可以像这样编写多语言内容: + +```ts | pure +// src/locales/zh-CN.ts +export default { + user: { + welcome: '{name},今天也是美好的一天!', + }, +}; +``` + +```ts | pure +// src/locales/en-US.ts +export default { + user: { + welcome: '{name}, what a nice day!', + }, +}; +``` + +在上面,我们编写了特殊的语法 `{name}`,这允许我们在运行时动态赋值: + +```ts | purex +import { FormattedMessage } from 'umi'; + +export default function Page() { + return ( +

+ +

+ ); +} +``` + +如果您希望通过 `intl` 对象来实现,那么可以这样对它赋值: + +```ts | purex +import { useIntl } from '@umijs/max'; + +export default function Page() { + const intl = useIntl(); + const msg = intl.formatMessage( + { + id: 'user.welcome', + }, + { + name: '张三', + }, + ); + + return

{msg}

; +} +``` + +注意,用于赋值的键值对对象应当作为 `formatMessage()` 方法的第二个参数传递。 + +渲染的结果如下: + +```html + +

张三,今天也是美好的一天!

+ + +

张三, what a nice day!

+``` + +## 在组件的参数中使用 + +在某些情况下,您需要将多语言内容作为参数传递给某个组件。可以通过 `intl` 对象来实现: + +```ts | purex +import { Alert } from 'antd'; +import { useIntl } from '@umijs/max'; + +export default function Page() { + const intl = useIntl(); + const msg = intl.formatMessage({ + id: 'welcome', + }); + + return ; +} +``` + +在底层,国际化插件基于 [`react-intl`](https://github.com/formatjs/formatjs/tree/main/packages/react-intl) 封装,并支持它的所有接口,详情可见[此文档](https://github.com/formatjs/formatjs/blob/main/website/docs/react-intl/api.md)。 + +在上面的代码中,我们就运用到了 `react-intl` 提供的 `useIntl()` 接口来初始化 `intl` 对象,并调用此对象的 [`formatMessage()`](https://github.com/formatjs/formatjs/blob/main/website/docs/react-intl/api.md#formatmessage) 方法来格式化字符串。 + +## 格式化字符串 + +您可能想要在多语言翻译中动态插值,那么可以像这样编写多语言内容: + +```ts | pure +// src/locales/zh-CN.ts +export default { + user: { + welcome: '{name},今天也是美好的一天!', + }, +}; +``` + +```ts | pure +// src/locales/en-US.ts +export default { + user: { + welcome: '{name}, what a nice day!', + }, +}; +``` + +在上面,我们编写了特殊的语法 `{name}`,这允许我们在运行时动态赋值: + +```ts | purex +import { FormattedMessage } from '@umijs/max'; + +export default function Page() { + return ( +

+ +

+ ); +} +``` + +如果您希望通过 `intl` 对象来实现,那么可以这样对它赋值: + +```ts | purex +import { useIntl } from '@umijs/max'; + +export default function Page() { + const intl = useIntl(); + const msg = intl.formatMessage( + { + id: 'user.welcome', + }, + { + name: '张三', + }, + ); + + return

{msg}

; +} +``` + +注意,用于赋值的键值对对象应当作为 `formatMessage()` 方法的第二个参数传递。 + +渲染的结果如下: + +```html + +

张三,今天也是美好的一天!

+ + +

张三, what a nice day!

+``` + +## 常用接口介绍 + +### `addLocale` 动态添加多语言支持 + +无需创建并编写单独的多语言文件,使用 `addLocale()` 接口可以在运行时动态添加语言支持。它接受三个参数: + +| 参数 | 类型 | 介绍 | +| --------- | -------- | ----------------------------- | +| `name` | `String` | 多语言的 Key | +| `message` | `Object` | 多语言的内容对象 | +| `options` | `Object` | `momentLocale` 和 `antd` 配置 | + +例如,您想要动态引入繁体中文的多语言支持,可以编写代码如下: + +```ts | pure +import { addLocale } from '@umijs/max'; +import zhTW from 'antd/es/locale/zh_TW'; + +addLocale( + 'zh-TW', + { + welcome: '歡迎光臨 Umi 的世界!', + }, + { + momentLocale: 'zh-tw', + antd: zhTW, + }, +); +``` + +### `getAllLocales` 获取多语言列表 + +通过 `getAllLocales()` 接口可以获取当前所有多语言选项的数组,包括通过 `addLocale()` 方法添加的多语言选项。该接口默认会在 `src/locales` 目录下寻找形如 `zh-CN.(js|json|ts)` 的文件,并返回多语言的 Key。 + +```ts | pure +import { getAllLocales } from '@umijs/max'; + +getAllLocales(); +// [en-US, zh-CN, ...] +``` + +### `getLocale` 获取当前选择的语言 + +通过 `getLocale()` 接口可以获取当前选择的语言: + +```ts | pure +import { getLocale } from '@umijs/max'; + +getLocale(); +// zh-CN +``` + +### `useIntl` 获取 `intl` 对象 + +`useIntl()` 很有可能会是您开发中最常用的接口,通过它可以获取 `intl` 对象,并进一步执行 `formatMessage()` 等方法来实现您多元的需求: + +```json +// src/locales/en-US.json +{ + "welcome": "Hi, {name}." +} +``` + +```ts | pure +import { useIntl } from '@umijs/max'; + +const intl = useIntl(); +const msg = intl.formatMessage( + { + id: 'welcome', + }, + { + name: 'Jackson', + }, +); +console.log(msg); +// Hi, Jackson. +``` + +关于 `intl` 对象的更多用法,请参阅 `react-intl` 的[接口文档](https://github.com/formatjs/formatjs/blob/main/website/docs/react-intl/api.md)。 + +### `setLocale` 设置语言 + +通过 `setLocale()` 接口可以使用编程的方法动态设置当前的语言。它有两个参数: + +| 参数 | 类型 | 介绍 | +| ------------ | --------- | ------------------------------------------ | +| `lang` | `String` | 切换到的语言 | +| `realReload` | `Boolean` | 切换时是否刷新页面,默认为 `true` 刷新页面 | + +```ts | pure +import { setLocale } from '@umijs/max'; + +// 切换时刷新页面 +setLocale('en-US'); + +// 切换时不刷新页面 +setLocale('en-US', false); +``` + +## 配置插件 + +您可以在 `config.ts` 中配置国际化插件。默认值如下: + +```ts | pure +export default { + locale: { + antd: false, // 如果项目依赖中包含 `antd`,则默认为 true + baseNavigator: true, + baseSeparator: '-', + default: 'zh-CN', + title: false, + useLocalStorage: true, + }, +}; +``` + +配置的详细介绍如下: + +| 配置项 | 类型 | 默认值 | 介绍 | +| ----------------- | --------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `antd` | `Boolean` | `false`;如果项目包含 `antd` 依赖,则为 `true` | `antd` 的国际化支持。更多介绍可参见[此文档](https://ant.design/docs/react/i18n-cn)。 | +| `baseNavigator` | `Boolean` | `true` | 开启**浏览器语言检测**。默认情况下,当前语言环境的识别按照:`localStorage` 中 `umi_locale` 值 > 浏览器检测 > `default` 设置的默认语言 > `zh-CN` | +| `baseSeparator` | `String` | `-` | 语言(Language)与国家(Country) 之间的**分割符**。默认情况下为 `-`,返回的语言及目录文件为 `zh-CN`、`en-US` 和 `sk` 等。若指定为 `_`,则 `default` 默认为 `zh_CN`。 | +| `default` | `String` | `zh-CN` | 项目**默认语言**。当检测不到具体语言时,使用 `default` 设置的默认语言。 | +| `title` | `Boolean` | `false` | 开启[**标题国际化**](#标题国际化)。 | +| `useLocalStorage` | `Boolean` | `true` | 自动使用 `localStorage` 保存当前使用的语言。 | + +### 标题国际化 + +在路由配置中添加 `title` 项即可启用国际化支持,自动将页面的标题转为对应的多语言内容。 + +例如,编写多语言文件如下: + +```ts | pure +// src/locales/zh-CN.ts +export default { + 'site.title': 'Umi - 企业级 React 应用开发框架', + 'about.title': 'Umi - 关于我', +}; +``` + +```ts | pure +// src/locales/en-US.ts +export default { + 'site.title': 'Umi - Enterprise-level React Application Framework', + 'about.title': 'Umi - About me', +}; +``` + +配置路由内容如下: + +```json +// route.json + [ + { + "path": "/login", + "component": "@/layouts/UserLayout" + }, + { + "path": "/", + "component": "@/layouts/BasicLayout", + "routes": [ + { + "path": "/", + "redirectTo": "/welcome" + }, + { + "path": "/welcome", + "name": "首页", + "icon": "welcome", + "locale": "welcome", + "component": "@/pages/Index/index" + }, + ] + } + ], +``` + +## 运行时拓展 + +国际化插件允许您在运行时对它进行一些拓展与定制。 + +### 自定义 `getLocale` + +您可以自定义获取页面语言 `getLocale()` 方法的逻辑,例如通过识别链接 `?locale=en-US`,将 `en-US` 作为当前页面的语言: + +```ts | pure +// src/app.ts +import qs from 'qs'; + +export const locale = { + getLocale() { + const { search } = window.location; + const { locale = 'zh-CN' } = qs.parse(search, { ignoreQueryPrefix: true }); + return locale; + }, +}; +``` + +## FAQ + +### 为什么不直接使用 `formatMessage` 这个语法糖? + +虽然 `formatMessage` 直接使用起来会非常方便,但是它脱离了 React 的生命周期,最严重的问题就是切换语言时无法触发 DOM 的重新渲染。为了解决这个问题,我们切换语言时就需要刷新一下浏览器,用户体验很差。所以推荐大家使用 `useIntl` 或者 `injectIntl`,可以实现同样的功能。 + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/mock.md b/examples/website2/docs/guide/mock.md new file mode 100644 index 000000000..4280a8863 --- /dev/null +++ b/examples/website2/docs/guide/mock.md @@ -0,0 +1,143 @@ +--- +nav: 教程 +group: 页面开发 +order: 4 +--- + +# Mock + +antdp 提供了开箱即用的 Mock 功能,能够用方便简单的方式来完成 Mock 数据的设置。 + + +什么是 Mock 数据:在前后端约定好 API 接口以后,前端可以使用 Mock 数据来在本地模拟出 API 应该要返回的数据,这样一来前后端开发就可以同时进行,不会因为后端 API +还在开发而导致前端的工作被阻塞。 + + +## 目录约定 + +Umi 约定 /mock 目录下的所有文件为 Mock 文件,例如这样的目录结构: + +```text +. +├── mock + ├── todos.ts + ├── items.ts + └── users.ts +└── src + └── pages + └── index.tsx +``` + +则 /mock 目录中的 todos.ts, items.ts 和 users.ts 就会被 Umi 视为 Mock 文件 来处理。 + +## Mock 文件 + +Mock 文件默认导出一个对象,而对象的每个 Key 对应了一个 Mock 接口,值则是这个接口所对应的返回数据,例如这样的 Mock 文件: + +```ts +// ./mock/users.ts + +export default { + // 返回值可以是数组形式 + 'GET /api/users': [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + + // 返回值也可以是对象形式 + 'GET /api/users/1': { id: 1, name: 'foo' }, +}; +``` + +就声明了两个 Mock 接口,透过 GET /api/users 可以拿到一个带有两个用户数据的数组,透过 GET /api/users/1 可以拿到某个用户的模拟数据。 + +## 请求方法 + +当 Http 的请求方法是 GET 时,可以省略方法部分,只需要路径即可,例如: + +```ts +// ./mock/users.ts + +export default { + '/api/users': [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + + '/api/users/1': { id: 1, name: 'foo' }, +}; +``` + +也可以用不同的请求方法,例如 POST: + +```ts +// ./mock/users.ts + +export default { + 'POST /api/users': { result: 'true' }, + + 'PUT /api/users/1': { id: 1, name: 'new-foo' }, +}; +``` + +## 自定义函数 + +除了直接静态声明返回值,也可以用函数的方式来声明如何计算返回值,例如: + +```ts +export default { + 'POST /api/users/create': (req, res) => { + // 添加跨域请求头 + res.setHeader('Access-Control-Allow-Origin', '*'); + res.end('ok'); + }, +}; +``` + +关于 `req` 和 `res` 的 API 可参考 [Express@4](https://moexpressjs.com/en/api.html) 官方文档 来进一步了解。 + +## defineMock + +另外,也可以使用 defineMock 类型帮助函数来提供编写 mock 对象的代码提示,如: + +```ts +import { defineMock } from '@umijs/max'; + +export default defineMock({ + '/api/users': [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + '/api/users/1': { id: 1, name: 'foo' }, + 'GET /api/users/2': (req, res) => { + res.status(200).json({ id: 2, name: 'bar' }); + }, +}); +``` + +`defineMock` 仅仅提供类型提示,入参与出参完全一致。 + +或是用环境变量的方式关闭: + +```bash +MOCK=none max dev +``` + +## 引入 Mock.js + +在 Mock 中我们经常使用 [Mock.js](http://mockjs.com/) 来帮我们方便的生成随机的模拟数据,如果你使用了 Umi 的 Mock 功能,建议你搭配这个库来提升模拟数据的真实性: + +```ts +import mockjs from 'mockjs'; + +export default { + // 使用 mockjs 等三方库 + 'GET /api/tags': mockjs.mock({ + 'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }], + }), +}; +``` + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/new-page.md b/examples/website2/docs/guide/new-page.md new file mode 100644 index 000000000..e935f89a0 --- /dev/null +++ b/examples/website2/docs/guide/new-page.md @@ -0,0 +1,181 @@ +--- +nav: 教程 +order: 1 +group: + title: 页面开发 + order: 2 +--- + +# 新增页面 + +这里的『页面』指配置了路由,能够通过链接直接访问的模块,要新建一个页面,通常可以在脚手架的基础上进行简单的配置。 + +## 页面代码结构推荐 + +为了让项目代码组织更加规范,让开发能够更方便的定位到相关页面组件代码,我们定义了一套规范,该规范当前只作为推荐的指导,并非强制。 + +```bash +src +├── components +└── pages + ├── Welcome # 路由组件下不应该再包含其他路由组件,基于这个约定就能清楚的区分路由组件和非路由组件了 + | ├── components # 对于复杂的页面可以再自己做更深层次的组织,但建议不要超过三层 + | | ├── Form.tsx + | ├── index.tsx # 页面组件的代码 + | └── index.less # 页面样式 + ├── Order # 路由组件下不应该再包含其他路由组件,基于这个约定就能清楚的区分路由组件和非路由组件了 + | ├── index.tsx + | └── index.less + ├── User + | ├── components # group 下公用的组件集合 + | ├── Login # group 下的页面 Login + | ├── Register # group 下的页面 Register + | └── util.ts # 这里可以有一些共用方法之类,不做推荐和约束,看业务场景自行做组织 + └── * # 其它页面组件代码 +``` + +所有路由组件(会配置在路由配置中的组件)我们推荐以大驼峰命名打平到 pages 下面第一级(复杂的项目可以增加 group 层级,在 group 下放置 pages)。不建议在路由组件内部再嵌套路由组件 - 不方便分辨一个组件是否是路由组件,而且不方便快速从全局定位到路由组件。 + +我们推荐尽可能的拆分路由组件为更细粒度的组件,对于多个页面可能会用到的组件我们推荐放到 src/components 中,对于只是被单个页面依赖的(区块)组件,我们推荐就近维护到路由组件文件夹下即可。 + +## 手动创建 + +新增 tsx、less 文件 +在 src / pages 下创建新的 tsx,less 文件。 如果有多个相关页面,您可以创建一个新文件夹来放置相关文件。 + +```diff +config +src +models + pages ++ NewPage ++ index.less ++ index.tsx + ... +... +package.json +``` + +为了更好的演示,我们初始化 NewPage.tsx 的内容如下: + +```bash +export default () => { + return
New Page
; +}; +``` + +## 新增路由 + +在脚手架中我们通过嵌套路由来实现布局模板。config 文件中的 routes.json 是一个数组,其中第一级数据就是我们的布局,如果你需要新增布局可以再直接增加一个新的一级数据。 + +```json +[ + // new + { + "path": "/new", + "name": "新页面", + "component": "@page/NewPage", + "icon": "home" + } +] +``` + +路由配置完成后,访问页面即可看到效果,如果需要在菜单中显示,需要配置 name,icon,hideMenu 等来辅助生成菜单。 + +具体值如下: + +- `index` 默认跳转 +- `path` 路径 +- `name` 名称 +- `icon` 图标 +- `components` 对应页面路径 +- `children` 子集 路由 +- `hideInMenu` 是否隐藏菜单 +- `side` 控制顶部和侧边菜单展示是否联动 + +布局及路由都配置好之后,回到之前新建的 `index.tsx`,可以开始写业务代码了! + +## 路由菜单权限 + +开启权限配置。需要在 config/config.ts 提供权限配置。 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: { ++ ANTD_AUTH_CONF: { ++ auth_menu: 'authMenu', ++ auth_btn: 'authBtn', ++ auth_check_url: '', + } +}); +``` + +### `ANTD_AUTH_CONF` 权限配置参数 + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | +| auth_menu | 储存菜单路由权限---本地 keys | `string` | `authMenu` | +| auth_btn | 储存按钮路径权限---本地 keys | `string` | `authBtn` | +| auth_check_url | 判断路径是否有权限的字段 默认值`menuUrl`,如果字段设置为`undefined`则`auth_menu`和`auth_btn`储存形式为 `["/web"]`,反之储存形式为`[{menuUrl:"/web"}]` | `string` | `menuUrl` | + +### 路由菜单 + +这是你的路由菜单(config/router.json) + +```json +[ + { + "path": "/login", + "component": "@/layouts/UserLayout" + }, + { + "path": "/", + "component": "@/layouts/BasicLayout", + "routes": [ + { + "path": "/", + "redirectTo": "/welcome" + }, + { + "path": "/*", + "component": "@/pages/404" + }, + { + "path": "/welcome", + "name": "首页", + "icon": "welcome", + "locale": "welcome", + "component": "@/pages/Home/index" + }, + { + "path": "/403", + "name": "403", + "hideInMenu": true, + "icon": "file-protect", + "component": "@/pages/403" + } + ] + } +] +``` + +登陆后后端返回的菜单列表可能如下 + +```js +const menus = ['/', '/welcome', '/404', '/403']; +``` + +### 路由匹配过程 + +- 1.当你登陆成功后,你需将其保存于你的 sessionStorage 中,储存的字段为你`ANTD_AUTH_CONF`中配的`auth_menu`字段,并在登陆后存储在`sessionStorage`中,如`sessionStorage.setItem('authMenu', JSON.stringify([]))` +- 2.当你跳转至页面时,会根据 sessionStorage 中`authMenu`进行权限匹配,如果没有权限则会跳往 403 页面 + +请保证 403 和 404 页面存在 + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/proxy.md b/examples/website2/docs/guide/proxy.md new file mode 100644 index 000000000..665622eed --- /dev/null +++ b/examples/website2/docs/guide/proxy.md @@ -0,0 +1,43 @@ +--- +nav: 教程 +order: 1 +group: + title: 数据管理 + order: 3 +--- + +# 跨域与代理 + +在前后端分离的今天,跨域也成了每个前端工程师都需要了解的基本知识,在各种面试题中的日经话题。这个文章就是想总结一下关于同源策略的前世今生,以及怎么解决它。 + +## 同源策略 + +在[MDN](https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy)中我们可以看到关于同源策略是一个安全机制。详细的说明如下: + +```bash +同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。 +``` + +这个机制本身出发点是很好的,但是同源的限制非常严格,url,端口任一不同都会造成跨域错误。 +![](https://user-images.githubusercontent.com/59959718/227846530-0b341502-b59e-4606-be22-04c60806e4db.png) + +而且在控制台中你不会发现任何问题。随着前后端分离越来越普遍,这件事就越来越常见。那么它应该如何解决呢? + +同源策略全称叫《浏览器的同源策略》,它是浏览器内建的一种安全机制。那么我们不要使用浏览器请求就能完美解决问题了。对于前端来说最方便的自然就是 node.js 了。 + +## 在开发中使用 + +```js +// config/config.js +proxy: { + '/api': { + 'target': 'http://jsonplaceholder.typicode.com/', + 'changeOrigin': true, + 'pathRewrite': { '^/api' : '' }, + } +} +``` + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/quick-start.md b/examples/website2/docs/guide/quick-start.md new file mode 100644 index 000000000..d8666912f --- /dev/null +++ b/examples/website2/docs/guide/quick-start.md @@ -0,0 +1,126 @@ +--- +nav: 教程 +group: + title: 入门 + order: 1 +order: 1 +--- + +# 快速上手 + +## 环境准备 + +首先得有 [node](https://nodejs.org/en),并确保 `node` 版本是 14 或以上。(推荐用 [nvm](https://github.com/nvm-sh/nvm) 来管理 node 版本,windows 下推荐用 [nvm-windows](https://github.com/coreybutler/nvm-windows)) +mac 或 linux 下安装 nvm。 + +```bash +$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash +$ nvm -v +0.39.1 +``` + +安装 node。(^16.14.0 || >=18.0.0) + +```bash +$ nvm install 16.19.1 +$ nvm use 16.19.1 +$ node -v +v16.19.1 +``` + +然后需要包管理工具。node 默认包含 npm,但也可以选择其他方案, + +- [pnpm](https://pnpm.io/installation), umi 团队推荐 +- [Yarn](https://yarnpkg.com/getting-started/install) + +安装 pnpm。 + +```bash +curl -fsSL https://get.pnpm.io/install.sh | sh - +$ pnpm -v +7.3.0 +``` + +## 创建 ts 项目 + +```shell +$ npm init antdp my-app --example basic +$ npm init antdp my-app -- --example basic +$ yarn create antdp [appName] +$ npm create antdp my-app +$ npx create-antdp my-app +``` + +或者直接下载: [`basic.zip`](https://antdpro.github.io/antdp/zip/basic.zip) + +## 创建 js 项目 + +```shell +$ npm init antdp my-app --example basicjs +$ npm init antdp my-app -- --example basicjs +$ yarn create antdp [appName] -- --example basicjs +$ npm create antdp my-app -- --example basicjs +$ npx create-antdp my-app -- --example basicjs +``` + +或者直接下载版本: [`basicjs.zip`](https://antdpro.github.io/antdp/zip/basicjs.zip) + +## 启动项目 + +- `cd [appName]` 进入目录 + +- `yarn install`下载依赖 + +- `yarn start`启动项目, 项目启动成功,你就会看见如下。 + +```bash +yarn run v1.22.17 +$ lerna exec --scope @example/antdp-base -- npm run start +lerna notice cli v6.6.1 +lerna notice filter including "@example/antdp-base" +lerna info filter [ '@example/antdp-base' ] +lerna info Executing command in 1 package: "npm run start" + +> start +> max dev + +info - [你知道吗?] CSS 不够灵活、编写动态样式太繁琐怎么办?试试 styled-components 插件,详见 https://umijs.org/docs/max/styled-components +info - Umi v4.0.65 +info - Preparing... +[HPM] Proxy created: /api/ -> https://preview.pro.ant.design +[HPM] Proxy rewrite rule created: "^" ~> "" + ╔════════════════════════════════════════════════════╗ + ║ App listening at: ║ + ║ > Local: http://localhost:8000 ║ +ready - ║ > Network: http://192.168.188.205:8000 ║ + ║ ║ + ║ Now you can open browser with the above addresses↑ ║ + ╚════════════════════════════════════════════════════╝ +``` + +在浏览器里打开 http://localhost:8000/ + +## 部署发布 + +执行 `yarn build` 命令, + +```bash +> umi build +event - compiled successfully in 1179 ms (567 modules) +event - build index.html +``` + +产物默认会生成到 ./dist 目录下, + +```bash +./dist +├── index.html +├── umi.css +└── umi.js +``` + +完成构建后,就可以把 dist 目录部署到服务器上了。 + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/request.md b/examples/website2/docs/guide/request.md new file mode 100644 index 000000000..6d894b369 --- /dev/null +++ b/examples/website2/docs/guide/request.md @@ -0,0 +1,582 @@ +--- +nav: 教程 +group: 页面开发 +order: 3 +--- + +# 请求 + +简介 +对于中后台应用来说,很大一部分工作就在于请求后端的 CRUD 的接口,为进一步降低用户对请求层的感知,我们移除了默认生成的 utils/request.ts 文件,改成通过配置化的方式暴露给开发者做请求的配置和增强处理;同时通过业务总结出一套标准的接口结构规范,并提供统一的接口解析、错误处理的能力;后续将持续完善可配置项、提供垂直场景如列表、登录失效等解决方案。 + +## request + +我们基于 umi-request 进行了二次封装了@antp/request,参见[umi-request](https://github.com/umijs/umi-request) + +### 参数 + +| 参数 | 说明 | 类型 | 默认值 | +| :--- | :------- | :----- | :----- | +| url | 请求地址 | string | - | + +### Options + +| 参数 | 说明 | 类型 | 默认值 | +| :----- | :------------------- | :------------ | :----- | +| method | 请求方法 | `POST \| GET` | - | +| data | 请求传递给后端的参数 | any | - | + +### 使用方法 + +```js +import request from '@antdp/request'; +request('/api/user', { method: 'POST', data: { name: 1 } }); +``` + +### 页面中调用接口 + +#### 配合 dva 调用接口 + +> 在 servers/index.js 中 + +```bash +import { request } from "@uiw-admin/utils" + +export const selectById = (params) => request("/api/selectById",{ method:"POST",data: { ...params } }) + +``` + +> 在 model/index.js 中 + +```bash +import { selectById } from '../servers'; + +export default { + namespace:'index', + state:{ + name:'', + }, + reducers:{ + updateState: (state, payload) => ({ + ...state, + ...payload, + }), + }, + effects:{ + selectById({ paylog },{ call,put }){ + const data = yield call(selectById, payload); + if(data.code === 1){ + yield put({ + type: 'updateData', + paylog:{ + name:data.data + } + }) + } + } + } +} + +``` + +> 在页面中调用 + +```bash +import React from 'react'; +import { useDispatch, useSelector } from 'dva'; + +export default const Index = () => { + const dispatch = useDispatch() + const { name } = useSelector(state => state.index) || {} + React.useEffect(()=>{ + dispatch({ + type: 'index/selectById', + payload:{ id:1 }, + }) + },[]) + return
{name}
+} +``` + +## umi 内置 request 和 useRequest + +在项目 config/config.ts 中添加如下 + +```diff +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, ++ request: { ++ dataField: 'data' ++ }, +); +``` + +构建时配置可以为 useRequest 配置 dataField ,该配置的默认值是 data。该配置的主要目的是方便 useRequest 直接消费数据。如果你想要在消费数据时拿到后端的原始数据,需要在这里配置 dataField 为 '' 。 + +比如你的后端返回的数据格式如下。 + +```bash +{ + success: true, + data: 123, + code: 1, +} +``` + +那么 `useRequest` 就可以直接消费 `data`。其值为 123,而不是 `{ success, data, code }` + +### 使用方法 + +通过 `import { request,useRequest } from '@umijs/max'` 或 `import { request } from '@@/plugin-request'` 你可以使用 umi 内置的请求方法。 + +`request` 接收的 `options`除了透传 [axios](https://axios-http.com/docs/req_config) 的所有 config 之外,umi 还额外添加了几个属性 `skipErrorHandler`,`getResponse`,`requestInterceptors` 和 `responseInterceptors` 。 + +示例如下: + +```bash +request('/api/user', { + params: { name : 1 }, + timeout: 2000, + // other axios options + skipErrorHandler: true, + getResponse: false, + requestInterceptors: [], + responseInterceptors: [], +} +``` + +当你的某个请求想要跳过错误处理时,可以通过将`skipErrorHandler`设为 `true` 来实现 + +request 默认返回的是你后端的数据,如果你想要拿到 axios 完整的 response 结构,可以通过传入 `{ getResponse: true }` 来实现。 + +`requestInterceptors` 和 `responseInterceptors` 的写法同运行时配置中的拦截器写法相同,它们为 request 注册拦截器。区别在于这里注册的拦截器是 "一次性" 的。另外,这里写的拦截器会在运行时配置中的拦截器之后被注册。\*\* + +注意: 当你使用了 errorHandler 时,在这里注册的 response 拦截器会失效,因为在 errorHandler 就会 throw error + +### 页面中调用接口 + +#### ✨ 配和 useRequest 调用接口 + +```bash +import React from 'react' +import { request,useRequest } from '@umijs/max'; + +const Index = () => { + const selectById = (params) => request("/api/selectById",{ method:"POST",data: { ...params } }) + const [ name ,setName ] = React.useState('') + const { run, loading } = useRequest(selectById, + manual: true, + onSuccess: (req) => { + setName(req); + }, + ) + + React.useEffect(()=>run( id:1 ),[]) + + return
{name}
+} +export default Index +``` + +## react-query + +我们基于 react-query 进行了二次封装了`useReactQuery` 和 `useReactMutation`,参见[react-query](https://tanstack.com/query/latest)(和 @tanstack/react-query 是同一个) + +### 启用方式 + +由于 umi 已内置`react-query`,你只需在`congfig/config.ts`中配置 + +```bash +import config from '@antdp/config'; +import proxy from './proxy'; +import router from './router.json'; +export default config(router, { + proxy, + define: {}, + reactQuery: { + // devtool: boolean,是否开启 react query 官方 devtool 工具,默认 false + devtool: false, + // queryClient: boolean, 是否注册全局的 QueryClient 和 QueryClientProvier,默认 true + queryClient: false, + }, +); +``` + +### API 请求 hooks + +下面是 API 请求示例,如 GET/POST 请求示例 + +#### useReactQuery + +主要用于**默认**触发请求数据,默认 `GET` 请求,变更使用 `method="POST"` 参数配置 + +```bash +useReactQuery({ + queryKey: ['user', userId], + url: `/api/user/list?id=${userId}` +}); +``` + + + +👆👆👆👆 上面是**推荐**使用 👆👆👆👆👆 + +```bash +import { fetchFn, useReactQuery } from '@antdp/hooks'; + +useReactQuery({ queryKey: ['user'], url: '/api/user/list' }); +useReactQuery({ queryKey: ['user'], url: '/api/user/list', method: 'POST' }); +useReactQuery({ queryKey: ['user', userId], queryFn: () => fetchFn(`/api/user/list?id=${userId}`) }); +useReactQuery({ + queryKey: ['user', userId], + queryFn: async () => { + return fetchFn(`/api/user/list?id=${userId}`); + }, +}); +useReactQuery({ + queryKey: ['user', userId], + queryFn: ({ queryKey }) => fetchFn(`/api/user/list?id=${queryKey[1]}`);, +}); +useReactQuery({ + queryKey: ['user'], + url: '/api/user/list', + initialData: [....], +}); + +const { isInitialLoading, isError, data, error, refetch, isFetching } = useQuery(...) +``` + +示例 + +```bash +import { useReactQuery } from '@antdp/hooks'; + +export default function HomePage() { + const { isLoading, isError, data } = useReactQuery({ + url: `/api/user/list`, + queryKey: ['user-list'], + }); + + return ( +
+

x react-query

+ {isError &&

请求 API 错误 ...

} + {isLoading &&

Loading ...

} + {data &&

现在有 {data.stargazers_count} 颗星!

} +
+ ); +} +``` + +##### Query 选项 + +```bash +const { data } = useReactQuery({ + /** 设置 Content-Type,默认值 `json`,'Content-Type' = 'application/json' */ + contentType: "json" | 'form'; + // 请求 API + url: '/api/user/list' + // 用于此查询的查询键。查询键将被 hash 成一个稳定的 hash 。当此键更改时,查询将自动更新(只要 enabled 未设置为 false) + queryKey: ['user-list', userId], + // 只要查询成功获取新数据,此函数就会触发。 + onSuccess: (data: TData) => void + // 如果查询遇到错误并将传递错误,则此函数将触发。 + onError: (error: TError) => void + // 每当成功获取查询或出错并传递数据或错误时,此函数都会触发。 + onSettled: (data?: TData, error?: TError) => void + // 此选项可用于转换或选择查询函数返回的部分数据。 它会影响返回的数据值,但不会影响存储在查询缓存中的内容。 + select: (data: TData) => unknown + select: (dt) => { + // 改变请求到的 data 数据,返回部分 data 数据 + return dt + }, + // 将此设置为 true 以启用暂停模式。 + // 当 true 时,useQuery 将在 status === 'loading' 时暂停 + // 当 true 时,useQuery 将在 status === 'error' 时抛出运行时错误 + suspense: true, + // 将此设置为 false 以禁止此查询自动运行。https://tanstack.com/query/v4/docs/react/guides/dependent-queries + // 在 userId 存在之前,查询不会执行 + enabled: !!userId, + // 如果为 false,失败的查询默认不会重试。如果为真,失败的查询将无限重试 + // 如果设置为数字,例如 3、失败的查询会重试,直到失败的查询计数满足那个数 + retry: boolean | number | (failureCount: number, error: TError) => boolean + // 默认值为 'online',请参阅网络模式:https://tanstack.com/query/v4/docs/react/guides/network-mode + networkMode: 'online' | 'always' | 'offlineFirst' + // 如果设置为 false,如果查询包含错误,则不会在挂载时重试。默认为真 + retryOnMount: boolean + // 此函数接收一个 retryAttempt 整数和实际错误,并返回在下一次尝试之前应用的延迟(以毫秒为单位) + // 像 attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000) 这样的函数应用指数退避 + // 像 attempt => attempt * 1000 这样的函数应用线性退避。 + retryDelay: number | (retryAttempt: number, error: TError) => number + // 默认值为 0,数据被认为过时后的时间(以毫秒为单位)。该值仅适用于定义它的挂钩 + // 如果设置为“Infinity”,数据将永远不会被认为是陈旧的 + staleTime: number | Infinity + // 在 SSR 期间默认为 5 * 60 * 1000(5 分钟)或无限 + // 未使用(unused)/非活动(inactive)缓存数据保留在内存中的时间(以毫秒为单位)。当查询的缓存变为未使用或不活动时,该缓存数据将在这段时间后被垃圾收集。 当指定不同的缓存时间时,将使用最长的一个 + // 如果设置为 Infinity,将禁用垃圾收集 + cacheTime: number | Infinity + // 如果设置为一个数字,所有查询将以毫秒为单位以该频率连续重新获取 + // 如果设置为一个函数,该函数将使用最新的数据执行并查询以计算频率 + refetchInterval: number | false | ((data: TData | undefined, query: Query) => number | false) + // 如果设置为 true,则设置为使用 refetchInterval 连续重新获取的查询将在其选项卡/窗口处于后台时继续重新获取 + refetchIntervalInBackground: boolean + // 默认为 true + // 如果设置为 true,如果数据过时,查询将在挂载时重新获取 + // 如果设置为 false,查询将不会在挂载时重新获取 + // 如果设置为 always,查询将始终在挂载时重新获取 + // 如果设置为一个函数,该函数将与查询一起执行以计算值 + refetchOnMount: boolean | "always" | ((query: Query) => boolean | "always") + // 默认为 true + // 如果设置为 true,如果数据陈旧,查询将重新获取窗口焦点 + // 如果设置为 false,查询将不会重新获取窗口焦点 + // 如果设置为 always,查询将始终重新获取窗口焦点 + // 如果设置为一个函数,该函数将与查询一起执行以计算值 + refetchOnWindowFocus: boolean | "always" | ((query: Query) => boolean | "always") + // 默认为 true + // 如果设置为 true,如果数据过时,查询将在重新连接时重新获取 + // 如果设置为 false,查询将不会在重新连接时重新获取 + // 如果设置为 always,查询将始终重新获取窗口焦点 + // 如果设置为一个函数,该函数将与查询一起执行以计算值 + refetchOnReconnect: boolean | "always" | ((query: Query) => boolean | "always") + // 如果设置,组件将仅在任何列出的属性更改时重新渲染 + // 例如,如果设置为 ['data', 'error'],组件将仅在数据或错误属性更改时重新呈现 + // 如果设置为“all”,组件将选择退出智能跟踪并在更新查询时重新呈现 + // 默认情况下,将跟踪对属性的访问,并且仅当跟踪的属性之一发生更改时,组件才会重新呈现 + notifyOnChangeProps: string[] | "all" + // 如果设置,此值将用作查询缓存的初始数据(只要尚未创建或缓存查询) + // 如果设置为一个函数,该函数将在共享/根查询初始化期间被调用一次,并期望同步返回 initialData + // 默认情况下,初始数据被认为是陈旧的,除非设置了 staleTime。 + // initialData 被持久化到缓存 + initialData: TData | () => TData + // 如果设置,该值将用作上次更新 initialData 本身的时间(以毫秒为单位)。 + initialDataUpdatedAt: number | (() => number | undefined) + // 如果设置,当查询仍在加载数据中且未提供 initialData 时,此值将用作此特定查询观察器的占位符数据。 + // `placeholderData` 不会持久化到缓存 + placeholderData: TData | () => TData + // 默认为 false 如果设置,则在获取新数据时将保留任何以前的数据,因为查询键已更改。 + keepPreviousData: boolean + // 默认为 true 如果设置为 false,将禁用查询结果之间的结构共享 + // 如果设置为一个函数,旧数据值和新数据值将通过该函数传递,该函数应将它们组合成解析数据以供查询。 这样,您可以保留旧数据的引用以提高性能,即使该数据包含不可序列化的值也是如此 + structuralSharing: boolean | ((oldData: TData | undefined, newData: TData) => TData) + // 默认为全局查询配置的 useErrorBoundary 值,未定义 + // 如果您希望在渲染阶段抛出错误并传播到最近的错误边界,请将此设置为 true + // 将此设置为 false 以禁用 suspense 将错误抛出到错误边界的默认行为 + // 如果设置为函数,它将传递错误和查询,它应该返回一个布尔值,指示是在错误边界中显示错误 (true) 还是将错误作为状态返回 (false) + useErrorBoundary: undefined | boolean | (error: TError, query: Query) => boolean + // 如果设置,则存储有关查询缓存条目的附加信息,可根据需要使用。 只要查询可用,它就可以访问,它也是提供给 queryFn 的 QueryFunctionContext 的一部分 + meta: Record + // 使用它来使用自定义 React 查询上下文。 否则,将使用 defaultContext + context: React.Context +}); +``` + +##### Fetch 选项 + +请求 `fetch` 相关参数 + +```bash +const { data } = useReactQuery({ + /** 用于设置请求正文的 BodyInit 对象或 null。*/ + body?: BodyInit | null; + /** 一个字符串,指示请求将如何与浏览器的缓存交互以设置请求的缓存。*/ + cache?: RequestCache; + /** 一个字符串,指示凭据是始终、从不还是仅在发送到同源 URL 时随请求一起发送。 设置请求的凭据。*/ + credentials?: RequestCredentials; + /** Headers 对象、对象字面量或包含两项的数组,用于设置请求的标头。*/ + headers?: HeadersInit; + /** 要按请求获取的资源的加密哈希。 设置请求的完整性。*/ + integrity?: string; + /** 设置请求的保活的布尔值。*/ + keepalive?: boolean; + /** 设置请求方法的字符串。(GET|POST|PUT....)*/ + method?: string; + /** 一个字符串,指示请求是使用 CORS,还是仅限于同源 URL。 设置请求的模式。*/ + mode?: RequestMode; + /** 一个字符串,指示请求是否遵循重定向、在遇到重定向时导致错误或返回重定向(以不透明的方式)。 放s request's redirect. */ + redirect?: RequestRedirect; + /** 一个字符串,其值为同源 URL、“about:client”或空字符串,用于设置请求的引荐来源网址。*/ + referrer?: string; + /** 设置请求的 referrerPolicy 的引用策略。*/ + referrerPolicy?: ReferrerPolicy; + /** 用于设置请求信号的 AbortSignal。*/ + signal?: AbortSignal | null; + /** 只能为空。 用于解除来自任何窗口的请求。*/ + window?: null; +}); +``` + +#### useReactMutation + +用于触发的 `API` 请求 + +```bash +useReactMutation({ + mutationKey: ['user', dataForm], + url: '/api/login' +}); +``` + + + +👆👆👆👆 上面是**推荐**使用,**dataForm** 用于给 `body` 传递的 _json_ 数据 👆👆👆👆👆 + +```bash +import { fetchFn, useReactMutation } from '@antdp/hooks'; + +useReactMutation({ mutationKey: ['user'], url: '/api/login' }); +useReactMutation({ mutationKey: ['user'], url: '/api/login', method: 'PUT' }); +useReactMutation({ mutationKey: ['user', dataForm], url: '/api/login' }); +useReactMutation({ + mutationKey: ['user', dataForm], + mutationFn: () => fetchFn(`/api/login?id=${dataForm.userId}`, { method: 'PUT' }) +}); +useReactMutation({ + mutationKey: ['user', dataForm], + mutationFn: () => fetchFn('/api/login', { method: 'POST', body: JSON.stringify(dataForm) }), +}); +useReactMutation({ + mutationKey: ['user', dataForm], + mutationFn: async () => { + return fetchFn(`/api/login?id=${dataForm.username}`, { method: 'DELETE', body: JSON.stringify(dataForm) }); + }, +}); +useReactMutation({ + mutationKey: ['user', dataForm], + mutationFn: (data) => { + return fetchFn(`/api/login`, { method: 'POST', body: JSON.stringify(data) }) + }, +}); +``` + +**登录页面**示例 + +```bash +import UserLogin from '@antdp/user-login'; +import { useReactMutation } from '@antdp/hooks'; + +const UserLayout = (props) => { + const mutation = useReactMutation({ url: '/api/users/login' }); + return ( + { + const { code, token, data } = await mutation.mutateAsync(values); + // 请求成功后操作 + }} + type="account" + loading={mutation.loading} + formBtns={[ + { + label: '登录', + attr: { + type: 'primary', + htmlType: 'submit', + style: { + marginRight: 20, + }, + }, + }, + { + label: '重置', + attr: { + type: 'primary', + }, + }, + ]} + > + + ); +}; + +export default UserLayout; + +``` + +在任何给定时刻,`mutation` 只能处于以下状态之一: + +- `isIdle` or `status === 'idle'` - mutation 当前处于空闲状态或处于新鲜/重置状态 +- `isLoading` or `status === 'loading'` - mutation 当前正在运行 +- `isError` or `status === 'error'` - mutation 遇到错误 +- `isSuccess` or `status === 'success'` - mutation 成功并且 mutation 数据可用 + +除了这些主要状态之外,还可以根据 `mutation` 状态获得更多信息: + +- `error` - 如果 mutation 处于错误状态,则可以通过 error 属性获得错误。 +- `data` - 如果 mutation 处于成功状态,则数据可通过 data 属性获得。 + +```bash +const mutation = useReactMutation({ + url: '/api/login', + mutationKey: ['user-login', data], + method: 'PUT' +}); +``` + +##### 副作用 mutation 选项 + +```bash +const mutation = useReactMutation({ + url: '/api/login', + onMutate: (variables) => { + // mutation 即将发生! + + // 可选地返回包含数据的上下文,例如在回滚时使用 + return { id: 1 } + }, + onError: (error, variables, context) => { + // 发生错误! + console.log(`rolling back optimistic update with id ${context.id}`) + }, + onSuccess: (data, variables, context) => { + // Boom baby! + }, + onSettled: (data, error, variables, context) => { + // 错误或成功......没关系! + }, +}) +``` + +您可能会发现,在调用 `mutate` 时,您想要触发除 `useReactMutation` 上定义的回调之外的其他回调。 这可用于触发组件特定的副作用。 为此,您可以在 `mutate` 变量之后向 `mutate` 函数提供任何相同的回调选项。 支持的选项包括:`onSuccess`、`onError` 和 `onSettled`。 请记住,如果您的组件在变更完成之前卸载,那么这些额外的回调将不会运行。 + +```bash +mutation.mutate(todo, { + onSuccess: (data, variables, context) => { + // I will fire second! + }, + onError: (error, variables, context) => { + // I will fire second! + }, + onSettled: (data, error, variables, context) => { + // I will fire second! + }, +}) +``` + +### Query Keys + +TanStack Query 的核心是基于查询键为您管理查询缓存。查询键必须是顶层的数组,并且可以像具有单个字符串的数组一样简单,也可以像包含许多字符串和嵌套对象的数组一样复杂。 只要查询键是可序列化的,并且对于查询数据是唯一的,就可以使用它! + +```bash +useReactQuery({ + url: `https://api.example.com/users?status=${status}&page=${page}`, + queryKey: ['use-list', { status, page }], +}); +``` + +### 请求重试 + +```bash +const { isLoading, isError, data } = useReactQuery({ + url: `/api/user/list?id=${userId}`, + queryKey: ['user-list', userId], + retry: 10, // 在显示错误之前将重试失败的请求 10 次 +}); +``` + +## License + +Licensed under the MIT License. diff --git a/examples/website2/docs/guide/update.md b/examples/website2/docs/guide/update.md new file mode 100644 index 000000000..c82078cde --- /dev/null +++ b/examples/website2/docs/guide/update.md @@ -0,0 +1,237 @@ +--- +nav: 教程 +group: 入门 +order: 3 +--- + +# 从 v1 到 v2 + +### 升级准备 + +- 1.请先升级到 1.x 的最新版本。 + +### 技术调整 + +- 1.[antd 从 v4 到 v5](https://ant.design/docs/react/migration-v5-cn) +- 2.[umi 从 v3 到 v4](https://umijs.org/docs/introduce/upgrade-to-umi-4) +- 3.`basic-layouts` 重构,并添加 `useLayouts` 的 hooks [`2b3ad38`](https://github.com/antdpro/antdp/commit/2b3ad38deca0b31b9f575980bf1239249ae738b5) +- 4.`Authorized` 添加 `AuthorizedConfigProvider和useAuthorizedonfig` [`cf75f09`](https://github.com/antdpro/antdp/commit/cf75f096ad0646a1e831f45141cc7c84c1442c2d) +- 5.`react` 升级至 `18.x` + +### 修复功能 + +- 🐞 fix:修复文档和权限取值判断 [`978d203`](https://github.com/antdpro/antdp/commit/978d2038c395d0252bb4409973703d776c10213c) +- 🐞 fix:修复预览标签 [`5968554`](https://github.com/antdpro/antdp/commit/5968554197f09bd5d8b1f75331f2102bf38e4ec2) +- 🐞 fix:修复 umi setup [`a128746`](https://github.com/antdpro/antdp/commit/a128746362ad5804d0e94c9e9be0daff1a1b5cf3) +- 🐞 fix:修复案例包版本 [`f45300b`](https://github.com/antdpro/antdp/commit/f45300b90841b2435745c9a3460fd74c2131383b) +- 🆎 type:修复类型 [`29eeb92`](https://github.com/antdpro/antdp/commit/29eeb926c64a1958d7e8723462b75d28bddb1c90) +- 🐞 fix:修复表单设置初始隐藏表单项 [`86ac78f`](https://github.com/antdpro/antdp/commit/86ac78f4af5c7409c981501f633b60989d5c97b0) +- 🐞 fix:修复本地文档预览网站 [`869d72b`](https://github.com/antdpro/antdp/commit/869d72bc69132fd5b4f2faa4044ffd923e8f16ce) +- 🆎 type:修复类型 [`2b68319`](https://github.com/antdpro/antdp/commit/2b683192c1f3af1fed393c6329e8789ad09b986a) +- 🐞 fix:修复菜单隐藏问题 [`9bc0070`](https://github.com/antdpro/antdp/commit/9bc00702e76eb8548dc7f0f9022afffa804f85cf) +- 💄 chore:修复 website 依赖问题 [`7ce8e30`](https://github.com/antdpro/antdp/commit/7ce8e301a0880b36d9ef923f3c4e4477663dafe7) +- 💄 chore:修复依赖 [`a17a49a`](https://github.com/antdpro/antdp/commit/a17a49acae6e63cf38a0c8fb8941b8cb902652b9) +- 🐞 fix:修复渲染判断 [`c5e96df`](https://github.com/antdpro/antdp/commit/c5e96df0d50922ce08beef55844a0efe76735bbc) +- 🐞 fix:修复命令运行 [`8083747`](https://github.com/antdpro/antdp/commit/80837475fd9b8aa177d53a99ef8b41cc12b93273) +- 🐞 fix:修复全屏按钮 [`4f0c851`](https://github.com/antdpro/antdp/commit/4f0c8515a5467e776bc243b33f8ac67fec6c5523) + +### 开始升级 + +#### 依赖层升级 + +```diff + "dependencies": { + ... +- "@antdp/antdp-ui": "1.8.27", +- "@antdp/authorized": "1.8.27", +- "@antdp/basic-layouts": "1.8.27", +- "@antdp/edit-table": "1.8.27", +- "@antdp/hooks": "1.8.27", +- "@antdp/page-loading": "1.8.27", +- "@antdp/request": "1.8.27", +- "@antdp/user-login": "1.8.27", +- "ahooks": "~3.3.0", +- "antd": "4.24.1", +- "react": "17.0.2", +- "react-dom": "17.0.2" ++ "@antdp/antdp-ui": "2.0.0", ++ "@antdp/authorized": "1.8.27", ++ "@antdp/basic-layouts": "1.8.27", ++ "@antdp/edit-table": "1.8.27", ++ "@antdp/hooks": "1.8.27", ++ "@antdp/page-loading": "1.8.27", ++ "@antdp/request": "1.8.27", ++ "@antdp/user-login": "1.8.27", ++ "ahooks": "~3.7.2", ++ "antd": "5.4.2", ++ "react": "18.2.0", ++ "react-dom": "18.2.0" + ... + } +``` + +```diff + "devDependencies": { +- "@antdp/config": "1.8.27", +- "@antdp/dependencies": "1.8.27", +- "@umijs/plugin-dva": "0.13.2", +- "@umijs/plugin-locale": "0.16.0", +- "@umijs/plugin-model": "2.6.2", +- "@umijs/plugin-request": "2.9.0", +- "@umijs/test": "3.5.20", +- "lint-staged": "~12.3.7", +- "prettier": "2.4.1", +- "react-sortable-hoc": "2.0.0", +- "umi": "~3.5.35" ++ "@antdp/config": "2.0.0-bate-4.1", ++ "@antdp/dependencies": "2.0.0-bate-4.1", ++ "@umijs/max": "~4.0.47", ++ "@umijs/plugin-model": "~2.6.2", ++ "lint-staged": "~13.0.4", ++ "prettier": "2.8.1" + } +``` + +#### 代码层修改 + +Umi 4 中将 react-router@5 升级到 react-router@6,所以路由相关的一些 api 存在着使用上的差异。`props` 默认为空对象,属性都不能直接从 props 中取出 + +#### AuthorizedConfigProvider + +使用 `AuthorizedConfigProvider`可以自己进行重新设置组件包裹内的所有按钮的权限参数,不使用默认配置的按钮权限配置 + +```diff + sessionStorage.setItem("authBtn",JSON.stringify([{menuUrl:"/api/select"}])) + + import React from "react" ++ import { AuthorizedBtn ,AuthorizedConfigProvider } from "@antdp/authorized" + + const Page = ()=>{ + return ( ++ + + + ++ + ) +} +export default Page; +``` + +#### history + +```diff ++ import { history } from '@umijs/max'; +export default function Page(props) { + return ( +
{ +- props.history.push('list'); ++ history.push('list'); + }}> +
+ ); +} +``` + +#### location + +```diff ++ import { useLocation } from '@umijs/max'; +export default function Page(props) { ++ let location = useLocation(); + return ( +
+- { props.location } ++ { location } +
+ ); +} +``` + +#### match + +```diff ++ import { useMatch } from '@umijs/max'; +export default function Page(props) { ++ const match = useMatch({ path: 'list/search/:type' }); + return ( +
+- { props.match } ++ { match } +
+ ); +} +``` + +在 class component 组件中的使用方式: + +```diff ++import { matchPath } from '@umijs/max'; +class Page extends Component { ++ match = matchPath({ path: 'list/search/:type' }, window.location.pathname); + state = {} + render() { + return ( +
+- {this.props.match.type} ++ {this.match.type} +
+ ) + } +} +``` + +#### SelectLang 和 useIntl + +```diff ++ import { SelectLang,useIntl } from '@umijs/max'; +import BasicLayouts from '@antdp/basic-layouts'; +export default function Page(props) { ++ const match = useMatch({ path: 'list/search/:type' }); + return ( + } ++ intlLanguage={useIntl()} + /> + ); +} +``` + +#### useModel + +```diff ++ import { useModel } from '@umijs/max'; +export default function Page(props) { ++ const { token } = useModel('user', (model) => ({ ...model })); + return ( +
{token}
+ ); +} +``` + +更多 `Umi` 相关 [`api`](https://umijs.org/docs/api/api) +需要注意 match 数据的差异: + +```bash +// match v5 +isExact: true +params: {} +path: "/users/abc" +url: "/users/abc" + +// match v6 +params:{ } +pathname: "/list/search/articles" +pathnameBase: "/list/search/articles" +pattern: {path: 'list/search/:type'} +``` + +更多改动和 api 变更,请查阅[`react-router@6`](https://reactrouter.com/en/6.10.0) + +#### location 中的 query 找不到? + +```diff +- const { query } = history.location; ++ import { parse } from 'query-string'; ++ const query = parse(history.location.search); +``` diff --git a/examples/website2/docs/index.md b/examples/website2/docs/index.md new file mode 100644 index 000000000..e122114fe --- /dev/null +++ b/examples/website2/docs/index.md @@ -0,0 +1,21 @@ +--- +title: antdp +hero: + title: antdp + description: 一个基于 antd5.x 和 umi 的初始级别项目,集成路由、dva(Redux)、选项卡等特性 + actions: + - text: 立即上手 + link: /guide/quick-start + - text: GitHub + link: https://github.com/antdpro/antdp +features: + - title: 简化 + emoji: 💎 + description: 简化 antd 5.x + 和 umi 框架配置使用 + - title: 减少 + emoji: 🌈 + description: 减少项目配置和依赖,将配置集成到包中开箱即用 + - title: '简单' + emoji: 🚀 + description: 更简单直观的配置权限和更改 Layout +--- diff --git a/examples/website2/package.json b/examples/website2/package.json new file mode 100644 index 000000000..21be8b9a5 --- /dev/null +++ b/examples/website2/package.json @@ -0,0 +1,29 @@ +{ + "name": "@example/website2", + "version": "2.0.21", + "private": true, + "description": "A static site based on dumi", + "license": "MIT", + "scripts": { + "build": "dumi build", + "dev": "dumi dev", + "start": "npm run dev" + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "lint-staged": { + "*.{md,json}": [ + "prettier --write --no-error-on-unmatched-pattern" + ] + }, + "devDependencies": { + "@commitlint/cli": "^17.1.2", + "@commitlint/config-conventional": "^17.1.0", + "antd": "5.6.1", + "dumi": "^2.2.0" + }, + "authors": [] +} diff --git a/examples/website2/public/logo.svg b/examples/website2/public/logo.svg new file mode 100644 index 000000000..280577b47 --- /dev/null +++ b/examples/website2/public/logo.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/examples/website2/tsconfig.json b/examples/website2/tsconfig.json new file mode 100644 index 000000000..3ea66c2c5 --- /dev/null +++ b/examples/website2/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "baseUrl": "./", + "paths": { + "@@/*": [".dumi/tmp/*"] + } + }, + "include": [".dumi/**/*", ".dumirc.ts"] +}