├── .prettierignore
├── src
├── hooks
│ ├── index.tsx
│ └── useCssInJs.ts
├── util
│ ├── index.tsx
│ ├── index.md
│ ├── SessionStorageUtils.ts
│ ├── ReactDomUtils.tsx
│ └── demo
│ │ └── reactdom.tsx
├── ext-select
│ ├── index.tsx
│ ├── index.md
│ ├── demo
│ │ └── base.tsx
│ └── ExtSelect.tsx
├── fieldset
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── styles.ts
│ └── fieldset.tsx
├── page-header
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── styles.tsx
│ └── page-header.tsx
├── app-toolbar
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── styles.ts
│ └── app-toolbar.tsx
├── ext-switch
│ ├── index.tsx
│ ├── index.md
│ ├── demo
│ │ └── base.tsx
│ └── ExtSwitch.tsx
├── fields-mapping
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── images.tsx
│ ├── styles.tsx
│ └── fields-mapping.tsx
├── grid-table
│ ├── index.tsx
│ ├── index.md
│ ├── demo
│ │ ├── base.tsx
│ │ └── fit-table.tsx
│ ├── styles.ts
│ └── grid-table.tsx
├── ext-input
│ ├── index.md
│ ├── demo
│ │ └── base.tsx
│ ├── ExtInput.tsx
│ ├── index.tsx
│ ├── ExtInputPassword.tsx
│ ├── ExtInputTextArea.tsx
│ └── ExtInputOTP.tsx
├── ext-radio
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── ExtRadio.tsx
│ └── ExtRadioGroup.tsx
├── picture-upload
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── styles.ts
│ └── PictureUpload.tsx
├── table-toolbar
│ ├── index.md
│ ├── index.tsx
│ ├── styles.ts
│ ├── demo
│ │ └── base.tsx
│ └── table-toolbar.tsx
├── video-upload
│ ├── index.tsx
│ ├── styles.ts
│ ├── VideoUpload.tsx
│ └── Icons.tsx
├── avatar-editor
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── styles.ts
│ ├── AvatarEditor.tsx
│ └── AvatarCropModal.tsx
├── ext-checkbox
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── ExtCheckbox.tsx
│ └── ExtCheckboxGroup.tsx
├── ext-tree-select
│ ├── index.tsx
│ ├── index.md
│ ├── demo
│ │ └── base.tsx
│ └── ExtTreeSelect.tsx
├── fetch-select
│ ├── index.tsx
│ ├── index.md
│ ├── demo
│ │ ├── base.tsx
│ │ └── set-initial-option.tsx
│ └── fetch-select.tsx
├── field-wrapper
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── styles.ts
│ └── field-wrapper.tsx
├── editable-desc
│ ├── index.tsx
│ ├── index.md
│ ├── styles.ts
│ ├── demo
│ │ ├── base.tsx
│ │ └── form.tsx
│ └── editable-desc.tsx
├── drawer-form
│ ├── index.md
│ ├── demo
│ │ ├── show.tsx
│ │ └── base.tsx
│ ├── drawer-inner-form.tsx
│ ├── index.tsx
│ └── drawer-form.tsx
├── ext-date-picker
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ ├── ExtDatePicker.tsx
│ └── ExtDateRangePicker.tsx
├── ext-input-number
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ └── ExtInputNumber.tsx
├── modal-form
│ ├── index.md
│ ├── demo
│ │ ├── show.tsx
│ │ └── base.tsx
│ ├── modal-inner-form.tsx
│ ├── modal-form.tsx
│ └── index.tsx
├── search-toolbar
│ ├── index.tsx
│ ├── index.md
│ ├── styled.ts
│ ├── demo
│ │ └── base.tsx
│ └── search-toolbar.tsx
├── fetch-tree-select
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ └── base.tsx
│ └── fetch-tree-select.tsx
├── ext-form-field
│ ├── index.tsx
│ └── ExtFormField.tsx
├── verification-code-input
│ ├── index.tsx
│ ├── styles.ts
│ └── verification-code-input.tsx
├── layout
│ ├── index.md
│ ├── index.tsx
│ ├── demo
│ │ ├── layout-nest.tsx
│ │ └── base.tsx
│ ├── item.tsx
│ ├── layout.tsx
│ ├── styles.ts
│ └── sider.tsx
└── index.ts
├── .stylelintrc
├── public
└── logo.png
├── images
└── shuque_wx.jpg
├── .gitignore
├── .eslintrc.js
├── .fatherrc.ts
├── docs
├── guide.md
└── index.md
├── .editorconfig
├── tsconfig.json
├── .prettierrc.js
├── .github
└── workflows
│ └── gh-pages.yml
├── .dumirc.ts
├── LICENSE
├── README.md
└── package.json
/.prettierignore:
--------------------------------------------------------------------------------
1 | /dist
2 | *.yaml
3 |
--------------------------------------------------------------------------------
/src/hooks/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './useCssInJs';
2 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@umijs/lint/dist/config/stylelint"
3 | }
4 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trionesdev/antd-react-ext/HEAD/public/logo.png
--------------------------------------------------------------------------------
/src/util/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './ReactDomUtils';
2 | export * from './SessionStorageUtils';
3 |
--------------------------------------------------------------------------------
/images/shuque_wx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trionesdev/antd-react-ext/HEAD/images/shuque_wx.jpg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /dist
3 | .dumi/tmp
4 | .dumi/tmp-test
5 | .dumi/tmp-production
6 | .DS_Store
7 | .idea
8 | .npmrc
9 |
--------------------------------------------------------------------------------
/src/ext-select/index.tsx:
--------------------------------------------------------------------------------
1 | import {ExtSelect,ExtSelectProps} from "./ExtSelect"
2 |
3 | export type {ExtSelectProps}
4 | export default ExtSelect
--------------------------------------------------------------------------------
/src/fieldset/index.md:
--------------------------------------------------------------------------------
1 | # Fieldset 分组标签
2 |
3 | > 对相关元素进行分组
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/page-header/index.md:
--------------------------------------------------------------------------------
1 | # PageHeader 页头
2 |
3 | > 页头
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/app-toolbar/index.md:
--------------------------------------------------------------------------------
1 | # AppToolbar 应用工具栏
2 |
3 | > 应用工具栏
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-switch/index.tsx:
--------------------------------------------------------------------------------
1 | import {ExtSwitch,ExtSwitchProps} from "./ExtSwitch"
2 |
3 | export type {ExtSwitchProps}
4 | export default ExtSwitch
5 |
--------------------------------------------------------------------------------
/src/fieldset/index.tsx:
--------------------------------------------------------------------------------
1 | import {Fieldset,FieldsetProps} from './fieldset';
2 |
3 | export type { FieldsetProps };
4 | export default Fieldset;
5 |
--------------------------------------------------------------------------------
/src/fields-mapping/index.md:
--------------------------------------------------------------------------------
1 | # FieldsMapping 字段映射
2 |
3 | > 字段映射
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/grid-table/index.tsx:
--------------------------------------------------------------------------------
1 | import GridTable, {GridTableProps} from './grid-table';
2 |
3 | export type {GridTableProps}
4 | export default GridTable
5 |
--------------------------------------------------------------------------------
/src/app-toolbar/index.tsx:
--------------------------------------------------------------------------------
1 | import AppToolbar, {AppToolbarProps} from "./app-toolbar"
2 |
3 | export type {AppToolbarProps}
4 | export default AppToolbar
5 |
--------------------------------------------------------------------------------
/src/ext-input/index.md:
--------------------------------------------------------------------------------
1 | # ExtInput 扩展Input
2 |
3 | > 增加Input的只读模式,用于展示
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-radio/index.md:
--------------------------------------------------------------------------------
1 | # ExtRadio 扩展Radio
2 |
3 | > 增加Radio的只读模式,用于展示
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/page-header/index.tsx:
--------------------------------------------------------------------------------
1 | import PageHeader, {PageHeaderProps} from "./page-header";
2 |
3 | export type {PageHeaderProps}
4 | export default PageHeader
5 |
--------------------------------------------------------------------------------
/src/picture-upload/index.md:
--------------------------------------------------------------------------------
1 | # PictureUpload 图片上传
2 |
3 | > 图片上传组件
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/table-toolbar/index.md:
--------------------------------------------------------------------------------
1 | # TableToolbar 表格工具栏
2 |
3 | > Table 的工具栏
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-select/index.md:
--------------------------------------------------------------------------------
1 | # ExtSelect 扩展Select
2 |
3 | > 增加Select的只读模式,用于展示
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-switch/index.md:
--------------------------------------------------------------------------------
1 | # ExtSwitch 扩展Switch
2 |
3 | > 增加Switch的只读模式,用于展示
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/video-upload/index.tsx:
--------------------------------------------------------------------------------
1 | import { VideoUpload, VideoUploadProps } from './VideoUpload';
2 | export type { VideoUploadProps };
3 | export default VideoUpload;
4 |
--------------------------------------------------------------------------------
/src/avatar-editor/index.md:
--------------------------------------------------------------------------------
1 | # AvatarEditor 头像编辑器
2 |
3 | > 头像编辑器,带图片裁剪功能的头像组件
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-checkbox/index.md:
--------------------------------------------------------------------------------
1 | # ExtCheckbox 扩展Checkbox
2 |
3 | > 增加Checkbox的只读模式,用于展示
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-tree-select/index.tsx:
--------------------------------------------------------------------------------
1 | import {ExtTreeSelect,ExtTreeSelectProps} from "./ExtTreeSelect"
2 |
3 | export type {ExtTreeSelectProps}
4 | export default ExtTreeSelect
5 |
--------------------------------------------------------------------------------
/src/fetch-select/index.tsx:
--------------------------------------------------------------------------------
1 | import { FetchSelect, FetchSelectProps } from './fetch-select';
2 |
3 | export type { FetchSelectProps };
4 | export default FetchSelect;
5 |
--------------------------------------------------------------------------------
/src/field-wrapper/index.md:
--------------------------------------------------------------------------------
1 | # FileWrapper 字段包装器
2 |
3 | > 字段包装器,用于将内容包装成antd的表单字段风格
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/table-toolbar/index.tsx:
--------------------------------------------------------------------------------
1 | import TableToolbar, {TableToolbarProps} from "./table-toolbar";
2 |
3 | export type {TableToolbarProps}
4 | export default TableToolbar
5 |
--------------------------------------------------------------------------------
/src/avatar-editor/index.tsx:
--------------------------------------------------------------------------------
1 | import { AvatarEditor, AvatarEditorProps } from './AvatarEditor';
2 |
3 | export type { AvatarEditorProps };
4 | export default AvatarEditor;
5 |
--------------------------------------------------------------------------------
/src/editable-desc/index.tsx:
--------------------------------------------------------------------------------
1 | import { EditableDesc, EditableDescProps } from './editable-desc';
2 |
3 | export type { EditableDescProps };
4 | export default EditableDesc;
5 |
--------------------------------------------------------------------------------
/src/ext-tree-select/index.md:
--------------------------------------------------------------------------------
1 | # ExtTreeSelect 扩展TreeSelect
2 |
3 | > 增加TreeSelect的只读模式,用于展示
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/field-wrapper/index.tsx:
--------------------------------------------------------------------------------
1 | import { FieldWrapper, FieldWrapperProps } from './field-wrapper';
2 |
3 | export type { FieldWrapperProps };
4 | export default FieldWrapper;
5 |
--------------------------------------------------------------------------------
/src/drawer-form/index.md:
--------------------------------------------------------------------------------
1 | # DrawerForm 抽屉表单
2 |
3 | > 抽屉表单
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-date-picker/index.md:
--------------------------------------------------------------------------------
1 | # ExtDatePicker 扩展DatePicker
2 |
3 | > 增加DatePicker的只读模式,用于展示
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-input-number/index.md:
--------------------------------------------------------------------------------
1 | # ExtInputNumber 扩展InputNumber
2 |
3 | > 增加InputNumber的只读模式,用于展示
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-input-number/index.tsx:
--------------------------------------------------------------------------------
1 | import {ExtInputNumber,ExtInputNumberProps} from "./ExtInputNumber"
2 |
3 | export type {ExtInputNumberProps}
4 | export default ExtInputNumber
5 |
--------------------------------------------------------------------------------
/src/fields-mapping/index.tsx:
--------------------------------------------------------------------------------
1 | import FieldsMapping, { FieldsMappingProps } from './fields-mapping';
2 |
3 | export type { FieldsMappingProps };
4 | export default FieldsMapping;
5 |
--------------------------------------------------------------------------------
/src/modal-form/index.md:
--------------------------------------------------------------------------------
1 | # ModalForm 模态框表单
2 |
3 | > 模态框表单
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/picture-upload/index.tsx:
--------------------------------------------------------------------------------
1 | import { PictureUpload, PictureUploadProps } from './PictureUpload';
2 |
3 | export type { PictureUploadProps };
4 | export default PictureUpload;
5 |
--------------------------------------------------------------------------------
/src/search-toolbar/index.tsx:
--------------------------------------------------------------------------------
1 | import SearchToolbar, { SearchToolbarProps } from './search-toolbar';
2 |
3 | export type { SearchToolbarProps };
4 | export default SearchToolbar;
5 |
--------------------------------------------------------------------------------
/src/fetch-tree-select/index.md:
--------------------------------------------------------------------------------
1 | # FetchTreeSelect 远程数据的TreeSelect
2 |
3 | > 获取远程数据的TreeSelect
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ext-form-field/index.tsx:
--------------------------------------------------------------------------------
1 | import {ExtFormField} from "./ExtFormField"
2 |
3 | export type {CommonExtFormFieldProps, ExtFormFieldProps} from "./ExtFormField"
4 | export default ExtFormField;
5 |
--------------------------------------------------------------------------------
/src/fetch-tree-select/index.tsx:
--------------------------------------------------------------------------------
1 | import { FetchTreeSelect, FetchTreeSelectProps } from './fetch-tree-select';
2 |
3 | export type { FetchTreeSelectProps };
4 | export default FetchTreeSelect;
5 |
--------------------------------------------------------------------------------
/src/editable-desc/index.md:
--------------------------------------------------------------------------------
1 | # EditableDesc 可编辑描述
2 |
3 | > 表单内容可编辑
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: require.resolve('@umijs/lint/dist/config/eslint'),
3 | rules: {
4 | 'no-unused-vars': 'off',
5 | '@typescript-eslint/no-unused-vars': ['off'],
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'father';
2 |
3 | export default defineConfig({
4 | // more father config: https://github.com/umijs/father/blob/master/docs/config.md
5 | esm: { output: 'dist' },
6 | });
7 |
--------------------------------------------------------------------------------
/src/grid-table/index.md:
--------------------------------------------------------------------------------
1 | # GridTable 表格
2 |
3 | > 表格
4 |
5 |
6 |
7 | > 撑满一个高度为300px的容器,内部高度滚动
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/search-toolbar/index.md:
--------------------------------------------------------------------------------
1 | # SearchToolbar 搜索工具栏
2 |
3 | > 搜索工具栏
4 |
5 | > 响应式栅格,配置建议 span={6} xxl={4} xl={6} lg={6} md={8} sm={12} xs={24}
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/util/index.md:
--------------------------------------------------------------------------------
1 | # Util 工具类
2 |
3 | > 工具类
4 |
5 | ReactDomUtils
6 |
7 | ReactDomUtils.render 通过静态函数创建组件,可以指定挂载到的元素,如不指定则挂载到body下。多用于创建模态框,抽屉等组件。
8 | 组件销毁,可以调用通过调用 onDestroy 函数。
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/verification-code-input/index.tsx:
--------------------------------------------------------------------------------
1 | import VerificationCodeInput, {
2 | VerificationCodeInputProps,
3 | } from './verification-code-input';
4 |
5 | export type { VerificationCodeInputProps };
6 |
7 | export default VerificationCodeInput;
8 |
--------------------------------------------------------------------------------
/docs/guide.md:
--------------------------------------------------------------------------------
1 | # TrionesDev Antd Ext
2 |
3 | ## 概述
4 |
5 | 该项目是基于 antd 的一个扩展组件库
6 |
7 | ## 安装
8 |
9 | ```
10 | npm install @trionesdev/antd-react-ext@latest
11 | ```
12 |
13 | ```
14 | yarn add @trionesdev/antd-react-ext@latest
15 | ```
16 |
--------------------------------------------------------------------------------
/src/field-wrapper/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FieldWrapper } from '../field-wrapper';
3 |
4 | export default () => {
5 | return (
6 |
7 | 这是一个字段
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/src/fieldset/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Fieldset} from '@trionesdev/antd-react-ext';
3 |
4 | export default ()=>{
5 | return
6 |
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/src/fetch-select/index.md:
--------------------------------------------------------------------------------
1 | # FetchSelect 远程数据的Select
2 |
3 | > 获取远程数据的Select,例如列表场景下使用,可以通过行数据中组装initialValueOption,满足Select在不请求数据的情况下,直接渲染Select的场景
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/picture-upload/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PictureUpload from '../index';
3 |
4 | export default () => {
5 | return (
6 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/src/layout/index.md:
--------------------------------------------------------------------------------
1 | # Layout 布局
2 |
3 | > 布局
4 |
5 |
6 |
7 |
8 | #### Layout
9 |
10 | #### Layout.Item
11 |
12 | #### Layout.Sider
13 |
14 |
--------------------------------------------------------------------------------
/src/modal-form/demo/show.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'antd';
2 | import React from 'react';
3 | import ModalForm from '../index';
4 |
5 | export default () => {
6 | return (
7 |
8 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/page-header/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { PageHeader } from '@trionesdev/antd-react-ext';
2 | import { Button } from 'antd';
3 | import React from 'react';
4 |
5 | export default () => (
6 |
11 | 发布
12 | ,
13 | ]}
14 | />
15 | );
16 |
--------------------------------------------------------------------------------
/src/drawer-form/demo/show.tsx:
--------------------------------------------------------------------------------
1 | import { DrawerForm } from '@trionesdev/antd-react-ext';
2 | import { Button } from 'antd';
3 | import React from 'react';
4 |
5 | export default () => {
6 | return (
7 |
8 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "declaration": true,
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "jsx": "react",
8 | "baseUrl": "./",
9 | "paths": {
10 | "@@/*": [".dumi/tmp/*"],
11 | "@trionesdev/antd-react-ext": ["src"],
12 | "@trionesdev/antd-react-ext/*": ["src/*", "*"]
13 | }
14 | },
15 | "include": [".dumirc.ts", "src/**/*"]
16 | }
17 |
--------------------------------------------------------------------------------
/src/ext-radio/index.tsx:
--------------------------------------------------------------------------------
1 | import {ExtRadio as InternalExtRadio, ExtRadioProps} from "./ExtRadio"
2 | import {ExtRadioGroup, ExtRadioGroupProps} from "./ExtRadioGroup"
3 |
4 | type CompoundedComponent = typeof InternalExtRadio & {
5 | Group: typeof ExtRadioGroup
6 | }
7 |
8 | const ExtRadio = InternalExtRadio as CompoundedComponent
9 | ExtRadio.Group = ExtRadioGroup
10 | export type {ExtRadioProps, ExtRadioGroupProps}
11 | export default ExtRadio
12 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pluginSearchDirs: false,
3 | plugins: [
4 | require.resolve('prettier-plugin-organize-imports'),
5 | require.resolve('prettier-plugin-packagejson'),
6 | ],
7 | printWidth: 80,
8 | proseWrap: 'never',
9 | singleQuote: true,
10 | trailingComma: 'all',
11 | overrides: [
12 | {
13 | files: '*.md',
14 | options: {
15 | proseWrap: 'preserve',
16 | },
17 | },
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: TrionesDev Antd Ext
4 | description: 基于Antd组件库的扩展
5 | actions:
6 | - text: Get Started →
7 | link: /guide
8 | features:
9 | - title: 丰富
10 | emoji: 💎
11 | description: 基于antd组件库进行扩展,根据业务需求,封装出符合业务要求的组件
12 | - title: 简单
13 | emoji: 🌈
14 | description: 不引入额外的UI组件库,除了antd其他的效果直接开发
15 | - title: 易用
16 | emoji: 🚀
17 | description: 尽量复用antd组件的属性配置,使用方式尽可能简单
18 | ---
19 |
20 | antd 组件扩展库
21 |
--------------------------------------------------------------------------------
/src/layout/index.tsx:
--------------------------------------------------------------------------------
1 | import { LayoutItem } from './item';
2 | import { Layout as InternalLayout, LayoutProps } from './layout';
3 | import { LayoutSider } from './sider';
4 |
5 | type CompoundedComponent = typeof InternalLayout & {
6 | Item: typeof LayoutItem;
7 | Sider: typeof LayoutSider;
8 | };
9 | const Layout = InternalLayout as CompoundedComponent;
10 | Layout.Item = LayoutItem;
11 | Layout.Sider = LayoutSider;
12 | export type { LayoutProps };
13 | export default Layout;
14 |
--------------------------------------------------------------------------------
/src/ext-checkbox/index.tsx:
--------------------------------------------------------------------------------
1 | import { ExtCheckBox as InternalExtCheckbox,ExtCheckBoxProps } from './ExtCheckbox';
2 | import { ExtCheckboxGroup,ExtCheckBoxGroupProps } from './ExtCheckboxGroup';
3 |
4 |
5 | type CompoundedComponent = typeof InternalExtCheckbox & {
6 | Group: typeof ExtCheckboxGroup;
7 | };
8 |
9 | const ExtCheckbox = InternalExtCheckbox as CompoundedComponent;
10 | ExtCheckbox.Group = ExtCheckboxGroup;
11 | export type { ExtCheckBoxProps, ExtCheckBoxGroupProps };
12 | export default ExtCheckbox;
13 |
--------------------------------------------------------------------------------
/src/ext-date-picker/index.tsx:
--------------------------------------------------------------------------------
1 | import { ExtDatePicker as InternalExtDatePicker,ExtDatePickerProps } from './ExtDatePicker';
2 | import { ExtDateRangePicker,ExtDateRangePickerProps } from './ExtDateRangePicker';
3 | type CompoundedComponent = typeof InternalExtDatePicker &{
4 | RangePicker: typeof ExtDateRangePicker;
5 | };
6 |
7 | const ExtDatePicker = InternalExtDatePicker as CompoundedComponent;
8 | ExtDatePicker.RangePicker = ExtDateRangePicker;
9 |
10 | export type { ExtDatePickerProps,ExtDateRangePickerProps };
11 |
12 | export default ExtDatePicker
13 |
--------------------------------------------------------------------------------
/src/ext-switch/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Switch } from 'antd';
3 | import { ExtSwitch } from '@trionesdev/antd-react-ext';
4 |
5 | export default ()=>{
6 | const [readOnly, setReadOnly] = useState(false);
7 | const [value, setValue] = useState();
8 | return (
9 | <>
10 |
11 |
12 |
13 |
14 | >
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/grid-table/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { GridTable } from '@trionesdev/antd-react-ext';
2 | import React from 'react';
3 |
4 | export default () => {
5 | const columns = [
6 | {
7 | title: '姓名',
8 | dataIndex: 'name',
9 | },
10 | {
11 | title: '年龄',
12 | dataIndex: 'age',
13 | },
14 | ];
15 |
16 | const dataScore = [
17 | { name: '小明', age: 19 },
18 | { name: '小王', age: 38 },
19 | ];
20 |
21 | return (
22 |
23 |
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/fieldset/styles.ts:
--------------------------------------------------------------------------------
1 | import { GlobalToken } from 'antd';
2 |
3 | export const genFieldsetStyle = (
4 | prefixCls: string,
5 | token: GlobalToken,
6 | ): any => {
7 | return {
8 | [`.${prefixCls}`]: {
9 | [`&-title`]: {
10 | color: token.colorTextBase,
11 | fontSize: token.fontSizeHeading5,
12 | fontWeight: token.fontWeightStrong,
13 | borderBottom: `1px solid ${token.colorBorder}`,
14 | marginBottom: '16px',
15 | paddingBottom: '8px',
16 | paddingTop: '16px',
17 | },
18 | },
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/src/fetch-select/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FetchSelect from '../index';
3 |
4 | export default () => {
5 | const [value, setValue] = React.useState(2);
6 | return (
7 | {
9 | return Promise.resolve([
10 | { id: 1, name: '小明' },
11 | { id: 2, name: '小红' },
12 | ]);
13 | }}
14 | fieldNames={{ label: 'name', value: 'id' }}
15 | initialValueOptions={[{ id: 2, name: '小红' }]}
16 | value={value}
17 | onChange={setValue}
18 | />
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/verification-code-input/styles.ts:
--------------------------------------------------------------------------------
1 | import { CSSInterpolation } from '@ant-design/cssinjs';
2 | import { GlobalToken } from 'antd';
3 |
4 | export const genValidationCodeInputStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | [`&-send`]: {
11 | cursor: 'pointer',
12 | '&:hover': {
13 | color: token.colorPrimary,
14 | },
15 | },
16 | [`&-counting`]: {
17 | cursor: 'not-allowed',
18 | color: token.colorTextDisabled,
19 | },
20 | },
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/src/ext-date-picker/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Input, Switch } from 'antd';
3 | import { ExtDatePicker, ExtInput } from '@trionesdev/antd-react-ext';
4 |
5 | export default () => {
6 | const [readOnly, setReadOnly] = useState(false);
7 | const [value, setValue] = useState();
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/src/video-upload/styles.ts:
--------------------------------------------------------------------------------
1 | import { GlobalToken } from 'antd';
2 |
3 | export const genVideoUploadStyle = (
4 | prefixCls: string,
5 | token: GlobalToken,
6 | ): any => {
7 | return {
8 | [`.${prefixCls}`]: {
9 | borderRadius: token.borderRadiusLG,
10 | border: `1px solid ${token.colorBorder}`,
11 | padding: 8,
12 | [`&-player`]: {
13 | overflow: 'hidden',
14 | [`&-upload-wrapper`]: {
15 | height: '100%',
16 | display: 'flex',
17 | justifyContent: 'center',
18 | alignItems: 'center',
19 | },
20 | },
21 | },
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/src/avatar-editor/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { AvatarEditor } from '@trionesdev/antd-react-ext';
2 | import { Button } from 'antd';
3 | import React from 'react';
4 |
5 | export default () => {
6 | const [avatar, setAvatar] = React.useState('');
7 |
8 | return (
9 |
10 |
11 |
20 |
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/search-toolbar/styled.ts:
--------------------------------------------------------------------------------
1 | import {GlobalToken} from 'antd';
2 | import {CSSInterpolation} from '@ant-design/cssinjs';
3 |
4 | export const genSearchToolbarStyle = (
5 | prefixCls: string,
6 | token: GlobalToken
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | paddingTop: 16,
11 | paddingLeft: 16,
12 | paddingRight: 16,
13 | boxSizing: 'border-box',
14 | [`.ant-form-item`]:{
15 | marginBottom:16
16 | },
17 | [`&-col-hidden`]: {
18 | display: 'none',
19 | },
20 | },
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/src/hooks/useCssInJs.ts:
--------------------------------------------------------------------------------
1 | import { CSSInterpolation, Theme, useStyleRegister } from '@ant-design/cssinjs';
2 | import { theme } from 'antd';
3 |
4 | const { useToken } = theme;
5 |
6 | export type CssInJsProps = {
7 | prefix: string;
8 | styleFun?: (prefix: string, token?: any) => CSSInterpolation;
9 | };
10 | export const useCssInJs = (params?: CssInJsProps) => {
11 | const { theme, token, hashId } = useToken();
12 | // @ts-ignore
13 | const ss: Theme = theme;
14 | useStyleRegister(
15 | { theme: ss, token, hashId, path: [`${params?.prefix}`] },
16 | () => [params?.styleFun?.(params?.prefix, token)],
17 | );
18 | return { hashId };
19 | };
20 |
--------------------------------------------------------------------------------
/src/editable-desc/styles.ts:
--------------------------------------------------------------------------------
1 | import { CSSInterpolation } from '@ant-design/cssinjs';
2 | import { GlobalToken } from 'antd';
3 |
4 | export const genEditableDescStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | display: 'inline-flex',
11 | gap: 4,
12 | alignItems: 'center',
13 | lineHeight: '31px',
14 | [`&-col-auto`]: {
15 | flex: '1 auto',
16 | },
17 | [`&-render`]: {
18 | minWidth: 24,
19 | minHeight: 31,
20 | [`&.editable`]: {
21 | cursor: 'pointer',
22 | },
23 | },
24 | },
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/src/fetch-tree-select/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { FetchTreeSelect } from '@trionesdev/antd-react-ext';
2 | import React from 'react';
3 |
4 | export default () => {
5 | const [value, setValue] = React.useState(2);
6 | return (
7 | {
10 | return Promise.resolve([
11 | { id: 1, name: '小明', children: [{ id: 11, name: '小明1' }] },
12 | { id: 2, name: '小红' },
13 | ]);
14 | }}
15 | fieldNames={{ label: 'name', value: 'id' }}
16 | initialValueOptions={[{ id: 2, name: '小红' }]}
17 | value={value}
18 | onChange={setValue}
19 | />
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/modal-form/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { ModalForm } from '@trionesdev/antd-react-ext';
2 | import { Button, Form, Input } from 'antd';
3 | import React from 'react';
4 |
5 | export default () => {
6 | const [open, setOpen] = React.useState(false);
7 | return (
8 |
9 | 新建}
11 | open={open}
12 | onTriggerClick={() => {
13 | setOpen(true);
14 | }}
15 |
16 | onCancel={() => setOpen(false)}
17 | title={`表单`}
18 | >
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/ext-input-number/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import {Input, Switch} from "antd";
3 | import {ExtInputNumber} from "@trionesdev/antd-react-ext";
4 |
5 | export default () => {
6 | const [readOnly, setReadOnly] = useState(false);
7 | const [value, setValue] = useState();
8 | return
9 |
10 |
11 |
12 |
13 | {
14 | setValue(v)
15 | }}/>
16 | {
17 | setValue(v)
18 | }}/>
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/table-toolbar/styles.ts:
--------------------------------------------------------------------------------
1 | import {GlobalToken} from "antd";
2 | import {CSSInterpolation} from "@ant-design/cssinjs";
3 |
4 | export const genTableToolbarStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | display: 'flex',
11 | justifyContent: 'space-between',
12 | padding: '8px',
13 | boxSizing: 'border-box',
14 | [`&-title`]: {
15 | display: 'flex',
16 | justifyContent: 'flex-start',
17 | alignItems: 'center',
18 | },
19 | [`&-extra`]: {
20 | display: 'flex',
21 | justifyContent: 'flex-end',
22 | },
23 | },
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: github pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master # default branch
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | deploy:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 | - run: npm install
17 | # 文档编译命令,如果是 react 模板需要修改为 npm run docs:build
18 | - run: npm run docs:build
19 | - name: Deploy
20 | uses: peaceiris/actions-gh-pages@v3
21 | with:
22 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
23 | # github_token: ${{ secrets.GITHUB_TOKEN }}
24 | # 文档目录,如果是 react 模板需要修改为 docs-dist
25 | publish_dir: ./docs-dist
26 |
--------------------------------------------------------------------------------
/src/app-toolbar/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { AppToolbar } from '@trionesdev/antd-react-ext';
2 | import { Button } from 'antd';
3 | import React from 'react';
4 |
5 | export default () => {
6 | const items = [
7 | {
8 | key: 'user',
9 | label: '用户',
10 | },
11 | {
12 | key: 'permission',
13 | label: '权限',
14 | },
15 | {
16 | key: '1',
17 | label: '功能清单',
18 | },
19 | ];
20 | return (
21 |
22 |
27 | 设置
28 | ,
29 | ]}
30 | />
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/drawer-form/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { DrawerForm } from '@trionesdev/antd-react-ext';
2 | import { Button, Form, Input } from 'antd';
3 | import React from 'react';
4 |
5 | export default () => {
6 | const [open, setOpen] = React.useState(false);
7 | return (
8 |
9 | 新建}
11 | title={`表单`}
12 | open={open}
13 | onTriggerClick={() => {
14 | setOpen(true);
15 | }}
16 | onClose={() => setOpen(false)}
17 | onCancel={() => setOpen(false)}
18 | >
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/ext-input/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Input, Switch } from 'antd';
3 | import { ExtInput } from '@trionesdev/antd-react-ext';
4 |
5 | export default () => {
6 | const [readOnly, setReadOnly] = useState(false);
7 | const [value, setValue] = useState();
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | {
17 | console.log(e.target.value);
18 | setValue(e.target.value);
19 | }}
20 | readonly={readOnly}
21 | />
22 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/layout/demo/layout-nest.tsx:
--------------------------------------------------------------------------------
1 | import { Layout } from '@trionesdev/antd-react-ext';
2 | import React from 'react';
3 |
4 | export default () => {
5 | return (
6 |
7 |
8 | header
9 |
10 | left
11 | content
12 |
13 | right
14 |
15 |
16 | footer
17 |
18 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/ext-input/ExtInput.tsx:
--------------------------------------------------------------------------------
1 | import { Input, InputProps } from 'antd';
2 | import React, { FC } from 'react';
3 | import ExtFormField, { CommonExtFormFieldProps } from '../ext-form-field';
4 |
5 | export type ExtInputProps = InputProps & CommonExtFormFieldProps;
6 |
7 | export const ExtInput: FC = ({
8 | readonly,
9 | valueRender,
10 | defaultRender,
11 | emptyPlaceholder,
12 | ...rest
13 | }) => {
14 | return (
15 |
23 |
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/field-wrapper/styles.ts:
--------------------------------------------------------------------------------
1 | import { CSSInterpolation } from '@ant-design/cssinjs';
2 | import { GlobalToken } from 'antd';
3 |
4 | export const genFieldWrapperStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | boxSizing: 'border-box',
11 | border: `1px solid ${token.colorBorder}`,
12 | borderRadius: token.borderRadius,
13 | paddingBlock: 4,
14 | paddingInline: 11,
15 | '&:hover': {
16 | border: `1px solid ${token.colorPrimaryBorderHover}`,
17 | },
18 | [`&-sm`]: {
19 | paddingBlock: 0,
20 | paddingInline: 11,
21 | },
22 | [`&-lg`]: {
23 | paddingBlock: 7,
24 | paddingInline: 11,
25 | },
26 | },
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/src/ext-input/index.tsx:
--------------------------------------------------------------------------------
1 | import {ExtInput as InternalExtInput, ExtInputProps} from "./ExtInput"
2 | import {ExtInputPassword, ExtInputPasswordProps} from "./ExtInputPassword"
3 | import {ExtInputTextArea, ExtInputTextAreaProps} from "./ExtInputTextArea"
4 | import {ExtInputOTP, ExtInputOPTProps} from "./ExtInputOTP"
5 |
6 | type CompoundedComponent = typeof InternalExtInput & {
7 | TextArea: typeof ExtInputTextArea;
8 | Password: typeof ExtInputPassword;
9 | OTP: typeof ExtInputOTP;
10 | };
11 |
12 | const ExtInput = InternalExtInput as CompoundedComponent;
13 | ExtInput.TextArea = ExtInputTextArea;
14 | ExtInput.Password = ExtInputPassword;
15 | ExtInput.OTP = ExtInputOTP;
16 |
17 | export type {ExtInputProps, ExtInputTextAreaProps, ExtInputPasswordProps, ExtInputOPTProps}
18 | export default ExtInput
19 |
--------------------------------------------------------------------------------
/src/table-toolbar/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { GridTable, TableToolbar } from '@trionesdev/antd-react-ext';
2 | import { Button } from 'antd';
3 | import React from 'react';
4 |
5 | export default () => {
6 | const columns = [
7 | {
8 | title: '姓名',
9 | dataIndex: 'name',
10 | },
11 | {
12 | title: '年龄',
13 | dataIndex: 'age',
14 | },
15 | ];
16 |
17 | const dataScore = [
18 | { name: '小明', age: 19 },
19 | { name: '小王', age: 38 },
20 | ];
21 |
22 | const tableBar = (
23 | 新建用户]}
26 | />
27 | );
28 |
29 | return (
30 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/fieldset/fieldset.tsx:
--------------------------------------------------------------------------------
1 | import { useCssInJs } from '@trionesdev/antd-react-ext';
2 | import classNames from 'classnames';
3 | import React, { FC } from 'react';
4 | import { genFieldsetStyle } from './styles';
5 |
6 | export type FieldsetProps = {
7 | children?: React.ReactNode;
8 | title?: React.ReactNode;
9 | legend?: React.ReactNode;
10 | };
11 | export const Fieldset: FC = ({ children, title, legend }) => {
12 | const prefixCls = 'triones-ant-fieldset';
13 | const { hashId } = useCssInJs({
14 | prefix: prefixCls,
15 | styleFun: genFieldsetStyle,
16 | });
17 | return (
18 |
19 | {(title || legend) && (
20 |
21 | {title || legend}
22 |
23 | )}
24 | {children}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/util/SessionStorageUtils.ts:
--------------------------------------------------------------------------------
1 | type StorageDataType = {
2 | data: any;
3 | expire: number;
4 | };
5 | export class SessionStorageUtils {
6 | static setExpireItem(key?: string, data?: any, expire?: number) {
7 | if (!key || !data) {
8 | return;
9 | }
10 | sessionStorage.setItem(
11 | key,
12 | JSON.stringify({
13 | data,
14 | expire: Date.now() + (expire ?? 0) * 1000,
15 | }),
16 | );
17 | }
18 |
19 | static getExpireItem(key?: string, expireRemove?: boolean) {
20 | if (!key) {
21 | return null;
22 | }
23 | const val = sessionStorage.getItem(key!);
24 | if (val) {
25 | const storageData = JSON.parse(val);
26 | if (Date.now() > storageData.expire) {
27 | sessionStorage.removeItem(key!);
28 | return null;
29 | } else {
30 | return storageData?.data;
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/layout/item.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import React, { FC } from 'react';
3 | import { useCssInJs } from '../hooks';
4 | import { genItemStyle } from './styles';
5 |
6 | export type LayoutItemProps = {
7 | children?: React.ReactNode;
8 | className?: string | undefined;
9 | style?: React.CSSProperties;
10 | auto?: boolean;
11 | };
12 | export const LayoutItem: FC = ({
13 | children,
14 | className,
15 | style,
16 | auto,
17 | }) => {
18 | const prefixCls = 'triones-ant-layout-item';
19 | const { hashId } = useCssInJs({
20 | prefix: prefixCls,
21 | styleFun: genItemStyle,
22 | });
23 | return (
24 |
33 | {children}
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/src/util/ReactDomUtils.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | export type ReactDomRenderType = {
5 | onDestroy?: () => void;
6 | container?: HTMLElement;
7 | };
8 |
9 | export class ReactDomUtils {
10 | static render(children: React.ReactElement, container?: HTMLElement) {
11 | const div = document.createElement('div');
12 | const body = container || document.body;
13 | body.appendChild(div);
14 |
15 | const root = ReactDOM.createRoot(div as HTMLElement);
16 |
17 | function destroy() {
18 | setTimeout(() => root.unmount());
19 | if (div.parentNode) {
20 | div.parentNode.removeChild(div);
21 | }
22 | }
23 |
24 | root.render(
25 | React.cloneElement(children as React.ReactElement, {
26 | ...children!.props,
27 | onDestroy: destroy,
28 | container: div,
29 | }),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/search-toolbar/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { SearchToolbar } from '@trionesdev/antd-react-ext';
2 | import { Input } from 'antd';
3 | import React from 'react';
4 |
5 | export default () => {
6 | const items: any[] = [
7 | {
8 | label: '年龄',
9 | name: 'age',
10 | children: ,
11 | },
12 | {
13 | label: '姓名',
14 | name: 'name',
15 | children: ,
16 | },
17 | {
18 | label: '手机号码',
19 | name: 'phone',
20 | children: ,
21 | },
22 | {
23 | label: '地址',
24 | name: 'addr',
25 | children: ,
26 | },
27 | {
28 | label: '邮箱',
29 | name: 'email',
30 | children: ,
31 | },
32 | ];
33 | return (
34 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/ext-input/ExtInputPassword.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from 'antd';
2 | import { PasswordProps } from 'antd/es/input/Password';
3 | import React, { FC } from 'react';
4 | import ExtFormField from '../ext-form-field';
5 |
6 | export type ExtInputPasswordProps = PasswordProps & {
7 | readonly?: boolean;
8 | valueRender?: ((value?: any) => React.ReactNode) | React.ReactNode;
9 | defaultRender?: React.ReactNode;
10 | emptyPlaceholder?: React.ReactNode;
11 | };
12 | export const ExtInputPassword: FC = ({
13 | readonly,
14 | valueRender,
15 | defaultRender,
16 | emptyPlaceholder,
17 | ...rest
18 | }) => {
19 | return (
20 |
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/ext-input/ExtInputTextArea.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from 'antd';
2 | import { TextAreaProps } from 'antd/es/input/TextArea';
3 | import React, { FC } from 'react';
4 | import ExtFormField from '../ext-form-field';
5 |
6 | export type ExtInputTextAreaProps = TextAreaProps & {
7 | readonly?: boolean;
8 | valueRender?: ((value?: any) => React.ReactNode) | React.ReactNode;
9 | defaultRender?: React.ReactNode;
10 | emptyPlaceholder?: React.ReactNode;
11 | };
12 | export const ExtInputTextArea: FC = ({
13 | readonly,
14 | valueRender,
15 | defaultRender,
16 | emptyPlaceholder,
17 | ...rest
18 | }) => {
19 | return (
20 |
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/layout/layout.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import React, { FC } from 'react';
3 | import { useCssInJs } from '../hooks';
4 | import { genLayoutStyle } from './styles';
5 |
6 | export type LayoutProps = {
7 | children?: React.ReactNode;
8 | className?: string | undefined;
9 | style?: React.CSSProperties;
10 | direction?: 'vertical' | 'horizontal';
11 | gap?: number;
12 | };
13 |
14 | export const Layout: FC = ({
15 | children,
16 | className,
17 | style,
18 | direction = 'horizontal',
19 | gap,
20 | }) => {
21 | const prefixCls = 'triones-ant-layout';
22 |
23 | const { hashId } = useCssInJs({
24 | prefix: prefixCls,
25 | styleFun: genLayoutStyle,
26 | });
27 |
28 | return (
29 |
38 | {children}
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/src/ext-checkbox/demo/base.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React, { useState } from 'react';
3 | import { Radio, Switch } from 'antd';
4 | import { ExtCheckbox } from '@trionesdev/antd-react-ext';
5 |
6 |
7 | export default ()=>{
8 | const [readOnly, setReadOnly] = useState(false);
9 | const [value, setValue] = useState();
10 | return (
11 | <>
12 |
13 |
14 |
15 |
16 |
17 | 是
18 |
19 | {
23 | setValue(v);
24 | }}
25 | options={[
26 | {
27 | label: '是',
28 | value: 1,
29 | },
30 | {
31 | label: '否',
32 | value: 2,
33 | },
34 | ]}
35 | />
36 |
37 | >
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/fetch-select/demo/set-initial-option.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'antd';
2 | import React, { useState } from 'react';
3 | import FetchSelect from '../index';
4 |
5 | export default () => {
6 | const [value, setValue] = React.useState(3);
7 | const [initialValueOption, setInitialValueOption] = useState();
8 | return (
9 |
10 | {
12 | return Promise.resolve([
13 | { id: 1, name: '小明' },
14 | { id: 2, name: '小红' },
15 | ]);
16 | }}
17 | fieldNames={{ label: 'name', value: 'id' }}
18 | dropdownFetch={true}
19 | initialValueOptions={initialValueOption}
20 | fixedOptions={[{ id: 4, name: '小蓝' }]}
21 | value={value}
22 | onChange={setValue}
23 | />
24 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/ext-radio/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { ExtRadio } from '@trionesdev/antd-react-ext';
2 | import React, { useState } from 'react';
3 | import { Radio, Switch } from 'antd';
4 |
5 |
6 | export default ()=>{
7 | const [readOnly, setReadOnly] = useState(false);
8 | const [value, setValue] = useState();
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 | 是
17 |
18 | {
22 | setValue(v.target.value);
23 | }}
24 | options={[
25 | {
26 | label: '是',
27 | value: 1,
28 | },
29 | {
30 | label: '否',
31 | value: 2,
32 | },
33 | ]}
34 | >
35 |
36 |
37 | >
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/page-header/styles.tsx:
--------------------------------------------------------------------------------
1 | import {GlobalToken} from "antd";
2 | import {CSSInterpolation} from "@ant-design/cssinjs";
3 |
4 | export const genPageHeaderStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | backgroundColor: 'white',
11 | padding: '8px',
12 | [`&-breadcrumb`]: {},
13 | [`&-heading`]: {
14 | display: 'flex',
15 | justifyContent: 'space-between',
16 | [`&-title`]: {
17 | color: '#000000d9',
18 | fontWeight: 600,
19 | fontSize: '20px',
20 | lineHeight: '32px',
21 | overflow: 'hidden',
22 | whiteSpace: 'nowrap',
23 | textOverflow: 'ellipsis',
24 | },
25 | [`&-sub-title`]: {
26 | color: '#00000073',
27 | fontSize: '14px',
28 | lineHeight: 1.5715,
29 | overflow: 'hidden',
30 | whiteSpace: 'nowrap',
31 | textOverflow: 'ellipsis',
32 | },
33 | },
34 | },
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/.dumirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 | import process from 'node:process';
3 | const apiParserEnable =
4 | process.env.NODE_ENV === 'production' || process.env.API_PARSER == 'true';
5 | export default defineConfig({
6 | base: '/antd-react-ext/',
7 | publicPath: '/antd-react-ext/',
8 | outputPath: 'docs-dist',
9 | apiParser: apiParserEnable ? {} : false,
10 | resolve: {
11 | // 配置入口文件路径,API 解析将从这里开始
12 | entryFile: './src/index.ts',
13 | },
14 | themeConfig: {
15 | editLink: true,
16 | name: 'Antd Extensions',
17 | logo: '/antd-react-ext/logo.png',
18 | nav: [
19 | { title: '指南', link: '/guide' },
20 | { title: '组件', link: '/components' },
21 | ],
22 | socialLinks: {
23 | github: 'https://github.com/trionesdev/antd-react-ext',
24 | zhihu: 'https://www.ithere.net/',
25 | },
26 | footer:
27 | 'Copyright © 2015-present TrionesDev
',
28 | },
29 |
30 | });
31 |
--------------------------------------------------------------------------------
/src/ext-tree-select/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import {Switch} from "antd";
3 | import {ExtTreeSelect} from "@trionesdev/antd-react-ext";
4 |
5 | export default ()=>{
6 | const [readOnly, setReadOnly] = useState(false);
7 | const [value, setValue] = useState();
8 | const [value2, setValue2] = useState();
9 | const [options, setOptions] = React.useState([
10 | {value: 1, label: '小明'},
11 | {value: 2, label: '小红'},
12 | ]);
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
27 |
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/ext-checkbox/ExtCheckbox.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox, CheckboxProps } from 'antd';
2 | import React, { FC, memo } from 'react';
3 | import ExtFormField from '../ext-form-field';
4 |
5 | export type ExtCheckBoxProps = CheckboxProps & {
6 | readonly?: boolean;
7 | valueRender?:
8 | | ((value?: any, options?: any) => React.ReactNode)
9 | | React.ReactNode;
10 | defaultRender?: React.ReactNode;
11 | emptyPlaceholder?: React.ReactNode;
12 | };
13 |
14 | export const ExtCheckBox: FC = memo(
15 | ({
16 | readonly = false,
17 | valueRender,
18 | defaultRender,
19 | emptyPlaceholder,
20 | ...rest
21 | }) => {
22 | const handleRender = (value: any, options: any) => {
23 | return rest.children;
24 | };
25 | return (
26 |
34 |
35 |
36 | );
37 | },
38 | );
39 |
--------------------------------------------------------------------------------
/src/ext-input-number/ExtInputNumber.tsx:
--------------------------------------------------------------------------------
1 | import {InputNumber, InputNumberProps} from "antd";
2 | import React, {FC} from "react";
3 | import ExtFormField from "../ext-form-field";
4 |
5 | export type ExtInputNumberProps = InputNumberProps & {
6 | readonly?: boolean;
7 | valueRender?: ((value?: any) => React.ReactNode) | React.ReactNode;
8 | defaultRender?: React.ReactNode;
9 | emptyPlaceholder?: React.ReactNode;
10 | };
11 |
12 | export const ExtInputNumber: FC = ({readonly, valueRender, defaultRender,emptyPlaceholder, ...rest}) => {
13 |
14 | const handleRender = (value: any, options: any) => {
15 | if (value){
16 | return <> {value}{rest.suffix}>
17 | }
18 | return emptyPlaceholder
19 | };
20 |
21 | return (
22 |
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/ext-radio/ExtRadio.tsx:
--------------------------------------------------------------------------------
1 | import { Radio, RadioProps } from 'antd';
2 | import React, { FC } from 'react';
3 | import ExtFormField from '../ext-form-field';
4 |
5 | export type ExtRadioProps = RadioProps & {
6 | readonly?: boolean;
7 | valueRender?:
8 | | ((value?: any, options?: any) => React.ReactNode)
9 | | React.ReactNode;
10 | defaultRender?: React.ReactNode;
11 | emptyPlaceholder?: React.ReactNode;
12 | };
13 | export const ExtRadio: FC = ({
14 | readonly = false,
15 | valueRender,
16 | defaultRender,
17 | emptyPlaceholder,
18 | ...rest
19 | }) => {
20 | const handleValueOptions = (value: any) => {};
21 |
22 | const handleRender = (value: any, options: any) => {
23 | return rest.children;
24 | };
25 |
26 | return (
27 |
36 |
37 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) fengxiaotx@163.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ant Design 扩展组件库
2 |
3 | [](https://npmjs.org/package/@trionesdev/antd-react-ext)
4 | [](https://npmjs.org/package/@trionesdev/antd-react-ext)
5 |
6 | > 基于And Design 组件库的扩展组件库。需要依赖 [ant-design](https://github.com/ant-design/ant-design)
7 |
8 | [文档网站](https://trionesdev.github.io/antd-react-ext/components/table-toolbar)
9 |
10 | ---
11 | ## 组件列表
12 | - [x] AppToolbar 应用工具栏
13 | - [x] AvatarEditor 头像编辑器
14 | - [x] DrawerForm 抽屉表单
15 | - [x] EditableDesc 可编辑描述
16 | - [x] FetchSelect 远程数据的Select
17 | - [x] FetchTreeSelect 远程数据的TreeSelect
18 | - [x] FieldsMapping 字段映射
19 | - [x] FileWrapper 字段包装器
20 | - [x] GridTable 表格
21 | - [x] Layout 布局
22 | - [x] ModalForm 模态框表单
23 | - [x] PageHeader 页头
24 | - [x] PictureUpload 图片上传
25 | - [x] SearchToolbar 搜索工具栏
26 | - [x] TableToolbar 表格工具栏
27 |
28 | ## LICENSE
29 |
30 | MIT
31 |
32 |
33 | ---
34 |
35 | ## 关注我们,一起交流
36 | > 留言回复不及时,可以通过关注公众号联系我们
37 |
38 |

39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/ext-radio/ExtRadioGroup.tsx:
--------------------------------------------------------------------------------
1 | import { Radio, RadioGroupProps } from 'antd';
2 | import React, { FC } from 'react';
3 | import ExtFormField from '../ext-form-field';
4 |
5 | export type ExtRadioGroupProps = Omit & {
6 | readonly?: boolean;
7 | valueRender?:
8 | | ((value?: any, option?: any) => React.ReactNode)
9 | | React.ReactNode;
10 | defaultRender?: React.ReactNode;
11 | emptyPlaceholder?: React.ReactNode;
12 | };
13 |
14 | export const ExtRadioGroup: FC = ({
15 | readonly = false,
16 | valueRender,
17 | defaultRender,
18 | emptyPlaceholder,
19 | ...rest
20 | }) => {
21 | const handleRender = (value: any, options: any) => {
22 | return options?.find((item: any) => item.value === value)?.label || value;
23 | };
24 |
25 | return (
26 |
36 |
37 |
38 | );
39 |
40 | };
41 |
--------------------------------------------------------------------------------
/src/ext-select/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import {Switch} from 'antd';
2 | import React, {useState} from 'react';
3 | import {ExtSelect} from "../ExtSelect";
4 |
5 | export default () => {
6 | const [readOnly, setReadOnly] = useState(false);
7 | const [value, setValue] = useState();
8 | const [value2, setValue2] = useState();
9 | const [options, setOptions] = React.useState([
10 | {value: 1, label: '小明'},
11 | {value: 2, label: '小红'},
12 | ]);
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
23 |
27 |
31 |
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/grid-table/demo/fit-table.tsx:
--------------------------------------------------------------------------------
1 | import { GridTable } from '@trionesdev/antd-react-ext';
2 | import React from 'react';
3 |
4 | export default () => {
5 | const columns = [
6 | {
7 | title: '姓名',
8 | dataIndex: 'name',
9 | },
10 | {
11 | title: '年龄',
12 | dataIndex: 'age',
13 | },
14 | ];
15 |
16 | const dataScore = [
17 | { name: '小明', age: 19 },
18 | { name: '小王', age: 38 },
19 | { name: '小王', age: 38 },
20 | { name: '小王', age: 38 },
21 | { name: '小王', age: 38 },
22 | { name: '小王', age: 38 },
23 | { name: '小王', age: 38 },
24 | { name: '小王', age: 38 },
25 | { name: '小王', age: 38 },
26 | { name: '小王', age: 38 },
27 | { name: '小王', age: 38 },
28 | { name: '小王', age: 38 },
29 | { name: '小王', age: 38 },
30 | { name: '小王', age: 38 },
31 | { name: '小王', age: 38 },
32 | { name: '小王', age: 38 },
33 | { name: '小王', age: 38 },
34 | { name: '小王', age: 38 },
35 | { name: '小王', age: 38 },
36 | { name: '小王', age: 38 },
37 | { name: '小王', age: 38 },
38 | { name: '小王', age: 38 },
39 | ];
40 |
41 | return (
42 |
43 |
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/src/table-toolbar/table-toolbar.tsx:
--------------------------------------------------------------------------------
1 | import { Space } from 'antd';
2 | import classNames from 'classnames';
3 | import React, { FC } from 'react';
4 | import { useCssInJs } from '../hooks';
5 | import { genTableToolbarStyle } from './styles';
6 |
7 | export type TableToolbarProps = {
8 | className?: string;
9 | style?: React.CSSProperties;
10 | /**
11 | * @description 标题
12 | * @default null
13 | */
14 | title?: React.ReactNode;
15 | /**
16 | * @description 操作区,位于 title 行的行尾
17 | * @default []
18 | */
19 | extra?: React.ReactNode;
20 | };
21 |
22 | const TableToolbar: FC = ({
23 | className,
24 | style,
25 | title,
26 | extra,
27 | }) => {
28 | const prefixCls = 'triones-ant-table-toolbar';
29 |
30 | const { hashId } = useCssInJs({
31 | prefix: prefixCls,
32 | styleFun: genTableToolbarStyle,
33 | });
34 |
35 | return (
36 |
37 |
38 | {title}
39 |
40 |
41 | {extra}
42 |
43 |
44 | );
45 | };
46 | export default TableToolbar;
47 |
--------------------------------------------------------------------------------
/src/ext-input/ExtInputOTP.tsx:
--------------------------------------------------------------------------------
1 | import {Input} from 'antd';
2 | import {OTPProps} from 'antd/es/input/OTP';
3 | import React, {FC} from 'react';
4 | import ExtFormField from '../ext-form-field';
5 |
6 | export type ExtInputOPTProps = OTPProps & {
7 | readonly?: boolean;
8 | valueRender?: ((value?: any) => React.ReactNode) | React.ReactNode;
9 | defaultRender?: React.ReactNode;
10 | emptyPlaceholder?: React.ReactNode;
11 | };
12 | export const ExtInputOTP: FC = ({
13 | readonly,
14 | valueRender,
15 | defaultRender,
16 | emptyPlaceholder,
17 | ...rest
18 | }) => {
19 | return (
20 |
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/ext-switch/ExtSwitch.tsx:
--------------------------------------------------------------------------------
1 | import {Switch, SwitchProps} from "antd";
2 | import React, {FC} from "react";
3 | import ExtFormField from "../ext-form-field";
4 |
5 | export type ExtSwitchProps = SwitchProps & {
6 | readonly?: boolean;
7 | valueRender?:
8 | | ((value?: any, option?: any) => React.ReactNode)
9 | | React.ReactNode;
10 | defaultRender?: React.ReactNode;
11 | };
12 |
13 | export const ExtSwitch: FC = ({
14 | readonly = false,
15 | valueRender,
16 | defaultRender,
17 | ...rest
18 | }) => {
19 |
20 | const handleRender = (value: any, options: any) => {
21 | return value ? (rest.checkedChildren || '是') : (rest.unCheckedChildren || '否');
22 | };
23 |
24 | return (
25 | {
32 | return handleRender(value, options);
33 | }}
34 | >
35 |
36 |
37 | );
38 | };
--------------------------------------------------------------------------------
/src/app-toolbar/styles.ts:
--------------------------------------------------------------------------------
1 | import { CSSInterpolation } from '@ant-design/cssinjs';
2 | import { GlobalToken } from 'antd';
3 |
4 | export const genAppToolbarStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | height: '60px',
11 | display: 'flex',
12 | alignItems: 'center',
13 | justifyContent: 'center',
14 | borderBottom: `1px solid ${token.colorBorder}`,
15 | padding: '0px 8px',
16 | boxSizing: 'border-box',
17 | [`&-heading`]: {
18 | display: 'flex',
19 | justifyContent: 'space-between',
20 | alignItems: 'center',
21 | width: '100%',
22 | gap: '8px',
23 |
24 | [`&-left`]: {
25 | ['.ant-avatar']: {
26 | display: 'flex',
27 | },
28 | [`&-title`]: {
29 | color: token.colorTextBase,
30 | fontWeight: 600,
31 | fontSize: '20px',
32 | lineHeight: '32px',
33 | overflow: 'hidden',
34 | whiteSpace: 'nowrap',
35 | textOverflow: 'ellipsis',
36 | },
37 | },
38 | [`&-right`]: {},
39 | [`.ant-menu-horizontal`]: {
40 | flex: '1 auto',
41 | height: '60px',
42 | backgroundColor: 'inherit',
43 | li: {
44 | display: 'flex',
45 | alignItems: 'center',
46 | },
47 | },
48 | },
49 | },
50 | };
51 | };
52 |
--------------------------------------------------------------------------------
/src/util/demo/reactdom.tsx:
--------------------------------------------------------------------------------
1 | import { ReactDomUtils } from '@trionesdev/antd-react-ext';
2 | import { Button, Modal } from 'antd';
3 | import React, { useRef, useState } from 'react';
4 |
5 | const El = ({ onDestroy }: { onDestroy?: () => void }) => {
6 | return (
7 |
8 | ReactDomUtils
9 |
10 | );
11 | };
12 |
13 | const ModalEl = ({
14 | container,
15 | onDestroy,
16 | }: {
17 | container?: any;
18 | onDestroy?: () => void;
19 | }) => {
20 | const [open, setOpen] = useState(true);
21 | const handleClose = () => {
22 | onDestroy?.();
23 | };
24 | return (
25 |
31 | 这是一个可以关闭的弹窗
32 |
33 | );
34 | };
35 |
36 | export default () => {
37 | const devRef = useRef();
38 |
39 | return (
40 | <>
41 |
42 |
49 |
56 |
63 | >
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/src/field-wrapper/field-wrapper.tsx:
--------------------------------------------------------------------------------
1 | import { useCssInJs } from '@trionesdev/antd-react-ext';
2 | import { SizeType } from 'antd/es/config-provider/SizeContext';
3 | import classNames from 'classnames';
4 | import React, { FC, useMemo } from 'react';
5 | import { genFieldWrapperStyle } from './styles';
6 |
7 | export type FieldWrapperProps = {
8 | /**
9 | * @description 类名
10 | * @default
11 | */
12 | className?: string;
13 | /**
14 | * @description 样式
15 | * @default
16 | */
17 | style?: React.CSSProperties;
18 | children?: React.ReactNode;
19 | /**
20 | * @description 大小
21 | * @default middle
22 | */
23 | size?: SizeType;
24 | [key: string]: any;
25 | };
26 |
27 | export const FieldWrapper: FC = ({
28 | className,
29 | style,
30 | size,
31 | children,
32 | ...props
33 | }) => {
34 | const prefixCls = 'triones-ant-field-wrapper';
35 | const { hashId } = useCssInJs({
36 | prefix: prefixCls,
37 | styleFun: genFieldWrapperStyle,
38 | });
39 |
40 | const sizeCls = useMemo(() => {
41 | switch (size) {
42 | case 'small':
43 | return `${prefixCls}-sm`;
44 | case 'large':
45 | return `${prefixCls}-lg`;
46 | default:
47 | return '';
48 | }
49 | }, [size]);
50 |
51 | return (
52 |
57 | {children}
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/modal-form/modal-inner-form.tsx:
--------------------------------------------------------------------------------
1 | import { Form, FormProps } from 'antd';
2 | import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
3 |
4 | export interface ModalInnerFormHandle {
5 | submit: () => void;
6 | resetFields: (fields?: any[]) => void;
7 | }
8 |
9 | type ModalInnerFormProps = {
10 | children: React.ReactElement | React.ReactNode;
11 | formValues?: any;
12 | onSubmit?: (values: any) => Promise | void;
13 | } & Omit;
14 | export const ModalInnerForm = forwardRef<
15 | ModalInnerFormHandle,
16 | ModalInnerFormProps
17 | >(({ children, formValues, onSubmit, ...rest }, componentRef) => {
18 | const [form] = Form.useForm();
19 | /**支持自己传入form,外部传入form的话使用外部传入的form */
20 | const finalFrom = rest.form ? rest.form : form;
21 | useImperativeHandle(componentRef, () => {
22 | return {
23 | submit: () => {
24 | finalFrom
25 | .validateFields()
26 | .then((values: any) => {
27 | onSubmit?.(values);
28 | })
29 | .catch((ex: any) => {
30 | console.error(ex.message);
31 | });
32 | },
33 | resetFields: (fields?: any[]) => {
34 | finalFrom.resetFields(fields);
35 | },
36 | };
37 | });
38 |
39 | useEffect(() => {
40 | if (formValues) {
41 | finalFrom.setFieldsValue(formValues);
42 | }
43 | }, [formValues]);
44 |
45 | return (
46 |
49 | );
50 | });
51 | export default ModalInnerForm;
52 |
--------------------------------------------------------------------------------
/src/drawer-form/drawer-inner-form.tsx:
--------------------------------------------------------------------------------
1 | import {Form, FormInstance, FormProps} from 'antd';
2 | import React, {forwardRef, useEffect, useImperativeHandle} from 'react';
3 |
4 | export interface DrawerInnerFormHandle {
5 | submit: () => void;
6 | }
7 |
8 | export type DrawerInnerFormProps = {
9 | children?: React.ReactElement | React.ReactNode;
10 | formValues?: any;
11 | onSubmit?: (values: any, form?: FormInstance) => Promise | void;
12 | } & Omit;
13 |
14 | export const DrawerInnerForm = forwardRef<
15 | DrawerInnerFormHandle,
16 | DrawerInnerFormProps
17 | >(({children, formValues, onSubmit, ...rest}, componentRef) => {
18 | const [form] = Form.useForm();
19 | /**支持自己传入form,外部传入form的话使用外部传入的form */
20 | const finalFrom = rest.form ? rest.form : form;
21 | useImperativeHandle(componentRef, () => {
22 | return {
23 | submit: () => {
24 | finalFrom
25 | .validateFields()
26 | .then((values: any) => {
27 | if (onSubmit) {
28 | return onSubmit(values, finalFrom);
29 | } else {
30 | return Promise.resolve();
31 | }
32 | })
33 | .catch((ex: any) => {
34 | console.log(ex);
35 | });
36 | },
37 | };
38 | });
39 |
40 | useEffect(() => {
41 | if (formValues) {
42 | finalFrom?.setFieldsValue(formValues);
43 | }
44 | }, [formValues]);
45 |
46 | return (
47 |
50 | );
51 | });
52 | export default DrawerInnerForm;
53 |
--------------------------------------------------------------------------------
/src/picture-upload/styles.ts:
--------------------------------------------------------------------------------
1 | import { GlobalToken } from 'antd';
2 |
3 | export const genPictureUploadStyle = (
4 | prefixCls: string,
5 | token: GlobalToken,
6 | ): any => {
7 | return {
8 | [`.${prefixCls}`]: {
9 | [`&-image`]: {
10 | position: 'relative',
11 | objectFit: 'cover',
12 | borderRadius: token.borderRadiusLG,
13 | '&:hover': {
14 | [`.${prefixCls}-image-tooltip`]: {
15 | visibility: 'visible',
16 | },
17 | },
18 | '.ant-image': {
19 | borderRadius: 4,
20 | overflow: 'hidden',
21 | '.ant-image-img':{
22 | objectFit: 'cover',
23 | }
24 | },
25 | [`&-tooltip`]: {
26 | position: 'absolute',
27 | left: '50%',
28 | bottom: 8,
29 | transform: 'translateX(-50%)',
30 | background: 'rgba(18, 18, 18, 0.75) none repeat scroll 0% 0%',
31 | borderRadius: 4,
32 | visibility: 'hidden',
33 | button: {
34 | color: 'white',
35 | '&:hover': {
36 | color: 'white!important',
37 | },
38 | },
39 | '.ant-divider': {
40 | borderInlineStart: '1px solid white',
41 | },
42 | },
43 | },
44 | '.ant-upload-wrapper': {
45 | width: '100%',
46 | height: '100%',
47 | display: 'flex',
48 | '.ant-upload': {
49 | width: '100%!important',
50 | height: '100%!important',
51 | },
52 | },
53 | },
54 | };
55 | };
56 |
--------------------------------------------------------------------------------
/src/ext-checkbox/ExtCheckboxGroup.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox } from 'antd';
2 | import { CheckboxGroupProps } from 'antd/es/checkbox';
3 | import React, { FC, memo } from 'react';
4 | import ExtFormField from '../ext-form-field';
5 | import { includes } from 'lodash-es';
6 |
7 | export type ExtCheckBoxGroupProps = Omit & {
8 | readonly?: boolean;
9 | valueRender?:
10 | | ((value?: any, options?: any) => React.ReactNode)
11 | | React.ReactNode;
12 | defaultRender?: React.ReactNode;
13 | emptyPlaceholder?: React.ReactNode;
14 | };
15 |
16 | export const ExtCheckboxGroup: FC = memo(
17 | ({
18 | readonly = false,
19 | valueRender,
20 | defaultRender,
21 | emptyPlaceholder,
22 | ...rest
23 | }) => {
24 |
25 | const handleValueOptions = (value: any) => {
26 | return rest.options?.filter((option:any) => {
27 | return includes(value, option['value']);
28 | });
29 | };
30 |
31 | const handleRender = (value: any, options: any) => {
32 | return options?.map((option: any) => option['label']).join(', ');
33 | };
34 |
35 | return (
36 |
46 |
47 |
48 | );
49 | },
50 | );
51 |
--------------------------------------------------------------------------------
/src/ext-date-picker/ExtDatePicker.tsx:
--------------------------------------------------------------------------------
1 | import ExtFormField from '../ext-form-field';
2 | import {DatePicker, DatePickerProps} from 'antd';
3 | import dayjs from 'dayjs';
4 | import React, {FC} from 'react';
5 |
6 | export type ExtDatePickerProps = DatePickerProps & {
7 | readonly?: boolean;
8 | valueRender?: ((value?: any) => React.ReactNode) | React.ReactNode;
9 | defaultRender?: React.ReactNode;
10 | emptyPlaceholder?: React.ReactNode;
11 | };
12 | export const ExtDatePicker: FC = ({
13 | readonly,
14 | valueRender,
15 | defaultRender,
16 | emptyPlaceholder,
17 | ...rest
18 | }) => {
19 | const handleFiledRender = (value: any, valueOptions: any) => {
20 | if (!value) {
21 | return undefined;
22 | }
23 | switch (rest.picker) {
24 | case 'month':
25 | return dayjs(value).format('YYYY-MM');
26 | case 'quarter':
27 | return dayjs(value).format('YYYY-Q');
28 | case 'week':
29 | return dayjs(value).format('YYYY-WW');
30 | case 'year':
31 | return dayjs(value).format('YYYY');
32 | default: {
33 | const format =
34 | (rest.format as any)?.format || rest.format || 'YYYY-MM-DD';
35 | return dayjs(value).format(format);
36 | }
37 | }
38 | };
39 |
40 | return (
41 |
50 |
51 |
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/src/fields-mapping/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { FieldsMapping } from '@trionesdev/antd-react-ext';
2 | import React from 'react';
3 | import { Column } from '../fields-mapping';
4 |
5 | export default () => {
6 | const sourceColumns: Column[] = [
7 | {
8 | key: 'field',
9 | title: '字段',
10 | width: 80,
11 | primaryKey: true,
12 | },
13 | {
14 | key: 'type',
15 | title: '类型',
16 | width: 80,
17 | },
18 | ];
19 | const sourceData = [
20 | {
21 | field: 'id',
22 | type: 'string',
23 | },
24 | {
25 | field: 'name',
26 | type: 'string',
27 | },
28 | {
29 | field: 'age',
30 | type: 'int',
31 | },
32 | ];
33 |
34 | const targetColumns: Column[] = [
35 | {
36 | key: 'field',
37 | title: '字段',
38 | width: 80,
39 | primaryKey: true,
40 | },
41 | {
42 | key: 'type',
43 | title: '类型',
44 | width: 80,
45 | },
46 | ];
47 | const targetData = [
48 | {
49 | field: 'id',
50 | type: 'string',
51 | },
52 |
53 | {
54 | field: 'age',
55 | type: 'int',
56 | },
57 | {
58 | field: 'name',
59 | type: 'string',
60 | },
61 | ];
62 |
63 | const mappingData = [
64 | { sourceKey: 'id', targetKey: 'id' },
65 | // {sourceKey:'age',targetKey:'age'},
66 | // {sourceKey:'name',targetKey:'name'},
67 | ];
68 |
69 | return (
70 |
77 | );
78 | };
79 |
--------------------------------------------------------------------------------
/src/layout/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { DesktopOutlined, PieChartOutlined } from '@ant-design/icons';
2 | import { Layout } from '@trionesdev/antd-react-ext';
3 | import { Menu } from 'antd';
4 | import React from 'react';
5 |
6 | export default () => {
7 | const menuItems = [
8 | {
9 | key: '1',
10 | label: 'Option A',
11 | icon: ,
12 | },
13 | {
14 | key: '2',
15 | label: 'Option B',
16 | icon: ,
17 | },
18 | ];
19 |
20 | return (
21 |
22 |
23 |
24 | sss
25 |
26 | auto
27 |
28 |
29 |
30 |
31 |
32 | sss
33 |
34 | auto
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | auto
45 |
46 |
47 |
48 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/src/modal-form/modal-form.tsx:
--------------------------------------------------------------------------------
1 | import { FormInstance, FormProps, Modal, ModalProps } from 'antd';
2 | import React, { FC, useRef, useState, type SyntheticEvent } from 'react';
3 | import ModalInnerForm, { ModalInnerFormHandle } from './modal-inner-form';
4 |
5 | export type ModalFormProps = {
6 | /**
7 | * @description 触发标签
8 | * @default
9 | */
10 | trigger?: React.ReactNode | React.ReactElement;
11 |
12 | /**
13 | * @description 触发标签点击
14 | * @default
15 | */
16 | onTriggerClick?: () => void;
17 | /**
18 | * @description 提交回调
19 | * @default
20 | */
21 | onSubmit?: (values: any) => Promise | void;
22 | form?: FormInstance;
23 | formValues?: any;
24 | formProps?: Omit;
25 | } & ModalProps;
26 |
27 | export const ModalForm: FC = ({
28 | trigger,
29 | onTriggerClick,
30 | onSubmit,
31 | form,
32 | formValues,
33 | formProps,
34 | ...rest
35 | }) => {
36 | const formRef = useRef({} as ModalInnerFormHandle);
37 | const [scopeOpen, setScopeOpen] = useState(false);
38 |
39 | const handleSubmit = (e: React.MouseEvent) => {
40 | rest?.onOk?.(e);
41 | if (onSubmit) {
42 | formRef.current.submit();
43 | }
44 | };
45 |
46 | return (
47 | <>
48 | {trigger && React.isValidElement(trigger) &&
49 | React.cloneElement(trigger, {
50 | ...trigger.props,
51 | onClick: (e?: SyntheticEvent) => {
52 | trigger.props.onClick?.(e);
53 | onTriggerClick?.();
54 | },
55 | })}
56 |
57 |
64 | {rest.children}
65 |
66 |
67 | >
68 | );
69 | };
70 |
--------------------------------------------------------------------------------
/src/modal-form/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { type SyntheticEvent } from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { ModalForm as InternalModalForm, ModalFormProps } from './modal-form';
4 |
5 | type ModalFormShowProps = Omit<
6 | ModalFormProps,
7 | 'open' | 'afterOpenChange' | 'onClose' | 'onCancel'
8 | > & {
9 | /**
10 | * @description 关闭回调,如果有回调函数,返回结果是 Promise.resolve(true) 的时候关闭。如果没有回调函数,直接关闭
11 | * @default
12 | */
13 | onClose?: (e: SyntheticEvent) => Promise;
14 | /**
15 | * @description 取消回调
16 | * @default
17 | */
18 | onCancel?: (e: React.MouseEvent) => Promise;
19 | };
20 | const show = (options?: ModalFormShowProps) => {
21 | const div = document.createElement('div');
22 | const body = document.body;
23 | body.appendChild(div);
24 | const root = ReactDOM.createRoot(div as HTMLElement);
25 |
26 | function destroy() {
27 | setTimeout(() => root.unmount());
28 | if (div.parentNode) {
29 | div.parentNode.removeChild(div);
30 | }
31 | }
32 |
33 | const handleClose = (e: SyntheticEvent) => {
34 | if (options?.onClose) {
35 | options?.onClose(e).then((result) => {
36 | destroy();
37 | });
38 | } else {
39 | destroy();
40 | }
41 | };
42 |
43 | const handleCancel = (e: React.MouseEvent) => {
44 | if (options?.onCancel) {
45 | options?.onCancel?.(e).then((res) => {
46 | destroy();
47 | });
48 | } else {
49 | destroy();
50 | }
51 | };
52 |
53 | root.render(
54 | ,
59 | );
60 | };
61 |
62 | type CompoundedComponent = typeof InternalModalForm & {
63 | show: typeof show;
64 | };
65 | export type { ModalFormProps };
66 | const ModalForm = InternalModalForm as CompoundedComponent;
67 | ModalForm.show = show;
68 | export default ModalForm;
69 |
--------------------------------------------------------------------------------
/src/drawer-form/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import {
4 | DrawerFormProps,
5 | DrawerForm as InternalDrawerForm,
6 | } from './drawer-form';
7 |
8 | type DrawerFormShowProps = Omit<
9 | DrawerFormProps,
10 | 'open' | 'afterOpenChange' | 'onClose' | 'onCancel'
11 | > & {
12 | /**
13 | * @description 关闭回调,如果有回调函数,返回结果是 Promise.resolve(true) 的时候关闭。如果没有回调函数,直接关闭
14 | * @default
15 | */
16 | onClose?: (e: React.MouseEvent | React.KeyboardEvent) => Promise;
17 | /**
18 | * @description 取消回调
19 | * @default
20 | */
21 | onCancel?: (e: React.MouseEvent) => Promise;
22 | };
23 |
24 | const show = (options?: DrawerFormShowProps) => {
25 | const div = document.createElement('div');
26 | const body = document.body;
27 | body.appendChild(div);
28 | const root = ReactDOM.createRoot(div as HTMLElement);
29 |
30 | function destroy() {
31 | setTimeout(() => root.unmount());
32 | if (div.parentNode) {
33 | div.parentNode.removeChild(div);
34 | }
35 | }
36 |
37 | const handleClose = (e: React.MouseEvent | React.KeyboardEvent) => {
38 | if (options?.onClose) {
39 | options?.onClose(e).then((result) => {
40 | destroy();
41 | });
42 | } else {
43 | destroy();
44 | }
45 | };
46 |
47 | const handleCancel = (e: React.MouseEvent) => {
48 | if (options?.onCancel) {
49 | options?.onCancel?.(e).then((res) => {
50 | destroy();
51 | });
52 | } else {
53 | destroy();
54 | }
55 | };
56 |
57 | root.render(
58 | ,
64 | );
65 | };
66 | type CompoundedComponent = typeof InternalDrawerForm & {
67 | show: typeof show;
68 | };
69 |
70 | export type { DrawerFormProps };
71 | const DrawerForm = InternalDrawerForm as CompoundedComponent;
72 | DrawerForm.show = show;
73 | export default DrawerForm;
74 |
--------------------------------------------------------------------------------
/src/app-toolbar/app-toolbar.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarProps, Menu, MenuProps, Space } from 'antd';
2 | import classNames from 'classnames';
3 | import React, { FC } from 'react';
4 | import { useCssInJs } from '../hooks';
5 | import { genAppToolbarStyle } from './styles';
6 | import { isEmpty } from 'lodash-es';
7 |
8 | export type AppToolbarProps = {
9 | className?: string;
10 | style?: React.CSSProperties;
11 | avatar?: AvatarProps;
12 | /**
13 | * @description 标题
14 | * @default null
15 | */
16 | title?: React.ReactNode;
17 | /**
18 | * @description 其他组件,在右侧展示
19 | * @default []
20 | */
21 | extra?: React.ReactNode;
22 | /**
23 | * @description 导航菜单项
24 | * @default null
25 | */
26 | navItems?: MenuProps['items'];
27 | selectedKeys?: string[];
28 | };
29 | const AppToolbar: FC = ({
30 | className,
31 | style,
32 | avatar,
33 | title,
34 | extra,
35 | navItems,
36 | selectedKeys,
37 | }) => {
38 | const prefixCls = 'triones-ant-app-toolbar';
39 | const { hashId } = useCssInJs({
40 | prefix: prefixCls,
41 | styleFun: genAppToolbarStyle,
42 | });
43 |
44 | return
45 |
46 |
47 |
48 | {avatar && (
49 |
52 | )}
53 |
56 | {title}
57 |
58 |
59 |
60 | {!isEmpty(navItems) && (
61 |
66 | )}
67 |
68 | {extra}
69 |
70 |
71 |
72 | };
73 | export default AppToolbar;
74 |
--------------------------------------------------------------------------------
/src/avatar-editor/styles.ts:
--------------------------------------------------------------------------------
1 | import {CSSInterpolation} from '@ant-design/cssinjs';
2 | import {GlobalToken} from 'antd';
3 |
4 | export const genAvatarEditorStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | boxSizing: 'border-box',
11 | borderRadius: token.borderRadius,
12 | overflow: 'hidden',
13 | [`&-avatar`]: {
14 | position: 'relative',
15 | '.ant-avatar-square': {
16 | borderRadius: '0px!important',
17 | },
18 | [`&:hover`]: {
19 | [`.${prefixCls}-avatar-mask`]: {
20 | visibility: 'visible',
21 | },
22 | },
23 | [`&-mask`]: {
24 | position: 'absolute',
25 | top: 0,
26 | left: 0,
27 | width: '100%',
28 | height: '100%',
29 | background: 'rgba(0,0,0,0.3)',
30 | display: 'flex',
31 | visibility: 'hidden',
32 | alignItems: 'center',
33 | justifyContent: 'center',
34 | color: '#fff',
35 | cursor: 'pointer',
36 | transition: 'all .3s',
37 | '&:hover': {
38 | background: 'rgba(0,0,0,.8)',
39 | },
40 | label: {
41 | fontSize: '30px',
42 | '.anticon': {
43 | cursor: 'pointer',
44 | },
45 | },
46 | },
47 | },
48 | '.avatar-uploader': {
49 | width: '100%',
50 | height: '100%',
51 | '.ant-upload-select': {
52 | width: '100%!important',
53 | height: '100%!important',
54 | '.ant-upload': {
55 | fontSize: '30px',
56 | color: '#0000004a',
57 | },
58 | },
59 | },
60 | },
61 | };
62 | };
63 |
64 | export const genAvatarCropModalStyle = (
65 | prefixCls: string,
66 | token: GlobalToken,
67 | ): CSSInterpolation => {
68 | return {
69 | [`.${prefixCls}`]: {
70 | '&-cropper': {},
71 | img: {
72 | width: '100%',
73 | maxWidth: '100%',
74 | },
75 | },
76 | };
77 | };
78 |
--------------------------------------------------------------------------------
/src/ext-select/ExtSelect.tsx:
--------------------------------------------------------------------------------
1 | import {Select, SelectProps} from 'antd';
2 | import { includes } from 'lodash-es';
3 | import React, {FC} from 'react';
4 | import ExtFormField from '../ext-form-field';
5 |
6 | export type ExtSelectProps = Omit & {
7 | readonly?: boolean;
8 | valueRender?:
9 | | ((value?: any, option?: any) => React.ReactNode)
10 | | React.ReactNode;
11 | defaultRender?: React.ReactNode;
12 | };
13 | export const ExtSelect: FC = ({
14 | readonly = false,
15 | valueRender,
16 | defaultRender,
17 | ...rest
18 | }) => {
19 | const valueField = rest.fieldNames?.value ?? 'value';
20 | const labelField = rest.fieldNames?.label ?? 'label';
21 |
22 | const handleValueOptions = (value: any) => {
23 | if (rest.mode === 'multiple' || rest.mode === 'tags') {
24 | return rest.options?.filter((option) => {
25 | return includes(value, option[valueField]);
26 | });
27 | } else {
28 | return rest.options?.find((option) => option[valueField] === value);
29 | }
30 | };
31 |
32 | const handleRender = (value: any, options: any) => {
33 | if (rest.mode === 'multiple' || rest.mode === 'tags') {
34 | return options?.map((option: any) => option[labelField]).join(', ');
35 | } else {
36 | return options?.[labelField];
37 | }
38 | };
39 |
40 | return (
41 | {
49 | return handleRender(value, options);
50 | }}
51 | >
52 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/ext-date-picker/ExtDateRangePicker.tsx:
--------------------------------------------------------------------------------
1 | import React, {FC} from "react";
2 | import {DatePicker} from "antd";
3 | import {RangePickerProps} from "antd/es/date-picker";
4 | import dayjs from "dayjs";
5 | import {ExtFormField} from '@trionesdev/antd-react-ext';
6 |
7 | export type ExtDateRangePickerProps = RangePickerProps & {
8 | readonly?: boolean;
9 | valueRender?: ((value?: any) => React.ReactNode) | React.ReactNode;
10 | defaultRender?: React.ReactNode;
11 | emptyPlaceholder?: React.ReactNode;
12 | };
13 |
14 | export const ExtDateRangePicker: FC = ({readonly, valueRender,defaultRender,emptyPlaceholder, ...rest}) => {
15 |
16 | const handleFiledRender = (value: any, valueOptions: any) => {
17 | if (!value) {
18 | return undefined;
19 | }
20 | switch (rest.picker) {
21 | case 'month':
22 | return `${value?.[0] ? dayjs(value?.[0]).format('YYYY-MM') : ''}~${value?.[1] ? dayjs(value?.[1]).format('YYYY-MM') : ''}`;
23 | case 'quarter':
24 | return `${value?.[0] ? dayjs(value?.[0]).format('YYYY-Q') : ''}~${value?.[1] ? dayjs(value?.[1]).format('YYYY-Q') : ''}`;
25 | case 'week':
26 | return `${value?.[0] ? dayjs(value?.[0]).format('YYYY-WW') : ''}~${value?.[1] ? dayjs(value?.[1]).format('YYYY-WW') : ''}`;
27 | case 'year':
28 | return `${value?.[0] ? dayjs(value?.[0]).format('YYYY') : ''}~${value?.[1] ? dayjs(value?.[1]).format('YYYY') : ''}`;
29 | default: {
30 | const format = (rest.format as any)?.format || rest.format || 'YYYY-MM-DD';
31 | return `${value?.[0] ? dayjs(value?.[0]).format(format) : ''}~${value?.[1] ? dayjs(value?.[1]).format(format) : ''}`;
32 | }
33 | }
34 | };
35 |
36 | return (
37 |
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/layout/styles.ts:
--------------------------------------------------------------------------------
1 | import {CSSInterpolation} from '@ant-design/cssinjs';
2 | import {GlobalToken} from 'antd';
3 |
4 | export const genLayoutStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | minWidth: 0,
11 | minHeight: 0,
12 | height: '100%',
13 | width: '100%',
14 | display: 'flex',
15 | boxSizing: 'border-box',
16 | [`&-vertical`]: {
17 | flexDirection: 'column',
18 | // width:0,
19 | // flex:'1 1 auto'
20 | [`>.${prefixCls}-item-auto`]: {
21 | minWidth: 0,
22 | minHeight: 0,
23 | },
24 | },
25 | [`&-horizontal`]: {
26 | flexDirection: 'row',
27 | // height:0,
28 | // flex:'1 1 auto'
29 | [`>.${prefixCls}-item-auto`]: {
30 | minWidth: 0,
31 | minHeight: 0,
32 | },
33 | },
34 | },
35 | };
36 | };
37 |
38 | export const genItemStyle = (
39 | prefixCls: string,
40 | token: GlobalToken,
41 | ): CSSInterpolation => {
42 | return {
43 | [`.${prefixCls}`]: {
44 | flexShrink: 0,
45 | backgroundColor: token.colorBgBase,
46 | [`&-auto`]: {
47 | boxSizing: 'border-box',
48 | flex: '1 auto',
49 | minWidth: 0,
50 | minHeight: 0,
51 | },
52 | },
53 | };
54 | };
55 |
56 | export const genSiderStyle = (
57 | prefixCls: string,
58 | token: GlobalToken,
59 | collapsedWidth: number,
60 | ): CSSInterpolation => {
61 | return {
62 | [`.${prefixCls}`]: {
63 | boxSizing: 'border-box',
64 | height: '100%',
65 | display: 'flex',
66 | minWidth: 0,
67 | minHeight: 0,
68 | flexShrink: 0,
69 | flexDirection: 'column',
70 | transition: 'all 0.2s,background 0s',
71 | borderRight: `1px solid ${token.colorBorder}`,
72 | [`&-children`]: {
73 | flex: '1 auto',
74 | overflowY: 'auto',
75 | overflowX: 'hidden',
76 | [`.ant-menu`]: {
77 | borderInlineEnd: '0px !important',
78 | [`& > .ant-menu-inline-collapsed`]: {
79 | width: collapsedWidth
80 | }
81 | },
82 | },
83 | [`&-trigger`]: {
84 | textAlign: 'center',
85 | lineHeight: '48px',
86 | cursor: 'pointer',
87 | },
88 | },
89 | };
90 | };
91 |
--------------------------------------------------------------------------------
/src/editable-desc/demo/base.tsx:
--------------------------------------------------------------------------------
1 | import { EditableDesc } from '@trionesdev/antd-react-ext';
2 | import { Button, Form, Input, Select, Space } from 'antd';
3 | import React, { useEffect, useState } from 'react';
4 |
5 | export default () => {
6 | const [form] = Form.useForm();
7 | const [editing, setEditing] = useState(false);
8 |
9 | const handleSave = () => {
10 | form.validateFields().then((values) => {
11 | setEditing(false);
12 | console.log(values);
13 | });
14 | };
15 |
16 | useEffect(() => {
17 | form.setFieldsValue({
18 | name: '小明',
19 | gender: 'MALE',
20 | description: '描述',
21 | });
22 | }, []);
23 |
24 | return (
25 |
27 |
28 |
29 |
30 |
31 |
32 | {
34 | if (v === 'MALE') {
35 | return '男';
36 | } else {
37 | return '女';
38 | }
39 | }}
40 | clickEdit={true}
41 | manualChange={true}
42 | editIcon={false}
43 | editing={editing}
44 | >
45 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
70 |
77 |
80 |
81 |
82 |
83 | );
84 | };
85 |
--------------------------------------------------------------------------------
/src/editable-desc/demo/form.tsx:
--------------------------------------------------------------------------------
1 | import { EditableDesc } from '@trionesdev/antd-react-ext';
2 | import { Button, Form, Input, Select, Space } from 'antd';
3 | import React, { useEffect, useState } from 'react';
4 |
5 | export default () => {
6 | const [form] = Form.useForm();
7 | const [editing, setEditing] = useState(false);
8 |
9 | const handleSave = () => {
10 | form.validateFields().then((values) => {
11 | setEditing(false);
12 | console.log(values);
13 | });
14 | };
15 |
16 | useEffect(() => {
17 | form.setFieldsValue({
18 | name: '小明',
19 | gender: 'MALE',
20 | description: '描述',
21 | });
22 | }, []);
23 |
24 | return (
25 |
27 |
28 |
29 |
30 |
31 |
32 | {
34 | if (v === 'MALE') {
35 | return '男';
36 | } else {
37 | return '女';
38 | }
39 | }}
40 | editing={editing}
41 | >
42 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
72 |
79 |
82 |
83 |
84 |
85 | );
86 | };
87 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@trionesdev/antd-react-ext",
3 | "version": "6.0.0-beta.0",
4 | "description": "Antd Ext",
5 | "license": "MIT",
6 | "module": "dist/index.js",
7 | "types": "dist/index.d.ts",
8 | "files": [
9 | "dist"
10 | ],
11 | "scripts": {
12 | "build": "father build",
13 | "build:watch": "father dev",
14 | "dev": "dumi dev",
15 | "docs:build": "dumi build",
16 | "docs:preview": "dumi preview",
17 | "doctor": "father doctor",
18 | "lint": "npm run lint:es && npm run lint:css",
19 | "lint:css": "stylelint \"{src,test}/**/*.{css,less}\"",
20 | "lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"",
21 | "prepare": "husky install && dumi setup",
22 | "prepublishOnly": "father doctor && npm run build",
23 | "publish": "npm publish --tag latest --registry=https://registry.npmjs.org/",
24 | "start": "npm run dev",
25 | "start-dev": "cross-env API_PARSER=true npm run dev"
26 | },
27 | "commitlint": {
28 | "extends": [
29 | "@commitlint/config-conventional"
30 | ]
31 | },
32 | "lint-staged": {
33 | "*.{md,json}": [
34 | "prettier --write --no-error-on-unmatched-pattern"
35 | ],
36 | "*.{css,less}": [
37 | "stylelint --fix",
38 | "prettier --write"
39 | ],
40 | "*.{js,jsx}": [
41 | "eslint --fix",
42 | "prettier --write"
43 | ],
44 | "*.{ts,tsx}": [
45 | "eslint --fix",
46 | "prettier --parser=typescript --write"
47 | ]
48 | },
49 | "dependencies": {
50 | "cropperjs": "^2.1.0",
51 | "dayjs": "^1.11.19",
52 | "lodash-es": "^4.17.21"
53 | },
54 | "devDependencies": {
55 | "@ant-design/cssinjs": "^2.0.1",
56 | "@ant-design/icons": "^6.1.0",
57 | "@commitlint/cli": "^17.1.2",
58 | "@commitlint/config-conventional": "^17.1.0",
59 | "@types/lodash-es": "^4.17.12",
60 | "@types/react": "^18.0.0",
61 | "@types/react-dom": "^18.0.0",
62 | "@umijs/lint": "^4.0.0",
63 | "antd": "^6.1.0",
64 | "dumi": "^2.4.21",
65 | "eslint": "^8.23.0",
66 | "father": "^4.5.2",
67 | "husky": "^8.0.1",
68 | "lint-staged": "^13.0.3",
69 | "lodash-es": "^4.17.21",
70 | "prettier": "^2.7.1",
71 | "prettier-plugin-organize-imports": "^3.0.0",
72 | "prettier-plugin-packagejson": "^2.2.18",
73 | "react": "^18.0.0",
74 | "react-dom": "^18.0.0",
75 | "stylelint": "^14.9.1"
76 | },
77 | "peerDependencies": {
78 | "@ant-design/cssinjs": "^2.0.1",
79 | "@ant-design/icons": "^6.1.0",
80 | "antd": "^6.1.0",
81 | "classnames": "^2.5.1",
82 | "cropperjs": "^2.0.0",
83 | "react": ">=16.9.0",
84 | "react-dom": ">=16.9.0"
85 | },
86 | "publishConfig": {
87 | "access": "public"
88 | },
89 | "authors": [
90 | "fengxiaotx@163.com"
91 | ]
92 | }
93 |
--------------------------------------------------------------------------------
/src/fields-mapping/images.tsx:
--------------------------------------------------------------------------------
1 | export const ImageRemove = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjkzNDYwMTY0OTg4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE1NjgiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCI+PHBhdGggZD0iTTc0NC40MTIzMzggNDEzLjI2ODA2YTU4LjA3NzYxMiA1OC4wNzc2MTIgMCAwIDAgNTUuNjMyMjM5LTYxLjEzNDMyOSA1Ny4yNjI0ODggNTcuMjYyNDg4IDAgMCAwLTE4LjU0NDA3OS00MC43NTYyMTkgNTYuNjUxMTQ0IDU2LjY1MTE0NCAwIDAgMC0zOS4zMjk3NTItMTUuNDg3MzYzaC0yLjAzNzgxMWE1OC4wNzc2MTIgNTguMDc3NjEyIDAgMCAwLTM3LjI5MTk0IDEwMC40NjQwOCA1Ny4yNjI0ODggNTcuMjYyNDg4IDAgMCAwIDQxLjU3MTM0MyAxNi45MTM4MzF6TTIxNy4yMzA2NDcgNzAxLjIxMDc0NmEzMS45OTM2MzIgMzEuOTkzNjMyIDAgMCAwIDMzLjIxNjMxOCAxLjAxODkwNmwyMzEuOTAyODg2LTEzMy44ODQxNzktOC43NjI1ODctMTUuMDc5ODAxek0yNTAuMjQzMTg0IDM3My4xMjMxODRhMzEuMzgyMjg5IDMxLjM4MjI4OSAwIDAgMC0yNC4yNDk5NSAwIDMzLjQyMDEgMzMuNDIwMSAwIDAgMC04Ljc2MjU4NyA0Ljg5MDc0NmwyNDQuNTM3MzEzIDExMC44NTY5MTYgNy4zMzYxMi0xNS44OTQ5MjZ6TTc0OS4wOTkzMDMgNTgxLjM4NzQ2M2gtNS43MDU4N2E1Ni42NTExNDQgNTYuNjUxMTQ0IDAgMSAwIDUuNzA1ODcgMHoiIHAtaWQ9IjE1NjkiIGZpbGw9IiMxNzc2ZmYiPjwvcGF0aD48cGF0aCBkPSJNNTA4LjIzMDA1IDcwLjkxNTgyMWE0MjcuOTQwMjk5IDQyNy45NDAyOTkgMCAxIDAgNDI3Ljk0MDI5OCA0MjcuOTQwMjk4IDQyNy45NDAyOTkgNDI3Ljk0MDI5OSAwIDAgMC00MjcuOTQwMjk4LTQyNy45NDAyOTh6IG0yNTAuMDM5NDAzIDY2MC44NjIwODloLTkuMzczOTMxYTkyLjUxNjYxNyA5Mi41MTY2MTcgMCAwIDEtOTIuMTA5MDU0LTgzLjc1NDAyOSA5MS4yOTM5MyA5MS4yOTM5MyAwIDAgMSAzLjI2MDQ5Ny0zNC4yMzUyMjRsLTE2MS41OTg0MDgtNzYuMDEwMzQ5IDIzLjQzNDgyNiA0MC43NTYyMTlMMjY0LjkxNTQyMyA3MjcuMjk0NzI2YTYxLjEzNDMyOCA2MS4xMzQzMjggMCAwIDEtMzAuMTU5NjAyIDguMTUxMjQ0IDYxLjEzNDMyOCA2MS4xMzQzMjggMCAwIDEtNTIuOTgzMDg1LTMwLjM2MzM4M2wtNy4xMzIzMzgtMTIuMjI2ODY2IDI3Ni41MzA5NDUtMTU5Ljc2NDM3OGExNy43Mjg5NTUgMTcuNzI4OTU1IDAgMCAxIDQuMjc5NDAzLTQuNDgzMTg0bDkuMTcwMTUtNS45MDk2NTJoLTEuNDI2NDY4YTYuMzE3MjE0IDYuMzE3MjE0IDAgMCAxLTIuMjQxNTkyLTEuNjMwMjQ4bC0yODUuMjkzNTMzLTEyOS42MDQ3NzYgNi4xMTM0MzMtMTMuMjQ1NzcyQTYxLjEzNDMyOCA2MS4xMzQzMjggMCAwIDEgMjYyLjI2NjI2OSAzNDYuNDI3ODYxbDI0NC41MzczMTMgMTEwLjg1NjkxNS0yMC4zNzgxMDkgNDIuOTk3ODExIDYuMTEzNDMyIDIuODUyOTM1TDY1Mi4wOTk1MDIgMzk3Ljc4MDY5N2ExMy4wNDE5OSAxMy4wNDE5OSAwIDAgMSA2LjExMzQzMy0yLjI0MTU5MyA5NC43NTgyMDkgOTQuNzU4MjA5IDAgMCAxLTkuMzczOTMtMzYuODg0Mzc4IDkyLjUxNjYxNyA5Mi41MTY2MTcgMCAxIDEgOTYuNzk2MDIgODkuNjYzNjgyaC0zLjQ2NDI3OWE5MS45MDUyNzQgOTEuOTA1Mjc0IDAgMCAxLTYyLjk2ODM1OC0yNC44NjEyOTQgNTYuODU0OTI1IDU2Ljg1NDkyNSAwIDAgMS01LjcwNTg3MS02LjUyMDk5NSAxNC4wNjA4OTYgMTQuMDYwODk2IDAgMCAxLTQuNjg2OTY1IDUuMDk0NTI4bC0xNDUuMjk1OTIgOTYuMTg0Njc2TDY3Mi40Nzc2MTIgNTg3LjkwODQ1OGE5Mi4zMTI4MzYgOTIuMzEyODM2IDAgMCAxIDE2OC45MzQ1MjcgNDIuMzg2NDY3IDkzLjEyNzk2IDkzLjEyNzk2IDAgMCAxLTgzLjE0MjY4NiAxMDEuNDgyOTg1eiIgcC1pZD0iMTU3MCIgZmlsbD0iIzE3NzZmZiI+PC9wYXRoPjwvc3ZnPg=="
2 |
--------------------------------------------------------------------------------
/src/grid-table/styles.ts:
--------------------------------------------------------------------------------
1 | import { GlobalToken } from 'antd';
2 |
3 | export const genGridTableStyle = (
4 | prefixCls: string,
5 | token: GlobalToken,
6 | ): any => {
7 | const Scrollbar: any = {
8 | '&::-webkit-scrollbar': {
9 | width: 8,
10 | height: 8,
11 | },
12 | '&::-webkit-scrollbar-track': {
13 | background: '#f0f0f0',
14 | borderRadius: 10,
15 | },
16 | '&::-webkit-scrollbar-thumb ': {
17 | background: '#b4b4b4',
18 | borderRadius: 10,
19 | },
20 | '&::-webkit-scrollbar-thumb:hover': {
21 | background: '#b4b4b4',
22 | borderRadius: 10,
23 | },
24 | '&::-webkit-scrollbar-thumb:active': {
25 | background: '#b4b4b4',
26 | borderRadius: 10,
27 | },
28 | };
29 | return {
30 | [`.${prefixCls}`]: {
31 | width: '100%',
32 | display: `flex`,
33 | flexDirection: 'column',
34 | boxSizing: 'border-box',
35 | '&.ant-table-fill': {
36 | height: '100%',
37 | '.ant-table-wrapper': {
38 | flex: '1 auto',
39 | overflow: 'hidden',
40 | '.ant-spin-nested-loading': {
41 | height: '100%',
42 | '.ant-spin-container': {
43 | height: '100%',
44 | display: 'flex',
45 | flexDirection: 'column',
46 | flex: '1 auto',
47 | '.ant-table-header': {
48 | flexShrink: 0,
49 | },
50 | '.ant-table-summary': {
51 | flexShrink: 0,
52 | },
53 | },
54 | },
55 | '.ant-table': {
56 | overflow: 'hidden',
57 | scrollbarColor: 'inherit !important',
58 | '.ant-table-container': {
59 | height: '100%',
60 | display: 'flex',
61 | flexDirection: 'column',
62 | '& > .ant-table-content': {
63 | backgroundColor: token.colorBgContainer,
64 | ...Scrollbar,
65 | },
66 | '& > .ant-table-body': {
67 | overflowY: 'auto !important',
68 | ...Scrollbar,
69 | },
70 | },
71 | },
72 | },
73 | },
74 | '.ant-table-wrapper': {
75 | '.ant-table': {
76 | overflow: 'hidden',
77 | scrollbarColor: 'inherit !important',
78 | '.ant-table-container': {
79 | height: '100%',
80 | display: 'flex',
81 | flexDirection: 'column',
82 | '& > .ant-table-content': {
83 | backgroundColor: token.colorBgContainer,
84 | ...Scrollbar,
85 | },
86 | '& > .ant-table-body': {
87 | overflowY: 'auto !important',
88 | ...Scrollbar,
89 | },
90 | },
91 | },
92 | },
93 | },
94 | };
95 | };
96 |
--------------------------------------------------------------------------------
/src/ext-tree-select/ExtTreeSelect.tsx:
--------------------------------------------------------------------------------
1 | import {TreeSelect, TreeSelectProps} from 'antd';
2 | import React, {FC, useEffect, useState} from 'react';
3 | import ExtFormField from '../ext-form-field';
4 | import {includes, isEmpty} from "lodash-es";
5 |
6 | export type ExtTreeSelectProps = TreeSelectProps & {
7 | readonly?: boolean;
8 | valueRender?:
9 | | ((value?: any, option?: any) => React.ReactNode)
10 | | React.ReactNode;
11 | defaultRender?: React.ReactNode;
12 | };
13 | export const ExtTreeSelect: FC = ({
14 | readonly,
15 | valueRender,
16 | defaultRender,
17 | ...rest
18 | }) => {
19 | const [options, setOptions] = useState([]);
20 | const valueField = rest.fieldNames?.value ?? 'value';
21 | const labelField = rest.fieldNames?.label ?? 'label';
22 |
23 | const handleOptions = (options: any[]) => {
24 | const newOptions: any[] = [];
25 | options.forEach((option) => {
26 | newOptions.push({
27 | ...option,
28 | });
29 | if (option.children) {
30 | const childrenOptions = handleOptions(option.children);
31 | newOptions.push(...childrenOptions);
32 | }
33 | });
34 | return newOptions;
35 | };
36 |
37 | const handleValueOptions = (value: any) => {
38 | if (rest.multiple) {
39 | return options?.filter((option) => {
40 | return includes(value, option[valueField]);
41 | });
42 | } else {
43 | return options?.find((option) => option[valueField] === value);
44 | }
45 | };
46 |
47 | const handleFiledRender = (value: any, valueOptions: any) => {
48 | if (rest.multiple) {
49 | return valueOptions?.map((option: any) => option[labelField]).join(', ');
50 | } else {
51 | return valueOptions?.[labelField];
52 | }
53 | };
54 |
55 | useEffect(() => {
56 | if (isEmpty(rest.treeData)) {
57 | } else {
58 | const newOptions = handleOptions(rest.treeData || []);
59 | setOptions(newOptions);
60 | }
61 | }, [rest.treeData]);
62 |
63 | return (
64 | handleFiledRender(value, options)}
72 | >
73 |
74 |
75 | );
76 | };
77 |
--------------------------------------------------------------------------------
/src/drawer-form/drawer-form.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Drawer,
4 | DrawerProps,
5 | FormInstance,
6 | FormProps,
7 | Space,
8 | } from 'antd';
9 | import React, { FC, useRef, type SyntheticEvent } from 'react';
10 | import DrawerInnerForm, { DrawerInnerFormHandle } from './drawer-inner-form';
11 |
12 | export type DrawerFormProps = {
13 | /**
14 | * @description 触发标签
15 | * @default
16 | */
17 | trigger?: React.ReactNode | React.ReactElement;
18 | /**
19 | * @description 取消按钮文本
20 | * @default 取消
21 | */
22 | cancelText?: string;
23 | /**
24 | * @description 确定按钮文本
25 | * @default 确定
26 | */
27 | okText?: string;
28 | footer?: React.ReactNode;
29 | /**
30 | * @description 触发标签点击
31 | * @default
32 | */
33 | onTriggerClick?: () => void;
34 | onOk?: (e: React.MouseEvent) => void;
35 | onCancel?: (e: React.MouseEvent) => void;
36 | /**
37 | * @description 提交回调
38 | * @default
39 | */
40 | onSubmit?: (values: any, form?: FormInstance) => Promise | void;
41 | form?: FormInstance;
42 | /**
43 | * @description 表单值
44 | * @default
45 | */
46 | formValues?: any;
47 | formProps?: Omit;
48 | } & DrawerProps;
49 |
50 | export const DrawerForm: FC = ({
51 | trigger,
52 | cancelText = '取消',
53 | okText = '确定',
54 | footer,
55 | onTriggerClick,
56 | onOk,
57 | onCancel,
58 |
59 | onSubmit,
60 | form,
61 | formValues,
62 | formProps,
63 | ...rest
64 | }) => {
65 | const formRef = useRef({} as DrawerInnerFormHandle);
66 |
67 | const handleSubmit = (e: React.MouseEvent) => {
68 | onOk?.(e);
69 | if (onSubmit) {
70 | formRef.current.submit();
71 | }
72 | };
73 |
74 | const handleCancel = (e: React.MouseEvent) => {
75 | onCancel?.(e);
76 | };
77 |
78 | const footerEl = (
79 |
80 |
81 |
82 |
85 |
86 |
87 | );
88 | return (
89 | <>
90 | {trigger &&
91 | React.isValidElement(trigger) &&
92 | React.cloneElement(trigger, {
93 | ...trigger.props,
94 | onClick: (e?: SyntheticEvent) => {
95 | trigger.props.onClick?.(e);
96 | onTriggerClick?.();
97 | },
98 | })}
99 |
100 |
107 | {rest.children}
108 |
109 |
110 | >
111 | );
112 | };
113 |
--------------------------------------------------------------------------------
/src/page-header/page-header.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowLeftOutlined } from '@ant-design/icons';
2 | import {
3 | Avatar,
4 | AvatarProps,
5 | Breadcrumb,
6 | BreadcrumbProps,
7 | Button,
8 | Space,
9 | } from 'antd';
10 | import classNames from 'classnames';
11 | import React, { FC } from 'react';
12 | import { useCssInJs } from '../hooks';
13 | import { genPageHeaderStyle } from './styles';
14 |
15 | export type PageHeaderProps = {
16 | className?: string;
17 | style?: React.CSSProperties;
18 | children?: React.ReactNode;
19 | /**
20 | * @description 是否展示返回icon
21 | * @default true
22 | */
23 | backIcon?: boolean;
24 | avatar?: AvatarProps;
25 | /**
26 | * @description 标题
27 | * @default null
28 | */
29 | title?: React.ReactNode;
30 | /**
31 | * @description 副标题
32 | * @default null
33 | */
34 | subTitle?: React.ReactNode;
35 | /**
36 | * @description 面包屑
37 | * @default null
38 | */
39 | breadcrumb?: BreadcrumbProps;
40 | /**
41 | * @description 额外元素,标题最右侧
42 | * @default null
43 | */
44 | extra?: React.ReactNode;
45 | /**
46 | * @description 点击返回回调
47 | * @default null
48 | */
49 | onBack?: () => void;
50 | /**
51 | * @description 底部
52 | * @default null
53 | */
54 | footer?: React.ReactNode;
55 | };
56 |
57 | const PageHeader: FC = ({
58 | className,
59 | style,
60 | children,
61 | backIcon = true,
62 | avatar,
63 | title,
64 | subTitle,
65 | breadcrumb,
66 | extra,
67 | onBack,
68 | footer,
69 | }) => {
70 | const prefixCls = 'triones-ant-page-header';
71 |
72 | const { hashId } = useCssInJs({
73 | prefix: prefixCls,
74 | styleFun: genPageHeaderStyle,
75 | });
76 |
77 | return (
78 |
79 | {breadcrumb && (
80 |
81 |
82 |
83 |
84 |
85 | )}
86 |
87 |
88 | {avatar && }
89 | {backIcon && (
90 | }
93 | onClick={onBack}
94 | />
95 | )}
96 | {title && (
97 |
98 | {title}
99 |
100 | )}
101 | {subTitle && (
102 |
105 | {subTitle}
106 |
107 | )}
108 |
109 |
{extra}
110 |
111 | {children &&
{children}
}
112 | {footer &&
{footer}
}
113 |
114 | );
115 | };
116 | export default PageHeader;
117 |
--------------------------------------------------------------------------------
/src/ext-form-field/ExtFormField.tsx:
--------------------------------------------------------------------------------
1 | import React, {FC, isValidElement, memo, PropsWithChildren, useEffect, useState,} from 'react';
2 |
3 | export type CommonExtFormFieldProps = {
4 | readonly?: boolean;
5 | valueRender?:
6 | | ((value?: any, options?: any) => React.ReactNode)
7 | | React.ReactNode;
8 | defaultRender?: React.ReactNode;
9 | emptyPlaceholder?:React.ReactNode
10 | };
11 |
12 | export type ExtFormFieldProps = CommonExtFormFieldProps & {
13 | value?: any;
14 | defaultValue?: any;
15 | onChange?: (...params:any) => void;
16 | options?: any | ((value?: any) => any[]);
17 | fieldRender?: (value?: any, options?: any) => React.ReactNode;
18 | };
19 | export const ExtFormField: FC> = memo(
20 | ({
21 | children,
22 | value,
23 | defaultValue,
24 | onChange,
25 | options,
26 | readonly,
27 | valueRender,
28 | defaultRender,
29 | fieldRender,
30 | emptyPlaceholder = --
31 | }) => {
32 |
33 | const [internalValue, setInternalValue] = useState(value || defaultValue);
34 | const handleRender = () => {
35 | const valueOptions = typeof options === 'function' ? options(internalValue) : options;
36 | if (valueRender) {
37 | if (typeof valueRender === 'function') {
38 | return valueRender(internalValue, valueOptions);
39 | } else {
40 | return valueRender;
41 | }
42 | }
43 | if (fieldRender) {
44 | return fieldRender(internalValue, valueOptions);
45 | }
46 | return internalValue;
47 | };
48 |
49 | useEffect(() => {
50 | if (value === undefined) {
51 | return;
52 | }
53 | if (value !== internalValue) {
54 | setInternalValue(value);
55 | }
56 | }, [value]);
57 |
58 | if (readonly) {
59 | return {handleRender() || defaultRender || emptyPlaceholder}
;
60 | }
61 |
62 | if (children){
63 | if(isValidElement(children)){
64 | return React.cloneElement(children , {
65 | ...children.props,
66 | value: internalValue,
67 | onChange: (...params: any) => {
68 | if (params[0] && typeof params[0] === 'object' && 'nativeEvent' in params[0]) {
69 | setInternalValue(params[0].target.value);
70 | } else {
71 | setInternalValue(params[0]);
72 | }
73 | if (children.props.onChange){
74 | children.props.onChange(...params);
75 | }
76 | onChange?.(...params);
77 | }
78 | })
79 | }
80 | return <>{children}>
81 | }
82 | return ;
83 |
84 | },
85 | );
86 |
--------------------------------------------------------------------------------
/src/layout/sider.tsx:
--------------------------------------------------------------------------------
1 | import {MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons';
2 | import {Button} from 'antd';
3 | import classNames from 'classnames';
4 | import React, {FC, useEffect, useState} from 'react';
5 | import {useCssInJs} from '../hooks';
6 | import {LayoutItem} from './item';
7 | import {genSiderStyle} from './styles';
8 |
9 | export type LayoutSiderProps = {
10 | children?: React.ReactElement;
11 | className?: string | undefined;
12 | style?: React.CSSProperties;
13 | collapsedWidth?: number;
14 | collapsed?: boolean;
15 | collapsible?: boolean;
16 | width?: number | string;
17 | onCollapse?: (collapsed: boolean) => void;
18 | trigger?: React.ReactNode;
19 | };
20 | export const LayoutSider: FC = ({
21 | children,
22 | className,
23 | style,
24 | collapsedWidth = 80,
25 | collapsed = false,
26 | collapsible = false,
27 | width = 200,
28 | trigger,
29 | onCollapse,
30 | }) => {
31 | const [scopeCollapsed, setScopeCollapsed] = useState(collapsed);
32 | const prefixCls = 'triones-ant-layout-sider';
33 |
34 | const {hashId} = useCssInJs({
35 | prefix: prefixCls,
36 | styleFun: (prefix, token) => genSiderStyle(prefixCls, token, collapsedWidth),
37 | });
38 | const handleCollapse = () => {
39 | const changeCollapsed = !scopeCollapsed;
40 | setScopeCollapsed(changeCollapsed);
41 | onCollapse?.(changeCollapsed);
42 | };
43 |
44 | useEffect(() => {
45 | setScopeCollapsed(collapsed);
46 | }, [collapsed]);
47 |
48 | const displayWidth = scopeCollapsed ? collapsedWidth : width;
49 | let childrenEl: any = children;
50 | if (childrenEl && childrenEl.type.displayName === 'Menu') {
51 | childrenEl = React.createElement(childrenEl.type, {
52 | ...Object.assign({}, childrenEl.props, {
53 | inlineCollapsed: scopeCollapsed,
54 | mode: 'inline',
55 | }),
56 | });
57 | }
58 |
59 | return (
60 |
70 |
71 | {childrenEl}
72 |
73 | {collapsedWidth > 0 && (
74 |
75 | {trigger || (
76 |
89 | )}
90 |
91 | );
92 | };
93 |
--------------------------------------------------------------------------------
/src/verification-code-input/verification-code-input.tsx:
--------------------------------------------------------------------------------
1 | import { useCssInJs } from '@trionesdev/antd-react-ext';
2 | import { Input, InputProps } from 'antd';
3 | import classNames from 'classnames';
4 | import React, { FC, useEffect, useRef, useState } from 'react';
5 | import { genValidationCodeInputStyle } from './styles';
6 |
7 | const prefixCls = 'triones-validation-code-input';
8 |
9 | export type VerificationCodeInputProps = Omit & {
10 | /**
11 | * @description 发送按钮文案
12 | * @default '获取验证码'
13 | */
14 | sendText?: string;
15 | /**
16 | * @description 重新发送按钮文案
17 | * @default '重新发送'
18 | */
19 | resendText?: string;
20 | /**
21 | * @description 倒计时间隔时间
22 | * @default 60
23 | */
24 | intervalSeconds?: number;
25 | /**
26 | * @description 发送验证码
27 | * @default () => true
28 | */
29 | onSend?: () => Promise;
30 | };
31 |
32 | const SendSuffix: FC = ({
33 | sendText,
34 | resendText,
35 | intervalSeconds = 60,
36 | onSend,
37 | }) => {
38 | const [send, setSend] = useState(false);
39 | const [seconds, setSeconds] = useState(0);
40 | const secondsRef = useRef(0);
41 | const timer = useRef(null);
42 |
43 | useEffect(() => {
44 | if (seconds < 1) {
45 | clearTimeout(timer.current);
46 | timer.current = null;
47 | } else {
48 | setSend(true);
49 | }
50 | }, [seconds]);
51 |
52 | useEffect(() => {
53 | return () => {
54 | clearTimeout(timer.current);
55 | };
56 | }, []);
57 |
58 | const { hashId } = useCssInJs({
59 | prefix: prefixCls,
60 | styleFun: genValidationCodeInputStyle,
61 | });
62 |
63 | const handleSuffix = () => {
64 | if (seconds > 0) {
65 | return (
66 |
67 | {seconds}s
68 |
69 | );
70 | }
71 | return (
72 | {
75 | if (!timer.current) {
76 | if (await onSend?.()) {
77 | secondsRef.current = intervalSeconds - 1;
78 | setSeconds(secondsRef.current);
79 | timer.current = setInterval(() => {
80 | secondsRef.current = secondsRef.current - 1;
81 | setSeconds(secondsRef.current);
82 | }, 1000);
83 | }
84 | }
85 | }}
86 | >
87 | {send ? resendText : sendText}
88 |
89 | );
90 | };
91 | return handleSuffix();
92 | };
93 |
94 | const VerificationCodeInput: FC = ({
95 | sendText = '获取验证码',
96 | resendText = '重新发送',
97 | intervalSeconds = 60,
98 | onSend,
99 | ...props
100 | }) => {
101 | const { hashId } = useCssInJs({
102 | prefix: prefixCls,
103 | styleFun: genValidationCodeInputStyle,
104 | });
105 |
106 | return (
107 |
117 | }
118 | />
119 | );
120 | };
121 | export default VerificationCodeInput;
122 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AppToolbar } from './app-toolbar';
2 | export type { AppToolbarProps } from './app-toolbar';
3 |
4 | export { default as AvatarEditor } from './avatar-editor';
5 | export type { AvatarEditorProps } from './avatar-editor';
6 |
7 | export { default as DrawerForm } from './drawer-form';
8 | export type { DrawerFormProps } from './drawer-form';
9 |
10 | export { default as EditableDesc } from './editable-desc';
11 | export type { EditableDescProps } from './editable-desc';
12 |
13 | export { default as ExtDatePicker} from "./ext-date-picker"
14 | export type { ExtDatePickerProps,ExtDateRangePickerProps } from "./ext-date-picker"
15 |
16 | export { default as ExtFormField } from './ext-form-field';
17 | export type { ExtFormFieldProps } from './ext-form-field';
18 |
19 | export { default as ExtInput } from './ext-input';
20 | export type { ExtInputProps } from './ext-input';
21 |
22 | export { default as ExtInputNumber } from './ext-input-number';
23 | export type { ExtInputNumberProps } from './ext-input-number';
24 |
25 | export { default as ExtCheckbox } from './ext-checkbox';
26 | export type { ExtCheckBoxProps, ExtCheckBoxGroupProps } from './ext-checkbox';
27 |
28 | export {default as ExtRadio} from "./ext-radio"
29 | export type {ExtRadioProps, ExtRadioGroupProps} from "./ext-radio"
30 |
31 | export { default as ExtSelect } from './ext-select';
32 | export type { ExtSelectProps } from './ext-select';
33 |
34 | export { default as ExtSwitch } from './ext-switch';
35 | export type { ExtSwitchProps } from './ext-switch';
36 |
37 | export { default as ExtTreeSelect } from './ext-tree-select';
38 | export type { ExtTreeSelectProps } from './ext-tree-select';
39 |
40 | export { default as FetchSelect } from './fetch-select';
41 | export type { FetchSelectProps } from './fetch-select';
42 |
43 | export { default as FetchTreeSelect } from './fetch-tree-select';
44 | export type { FetchTreeSelectProps } from './fetch-tree-select';
45 |
46 | export { default as Fieldset } from './fieldset';
47 | export type { FieldsetProps } from './fieldset';
48 |
49 | export { default as FieldsMapping } from './fields-mapping';
50 | export type { FieldsMappingProps } from './fields-mapping';
51 |
52 | export { default as FieldWrapper } from './field-wrapper';
53 | export type { FieldWrapperProps } from './field-wrapper';
54 |
55 | export { default as GridTable } from './grid-table';
56 | export type { GridTableProps } from './grid-table';
57 |
58 | export { default as Layout } from './layout';
59 | export type { LayoutProps } from './layout';
60 |
61 | export { default as ModalForm } from './modal-form';
62 | export type { ModalFormProps } from './modal-form';
63 |
64 | export { default as PageHeader } from './page-header';
65 | export type { PageHeaderProps } from './page-header';
66 |
67 | export { default as PictureUpload } from './picture-upload';
68 | export type { PictureUploadProps } from './picture-upload';
69 |
70 | export { default as SearchToolbar } from './search-toolbar';
71 | export type { SearchToolbarProps } from './search-toolbar';
72 |
73 | export { default as TableToolbar } from './table-toolbar';
74 | export type { TableToolbarProps } from './table-toolbar';
75 |
76 | export { default as VerificationCodeInput } from './verification-code-input';
77 | export type { VerificationCodeInputProps } from './verification-code-input';
78 |
79 | export { default as VideoUpload } from './video-upload';
80 | export type { VideoUploadProps } from './video-upload';
81 |
82 | //region utils
83 | export * from './hooks';
84 | export * from './util';
85 | //endregion
86 |
--------------------------------------------------------------------------------
/src/fields-mapping/styles.tsx:
--------------------------------------------------------------------------------
1 | import {GlobalToken} from "antd";
2 | import {CSSInterpolation} from "@ant-design/cssinjs";
3 |
4 | export const genFieldsMappingStyle = (
5 | prefixCls: string,
6 | token: GlobalToken,
7 | ): CSSInterpolation => {
8 | return {
9 | [`.${prefixCls}`]: {
10 | position: 'relative',
11 | minWidth: '100%',
12 | minHeight: '100%',
13 | display: 'flex',
14 | justifyContent: 'space-between',
15 | boxSizing: 'border-box',
16 | [`&.drawing`]: {
17 | userSelect: 'none'
18 | },
19 | [`&-table`]: {
20 | color: '#666',
21 | fontSize: '12px',
22 | zIndex: 3,
23 | [`&-head`]: {
24 | backgroundColor: '#f0f0f0',
25 | },
26 | [`&-row`]: {
27 | display: 'flex',
28 | gap: '8px',
29 | height: 36,
30 | position: 'relative',
31 | [`&-port`]: {
32 | [`&::before`]: {
33 | content: '\' \'',
34 | display: 'inline-block',
35 | position: 'absolute',
36 | backgroundColor: token.colorPrimary,
37 | width: 6,
38 | height: 6,
39 | borderRadius: 6,
40 | boxShadow: '0px 0.5px 1.5px rgba(0, 0, 0, 0.22) inset'
41 | },
42 | position: 'absolute',
43 | top: 'calc(50% - 7px)',
44 | width: 14,
45 | height: 14,
46 | borderRadius: 14,
47 | backgroundColor: '#FFF',
48 | padding: 4,
49 | boxShadow: '0px 1px 2px rgba(0, 0, 0, 0.2)',
50 | cursor: 'crosshair',
51 | boxSizing: 'border-box'
52 | }
53 | },
54 | [`&-body`]: {
55 | backgroundColor: '#fcfcfc',
56 | [`.${prefixCls}`]: {
57 | [`&-table`]: {
58 | [`&-row`]: {
59 | borderBottom: '1px dashed #dddddd'
60 | }
61 | }
62 | }
63 | },
64 | [`&-cell`]: {
65 | height: '100%',
66 | overflow: 'hidden',
67 | textOverflow: 'ellipsis',
68 | whiteSpace: 'nowrap',
69 | boxSizing: 'border-box',
70 | display: 'flex',
71 | alignItems: 'center',
72 | padding: '8px 16px'
73 | },
74 |
75 | },
76 | [`&-source`]: {
77 | [`.${prefixCls}`]: {
78 | [`&-table`]: {
79 | [`&-row`]: {
80 | [`&-port`]: {
81 | right: '-7px'
82 | }
83 | }
84 | }
85 | }
86 | },
87 | [`&-target`]: {
88 | [`.${prefixCls}`]: {
89 | [`&-table`]: {
90 | [`&-row`]: {
91 | [`&-port`]: {
92 | left: '-7px'
93 | }
94 | }
95 | }
96 | }
97 | },
98 | [`&-lines`]: {
99 | position: 'absolute',
100 | width: '100%',
101 | height: '100%',
102 | boxSizing: 'border-box',
103 | [`svg`]: {
104 | [`.${prefixCls}-g`]: {
105 | [`&:hover`]: {
106 | [`.${prefixCls}-line-remove`]: {
107 | opacity: 1
108 | }
109 | }
110 | },
111 | [`.${prefixCls}-line`]: {
112 | stroke: token.colorPrimary,
113 | fill: 'none',
114 | strokeWidth:'2px'
115 | },
116 | [`.${prefixCls}-line-remove`]: {
117 | cursor: 'pointer',
118 | opacity: 0
119 | }
120 | }
121 | }
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/fetch-tree-select/fetch-tree-select.tsx:
--------------------------------------------------------------------------------
1 | import React, {FC, memo, useCallback, useEffect, useMemo, useState} from 'react';
2 | import ExtTreeSelect, {ExtTreeSelectProps} from "../ext-tree-select";
3 | import {SessionStorageUtils} from "@trionesdev/antd-react-ext";
4 | import {concat, debounce, isEmpty} from "lodash-es";
5 |
6 | export type FetchTreeSelectProps = {
7 | /**
8 | * @description 初始化值选项,用于列表等场景的时候,Select 组件可以展示,不需要去请求选项
9 | * @default
10 | */
11 | initialValueOptions?: any[];
12 | /**
13 | * @description 固定选项
14 | * @default
15 | */
16 | fixedOptions?: any[];
17 | /**
18 | * @description 是否下拉的时候请求数据
19 | * @default false
20 | */
21 | dropdownFetch?: boolean;
22 | /**
23 | * @description 是否允许请求
24 | * @default true
25 | */
26 | fetchEnable?: boolean;
27 | /**
28 | * @description 是否每次都请求数据,默认只请求一次
29 | * @default false
30 | */
31 | fetchAlways?: boolean;
32 | /**
33 | * @description 请求方法
34 | * @default undefined
35 | */
36 | fetchRequest?: (searchValue?: string) => Promise;
37 | /**
38 | * @description 缓存key
39 | * @default undefined
40 | */
41 | cacheKey?: string;
42 | /**
43 | * @description 缓存过期时间
44 | * @default 0
45 | */
46 | cacheExpire?: number;
47 | } & Omit;
48 | export const FetchTreeSelect: FC = memo(({
49 | initialValueOptions = [],
50 | fixedOptions,
51 | dropdownFetch = false,
52 | fetchEnable = true,
53 | fetchAlways,
54 | fetchRequest,
55 | cacheKey,
56 | cacheExpire = 0,
57 | ...props
58 | }) => {
59 |
60 | const [fetched, setFetched] = useState(false);
61 | const [fetchedOptions, setFetchedOptions] = useState([])
62 |
63 |
64 | const options = useMemo(() => {
65 | if (isEmpty(fetchedOptions)) {
66 | return concat([], fixedOptions || [], initialValueOptions || [])
67 | } else {
68 | return concat([], fixedOptions || [], fetchedOptions || [])
69 | }
70 | }, [fixedOptions, initialValueOptions, fetchedOptions])
71 |
72 | const handleQuery = useCallback(
73 | (searchValue?: string) => {
74 | const request = fetchRequest
75 | ? fetchRequest(searchValue)
76 | : Promise.resolve([]);
77 | const data = SessionStorageUtils.getExpireItem(cacheKey, true);
78 | if (!isEmpty(data) && cacheExpire) {
79 | setFetchedOptions(data || [])
80 | setFetched(true);
81 | return;
82 | }
83 | request
84 | .then((data) => {
85 | setFetchedOptions(data || [])
86 | SessionStorageUtils.setExpireItem(cacheKey, data, cacheExpire);
87 | })
88 | .finally(() => {
89 | setFetched(true);
90 | });
91 | },
92 | [fetchRequest, cacheKey],
93 | );
94 |
95 |
96 | useEffect(() => {
97 | if (!dropdownFetch) {
98 | handleQuery();
99 | }
100 | }, []);
101 |
102 |
103 | return (
104 | {
109 | if (open && dropdownFetch && fetchEnable && (fetchAlways || !fetched)) {
110 | handleQuery();
111 | }
112 | props.onOpenChange?.(open);
113 | }}
114 | />
115 | );
116 | });
117 |
--------------------------------------------------------------------------------
/src/fetch-select/fetch-select.tsx:
--------------------------------------------------------------------------------
1 | import {isEmpty, concat, debounce} from 'lodash-es';
2 | import React, {FC, memo, useCallback, useEffect, useMemo, useState} from 'react';
3 | import ExtSelect, {ExtSelectProps} from "../ext-select";
4 | import {SessionStorageUtils} from "@trionesdev/antd-react-ext";
5 |
6 |
7 | export type FetchSelectProps = {
8 | /**
9 | * @description 初始化值选项,用于列表等场景的时候,Select 组件可以展示,不需要去请求选项
10 | * @default
11 | */
12 | initialValueOptions?: any[];
13 | /**
14 | * @description 固定选项
15 | * @default
16 | */
17 | fixedOptions?: any[];
18 | /**
19 | * @description 是否下拉的时候请求数据
20 | * @default false
21 | */
22 | dropdownFetch?: boolean;
23 | /**
24 | * @description 是否允许请求
25 | * @default true
26 | */
27 | fetchEnable?: boolean;
28 | /**
29 | * @description 是否每次都请求数据,默认只请求一次
30 | * @default false
31 | */
32 | fetchAlways?: boolean;
33 | fetchRequest?: (searchValue?: string) => Promise;
34 | defaultRender?: React.ReactNode;
35 | cacheKey?: string;
36 | cacheExpire?: number;
37 | } & Omit;
38 | export const FetchSelect: FC = memo(({
39 | initialValueOptions = [],
40 | fixedOptions,
41 | dropdownFetch = false,
42 | fetchEnable = true,
43 | fetchAlways = false,
44 | fetchRequest,
45 | defaultRender,
46 | cacheKey,
47 | cacheExpire = 0,
48 | ...props
49 | }) => {
50 | const [fetched, setFetched] = useState(false);
51 | const [fetchedOptions, setFetchedOptions] = useState([])
52 |
53 | const options = useMemo(() => {
54 | if (isEmpty(fetchedOptions)) {
55 | return concat([], fixedOptions || [], initialValueOptions || [])
56 | } else {
57 | return concat([], fixedOptions || [], fetchedOptions || [])
58 | }
59 | }, [fixedOptions, initialValueOptions, fetchedOptions])
60 |
61 | const handleQuery = useCallback(
62 | (searchValue?: string) => {
63 | const request =
64 | fetchRequest && fetchEnable
65 | ? fetchRequest(searchValue)
66 | : Promise.resolve([]);
67 | const data = SessionStorageUtils.getExpireItem(cacheKey, true);
68 | if (data) {
69 | setFetchedOptions(data || [])
70 | setFetched(true);
71 | return;
72 | }
73 | request
74 | .then((data: any) => {
75 | setFetchedOptions(data || [])
76 | SessionStorageUtils.setExpireItem(cacheKey, data, cacheExpire);
77 | })
78 | .finally(() => {
79 | setFetched(true);
80 | });
81 | },
82 | [fetchRequest],
83 | );
84 |
85 | useEffect(() => {
86 | if (!dropdownFetch) {
87 | handleQuery();
88 | }
89 | }, []);
90 |
91 | return (
92 | {
97 | if (open && dropdownFetch && fetchEnable && (fetchAlways || !fetched)) {
98 | handleQuery();
99 | }
100 | props.onOpenChange?.(open);
101 | }}
102 | defaultRender={defaultRender}
103 | />
104 | );
105 | });
106 |
--------------------------------------------------------------------------------
/src/avatar-editor/AvatarEditor.tsx:
--------------------------------------------------------------------------------
1 | import { CameraOutlined, UserOutlined } from '@ant-design/icons';
2 | import { useCssInJs } from '@trionesdev/antd-react-ext';
3 | import { Avatar, Spin } from 'antd';
4 | import classNames from 'classnames';
5 | import { eq } from 'lodash-es';
6 | import React, { CSSProperties, FC, useEffect, useState } from 'react';
7 | import { AvatarCropModal } from './AvatarCropModal';
8 | import { genAvatarEditorStyle } from './styles';
9 |
10 | export type AvatarEditorProps = {
11 | value?: string;
12 | onChange?: (value: string) => void;
13 | style?: CSSProperties;
14 | className?: string;
15 | icon?: React.ReactNode;
16 | /**
17 | * @description 大小
18 | * @default 200
19 | */
20 | size?: number;
21 | /**
22 | * @description 可以选择的文件类型
23 | * @default .jpg,.jpeg,.png,.svg,.webp
24 | */
25 | accept?: string;
26 | /**
27 | * @description 是否可以编辑
28 | * @default true
29 | */
30 | editable?: boolean;
31 | /**
32 | * @description 自定义上传请求,默认为上传到服务器,如果为空,则直接返回图片地址
33 | * @default
34 | */
35 | uploadRequest?: (file: File) => Promise;
36 | };
37 |
38 | export const AvatarEditor: FC = ({
39 | value,
40 | onChange,
41 | style,
42 | className,
43 | icon,
44 | size = 200,
45 | editable = true,
46 | accept = '.jpg,.jpeg,.png,.svg,.webp',
47 | uploadRequest,
48 | }) => {
49 | const [innerValue, setInnerValue] = useState(value || '');
50 | const [open, setOpen] = useState(false);
51 | const [cropImage, setCropImage] = useState();
52 | const [loading, setLoading] = useState(false);
53 |
54 | const getBase64 = (img: any, callback: (url: string) => void) => {
55 | const reader = new FileReader();
56 | reader.addEventListener('load', () => callback(reader.result as string));
57 | reader.readAsDataURL(img);
58 | };
59 |
60 | const handleImageSelect = async (e: any) => {
61 | const file = e.target.files?.[0];
62 | e.target.value = '';
63 | if (!file) {
64 | return;
65 | }
66 | getBase64(file, (url) => {
67 | setCropImage(url);
68 | setOpen(true);
69 | });
70 | };
71 |
72 | const handleUpload = (dataURL: any) => {
73 | if (uploadRequest) {
74 | const binaryData = atob(dataURL.split(',')[1]);
75 | const array = new Uint8Array(binaryData.length);
76 | for (let i = 0; i < binaryData.length; i++) {
77 | array[i] = binaryData.charCodeAt(i);
78 | }
79 | const blob = new Blob([array], { type: 'application/octet-stream' });
80 | const typeInfo = dataURL.split(';')[0];
81 | const mimeType = typeInfo.split(':')[1];
82 | const fileExt = mimeType.split('/')[1];
83 | const file = new File([blob], `file.${fileExt}`, { type: mimeType });
84 | setLoading(true);
85 | uploadRequest(file)
86 | .then(async (url: string) => {
87 | setInnerValue(url);
88 | onChange?.(url);
89 | })
90 | .finally(() => {
91 | setLoading(false);
92 | });
93 | setOpen(false);
94 | } else {
95 | setInnerValue(dataURL);
96 | setOpen(false);
97 | onChange?.(dataURL);
98 | }
99 | };
100 |
101 | useEffect(() => {
102 | if (!eq(value || '', innerValue || '')) {
103 | setInnerValue(value || '');
104 | }
105 | }, [value]);
106 |
107 | const prefixCls = 'triones-avatar-editor';
108 | const { hashId } = useCssInJs({
109 | prefix: prefixCls,
110 | styleFun: genAvatarEditorStyle,
111 | });
112 | return (
113 |
117 |
118 |
119 |
120 |
}
124 | src={innerValue || null}
125 | />
126 | {editable && (
127 |
128 |
137 |
138 | )}
139 |
140 |
141 |
142 |
{
146 | setOpen(false);
147 | setCropImage(undefined);
148 | }}
149 | onOk={handleUpload}
150 | />
151 |
152 | );
153 | };
154 |
--------------------------------------------------------------------------------
/src/picture-upload/PictureUpload.tsx:
--------------------------------------------------------------------------------
1 | import { UploadOutlined } from '@ant-design/icons';
2 | import { useCssInJs } from '@trionesdev/antd-react-ext';
3 | import {
4 | Button,
5 | Divider,
6 | Image,
7 | Space,
8 | Spin,
9 | Upload,
10 | message,
11 | Empty,
12 | } from 'antd';
13 | import classNames from 'classnames';
14 | import React, { CSSProperties, FC, useEffect, useState } from 'react';
15 | import { genPictureUploadStyle } from './styles';
16 |
17 | export type PictureUploadProps = {
18 | readonly?: boolean;
19 | value?: string;
20 | onChange?: (value: string) => void;
21 | style?: CSSProperties;
22 | className?: string;
23 | width?: number;
24 | height?: number;
25 | preview?: boolean;
26 | /**
27 | * @description 可选择的文件类型
28 | * @default .jpg,.jpeg,.png,.webp,.svg
29 | */
30 | accept?: string;
31 | /**
32 | * @description 大小限制,单位:字节
33 | * @default
34 | */
35 | limitSize?: number;
36 | /**
37 | * @description 上传请求,返回图片地址
38 | * @default
39 | */
40 | uploadRequest?: (file: File) => Promise;
41 | };
42 | export const PictureUpload: FC = ({
43 | readonly,
44 | value,
45 | onChange,
46 | style,
47 | className,
48 | width = 320,
49 | height = 180,
50 | preview = true,
51 | limitSize,
52 | accept = '.jpg,.jpeg,.png,.webp,.svg',
53 | uploadRequest,
54 | }) => {
55 | const [scopeValue, setScopeValue] = useState(value);
56 | const [loading, setLoading] = useState(false);
57 |
58 | const getBase64 = (img: any, callback: (url: string) => void) => {
59 | const reader = new FileReader();
60 | reader.addEventListener('load', () => callback(reader.result as string));
61 | reader.readAsDataURL(img);
62 | };
63 |
64 | const handleBeforeUpload = (param: any) => {
65 | if (limitSize && param.file.size > limitSize) {
66 | message.warning('图片大小不能超过' + limitSize / 1024 + 'KB');
67 | return false;
68 | }
69 | return true;
70 | };
71 |
72 | const handleUpload = async (param: any) => {
73 | if (!param.file) {
74 | return;
75 | }
76 |
77 | if (uploadRequest) {
78 | setLoading(true);
79 | uploadRequest?.(param.file)
80 | .then((url) => {
81 | setScopeValue(url);
82 | })
83 | .finally(() => {
84 | setLoading(false);
85 | });
86 | } else {
87 | getBase64(param.file, (url) => {
88 | setScopeValue(url);
89 | });
90 | }
91 | };
92 |
93 | const handleClean = () => {
94 | setScopeValue('');
95 | };
96 |
97 | useEffect(() => {
98 | if (value !== scopeValue) {
99 | setScopeValue(value);
100 | }
101 | }, [value]);
102 |
103 | useEffect(() => {
104 | onChange?.(scopeValue);
105 | }, [scopeValue]);
106 |
107 | const prefixCls = `triones-picture-upload`;
108 | const { hashId } = useCssInJs({
109 | prefix: prefixCls,
110 | styleFun: genPictureUploadStyle,
111 | });
112 | return (
113 |
117 |
118 |
119 | {readonly ? (scopeValue ?
120 |
121 |
127 |
:
128 | ) : scopeValue ? (
129 |
130 |
136 |
137 | }>
138 |
144 |
145 |
146 |
149 |
150 |
151 |
152 | ) : (
153 |
162 |
163 |
164 | )}
165 |
166 |
167 |
168 | );
169 | };
170 |
--------------------------------------------------------------------------------
/src/video-upload/VideoUpload.tsx:
--------------------------------------------------------------------------------
1 | import { UploadOutlined } from '@ant-design/icons';
2 | import { useCssInJs } from '@trionesdev/antd-react-ext';
3 | import { Button, Col, Flex, Input, message, Row, Upload } from 'antd';
4 | import classNames from 'classnames';
5 | import { eq } from 'lodash-es';
6 | import React, { FC, useEffect, useState } from 'react';
7 | import PictureUpload from '../picture-upload';
8 | import { NoFile } from './Icons';
9 | import { genVideoUploadStyle } from './styles';
10 |
11 | export type VideoUploadProps = {
12 | value?: {
13 | url?: string;
14 | poster?: string;
15 | };
16 | onChange?: (value: { url?: string; poster?: string }) => void;
17 | style?: React.CSSProperties;
18 | className?: string;
19 | width?: number;
20 | height?: number;
21 | preview?: boolean;
22 | videoAccept?: string;
23 | videoLimitSize?: number;
24 | videoUploadRequest?: (file: File) => Promise;
25 | posterUploadRequest?: (file: File) => Promise;
26 | };
27 | export const VideoUpload: FC = ({
28 | value,
29 | onChange,
30 | style,
31 | className,
32 | width,
33 | height,
34 | videoAccept,
35 | videoLimitSize,
36 | videoUploadRequest,
37 | posterUploadRequest,
38 | }) => {
39 | const [innerValue, setInnerValue] = useState<{
40 | url?: string;
41 | poster?: string;
42 | }>(value || {});
43 | const [url, setUrl] = useState();
44 | const [fileList, setFileList] = useState();
45 |
46 | const handleBeforeUpload = (param: any) => {
47 | if (videoLimitSize && param.file.size > videoLimitSize) {
48 | message.warning('视频大小不能超过' + videoLimitSize / 1024 + 'KB');
49 | return false;
50 | }
51 | return true;
52 | };
53 |
54 | const handleUpload = async (param: any) => {
55 | if (!param.file) {
56 | return;
57 | }
58 |
59 | if (videoUploadRequest) {
60 | videoUploadRequest?.(param.file)
61 | .then((url) => {
62 | setInnerValue({ ...innerValue, url: url });
63 | setFileList(
64 | (fileList || []).map((file: any) => {
65 | return { ...file, status: 'done' };
66 | }),
67 | );
68 | })
69 | .catch((ex: any) => {
70 | message.error(ex.message);
71 | setFileList(
72 | (fileList || []).map((file: any) => {
73 | return { ...file, status: 'error' };
74 | }),
75 | );
76 | });
77 | } else {
78 | setFileList(
79 | (fileList || []).map((file: any) => {
80 | return { ...file, status: 'error' };
81 | }),
82 | );
83 | }
84 | };
85 |
86 | const handleSetInnerValue = (innerValue: any) => {
87 | setInnerValue(innerValue || {});
88 | onChange?.(innerValue);
89 | };
90 |
91 | useEffect(() => {
92 | if (!eq(value || {}, innerValue || {})) {
93 | setInnerValue(value || {});
94 | }
95 | }, [value]);
96 |
97 | const prefixCls = `triones-video-upload`;
98 | const { hashId } = useCssInJs({
99 | prefix: prefixCls,
100 | styleFun: genVideoUploadStyle,
101 | });
102 | return (
103 |
104 |
105 |
109 | {innerValue?.url ? (
110 |
116 | ) : (
117 |
118 | )}
119 |
120 |
121 | 视频封面
122 | {
127 | handleSetInnerValue({ ...innerValue, poster: value });
128 | }}
129 | />
130 | 视频文件
131 |
132 | {
141 | setFileList(fileList);
142 | }}
143 | >
144 | }>上传视频
145 |
146 |
147 | 视频链接
148 |
149 |
150 | {
156 | setUrl(e.target.value);
157 | }}
158 | />
159 |
167 |
168 |
169 |
170 |
171 |
172 | );
173 | };
174 |
--------------------------------------------------------------------------------
/src/editable-desc/editable-desc.tsx:
--------------------------------------------------------------------------------
1 | import { CheckOutlined, CloseOutlined, EditOutlined } from '@ant-design/icons';
2 | import { Button, Space } from 'antd';
3 | import classNames from 'classnames';
4 | import { isEqual } from 'lodash-es';
5 | import React, { FC, isValidElement, useEffect, useState } from 'react';
6 | import { useCssInJs } from '../hooks';
7 | import { genEditableDescStyle } from './styles';
8 |
9 | export type EditableDescProps = {
10 | children?: React.ReactElement;
11 | /**
12 | * @description 是否编辑状态
13 | * @default false
14 | */
15 | editing?: boolean;
16 | /**
17 | * @description 值
18 | * @default
19 | */
20 | value?: any;
21 | /**
22 | * @description 值渲染
23 | * @default
24 | */
25 | valueRender?: ((value?: any) => React.ReactNode) | React.ReactNode;
26 | onChange?: (val: any) => void;
27 | /**
28 | * @description 将按钮宽度调整为其父宽度的选项
29 | * @default
30 | */
31 | block?: boolean;
32 | /**
33 | * @description 占位符
34 | * @default
35 | */
36 | placeholder?: React.ReactNode;
37 | /**
38 | * @description 点击进入编辑,如果设置为true,建议每个每个组件配合 afterEditingChange 独立控制
39 | * @default false
40 | */
41 | clickEdit?: boolean;
42 | /**
43 | * @description 手动变更, 为ture 的时候,修改后的值不会直接变化,需要手动确认或取消
44 | * @default false
45 | */
46 | manualChange?: boolean;
47 | /**
48 | * @description 是否展示编辑Icon
49 | * @default false
50 | */
51 | editIcon?: boolean;
52 | /**
53 | * @description 确定时调用
54 | * @default
55 | */
56 | onOk?: (val: any) => Promise;
57 | /**
58 | * @description 取消时调用
59 | * @default false
60 | */
61 | onCancel?: () => void;
62 | /**
63 | * @description 编辑状态改变时调用
64 | * @param editing
65 | */
66 | afterEditingChange?: (editing: boolean) => void;
67 | };
68 |
69 | export const EditableDesc: FC = ({
70 | children,
71 | editing = false,
72 | value,
73 | valueRender,
74 | onChange,
75 | block = false,
76 | placeholder,
77 | clickEdit = false,
78 | manualChange = false,
79 | editIcon = false,
80 | onOk,
81 | onCancel,
82 | afterEditingChange,
83 | }) => {
84 | const [scopeEditing, setScopeEditing] = useState(editing);
85 | const [scopeValue, setScopeValue] = useState(value);
86 | const prefixCls = 'triones-ant-editable-desc';
87 | const { hashId } = useCssInJs({
88 | prefix: prefixCls,
89 | styleFun: genEditableDescStyle,
90 | });
91 |
92 | const handleWrapperClick = () => {
93 | if (clickEdit) {
94 | setScopeEditing(true);
95 | }
96 | };
97 |
98 | const handleChange = (val: any) => {
99 | if (manualChange) {
100 | if (val?.target) {
101 | setScopeValue((val.target as HTMLInputElement).value);
102 | } else {
103 | setScopeValue(val);
104 | }
105 | } else {
106 | if (onChange) {
107 | onChange(val);
108 | if (val?.target) {
109 | setScopeValue((val.target as HTMLInputElement).value);
110 | } else {
111 | setScopeValue(val);
112 | }
113 | }
114 | }
115 | };
116 |
117 | const handleOk = () => {
118 | let okAction = onOk?.(scopeValue) || Promise.resolve();
119 | okAction.then(() => {
120 | setScopeEditing(false);
121 | onChange?.(scopeValue);
122 | });
123 | };
124 | const handleCancel = () => {
125 | setScopeEditing(false);
126 | setScopeValue(value);
127 | onCancel?.();
128 | };
129 |
130 | useEffect(() => {
131 | if (!isEqual(value, scopeValue)) {
132 | setScopeValue(value);
133 | }
134 | }, [value]);
135 |
136 | useEffect(() => {
137 | setScopeEditing(editing);
138 | }, [editing]);
139 |
140 | useEffect(() => {
141 | afterEditingChange?.(scopeEditing);
142 | }, [scopeEditing]);
143 |
144 | const text = valueRender
145 | ? typeof valueRender === 'function'
146 | ? valueRender(scopeValue)
147 | : valueRender
148 | : scopeValue;
149 |
150 | return (
151 |
155 |
156 | {scopeEditing ? (
157 | children && isValidElement(children) ? (
158 | React.cloneElement(children as React.ReactElement, {
159 | onChange: handleChange,
160 | value: scopeValue,
161 | })
162 | ) : null
163 | ) : (
164 |
172 | {scopeValue ? text : placeholder}
173 |
174 | )}
175 |
176 | {!scopeEditing && editIcon && (
177 |
}
181 | onClick={() => {
182 | setScopeEditing(true);
183 | }}
184 | />
185 | )}
186 | {manualChange && scopeEditing && (
187 |
188 | }
192 | onClick={handleOk}
193 | />
194 | }
198 | onClick={handleCancel}
199 | />
200 |
201 | )}
202 |
203 | );
204 | };
205 |
--------------------------------------------------------------------------------
/src/search-toolbar/search-toolbar.tsx:
--------------------------------------------------------------------------------
1 | import { DownOutlined, UpOutlined } from '@ant-design/icons';
2 | import { Button, Col, Form, FormItemProps, Grid, Row, Space } from 'antd';
3 | import classNames from 'classnames';
4 | import { size as _size } from 'lodash-es';
5 | import React, { FC, useEffect, useState } from 'react';
6 | import { useCssInJs } from '../hooks';
7 | import { genSearchToolbarStyle } from './styled';
8 |
9 | const { useBreakpoint } = Grid;
10 |
11 | export type SearchToolbarProps = {
12 | style?: React.CSSProperties;
13 | className?: string;
14 | expanded?: boolean;
15 | items?: FormItemProps[];
16 | layout?: 'horizontal' | 'inline' | 'vertical';
17 | labelCol?: { span?: number; offset?: number };
18 | labelAlign?: 'left' | 'right';
19 | size?: 'large' | 'middle' | 'small';
20 | initialValues?: any;
21 | afterExpandChange?: (expanded: boolean) => void;
22 | /**
23 | * @description 查询参数改变后回调
24 | * @default
25 | */
26 | onSearchParamsChange?: (values: any) => void;
27 | onSearch?: (values: any) => void;
28 | onReset?: () => void;
29 | span?: number;
30 | xs?: number;
31 | sm?: number;
32 | md?: number;
33 | lg?: number;
34 | xl?: number;
35 | xxl?: number;
36 | };
37 | const SearchToolbar: FC = ({
38 | style,
39 | className,
40 | expanded = true,
41 | items,
42 | layout,
43 | labelCol,
44 | labelAlign,
45 | size,
46 | initialValues,
47 | afterExpandChange,
48 | onSearch,
49 | onSearchParamsChange,
50 | onReset,
51 | span = 6,
52 | xs,
53 | sm,
54 | md,
55 | lg,
56 | xl,
57 | xxl,
58 | }) => {
59 | const [form] = Form.useForm();
60 | const screens = useBreakpoint();
61 | const [scopeExpanded, setScopeExpanded] = useState(expanded);
62 | const [colSpan, setColSpan] = useState(span);
63 | const [rowColSize, setRowColSize] = useState(4);
64 | const [offsetSpan, setOffsetSpan] = useState(0);
65 | const [expandable, setExpandable] = useState(false);
66 |
67 | //计算需要补偿的列数
68 | const handleCompleteCompensateColCount = (
69 | rowColSize: number,
70 | itemSize: number,
71 | ) => {
72 | if (scopeExpanded) {
73 | const remainder = (itemSize + 1) % rowColSize;
74 | return remainder === 0 ? 0 : rowColSize - (remainder % rowColSize);
75 | } else {
76 | return itemSize > rowColSize - 1 ? 0 : rowColSize - itemSize - 1;
77 | }
78 | };
79 |
80 | const handleCompute = () => {
81 | const itemSize = _size(items);
82 | const rowColSize = 24 / colSpan;
83 | const compensateColCount = handleCompleteCompensateColCount(
84 | rowColSize,
85 | itemSize,
86 | ); //需要补偿列数
87 | const offsetSpan = colSpan * compensateColCount;
88 |
89 | setRowColSize(rowColSize);
90 | setOffsetSpan(offsetSpan);
91 | setExpandable(itemSize > rowColSize - 1);
92 | };
93 |
94 | const handleSearch = () => {
95 | form.validateFields().then((values: any) => {
96 | onSearch?.(values);
97 | });
98 | };
99 |
100 | const handleReset = () => {
101 | form.resetFields();
102 | onSearchParamsChange?.({});
103 | onReset?.();
104 | };
105 |
106 | useEffect(() => {
107 | let colSpan = span;
108 | if (screens.xxl) {
109 | colSpan = xxl || span;
110 | } else if (screens.xl) {
111 | colSpan = xl || span;
112 | } else if (screens.lg) {
113 | colSpan = lg || span;
114 | } else if (screens.md) {
115 | colSpan = md || span;
116 | } else if (screens.sm) {
117 | colSpan = sm || span;
118 | } else if (screens.xs) {
119 | colSpan = xs || span;
120 | }
121 | setColSpan(colSpan);
122 | }, [screens]);
123 |
124 | useEffect(() => {
125 | handleCompute();
126 | }, [items, colSpan, scopeExpanded]);
127 |
128 | const colSpanProps = {
129 | span,
130 | xs,
131 | sm,
132 | md,
133 | lg,
134 | xl,
135 | xxl,
136 | };
137 |
138 | useEffect(() => {}, [scopeExpanded]);
139 |
140 | const prefixCls = 'triones-ant-search-toolbar';
141 | const { hashId } = useCssInJs({
142 | prefix: prefixCls,
143 | styleFun: genSearchToolbarStyle,
144 | });
145 |
146 | return (
147 |
148 |
207 |
208 | );
209 | };
210 | export default SearchToolbar;
211 |
--------------------------------------------------------------------------------
/src/grid-table/grid-table.tsx:
--------------------------------------------------------------------------------
1 | import { Table, TableProps } from 'antd';
2 | import classNames from 'classnames';
3 | import { assign, debounce } from 'lodash-es';
4 | import React, { FC, useEffect, useState } from 'react';
5 |
6 | import { useCssInJs } from '../hooks';
7 | import { genGridTableStyle } from './styles';
8 |
9 | export type GridTableProps = TableProps & {
10 | /**
11 | * @description 是否撑满外部容器
12 | * @default false
13 | */
14 | fit?: boolean;
15 | /**
16 | * @description 是否展示返回icon
17 | * @default
18 | */
19 | toolbar?: React.ReactNode;
20 | };
21 |
22 | const GridTable: FC = (
23 | { fit = false, toolbar, style, ...props },
24 | context,
25 | ) => {
26 | const gridTableRef = React.useRef(null);
27 |
28 | const [containerHeight, setContainerHeight] = useState(0);
29 | const [containerWidth, setContainerWidth] = useState(0);
30 | const [headerHeight, setHeaderHeight] = useState(0);
31 | const [bodyHeight, setBodyHeight] = useState(0);
32 | const [bodyWidth, setBodyWidth] = useState(0);
33 | const [footerHeight, setFooterHeight] = useState(0);
34 |
35 | const [scrollX, setScrollX] = useState(false);
36 | const [scrollY, setScrollY] = useState(false);
37 |
38 | const prefixCls = 'triones-ant-grid-table';
39 | const { hashId } = useCssInJs({
40 | prefix: prefixCls,
41 | styleFun: genGridTableStyle,
42 | });
43 |
44 | const resizeObserverTableContainer = new ResizeObserver((entries) => {
45 | for (let entry of entries) {
46 | const { target, contentRect } = entry;
47 | const { height, width } = contentRect;
48 | if (height > 0) {
49 | setContainerHeight(height);
50 | }
51 | if (width > 0) {
52 | setContainerWidth(width);
53 | }
54 | }
55 | });
56 |
57 | const resizeObserverTableHeader = new ResizeObserver((entries) => {
58 | for (let entry of entries) {
59 | const { target, contentRect } = entry;
60 | const { height } = contentRect;
61 | if (height > 0) {
62 | setHeaderHeight(height);
63 | }
64 | }
65 | });
66 |
67 | const resizeObserverTableBody = new ResizeObserver((entries) => {
68 | for (let entry of entries) {
69 | const { target, contentRect } = entry;
70 | const { height, width } = contentRect;
71 | if (height > 0) {
72 | setBodyHeight(height);
73 | }
74 | if (width > 0) {
75 | setBodyWidth(width);
76 | }
77 | }
78 | });
79 |
80 | const resizeObserverTableFooter = new ResizeObserver((entries) => {
81 | for (let entry of entries) {
82 | const { target, contentRect } = entry;
83 | const { height } = contentRect;
84 | if (height > 0) {
85 | setFooterHeight(height);
86 | }
87 | }
88 | });
89 |
90 | const handleObserverTableContent = () => {
91 | const hasFixedRight = gridTableRef.current?.querySelector(
92 | '.ant-table-has-fix-right',
93 | ) as HTMLDivElement;
94 | if (hasFixedRight) {
95 | const fixedHeader = gridTableRef.current?.querySelector(
96 | '.ant-table-container .ant-table-thead',
97 | ) as HTMLDivElement;
98 | const fixedBody = gridTableRef.current?.querySelector(
99 | '.ant-table-container .ant-table-tbody',
100 | ) as HTMLDivElement;
101 | const fixedFooter = gridTableRef.current?.querySelector(
102 | '.ant-table-container .ant-table-summary',
103 | ) as HTMLDivElement;
104 | if (fixedHeader) {
105 | resizeObserverTableHeader.observe(fixedHeader);
106 | }
107 | if (fixedBody) {
108 | resizeObserverTableBody.observe(fixedBody);
109 | }
110 | if (fixedFooter) {
111 | resizeObserverTableFooter.observe(fixedFooter);
112 | }
113 | } else {
114 | const tableHeader = gridTableRef.current?.querySelector(
115 | '.ant-table-container .ant-table-thead',
116 | ) as HTMLDivElement;
117 | const tableBody = gridTableRef.current?.querySelector(
118 | '.ant-table-container .ant-table-tbody',
119 | ) as HTMLDivElement;
120 | const tableFooter = gridTableRef.current?.querySelector(
121 | '.ant-table-container .ant-table-summary',
122 | ) as HTMLDivElement;
123 | if (tableHeader) {
124 | resizeObserverTableHeader.observe(tableHeader);
125 | }
126 | if (tableBody) {
127 | resizeObserverTableBody.observe(tableBody);
128 | }
129 | if (tableFooter) {
130 | resizeObserverTableFooter.observe(tableFooter);
131 | }
132 | }
133 | };
134 |
135 | const mutationObserver = new MutationObserver((mutations) => {
136 | mutations.forEach((mutation) => {
137 | handleObserverTableContent();
138 | });
139 | });
140 |
141 | useEffect(
142 | debounce(() => {
143 | if (containerWidth < bodyWidth) {
144 | if (!props.scroll?.x) {
145 | setScrollX(true);
146 | }
147 | if (
148 | Math.ceil(containerHeight) <
149 | Math.ceil(headerHeight + bodyHeight + footerHeight + 8)
150 | ) {
151 | setScrollY(true);
152 | } else {
153 | setScrollY(false);
154 | }
155 | } else {
156 | if (!props.scroll?.x) {
157 | setScrollX(false);
158 | }
159 | if (
160 | Math.ceil(containerHeight) <
161 | Math.ceil(headerHeight + bodyHeight + footerHeight)
162 | ) {
163 | setScrollY(true);
164 | } else {
165 | setScrollY(false);
166 | }
167 | }
168 | }, 500),
169 | [
170 | containerHeight,
171 | containerWidth,
172 | headerHeight,
173 | bodyHeight,
174 | bodyWidth,
175 | footerHeight,
176 | ],
177 | );
178 |
179 | useEffect(() => {
180 | const containerEl = gridTableRef.current!.querySelector(
181 | '.ant-table-container',
182 | ) as HTMLDivElement;
183 | resizeObserverTableContainer.observe(containerEl);
184 | mutationObserver.observe(containerEl, {
185 | childList: true,
186 | subtree: true,
187 | });
188 | handleObserverTableContent();
189 |
190 | return () => {
191 | resizeObserverTableContainer.disconnect();
192 | resizeObserverTableHeader.disconnect();
193 | resizeObserverTableBody.disconnect();
194 | resizeObserverTableFooter.disconnect();
195 | mutationObserver.disconnect();
196 | };
197 | }, []);
198 |
199 | return (
200 |
210 | <>
211 | {toolbar}
212 |
225 | >
226 |
227 | );
228 | };
229 | export default Object.assign(GridTable, {
230 | Column: Table.Column,
231 | ColumnGroup: Table.ColumnGroup,
232 | });
233 |
--------------------------------------------------------------------------------
/src/video-upload/Icons.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react-dom';
2 |
3 | export const NoFile = () => {
4 | return (
5 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/src/avatar-editor/AvatarCropModal.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | RotateLeftOutlined,
3 | RotateRightOutlined,
4 | ZoomInOutlined,
5 | ZoomOutOutlined,
6 | } from '@ant-design/icons';
7 | import { useCssInJs } from '@trionesdev/antd-react-ext';
8 | import { Button, Flex, Modal, Slider, Space } from 'antd';
9 | import classNames from 'classnames';
10 | import Cropper from 'cropperjs';
11 | import React, { FC, useEffect, useRef, useState } from 'react';
12 | import { genAvatarCropModalStyle } from './styles';
13 |
14 | type AvatarCropModalProps = {
15 | open?: boolean;
16 | cropImage?: string;
17 | onCancel?: () => void;
18 | onOk?: (dataUrl: string) => void;
19 | };
20 |
21 | export const AvatarCropModal: FC = ({
22 | open,
23 | cropImage,
24 | onCancel,
25 | onOk,
26 | }) => {
27 | const cropContainerRef = useRef(null);
28 | const [cropper, setCropper] = useState();
29 | const [initTransform, setInitTransform] = useState();
30 | const [zoom, setZoom] = useState(1);
31 | const [rotate, setRotate] = useState(0);
32 |
33 | const handleInit = () => {
34 | const image = new Image();
35 | if (typeof cropImage === 'string') {
36 | image.src = cropImage;
37 | }
38 | image.alt = 'Cropper';
39 | const cropperInstance = new Cropper(image, {
40 | container: cropContainerRef.current!,
41 | template:
42 | '\n' +
43 | ' \n' +
44 | ' \n' +
45 | ' \n' +
46 | ' \n' +
47 | ' \n' +
48 | ' \n' +
49 | '',
50 | });
51 | cropperInstance.getCropperImage()?.$ready((image) => {
52 | // console.log(image.naturalWidth, image.naturalHeight);
53 | //region copperjs 存在同一张图片第二次打开的时候,缩放比例会重置为1,这里手动设置一下
54 | let transform = cropperInstance.getCropperImage()!.$getTransform();
55 | let scale = 1;
56 | if (image.naturalWidth > image.naturalHeight) {
57 | scale = 300 / image.naturalHeight;
58 | } else {
59 | scale = 300 / image.naturalWidth;
60 | }
61 | if (Math.abs(transform[0] - scale) > 0.001) {
62 | transform = [scale, 0, 0, scale, transform[4], transform[5]];
63 | cropperInstance.getCropperImage()!.$setTransform(transform);
64 | setInitTransform(transform);
65 | console.log(transform);
66 | } else {
67 | setInitTransform(transform);
68 | console.log(transform);
69 | }
70 | //endregion
71 |
72 | cropperInstance
73 | .getCropperImage()
74 | ?.addEventListener('transform', (event: any) => {
75 | console.log('event', event);
76 | const matrix = event.detail.matrix;
77 | const oldMatrix = event.detail.oldMatrix;
78 | // const copperImage = cropperInstance.getCropperImage();
79 | // const canvas = cropperInstance.getCropperCanvas();
80 | // const canvasRect = canvas?.getBoundingClientRect();
81 | // const imageClone = copperImage?.cloneNode(true) as CropperImage;
82 | // imageClone.style.transform = `matrix(${event.detail.matrix.join(', ')})`;
83 | // imageClone.style.opacity = '0';
84 | // canvas?.appendChild(imageClone)
85 | // const imageRect = imageClone.getBoundingClientRect();
86 | // canvas?.removeChild(imageClone)
87 |
88 | /**
89 | * 计算出动作前后的缩放比,判断当时动作是否为缩放操作
90 | */
91 | const scaleX1 = Math.sqrt(
92 | matrix[0] * matrix[0] + matrix[1] * matrix[1],
93 | );
94 | const scaleY1 = Math.sqrt(
95 | matrix[2] * matrix[2] + matrix[3] * matrix[3],
96 | );
97 |
98 | const scaleX2 = Math.sqrt(
99 | oldMatrix[0] * oldMatrix[0] + oldMatrix[1] * oldMatrix[1],
100 | );
101 | const scaleY2 = Math.sqrt(
102 | oldMatrix[2] * oldMatrix[2] + oldMatrix[3] * oldMatrix[3],
103 | );
104 |
105 | // console.log(scaleX1, scaleY1, scaleX2, scaleY2)
106 | if (scaleX1 === scaleX2 && scaleY1 === scaleY2) {
107 | //非缩放操作
108 | return;
109 | } else {
110 | const scaleX_origin = Math.sqrt(
111 | transform[0] * transform[0] + transform[1] * transform[1],
112 | );
113 | const scaleY_origin = Math.sqrt(
114 | transform[2] * transform[2] + transform[3] * transform[3],
115 | );
116 | // console.log(scaleX_origin, scaleY_origin)
117 | if (scaleX1 < scaleX_origin || scaleY1 < scaleY_origin) {
118 | //缩小到比初始化小,如果缩放小于初始化,则不允许继续缩小
119 | event.preventDefault();
120 | setZoom(1);
121 | } else if (
122 | scaleX1 > scaleX_origin * 3 + 0.001 ||
123 | scaleY1 > scaleY_origin * 3 + 0.001
124 | ) {
125 | //放大到3倍
126 | event.preventDefault();
127 | setZoom(3);
128 | } else {
129 | setZoom(scaleX2 / scaleX_origin);
130 | }
131 | }
132 | });
133 | });
134 |
135 | setCropper(cropperInstance);
136 | };
137 |
138 | // 缩放,不使用zoom接口,zoom是根据当前图像进行缩放的,我们的需求是根据原始图像进行缩放,否则图像的缩放比例会比较难以计算
139 | const handleZoomChange = (value: number) => {
140 | // console.log(initTransform)
141 | //获取当前的矩阵信息
142 | const currentImageTransform = cropper?.getCropperImage().$getTransform();
143 | //获取原始图像的缩放比
144 | const scaleX_origin = Math.sqrt(
145 | initTransform![0] * initTransform![0] +
146 | initTransform![1] * initTransform![1],
147 | );
148 | const scaleY_origin = Math.sqrt(
149 | initTransform![2] * initTransform![2] +
150 | initTransform![3] * initTransform![3],
151 | );
152 | //获取当前图像的缩放比
153 | const currentScaleX = Math.sqrt(
154 | currentImageTransform![0] * currentImageTransform![0] +
155 | currentImageTransform![1] * currentImageTransform![1],
156 | );
157 | const currentScaleY = Math.sqrt(
158 | currentImageTransform![2] * currentImageTransform![2] +
159 | currentImageTransform![3] * currentImageTransform![3],
160 | );
161 |
162 | const scaleX = (scaleX_origin * value) / currentScaleX;
163 | const scaleY = (scaleY_origin * value) / currentScaleY;
164 | // console.log(scaleX, scaleY)
165 | cropper
166 | ?.getCropperImage()
167 | .$setTransform(
168 | currentImageTransform![0] * scaleX,
169 | currentImageTransform![1] * scaleX,
170 | currentImageTransform![2] * scaleY,
171 | currentImageTransform![3] * scaleY,
172 | currentImageTransform![4],
173 | currentImageTransform![5],
174 | );
175 | setZoom(value);
176 | };
177 |
178 | const handleRotateChange = (value: number) => {
179 | //获取当前的矩阵信息
180 | const currentImageTransform = cropper?.getCropperImage().$getTransform();
181 | //获取原始图像的缩放比
182 | const currentScaleX = Math.sqrt(
183 | currentImageTransform![0] * currentImageTransform![0] +
184 | currentImageTransform![1] * currentImageTransform![1],
185 | );
186 | const currentScaleY = Math.sqrt(
187 | currentImageTransform![2] * currentImageTransform![2] +
188 | currentImageTransform![3] * currentImageTransform![3],
189 | );
190 | //计算出角度
191 | const radians = (Math.PI / 180) * value;
192 | //变换图像
193 | cropper
194 | ?.getCropperImage()
195 | .$setTransform(
196 | Math.cos(radians) * currentScaleX,
197 | -Math.sin(radians) * currentScaleX,
198 | Math.sin(radians) * currentScaleY,
199 | Math.cos(radians) * currentScaleY,
200 | currentImageTransform![4],
201 | currentImageTransform![5],
202 | );
203 |
204 | setRotate(value);
205 | };
206 |
207 | const handleOk = () => {
208 | cropper
209 | .getCropperSelection()
210 | ?.$toCanvas()
211 | .then((canvas: any) => {
212 | onOk?.(canvas.toDataURL());
213 | });
214 | };
215 |
216 | useEffect(() => {
217 | return () => {
218 | setInitTransform(undefined);
219 | cropper?.getCropperImage()?.removeEventListener('transform');
220 | };
221 | }, []);
222 |
223 | const prefixCls = 'triones-avatar-crop-modal';
224 | const { hashId } = useCssInJs({
225 | prefix: prefixCls,
226 | styleFun: genAvatarCropModalStyle,
227 | });
228 |
229 | return (
230 | {
241 | return (
242 |
243 | {originNode}
244 |
245 | );
246 | }}
247 | afterOpenChange={(open) => {
248 | if (open) {
249 | handleInit();
250 | } else {
251 | // cropper?.destroy()
252 | setCropper(undefined);
253 | }
254 | }}
255 | >
256 |
257 |
273 |
274 | }
277 | disabled={zoom <= 1}
278 | onClick={() => {
279 | if (zoom - 0.1 >= 1) {
280 | handleZoomChange(zoom - 0.1);
281 | }
282 | }}
283 | />
284 |
293 | }
296 | disabled={zoom >= 3}
297 | onClick={() => {
298 | if (zoom + 0.1 <= 3) {
299 | handleZoomChange(zoom + 0.1);
300 | }
301 | }}
302 | />
303 |
304 |
305 | }
308 | disabled={rotate <= -180}
309 | onClick={() => {
310 | if (rotate - 1 >= -180) {
311 | handleRotateChange(rotate - 1);
312 | }
313 | }}
314 | />
315 |
324 | }
327 | disabled={rotate >= 180}
328 | onClick={() => {
329 | if (rotate + 1 <= 180) {
330 | handleRotateChange(rotate + 1);
331 | }
332 | }}
333 | />
334 |
335 |
336 |
337 | );
338 | };
339 |
--------------------------------------------------------------------------------
/src/fields-mapping/fields-mapping.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import { cloneDeep, debounce, eq, find, forEach, isEmpty, isEqual } from 'lodash-es';
3 | import React, { FC, useEffect, useRef, useState } from 'react';
4 | import { useCssInJs } from '../hooks';
5 | import { ImageRemove } from './images';
6 | import { genFieldsMappingStyle } from './styles';
7 |
8 | type Config = {
9 | source?: { mutiple?: boolean };
10 | target?: { mutiple?: boolean };
11 | removeIconSize?: number;
12 | defaultColumnWidth?: number;
13 | };
14 |
15 | export type Column = {
16 | key?: string;
17 | title?: string;
18 | width?: string | number;
19 | primaryKey?: boolean;
20 | };
21 |
22 | type MappingItem = {
23 | sourceKey: string;
24 | targetKey: string;
25 | };
26 |
27 | type Line = {
28 | source: {
29 | key: string;
30 | x: number;
31 | y: number;
32 | };
33 | target: {
34 | key: string;
35 | x: number;
36 | y: number;
37 | };
38 | };
39 |
40 | export type FieldsMappingProps = {
41 | /**
42 | * @description 来源列
43 | * @default
44 | */
45 | sourceColumns?: Column[];
46 | /**
47 | * @description 目标列
48 | * @default
49 | */
50 | targetColumns?: Column[];
51 | /**
52 | * @description 来源数据
53 | * @default
54 | */
55 | sourceData?: any[];
56 | /**
57 | * @description 目标数据
58 | * @default
59 | */
60 | targetData?: any[];
61 | /**
62 | * @description 映射数据
63 | * @default
64 | */
65 | mappingData?: MappingItem[];
66 | /**
67 | * @description 映射数据变化
68 | * @default
69 | */
70 | onMappingChange?: (values: MappingItem[]) => void;
71 | /**
72 | * @description 配置
73 | * @default
74 | */
75 | config?: Config;
76 | };
77 |
78 | const FieldsMapping: FC = ({
79 | sourceColumns,
80 | targetColumns,
81 | sourceData,
82 | targetData,
83 | mappingData,
84 | onMappingChange,
85 | config,
86 | }) => {
87 | const dataKey = 'data-key';
88 | const prefixCls = 'triones-ant-fields-mapping';
89 | const rootRef = useRef();
90 | const sourceRef = useRef();
91 | const targetRef = useRef();
92 | const [tmpSourceKey, setTmpSourceKey] = useState();
93 | const [drawing, setDrawing] = useState(false);
94 | const [lineFrom, setLineForm] = useState<
95 | { x?: number; y?: number } | undefined
96 | >();
97 | const [lineTo, setLineTo] = useState<
98 | { x?: number; y?: number } | undefined
99 | >();
100 | const [scopeMappingData, setScopeMappingData] = useState(mappingData || []);
101 | const [lines, setLiens] = useState([]);
102 |
103 | const removeIconSize = config?.removeIconSize || 24;
104 | const defaultColumnWidth = config?.defaultColumnWidth || 80;
105 |
106 | const sourcePrimaryKey = sourceColumns?.find((row) => {
107 | return row.primaryKey === true;
108 | })?.key;
109 | const targetPrimaryKey = targetColumns?.find((row) => {
110 | return row.primaryKey === true;
111 | })?.key;
112 |
113 | const handleGetPortReactivePosition = (el: any) => {
114 | const rootClient = rootRef.current!.getBoundingClientRect();
115 | const elClient = el.getBoundingClientRect();
116 | return {
117 | x: elClient.left + elClient.width / 2 - rootClient.left,
118 | y: elClient.top + elClient.height / 2 - rootClient.top,
119 | };
120 | };
121 |
122 | const handleAddLine = (source: string, target: string) => {
123 | scopeMappingData?.push({ sourceKey: source, targetKey: target });
124 | setScopeMappingData(cloneDeep(scopeMappingData));
125 | setTmpSourceKey(undefined);
126 | };
127 |
128 | const handleMouseDown = (e: any) => {
129 | const sourceKey = e.target['closest'](
130 | `.${prefixCls}-table-row`,
131 | )?.getAttribute(dataKey);
132 | if (
133 | find(scopeMappingData, (item: MappingItem) => {
134 | return item.sourceKey === sourceKey;
135 | }) &&
136 | !config?.source?.mutiple
137 | ) {
138 | return;
139 | }
140 | setTmpSourceKey(sourceKey);
141 | const position = handleGetPortReactivePosition(e.target);
142 |
143 | setDrawing(true);
144 | setLineForm(position);
145 | setLineTo(position);
146 | };
147 |
148 | const handleMouseMove = (e: any) => {
149 | if (!drawing) {
150 | return;
151 | }
152 | const rootClient = rootRef.current!.getBoundingClientRect();
153 | const scrollTop = window.scrollY || window.document.body.scrollTop || 0;
154 | const scrollLeft = window.scrollX || window.document.body.scrollLeft || 0;
155 | const targetPoint = {
156 | x: e.pageX - rootClient.left - scrollLeft,
157 | y: e.pageY - rootClient.top - scrollTop,
158 | };
159 | setLineTo(targetPoint);
160 | };
161 |
162 | const handleMouseUp = (e: any) => {
163 | setDrawing(false);
164 | setLineForm(undefined);
165 | setLineTo(undefined);
166 |
167 | const targetKey = e.target['closest'](
168 | `.${prefixCls}-table-row`,
169 | )?.getAttribute(dataKey); // 找到上层最近的row
170 | if (
171 | !targetKey ||
172 | (find(scopeMappingData, (item: MappingItem) => {
173 | return item.targetKey === targetKey;
174 | }) &&
175 | !config?.target?.mutiple)
176 | ) {
177 | return;
178 | }
179 | if (tmpSourceKey) {
180 | handleAddLine(tmpSourceKey!, targetKey);
181 | }
182 | };
183 |
184 | const handleCloseLine = (line: Line) => {
185 | const newMappingData = scopeMappingData?.filter((lineItem) => {
186 | return (
187 | !eq(lineItem.sourceKey, line.source.key) &&
188 | !eq(lineItem.targetKey, line.target.key)
189 | );
190 | });
191 | setScopeMappingData(newMappingData);
192 | };
193 |
194 | const handleDrawLines = () => {
195 | if (isEmpty(sourceData) || isEmpty(targetData)) {
196 | return;
197 | }
198 | const newLines: Line[] = [];
199 | forEach(scopeMappingData, (item) => {
200 | const sourcePort = sourceRef.current
201 | ?.querySelector(`[${dataKey}=${item.sourceKey}]`)
202 | ?.querySelector(`.${prefixCls}-table-row-port`);
203 | const targetPort = targetRef.current
204 | ?.querySelector(`[${dataKey}=${item.targetKey}]`)
205 | ?.querySelector(`.${prefixCls}-table-row-port`);
206 | if (sourcePort && targetPort) {
207 | const sourcePoint = handleGetPortReactivePosition(sourcePort);
208 | const targetPoint = handleGetPortReactivePosition(targetPort);
209 | newLines.push({
210 | source: { key: item.sourceKey, x: sourcePoint.x, y: sourcePoint.y },
211 | target: { key: item.targetKey, x: targetPoint.x, y: targetPoint.y },
212 | });
213 | }
214 | });
215 | setLiens(newLines);
216 | };
217 |
218 | const handleResize = () => {
219 | handleDrawLines();
220 | };
221 |
222 | useEffect(() => {
223 | handleDrawLines();
224 | onMappingChange?.(scopeMappingData || []);
225 | }, [scopeMappingData]);
226 |
227 | useEffect(() => {
228 | if (!isEqual(mappingData || [], scopeMappingData || [])) {
229 | setScopeMappingData(mappingData || []);
230 | }
231 | }, [mappingData]);
232 |
233 | useEffect(() => {
234 | //region 监听div resize事件
235 | let observer = new ResizeObserver(() => debounce(handleResize, 200)());
236 | observer.observe(rootRef.current);
237 | //endregion
238 |
239 | if (drawing) {
240 | document.addEventListener('mouseup', handleMouseUp);
241 | }
242 |
243 | return () => {
244 | if (rootRef.current) {
245 | observer.unobserve(rootRef.current);
246 | }
247 | observer.disconnect();
248 | document.removeEventListener('mouseup', handleMouseUp);
249 | };
250 | }, [drawing]);
251 |
252 | useEffect(() => {
253 | rootRef.current?.addEventListener('resize', handleResize);
254 | return () => {
255 | rootRef.current?.removeEventListener('resize', handleResize);
256 | };
257 | }, []);
258 |
259 | const { hashId } = useCssInJs({
260 | prefix: prefixCls,
261 | styleFun: genFieldsMappingStyle,
262 | });
263 | return (
264 |
269 |
277 |
278 |
279 | {sourceColumns?.map((column) => (
280 |
285 | {column.title}
286 |
287 | ))}
288 |
289 |
290 |
291 | {sourceData?.map((row, index) => {
292 | return (
293 |
299 | {sourceColumns?.map((column, index) => (
300 |
305 | {row[column.key!]}
306 |
307 | ))}
308 |
312 |
313 | );
314 | })}
315 |
316 |
317 |
325 |
326 |
327 | {targetColumns?.map((column) => (
328 |
333 | {column.title}
334 |
335 | ))}
336 |
337 |
338 |
339 | {targetData?.map((row, index) => {
340 | return (
341 |
347 | {targetColumns?.map((column, index) => (
348 |
353 | {row[column.key!]}
354 |
355 | ))}
356 |
359 |
360 | );
361 | })}
362 |
363 |
364 |
365 |
403 |
404 |
405 | );
406 | };
407 |
408 | export default FieldsMapping;
409 |
--------------------------------------------------------------------------------