fix: 新增业务组件BoxTree、Tree
This commit is contained in:
parent
e03338ab02
commit
8b2d7cfe3e
@ -1,10 +1,11 @@
|
||||
import { defineConfig } from 'dumi';
|
||||
import path from 'path';
|
||||
|
||||
console.log(path.join(__dirname, 'packages/hooks/src'));
|
||||
|
||||
export default defineConfig({
|
||||
logo: '/logo.jpg',
|
||||
favicons: ['/logo.jpg'],
|
||||
history: { type: 'hash' },
|
||||
themeConfig: {
|
||||
name: 'Lambo',
|
||||
socialLinks: {
|
||||
|
@ -2,6 +2,12 @@ import { defineConfig } from 'father';
|
||||
|
||||
export default defineConfig({
|
||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
||||
esm: { output: 'es' },
|
||||
cjs: { output: 'lib' },
|
||||
esm: {
|
||||
output: 'es',
|
||||
ignores: ['**/demo/*', 'src/**/demo/*']
|
||||
},
|
||||
cjs: {
|
||||
output: 'lib',
|
||||
ignores: ['**/demo/*', 'src/**/demo/*']
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,18 @@
|
||||
# @zhst/biz
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 新增业务组件 Tree、TreeTransfer、TreeTransferModal、BoxSelectTree
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.7.0
|
||||
- @zhst/func@0.7.0
|
||||
- @zhst/meta@0.8.0
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
## 使用
|
||||
|
||||
```jsx
|
||||
```js
|
||||
import React from 'react';
|
||||
import { Demo } from '@zhst/biz'
|
||||
|
||||
|
40
packages/biz/es/BigImageModal/BigImageModal.d.ts
vendored
40
packages/biz/es/BigImageModal/BigImageModal.d.ts
vendored
@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { ModalProps, DescriptionsProps, TabsProps, VideoViewRef, ImgViewRef } from '@zhst/meta';
|
||||
import './index.less';
|
||||
export declare const componentPrefix = "zhst-image";
|
||||
export type TAB_TYPE = 'COMPATER' | 'NORMAL' | 'VIDEO';
|
||||
export type MODEL_TYPE = 'VIDEO' | 'IMAGE';
|
||||
export interface BigImageModalProps extends ModalProps {
|
||||
visible: boolean;
|
||||
activeTab?: TAB_TYPE;
|
||||
attributeList: {
|
||||
title: string;
|
||||
children: Pick<DescriptionsProps, 'items'>;
|
||||
};
|
||||
tabs: {
|
||||
data: Pick<TabsProps, 'items'> & {
|
||||
key: TAB_TYPE;
|
||||
};
|
||||
};
|
||||
dataSource: any;
|
||||
imageData: any;
|
||||
relatedData: any;
|
||||
isRelated?: boolean;
|
||||
footer?: React.ReactNode;
|
||||
showCarousel?: boolean;
|
||||
onTabChange?: (newVal?: TAB_TYPE, oldVal?: TAB_TYPE) => void;
|
||||
onIndexChange?: (newVal?: number, oldVal?: number) => void;
|
||||
transformPropFunc: (data: any) => void;
|
||||
}
|
||||
interface BigModalRef {
|
||||
tab: TAB_TYPE;
|
||||
setTab: (tab: TAB_TYPE) => void;
|
||||
modalRef: ModalProps;
|
||||
activeKey: string;
|
||||
setActiveKey: (val: string) => void;
|
||||
videoPlayerRef: VideoViewRef;
|
||||
combineImageRef: any;
|
||||
bigImagePreviewRef: ImgViewRef;
|
||||
}
|
||||
declare const BigImageModal: React.FC<BigImageModalProps, BigModalRef>;
|
||||
export default BigImageModal;
|
@ -1,13 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import './index.less';
|
||||
declare const Navigation: React.FC<{
|
||||
show?: boolean;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
prev?: boolean;
|
||||
next?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
color?: string;
|
||||
hoverColor?: string;
|
||||
}>;
|
||||
export default Navigation;
|
@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Button } from 'antd';
|
||||
import { Icon } from '@zhst/meta';
|
||||
import { Icon, Button } from '@zhst/meta';
|
||||
import "./index.less";
|
||||
var componentName = "zhst-image__nav";
|
||||
var Navigation = function Navigation(props) {
|
||||
|
3
packages/biz/es/Demo/index.d.ts
vendored
3
packages/biz/es/Demo/index.d.ts
vendored
@ -1,3 +0,0 @@
|
||||
import React from 'react';
|
||||
declare const _default: () => React.JSX.Element;
|
||||
export default _default;
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Button } from 'antd';
|
||||
import { Button } from '@zhst/meta';
|
||||
import { useThrottleFn } from '@zhst/hooks';
|
||||
export default (function () {
|
||||
var _useThrottleFn = useThrottleFn(function () {
|
||||
|
File diff suppressed because one or more lines are too long
5
packages/biz/es/index.d.ts
vendored
5
packages/biz/es/index.d.ts
vendored
@ -1,2 +1,5 @@
|
||||
export { default as Demo } from './Demo';
|
||||
export { default as BigImageModal } from './BigImageModal';
|
||||
export { default as BoxSelectTree } from './boxSelectTree';
|
||||
export { default as Tree } from './tree';
|
||||
export { default as TreeTransfer } from './treeTransfer';
|
||||
export { default as TreeTransferModal } from './treeTransferModal';
|
||||
|
@ -1,2 +1,5 @@
|
||||
export { default as Demo } from "./Demo";
|
||||
export { default as BigImageModal } from "./BigImageModal";
|
||||
export { default as BoxSelectTree } from "./boxSelectTree";
|
||||
export { default as Tree } from "./tree";
|
||||
export { default as TreeTransfer } from "./treeTransfer";
|
||||
export { default as TreeTransferModal } from "./treeTransferModal";
|
@ -2,8 +2,8 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
|
||||
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
||||
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
||||
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
||||
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
||||
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
||||
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
||||
//@ts-nocheck
|
||||
import channel from "./ws";
|
||||
var startChannel = function startChannel(topic, req, callback) {
|
||||
|
@ -3,8 +3,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
||||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
||||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
||||
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
||||
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
||||
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
||||
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
||||
// @ts-nocheck
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { has } from '@zhst/func';
|
||||
|
@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { ModalProps, DescriptionsProps, TabsProps, VideoViewRef, ImgViewRef } from '@zhst/meta';
|
||||
import './index.less';
|
||||
export declare const componentPrefix = "zhst-image";
|
||||
export type TAB_TYPE = 'COMPATER' | 'NORMAL' | 'VIDEO';
|
||||
export type MODEL_TYPE = 'VIDEO' | 'IMAGE';
|
||||
export interface BigImageModalProps extends ModalProps {
|
||||
visible: boolean;
|
||||
activeTab?: TAB_TYPE;
|
||||
attributeList: {
|
||||
title: string;
|
||||
children: Pick<DescriptionsProps, 'items'>;
|
||||
};
|
||||
tabs: {
|
||||
data: Pick<TabsProps, 'items'> & {
|
||||
key: TAB_TYPE;
|
||||
};
|
||||
};
|
||||
dataSource: any;
|
||||
imageData: any;
|
||||
relatedData: any;
|
||||
isRelated?: boolean;
|
||||
footer?: React.ReactNode;
|
||||
showCarousel?: boolean;
|
||||
onTabChange?: (newVal?: TAB_TYPE, oldVal?: TAB_TYPE) => void;
|
||||
onIndexChange?: (newVal?: number, oldVal?: number) => void;
|
||||
transformPropFunc: (data: any) => void;
|
||||
}
|
||||
interface BigModalRef {
|
||||
tab: TAB_TYPE;
|
||||
setTab: (tab: TAB_TYPE) => void;
|
||||
modalRef: ModalProps;
|
||||
activeKey: string;
|
||||
setActiveKey: (val: string) => void;
|
||||
videoPlayerRef: VideoViewRef;
|
||||
combineImageRef: any;
|
||||
bigImagePreviewRef: ImgViewRef;
|
||||
}
|
||||
declare const BigImageModal: React.FC<BigImageModalProps, BigModalRef>;
|
||||
export default BigImageModal;
|
@ -1,13 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import './index.less';
|
||||
declare const Navigation: React.FC<{
|
||||
show?: boolean;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
prev?: boolean;
|
||||
next?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
color?: string;
|
||||
hoverColor?: string;
|
||||
}>;
|
||||
export default Navigation;
|
@ -34,7 +34,6 @@ __export(navigation_exports, {
|
||||
module.exports = __toCommonJS(navigation_exports);
|
||||
var React = __toESM(require("react"));
|
||||
var import_classnames = __toESM(require("classnames"));
|
||||
var import_antd = require("antd");
|
||||
var import_meta = require("@zhst/meta");
|
||||
var import_index = require("./index.less");
|
||||
var componentName = `zhst-image__nav`;
|
||||
@ -52,7 +51,7 @@ var Navigation = (props) => {
|
||||
className
|
||||
)
|
||||
},
|
||||
/* @__PURE__ */ React.createElement(import_antd.Button, { type: "text", disabled, onClick }, /* @__PURE__ */ React.createElement(import_meta.Icon, { size: 28, color, icon: prev ? "icon-qiehuanzuo" : "icon-qiehuanyou" }))
|
||||
/* @__PURE__ */ React.createElement(import_meta.Button, { type: "text", disabled, onClick }, /* @__PURE__ */ React.createElement(import_meta.Icon, { size: 28, color, icon: prev ? "icon-qiehuanzuo" : "icon-qiehuanyou" }))
|
||||
);
|
||||
};
|
||||
var navigation_default = Navigation;
|
||||
|
3
packages/biz/lib/Demo/index.d.ts
vendored
3
packages/biz/lib/Demo/index.d.ts
vendored
@ -1,3 +0,0 @@
|
||||
import React from 'react';
|
||||
declare const _default: () => React.JSX.Element;
|
||||
export default _default;
|
@ -33,9 +33,9 @@ __export(Demo_exports, {
|
||||
});
|
||||
module.exports = __toCommonJS(Demo_exports);
|
||||
var import_react = __toESM(require("react"));
|
||||
var import_antd = require("antd");
|
||||
var import_meta = require("@zhst/meta");
|
||||
var import_hooks = require("@zhst/hooks");
|
||||
var Demo_default = () => {
|
||||
const { run } = (0, import_hooks.useThrottleFn)(() => console.log("123"));
|
||||
return /* @__PURE__ */ import_react.default.createElement(import_antd.Button, { onClick: () => run() }, "测试");
|
||||
return /* @__PURE__ */ import_react.default.createElement(import_meta.Button, { onClick: () => run() }, "测试");
|
||||
};
|
||||
|
@ -53,7 +53,7 @@ var turf = __toESM(require("@turf/turf"));
|
||||
var import_useTaskState = require("../useTaskState");
|
||||
var import_Tree = __toESM(require("@common/components/CameraTree/Tree"));
|
||||
var import_request = __toESM(require("../../utils/request"));
|
||||
var import_antd = require("antd");
|
||||
var import_meta = require("@zhst/meta");
|
||||
var import_utils = require("@common/components/CameraTree/utils");
|
||||
var import_func = require("@zhst/func");
|
||||
var defaultFaceThreshold = 0.68;
|
||||
@ -85,10 +85,10 @@ var operateTrackById = async (smartTrackId, operationType) => {
|
||||
url: "/singer.SmartTrackService/OperationSmartTrack",
|
||||
data
|
||||
});
|
||||
import_antd.message.success("操作成功");
|
||||
import_meta.message.success("操作成功");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
import_antd.message.success("操作失败");
|
||||
import_meta.message.success("操作失败");
|
||||
}
|
||||
};
|
||||
var deleteTrackById = async (smartTrackId) => {
|
||||
@ -239,7 +239,7 @@ var ModifyTrackCameras = async (value) => {
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
import_antd.message.error(err);
|
||||
import_meta.message.error(err);
|
||||
}
|
||||
};
|
||||
var setIntelligentTrackCircleInfo = async (smartTrackId, circleCenter) => {
|
||||
@ -256,7 +256,7 @@ var setIntelligentTrackCircleInfo = async (smartTrackId, circleCenter) => {
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
import_antd.message.error(err);
|
||||
import_meta.message.error(err);
|
||||
}
|
||||
};
|
||||
var getTackCameraInfo = async (value) => {
|
||||
|
5
packages/biz/lib/index.d.ts
vendored
5
packages/biz/lib/index.d.ts
vendored
@ -1,2 +1,5 @@
|
||||
export { default as Demo } from './Demo';
|
||||
export { default as BigImageModal } from './BigImageModal';
|
||||
export { default as BoxSelectTree } from './boxSelectTree';
|
||||
export { default as Tree } from './tree';
|
||||
export { default as TreeTransfer } from './treeTransfer';
|
||||
export { default as TreeTransferModal } from './treeTransferModal';
|
||||
|
@ -30,13 +30,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
||||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
BigImageModal: () => import_BigImageModal.default,
|
||||
Demo: () => import_Demo.default
|
||||
BoxSelectTree: () => import_boxSelectTree.default,
|
||||
Tree: () => import_tree.default,
|
||||
TreeTransfer: () => import_treeTransfer.default,
|
||||
TreeTransferModal: () => import_treeTransferModal.default
|
||||
});
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
var import_Demo = __toESM(require("./Demo"));
|
||||
var import_BigImageModal = __toESM(require("./BigImageModal"));
|
||||
var import_boxSelectTree = __toESM(require("./boxSelectTree"));
|
||||
var import_tree = __toESM(require("./tree"));
|
||||
var import_treeTransfer = __toESM(require("./treeTransfer"));
|
||||
var import_treeTransferModal = __toESM(require("./treeTransferModal"));
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
BigImageModal,
|
||||
Demo
|
||||
BoxSelectTree,
|
||||
Tree,
|
||||
TreeTransfer,
|
||||
TreeTransferModal
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/biz",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"description": "业务库",
|
||||
"keywords": [
|
||||
"business",
|
||||
@ -20,7 +20,7 @@
|
||||
"typings": "es/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts"
|
||||
"import": "./es/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
@ -39,6 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/pro-components": "^2.6.49",
|
||||
"@zhst/func": "workspace:^",
|
||||
"@zhst/hooks": "workspace:^",
|
||||
"@zhst/meta": "workspace:^",
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
group: 数据展示
|
||||
group: 进阶组件
|
||||
category: Components
|
||||
subtitle: 大图预览组件
|
||||
title: BigImagePreview 大图预览组件
|
||||
|
93
packages/biz/src/boxSelectTree/boxSelectTree.tsx
Normal file
93
packages/biz/src/boxSelectTree/boxSelectTree.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import React, { FC } from 'react';
|
||||
import { InputProps, Tabs, TabsProps, TreeDataNode, TreeProps } from 'antd'
|
||||
import BoxPanel from './components/boxPanel';
|
||||
import { ModalFormProps } from '@ant-design/pro-components';
|
||||
|
||||
export interface BoxSelectTreeProps {
|
||||
boxDataSource: TreeDataNode[]
|
||||
data: TreeDataNode[]
|
||||
onSearch?: (e: any) => void // 搜索
|
||||
onItemSelect?: TreeProps['onSelect']
|
||||
onItemCheck?: TreeProps['onCheck']
|
||||
onTabChange?: (e: any) => void
|
||||
onBoxBatchDelete?: (data?: any) => void
|
||||
onBoxDelete?: (data?: any) => void
|
||||
onCreateSubmit?: ModalFormProps['onFinish']
|
||||
tabsProps?: TabsProps
|
||||
searchInputProps?: InputProps
|
||||
treeProps?: TreeProps
|
||||
}
|
||||
|
||||
const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
|
||||
const {
|
||||
data,
|
||||
boxDataSource = [],
|
||||
onTabChange,
|
||||
onSearch,
|
||||
onItemCheck,
|
||||
onItemSelect,
|
||||
onBoxBatchDelete,
|
||||
onBoxDelete,
|
||||
onCreateSubmit,
|
||||
tabsProps,
|
||||
searchInputProps,
|
||||
treeProps
|
||||
} = props
|
||||
|
||||
const onChange = (key: string) => {
|
||||
onTabChange?.(key)
|
||||
};
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: <div style={{ textAlign:'center', width: '160px' }} >盒子组</div>,
|
||||
children: (
|
||||
<BoxPanel
|
||||
searchInputProps={searchInputProps}
|
||||
boxDataSource={boxDataSource}
|
||||
treeProps={treeProps}
|
||||
data={data}
|
||||
onCreateSubmit={onCreateSubmit}
|
||||
onBoxBatchDelete={onBoxBatchDelete}
|
||||
onBoxDelete={onBoxDelete}
|
||||
onSearch={onSearch}
|
||||
onItemCheck={onItemCheck}
|
||||
onItemSelect={onItemSelect}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: <div style={{ textAlign:'center', width: '160px' }} >盒子</div>,
|
||||
children: (
|
||||
<BoxPanel
|
||||
boxDataSource={boxDataSource}
|
||||
searchInputProps={searchInputProps}
|
||||
treeProps={treeProps}
|
||||
data={data}
|
||||
onBoxBatchDelete={onBoxBatchDelete}
|
||||
onCreateSubmit={onCreateSubmit}
|
||||
onBoxDelete={onBoxDelete}
|
||||
onSearch={onSearch}
|
||||
onItemCheck={onItemCheck}
|
||||
onItemSelect={onItemSelect}
|
||||
/>
|
||||
)
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
centered
|
||||
items={items}
|
||||
onChange={onChange}
|
||||
tabBarGutter={0}
|
||||
indicator={{ size: (origin) => origin, align: 'center' }}
|
||||
{...tabsProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoxSelectTree;
|
175
packages/biz/src/boxSelectTree/components/boxPanel/index.tsx
Normal file
175
packages/biz/src/boxSelectTree/components/boxPanel/index.tsx
Normal file
@ -0,0 +1,175 @@
|
||||
import React, { FC, useState, useRef } from 'react';
|
||||
import{ Button, Divider, Input, Space, TreeDataNode } from 'antd'
|
||||
import { ModalForm, ModalFormProps, ProFormInstance, ProFormText } from '@ant-design/pro-components'
|
||||
import { DiffOutlined, SwitcherOutlined } from '@ant-design/icons'
|
||||
import type { TreeProps, InputProps } from 'antd';
|
||||
import TreeTransferModal from '../../../treeTransferModal'
|
||||
import BoxTree from '../../../tree';
|
||||
import './index.less'
|
||||
|
||||
export interface BoxGroupPanelProps {
|
||||
searchInputProps?: InputProps
|
||||
treeProps?: TreeProps
|
||||
data: TreeDataNode[]
|
||||
boxDataSource: TreeDataNode[]
|
||||
handleImport?: () => void
|
||||
onSearch?: (e: any) => void
|
||||
onItemCheck?: TreeProps['onCheck']
|
||||
onItemSelect?: TreeProps['onSelect']
|
||||
onBoxBatchDelete?: (data?: any) => void
|
||||
onBoxDelete?: (data?: any) => void
|
||||
onCreateSubmit?: ModalFormProps['onFinish']
|
||||
}
|
||||
|
||||
const BoxGroupPanel: FC<BoxGroupPanelProps> = (props) => {
|
||||
const {
|
||||
searchInputProps,
|
||||
data = [],
|
||||
onSearch,
|
||||
treeProps,
|
||||
onItemCheck,
|
||||
onItemSelect,
|
||||
onCreateSubmit,
|
||||
onBoxBatchDelete,
|
||||
onBoxDelete,
|
||||
boxDataSource
|
||||
} = props
|
||||
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
|
||||
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
|
||||
const [boxChoiceOpen, setBoxChoiceOpen] = useState(false)
|
||||
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
||||
const createFormRef = useRef<
|
||||
ProFormInstance<{
|
||||
name: string;
|
||||
company?: string;
|
||||
useMode?: string;
|
||||
}>
|
||||
>()
|
||||
|
||||
/**
|
||||
* 修改选择状态
|
||||
* @param _data
|
||||
*/
|
||||
const handleCheckable = () => {
|
||||
setIsTreeCheckable(pre => !pre)
|
||||
}
|
||||
|
||||
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
|
||||
let _targetItems: TreeDataNode[] = []
|
||||
setCheckedKeys(keys)
|
||||
info.checkedNodes.forEach(o => {
|
||||
o.isLeaf && _targetItems.push(o)
|
||||
})
|
||||
setTargetItems(_targetItems)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param key
|
||||
* @param param1
|
||||
*/
|
||||
const onItemDelete = (key: any, { keys }: any) => {
|
||||
setCheckedKeys(pre => {
|
||||
const newKeys = pre.filter(_key => !keys.includes(_key))
|
||||
return newKeys
|
||||
})
|
||||
setTargetItems(pre => pre.filter(o => o.key !== key))
|
||||
}
|
||||
|
||||
const onOk = (data: any) => {
|
||||
console.log('data', data)
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
setCheckedKeys([])
|
||||
setTargetItems([])
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '0 16px' }}>
|
||||
<TreeTransferModal
|
||||
open={boxChoiceOpen}
|
||||
onCancel={() => setBoxChoiceOpen(false)}
|
||||
onRadioChange={(val) => console.log('radio', val)} // 顶部 radio 事件
|
||||
dataSource={boxDataSource} // 数据源
|
||||
targetItems={targetItems} // 右侧选中项
|
||||
checkedKeys={checkedKeys} // 左侧选中
|
||||
onReset={onReset} // 重置按钮事件
|
||||
onOk={onOk} // 确定按钮事件
|
||||
onTreeCheck={onTreeCheck} // 树check选中事件
|
||||
onItemDelete={onItemDelete} // 右侧点击删除事件
|
||||
/>
|
||||
<Space size={12} direction='vertical'>
|
||||
<Space>
|
||||
<Input size='middle' onChange={(e) => onSearch?.(e)} placeholder='请输入盒子名称' {...searchInputProps} />
|
||||
<Button style={{ width: '80px' }} type='primary' >导入盒子</Button>
|
||||
</Space>
|
||||
<Space align='center'>
|
||||
<ModalForm
|
||||
width={'600px'}
|
||||
formRef={createFormRef}
|
||||
title="新建组"
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
layout='horizontal'
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 18 }}
|
||||
trigger={<Button type='link' >新建组</Button>}
|
||||
submitter={{
|
||||
searchConfig: {
|
||||
submitText: '确定',
|
||||
resetText: '取消',
|
||||
},
|
||||
}}
|
||||
onFinish={onCreateSubmit}
|
||||
>
|
||||
<ProFormText
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
width="md"
|
||||
name="name"
|
||||
label="盒子组名称"
|
||||
placeholder="请输入盒子名称"
|
||||
/>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="boxList"
|
||||
label="盒子选择"
|
||||
placeholder="已选择0个盒子"
|
||||
fieldProps={{
|
||||
readOnly: true,
|
||||
suffix: (
|
||||
<Space>
|
||||
<a onClick={() => {
|
||||
createFormRef.current?.setFieldValue('boxList', null)
|
||||
}} >恢复默认</a>
|
||||
<a onClick={() => setBoxChoiceOpen(true)}>范围选择</a>
|
||||
</Space>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</ModalForm>
|
||||
<Divider type="vertical" />
|
||||
{/* @ts-ignore */}
|
||||
<Button danger type='link' disabled={treeProps?.checkedKeys?.length <= 0} onClick={onBoxBatchDelete} >删除</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button type="link" onClick={() => handleCheckable()} icon={isTreeCheckable ? <DiffOutlined /> : <SwitcherOutlined />} />
|
||||
<Button type="link" onClick={() => handleCheckable()} icon={isTreeCheckable ? <DiffOutlined /> : <SwitcherOutlined />} />
|
||||
</Space>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<BoxTree
|
||||
treeCheckable={isTreeCheckable}
|
||||
data={data}
|
||||
onItemSelect={onItemSelect}
|
||||
onItemCheck={onItemCheck}
|
||||
onItemDelete={onBoxDelete}
|
||||
{...treeProps}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BoxGroupPanel
|
72
packages/biz/src/boxSelectTree/demo/basic.tsx
Normal file
72
packages/biz/src/boxSelectTree/demo/basic.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React, { useState } from 'react';
|
||||
import { BoxSelectTree } from '@zhst/biz';
|
||||
import { treeData, boxDataSource } from './mock'
|
||||
import { Select, TreeProps, Modal, Checkbox } from 'antd';
|
||||
|
||||
const { Option } = Select
|
||||
|
||||
const demo = () => {
|
||||
const [searchType, setSearchType] = useState('1')
|
||||
const [searchVal, setSearchVal] = useState('')
|
||||
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const onTreeCheck: TreeProps['onCheck'] = (keys: any) => {
|
||||
setCheckedKeys(keys)
|
||||
}
|
||||
|
||||
const onBoxBatchDelete = () => {
|
||||
console.log('盒子批量删除', checkedKeys)
|
||||
modal.warning({
|
||||
content: (
|
||||
<div>
|
||||
<p>请确认是否从系统中删除盒子?</p>
|
||||
<Checkbox>所选盒子同时从系统中删除</Checkbox>
|
||||
</div>
|
||||
),
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
onOk() {},
|
||||
onCancel() {}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ border: '1px solid #ccc', width: '320px' }}>
|
||||
{contextHolder}
|
||||
<BoxSelectTree
|
||||
data={treeData}
|
||||
boxDataSource={boxDataSource}
|
||||
onSearch={e => console.log('搜索', e)}
|
||||
onCreateSubmit={async () => { return true }}
|
||||
onItemCheck={onTreeCheck}
|
||||
onItemSelect={e => console.log('onItemSelect', e)}
|
||||
onTabChange={e => console.log('tabChange', e)}
|
||||
onBoxDelete={data => console.log('盒子删除', data)}
|
||||
onBoxBatchDelete={onBoxBatchDelete}
|
||||
searchInputProps={{
|
||||
addonBefore: (
|
||||
<Select
|
||||
value={searchType}
|
||||
onChange={_type => {
|
||||
setSearchType(_type)
|
||||
setSearchVal('')
|
||||
}}
|
||||
style={{ width: '72px' }}
|
||||
>
|
||||
<Option value="1">盒子</Option>
|
||||
<Option value="2">盒子组</Option>
|
||||
</Select>
|
||||
),
|
||||
onChange: e => setSearchVal(e.target.value),
|
||||
value: searchVal
|
||||
}}
|
||||
treeProps={{
|
||||
checkedKeys
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default demo;
|
69
packages/biz/src/boxSelectTree/demo/mock.tsx
Normal file
69
packages/biz/src/boxSelectTree/demo/mock.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { TreeDataNode } from "antd";
|
||||
|
||||
export const treeData: TreeDataNode[] = [
|
||||
{
|
||||
title: '全部盒子',
|
||||
key: '0-0',
|
||||
children: [
|
||||
{
|
||||
title: '盒子组1',
|
||||
key: '0-0-0',
|
||||
children: [
|
||||
{
|
||||
title: '摄像头1',
|
||||
key: '0-0-0-0',
|
||||
},
|
||||
{
|
||||
title: '摄像头2',
|
||||
key: '0-0-0-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '盒子组2',
|
||||
key: '0-0-1',
|
||||
children: [
|
||||
{
|
||||
title: '摄像头4',
|
||||
key: '0-0-1-0'
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
export const boxDataSource: TreeDataNode[] = [
|
||||
{
|
||||
key: '0-0',
|
||||
title: '分组0-0',
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
},
|
||||
{
|
||||
key: '0-1',
|
||||
title: '分组0-1',
|
||||
isLeaf: false,
|
||||
children: [
|
||||
{ key: '0-1-0', title: '分组0-1-0', isLeaf: true, checkable: false },
|
||||
{ key: '0-1-1', title: '分组0-1-1', isLeaf: true, checkable: false },
|
||||
{ key: '0-1-2', title: '分组0-1-2', isLeaf: true, checkable: false },
|
||||
{
|
||||
key: '0-1-3',
|
||||
title: '分组0-1-3',
|
||||
isLeaf: false,
|
||||
children: [
|
||||
{ key: '0-1-3-1', title: '分组0-1-3-1', isLeaf: true },
|
||||
{ key: '0-1-3-2', title: '分组0-1-3-2', isLeaf: true },
|
||||
{ key: '0-1-3-3', title: '分组0-1-3-3', isLeaf: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ key: '0-2', title: '分组0-2', isLeaf: false, checkable: false, },
|
||||
{ key: '0-3', title: '分组0-3', isLeaf: false, checkable: false, },
|
||||
{ key: '0-4', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
{ key: '0-5', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
{ key: '0-6', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
];
|
26
packages/biz/src/boxSelectTree/index.md
Normal file
26
packages/biz/src/boxSelectTree/index.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
category: Components
|
||||
title: BoxSelectTree 盒子树
|
||||
demo:
|
||||
cols: 2
|
||||
group:
|
||||
title: 进阶组件
|
||||
order: 2
|
||||
---
|
||||
|
||||
盒子树
|
||||
|
||||
## 代码演示
|
||||
|
||||
<code src="./demo/basic.tsx">基本用法</code>
|
||||
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| data | 数据源 | Array[] | [] | - |
|
||||
| onSearch | 搜索监听 | function: (e) => void | - | - |
|
||||
| onItemSelect | 树当前选中(单选) | function: (e) => void | - | - |
|
||||
| onItemCheck | 树选择(支持多选) | function: (e) => void | - | - |
|
||||
| tabsProps | Tabs组件的Props | antd的Tabs组件 | - | - |
|
||||
| searchInputProps | 搜索框的Props | antd的Input组件 | - | - |
|
||||
| onTabChange | tab切换监听 | function: (e) => void | - | - |
|
3
packages/biz/src/boxSelectTree/index.tsx
Normal file
3
packages/biz/src/boxSelectTree/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import BoxSelectTree from './boxSelectTree';
|
||||
|
||||
export default BoxSelectTree;
|
34
packages/biz/src/boxSelectTree/mock.tsx
Normal file
34
packages/biz/src/boxSelectTree/mock.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { TreeDataNode } from "antd";
|
||||
|
||||
export const treeData: TreeDataNode[] = [
|
||||
{
|
||||
title: '全部盒子',
|
||||
key: '0-0',
|
||||
children: [
|
||||
{
|
||||
title: '盒子组1',
|
||||
key: '0-0-0',
|
||||
children: [
|
||||
{
|
||||
title: '摄像头1',
|
||||
key: '0-0-0-0',
|
||||
},
|
||||
{
|
||||
title: '摄像头2',
|
||||
key: '0-0-0-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '盒子组2',
|
||||
key: '0-0-1',
|
||||
children: [
|
||||
{
|
||||
title: '摄像头4',
|
||||
key: '0-0-1-0'
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
@ -1,2 +1,5 @@
|
||||
export { default as Demo } from './Demo';
|
||||
export { default as BigImageModal } from './BigImageModal'
|
||||
export { default as BoxSelectTree } from './boxSelectTree'
|
||||
export { default as Tree } from './tree'
|
||||
export { default as TreeTransfer } from './treeTransfer'
|
||||
export { default as TreeTransferModal } from './treeTransferModal'
|
||||
|
76
packages/biz/src/tree/boxTree.tsx
Normal file
76
packages/biz/src/tree/boxTree.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Tree, Badge, TreeDataNode, Space, TreeProps } from 'antd';
|
||||
import { CloseOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons'
|
||||
import { ModalForm, ProFormText } from '@ant-design/pro-components';
|
||||
import './index.less'
|
||||
|
||||
const componentName = 'zhst-biz-tree'
|
||||
|
||||
export interface BoxTreeProps extends TreeProps {
|
||||
data: TreeDataNode[]
|
||||
treeCheckable?: boolean
|
||||
showItemOption?: boolean
|
||||
treeProps?: TreeProps
|
||||
onItemCheck?: TreeProps['onCheck']
|
||||
onItemSelect?: TreeProps['onSelect']
|
||||
onItemSetting?: (_data: any) => void
|
||||
onItemDelete?: (_data: any) => void
|
||||
onRenameFinish?: (_data: any, _nodeData: any) => Promise<any>
|
||||
}
|
||||
|
||||
const boxTree: FC<BoxTreeProps> = (props) => {
|
||||
const { onItemSelect, onItemCheck, onItemSetting, onItemDelete, data = [], showItemOption = true, treeCheckable = false, onRenameFinish } = props
|
||||
|
||||
return (
|
||||
<Tree
|
||||
checkable={treeCheckable}
|
||||
blockNode
|
||||
onSelect={onItemSelect}
|
||||
onCheck={onItemCheck}
|
||||
treeData={data}
|
||||
titleRender={(_nodeData) => {
|
||||
return (
|
||||
<div className={`${componentName}-item-render`}>
|
||||
{!_nodeData.children && <Badge style={{ marginRight: '6px' }} status="success" />}
|
||||
{_nodeData.title as any}
|
||||
{showItemOption && <Space className={`${componentName}-item-render_right`} style={{ float:'right' }} >
|
||||
<ModalForm
|
||||
title="重命名"
|
||||
width={600}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
layout='horizontal'
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 18 }}
|
||||
trigger={<EditOutlined />}
|
||||
submitter={{
|
||||
searchConfig: {
|
||||
submitText: '确定',
|
||||
resetText: '取消',
|
||||
},
|
||||
}}
|
||||
onFinish={async (value) => onRenameFinish?.(value, _nodeData)}
|
||||
>
|
||||
<ProFormText
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
width="md"
|
||||
name="name"
|
||||
label="盒子名称"
|
||||
placeholder="请输入盒子名称"
|
||||
/>
|
||||
</ModalForm>
|
||||
<SettingOutlined onClick={() => onItemSetting?.(_nodeData)} />
|
||||
<CloseOutlined onClick={() => onItemDelete?.(_nodeData)} />
|
||||
</Space>}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default boxTree;
|
20
packages/biz/src/tree/demo/basic.tsx
Normal file
20
packages/biz/src/tree/demo/basic.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Tree } from '@zhst/biz';
|
||||
import { treeData } from './mock'
|
||||
|
||||
const demo = () => {
|
||||
return (
|
||||
<div style={{ width: '320px' }}>
|
||||
<Tree
|
||||
data={treeData}
|
||||
onItemCheck={e => console.log('多选框', e)}
|
||||
onItemSelect={e => console.log('当前选中', e)}
|
||||
onItemDelete={(e) => console.log('删除', e)}
|
||||
onItemSetting={e => console.log('配置', e)}
|
||||
onRenameFinish={async (data, pData) => console.log('重命名表单提交', data, pData)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default demo;
|
30
packages/biz/src/tree/demo/customTitleRender.tsx
Normal file
30
packages/biz/src/tree/demo/customTitleRender.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { Tree } from '@zhst/biz';
|
||||
import { Tooltip } from 'antd';
|
||||
import { treeData } from './mock'
|
||||
|
||||
const demo = () => {
|
||||
return (
|
||||
<div style={{ width: '320px' }}>
|
||||
<Tree
|
||||
data={treeData}
|
||||
titleRender={(_nodeData) => {
|
||||
const { title } = _nodeData as any
|
||||
return (
|
||||
<div>
|
||||
{title}
|
||||
<div style={{ float: 'right' }} >
|
||||
<Tooltip placement="right" title={'存在0个'}>
|
||||
<a >包含0个</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default demo;
|
34
packages/biz/src/tree/demo/mock.tsx
Normal file
34
packages/biz/src/tree/demo/mock.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { TreeDataNode } from "antd";
|
||||
|
||||
export const treeData: TreeDataNode[] = [
|
||||
{
|
||||
title: '全部盒子',
|
||||
key: '0-0',
|
||||
children: [
|
||||
{
|
||||
title: '盒子组1',
|
||||
key: '0-0-0',
|
||||
children: [
|
||||
{
|
||||
title: '摄像头1',
|
||||
key: '0-0-0-0',
|
||||
},
|
||||
{
|
||||
title: '摄像头2',
|
||||
key: '0-0-0-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '盒子组2',
|
||||
key: '0-0-1',
|
||||
children: [
|
||||
{
|
||||
title: '摄像头4',
|
||||
key: '0-0-1-0'
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
16
packages/biz/src/tree/demo/noOption.tsx
Normal file
16
packages/biz/src/tree/demo/noOption.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { Tree } from '@zhst/biz';
|
||||
import { treeData } from './mock'
|
||||
|
||||
const demo = () => {
|
||||
return (
|
||||
<div style={{ width: '320px' }}>
|
||||
<Tree
|
||||
data={treeData}
|
||||
showItemOption={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default demo;
|
9
packages/biz/src/tree/index.less
Normal file
9
packages/biz/src/tree/index.less
Normal file
@ -0,0 +1,9 @@
|
||||
.zhst-biz-tree-item-render {
|
||||
&_right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover &_right {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
21
packages/biz/src/tree/index.md
Normal file
21
packages/biz/src/tree/index.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
category: Components
|
||||
title: Tree 树
|
||||
demo:
|
||||
cols: 2
|
||||
group:
|
||||
title: 数据展示
|
||||
order: 2
|
||||
---
|
||||
|
||||
|
||||
## 代码演示
|
||||
|
||||
<code src="./demo/basic.tsx">基本用法</code>
|
||||
<code src="./demo/customTitleRender.tsx">自定义渲染界面</code>
|
||||
<code src="./demo/noOption.tsx">不展示配置项</code>
|
||||
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| data | 数据源 | Array[] | [] | - |
|
3
packages/biz/src/tree/index.tsx
Normal file
3
packages/biz/src/tree/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import BoxTree from './boxTree';
|
||||
|
||||
export default BoxTree;
|
124
packages/biz/src/treeTransfer/TreeTransfer.tsx
Normal file
124
packages/biz/src/treeTransfer/TreeTransfer.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Card, Flex, Input, Tree } from 'antd';
|
||||
import theme from 'antd/es/theme'
|
||||
import { TransferProps, TreeDataNode, TreeProps } from 'antd';
|
||||
import './index.less'
|
||||
import { DeleteOutlined, DoubleRightOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import { getAllRootKeyById } from './treeTransferHelper';
|
||||
|
||||
const componentName = 'zhst-biz-treeTransfer'
|
||||
|
||||
export interface TreeTransferProps {
|
||||
dataSource: TreeDataNode[]
|
||||
treeProps?: TreeProps
|
||||
targetItems: TreeDataNode[];
|
||||
checkedKeys: string[];
|
||||
onTreeSelect?: TreeProps['onSelect']
|
||||
onTreeCheck?: TreeProps['onCheck']
|
||||
onItemDelete?: (key: string, info?: { root: TreeDataNode[], keys: string[] }) => void
|
||||
onChange?: TransferProps['onChange'];
|
||||
onOk?: (data: any) => void;
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
const { useToken } = theme
|
||||
|
||||
const TreeTransfer: React.FC<TreeTransferProps> = ({
|
||||
dataSource,
|
||||
treeProps,
|
||||
targetItems = [],
|
||||
checkedKeys = [],
|
||||
onTreeCheck,
|
||||
onTreeSelect,
|
||||
onItemDelete,
|
||||
onOk,
|
||||
onReset
|
||||
}) => {
|
||||
|
||||
const { token } = useToken()
|
||||
const [keyWords, setKeyWords ] = useState('')
|
||||
|
||||
function findNodesWithKeyword(_keyWords: string, _treeData: TreeDataNode[]) {
|
||||
// @ts-ignore
|
||||
function dfs(node: any) {
|
||||
return node.filter((item: { title: string | string[]; }) => item.title.includes(_keyWords))
|
||||
}
|
||||
|
||||
const data = dfs(_treeData)
|
||||
return data || [];
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap={20} className={componentName} align='center' justify='center'>
|
||||
<div className={`${componentName}-left`}>
|
||||
<Card
|
||||
className={`${componentName}-left_card`}
|
||||
title={<div style={{ textAlign: 'center' }} >可选择的范围</div>}
|
||||
bodyStyle={{ padding: 12 }}
|
||||
>
|
||||
<Input prefix={<SearchOutlined />} onChange={e => setKeyWords(e.target.value)} placeholder='请输入设备名称' />
|
||||
<Tree
|
||||
style={{ marginTop: '6px' }}
|
||||
height={420}
|
||||
blockNode
|
||||
checkable
|
||||
checkedKeys={checkedKeys}
|
||||
treeData={findNodesWithKeyword(keyWords, dataSource)}
|
||||
onCheck={(keys, info) => onTreeCheck?.(keys, info)}
|
||||
onSelect={(keys, info) => onTreeSelect?.(keys, info)}
|
||||
{...treeProps}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
<DoubleRightOutlined/>
|
||||
<div className={`${componentName}-right`}>
|
||||
<Card
|
||||
className={`${componentName}-right_card`}
|
||||
title={<div style={{ textAlign: 'center' }}>已选择的范围</div>}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
|
||||
>
|
||||
<div
|
||||
className={`${componentName}-right_card__items`}
|
||||
>
|
||||
{targetItems.map(item => (
|
||||
<div
|
||||
className={`${componentName}-right_card__items___item`}
|
||||
key={item.key}
|
||||
onMouseEnter={(e: any) => {
|
||||
e.target.style.backgroundColor = token.colorPrimaryBg
|
||||
e.target.style.color = token.colorPrimary
|
||||
}}
|
||||
onMouseLeave={(e: any) => {
|
||||
e.target.style.color = token.colorText
|
||||
e.target.style.backgroundColor = null
|
||||
}}
|
||||
>
|
||||
{item.title as any}
|
||||
<div style={{ float: 'right' }}>
|
||||
<DeleteOutlined onClick={() => {
|
||||
const { root, keys } = getAllRootKeyById(item.key as string, dataSource)
|
||||
onItemDelete?.(item.key as string, { root, keys })
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
<Flex
|
||||
className={`${componentName}-right_card__btns`}
|
||||
>
|
||||
<Button style={{ marginRight: 8, width: '50%' }} disabled={targetItems.length <= 0} onClick={onReset}>重置</Button>
|
||||
<Button
|
||||
style={{ width: '50%' }}
|
||||
type='primary'
|
||||
onClick={() => onOk?.(targetItems)}
|
||||
>确定</Button>
|
||||
</Flex>
|
||||
</Card>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default TreeTransfer
|
56
packages/biz/src/treeTransfer/demo/basic.tsx
Normal file
56
packages/biz/src/treeTransfer/demo/basic.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { useState } from 'react';
|
||||
import { TreeTransfer } from '@zhst/biz';
|
||||
import { TreeDataNode } from 'antd';
|
||||
import { TreeProps } from 'antd/lib';
|
||||
import { boxDataSource } from './mock'
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
|
||||
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
||||
|
||||
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
|
||||
let _targetItems: TreeDataNode[] = []
|
||||
setCheckedKeys(keys)
|
||||
info.checkedNodes.forEach(o => {
|
||||
o.isLeaf && _targetItems.push(o)
|
||||
})
|
||||
setTargetItems(_targetItems)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param key
|
||||
* @param param1
|
||||
*/
|
||||
const onItemDelete = (key: any, { keys }: any) => {
|
||||
setCheckedKeys(pre => {
|
||||
const newKeys = pre.filter(_key => !keys.includes(_key))
|
||||
console.log('newKeys', newKeys, keys)
|
||||
return newKeys
|
||||
})
|
||||
setTargetItems(pre => pre.filter(o => o.key !== key))
|
||||
}
|
||||
|
||||
const onOk = (data: any) => {
|
||||
console.log('data', data)
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
setCheckedKeys([])
|
||||
setTargetItems([])
|
||||
}
|
||||
|
||||
return (
|
||||
<TreeTransfer
|
||||
dataSource={boxDataSource}
|
||||
targetItems={targetItems}
|
||||
checkedKeys={checkedKeys}
|
||||
onTreeCheck={onTreeCheck}
|
||||
onItemDelete={onItemDelete}
|
||||
onOk={onOk}
|
||||
onReset={onReset}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export default App;
|
35
packages/biz/src/treeTransfer/demo/mock.ts
Normal file
35
packages/biz/src/treeTransfer/demo/mock.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { TreeDataNode } from "antd";
|
||||
|
||||
export const boxDataSource: TreeDataNode[] = [
|
||||
{
|
||||
key: '0-0',
|
||||
title: '分组0-0',
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
},
|
||||
{
|
||||
key: '0-1',
|
||||
title: '分组0-1',
|
||||
isLeaf: false,
|
||||
children: [
|
||||
{ key: '0-1-0', title: '分组0-1-0', isLeaf: true, checkable: false },
|
||||
{ key: '0-1-1', title: '分组0-1-1', isLeaf: true, checkable: false },
|
||||
{ key: '0-1-2', title: '分组0-1-2', isLeaf: true, checkable: false },
|
||||
{
|
||||
key: '0-1-3',
|
||||
title: '分组0-1-3',
|
||||
isLeaf: false,
|
||||
children: [
|
||||
{ key: '0-1-3-1', title: '分组0-1-3-1', isLeaf: true },
|
||||
{ key: '0-1-3-2', title: '分组0-1-3-2', isLeaf: true },
|
||||
{ key: '0-1-3-3', title: '分组0-1-3-3', isLeaf: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ key: '0-2', title: '分组0-2', isLeaf: false, checkable: false, },
|
||||
{ key: '0-3', title: '分组0-3', isLeaf: false, checkable: false, },
|
||||
{ key: '0-4', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
{ key: '0-5', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
{ key: '0-6', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
];
|
127
packages/biz/src/treeTransfer/demo/withModal.tsx
Normal file
127
packages/biz/src/treeTransfer/demo/withModal.tsx
Normal file
@ -0,0 +1,127 @@
|
||||
import React, { useState } from 'react';
|
||||
import { TreeTransfer } from '@zhst/biz';
|
||||
import { Button, Modal, Radio, TreeDataNode } from 'antd';
|
||||
import { TreeProps } from 'antd/lib';
|
||||
|
||||
const treeData: TreeDataNode[] = [
|
||||
{
|
||||
key: '0-0',
|
||||
title: '分组0-0',
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
},
|
||||
{
|
||||
key: '0-1',
|
||||
title: '分组0-1',
|
||||
isLeaf: false,
|
||||
children: [
|
||||
{ key: '0-1-0', title: '分组0-1-0', isLeaf: true, checkable: false },
|
||||
{ key: '0-1-1', title: '分组0-1-1', isLeaf: true, checkable: false },
|
||||
{ key: '0-1-2', title: '分组0-1-2', isLeaf: true, checkable: false },
|
||||
{
|
||||
key: '0-1-3',
|
||||
title: '分组0-1-3',
|
||||
isLeaf: false,
|
||||
children: [
|
||||
{ key: '0-1-3-1', title: '分组0-1-3-1', isLeaf: true },
|
||||
{ key: '0-1-3-2', title: '分组0-1-3-2', isLeaf: true },
|
||||
{ key: '0-1-3-3', title: '分组0-1-3-3', isLeaf: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ key: '0-2', title: '分组0-2', isLeaf: false, checkable: false, },
|
||||
{ key: '0-3', title: '分组0-3', isLeaf: false, checkable: false, },
|
||||
{ key: '0-4', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
{ key: '0-5', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
{ key: '0-6', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
];
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
|
||||
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
||||
const [type, setType] = useState('box')
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
|
||||
let _targetItems: TreeDataNode[] = []
|
||||
setCheckedKeys(keys)
|
||||
info.checkedNodes.forEach(o => {
|
||||
o.isLeaf && _targetItems.push(o)
|
||||
})
|
||||
setTargetItems(_targetItems)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param key
|
||||
* @param param1
|
||||
*/
|
||||
const onItemDelete = (key: any, { keys }: any) => {
|
||||
setCheckedKeys(pre => {
|
||||
const newKeys = pre.filter(_key => !keys.includes(_key))
|
||||
console.log('newKeys', newKeys, keys)
|
||||
return newKeys
|
||||
})
|
||||
setTargetItems(pre => pre.filter(o => o.key !== key))
|
||||
}
|
||||
|
||||
const onOk = (data: any) => {
|
||||
console.log('data', data)
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
setCheckedKeys([])
|
||||
setTargetItems([])
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button type="primary" onClick={() => setOpen(true)} >选择盒子</Button>
|
||||
<Modal
|
||||
open={open}
|
||||
destroyOnClose
|
||||
title="统计点位"
|
||||
width="948px"
|
||||
onCancel={() => setOpen(false)}
|
||||
footer={null}
|
||||
>
|
||||
<div>
|
||||
<Radio.Group
|
||||
onChange={e => setType(e.target.value)}
|
||||
style={{ marginLeft: '24px', padding: '20px 0' }}
|
||||
value={type}
|
||||
>
|
||||
<Radio value={'box'}>盒子</Radio>
|
||||
<Radio value={'boxGroup'}>盒子组</Radio>
|
||||
</Radio.Group>
|
||||
{type === 'box' ?
|
||||
(
|
||||
<TreeTransfer
|
||||
dataSource={treeData}
|
||||
targetItems={targetItems}
|
||||
checkedKeys={checkedKeys}
|
||||
onTreeCheck={onTreeCheck}
|
||||
onItemDelete={onItemDelete}
|
||||
onOk={onOk}
|
||||
onReset={onReset}
|
||||
/>
|
||||
) : (
|
||||
<TreeTransfer
|
||||
dataSource={treeData}
|
||||
targetItems={targetItems}
|
||||
checkedKeys={checkedKeys}
|
||||
onTreeCheck={onTreeCheck}
|
||||
onItemDelete={onItemDelete}
|
||||
onOk={onOk}
|
||||
onReset={onReset}
|
||||
/>
|
||||
)}
|
||||
<p style={{ textAlign: 'right' }} >已选中{targetItems.length}个点位</p>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default App;
|
43
packages/biz/src/treeTransfer/index.less
Normal file
43
packages/biz/src/treeTransfer/index.less
Normal file
@ -0,0 +1,43 @@
|
||||
.zhst-biz-treeTransfer {
|
||||
&-left {
|
||||
&_card {
|
||||
width: 500px;
|
||||
height: 522px;
|
||||
background-color: #FCFCFC;
|
||||
}
|
||||
}
|
||||
|
||||
&-right {
|
||||
&_card {
|
||||
width: 300px;
|
||||
height: 522px;
|
||||
background-color: #FCFCFC;
|
||||
|
||||
&__items {
|
||||
padding: 8px 4px;
|
||||
overflow: scroll;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&___item {
|
||||
margin: 0;
|
||||
padding: 4px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&__btns {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
packages/biz/src/treeTransfer/index.md
Normal file
18
packages/biz/src/treeTransfer/index.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
category: Components
|
||||
title: TreeTransfer 树穿梭框
|
||||
group:
|
||||
title: 数据展示
|
||||
order: 2
|
||||
---
|
||||
|
||||
盒子树
|
||||
|
||||
## 代码演示
|
||||
|
||||
<code src="./demo/basic.tsx">基本用法</code>
|
||||
<code src="./demo/withModal.tsx">和Modal组合使用</code>
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| data | 数据源 | Array[] | [] | - |
|
3
packages/biz/src/treeTransfer/index.tsx
Normal file
3
packages/biz/src/treeTransfer/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import TreeTransfer from "./TreeTransfer";
|
||||
|
||||
export default TreeTransfer
|
62
packages/biz/src/treeTransfer/treeTransferHelper.ts
Normal file
62
packages/biz/src/treeTransfer/treeTransferHelper.ts
Normal file
@ -0,0 +1,62 @@
|
||||
export const isChecked = (selectedKeys: React.Key[], eventKey: React.Key) =>
|
||||
selectedKeys.includes(eventKey);
|
||||
|
||||
function isObject(value: any) {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过子元素找到父级节点
|
||||
* @param objects
|
||||
* @param element
|
||||
* @returns
|
||||
*/
|
||||
export const findParentByChild = (objects: any[], propertyValue: string | number, propertyKey: string = 'key') => {
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
const obj = objects[i];
|
||||
|
||||
if (obj[propertyKey] === propertyValue) {
|
||||
return obj
|
||||
} else if (typeof obj === 'object') {
|
||||
const found = findParentByChild(Object.values(obj), propertyValue);
|
||||
|
||||
if (found) {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
return null; // 如果找不到包含具有指定属性的子对象的父对象,返回 null
|
||||
}
|
||||
|
||||
export const getAllRootKeyById = (val: string | number, list: any[], key: string = 'key') => {
|
||||
let keys: any[] = []
|
||||
const findParentByChild = (propertyValue: string | number, objects: any[], propertyKey: string) => {
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
const obj = objects[i];
|
||||
|
||||
if (obj[propertyKey] === propertyValue) {
|
||||
console.log('obj', obj)
|
||||
return obj
|
||||
} else if (typeof obj === 'object') {
|
||||
const found = findParentByChild(propertyValue, Object.values(obj), propertyKey);
|
||||
|
||||
if (found) {
|
||||
if (isObject(found)) {
|
||||
keys.push(found.key)
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
return null; // 如果找不到包含具有指定属性的子对象的父对象,返回 null
|
||||
}
|
||||
|
||||
const data = findParentByChild(val, list, key)
|
||||
data.key && keys.push(data.key)
|
||||
|
||||
return {
|
||||
root: data,
|
||||
keys
|
||||
}
|
||||
}
|
91
packages/biz/src/treeTransferModal/TreeTransferModal.tsx
Normal file
91
packages/biz/src/treeTransferModal/TreeTransferModal.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import TreeTransfer from '../treeTransfer';
|
||||
import { Modal, ModalProps, Radio, RadioGroupProps, TransferProps, TreeDataNode } from 'antd';
|
||||
import { TreeProps } from 'antd/lib';
|
||||
|
||||
export interface TreeTransferModalProps {
|
||||
dataSource: TreeDataNode[]
|
||||
treeProps?: TreeProps
|
||||
targetItems: TreeDataNode[];
|
||||
checkedKeys: string[];
|
||||
onTreeSelect?: TreeProps['onSelect']
|
||||
onTreeCheck?: TreeProps['onCheck']
|
||||
onItemDelete?: (key: string, info?: { root: TreeDataNode[], keys: string[] }) => void
|
||||
onChange?: TransferProps['onChange'];
|
||||
onOk?: (data: any) => void;
|
||||
onReset?: () => void;
|
||||
open?: boolean
|
||||
onCancel?: ModalProps['onCancel']
|
||||
onRadioChange?: RadioGroupProps['onChange']
|
||||
modalProps?: ModalProps
|
||||
}
|
||||
|
||||
const TreeTransferModal: FC<TreeTransferModalProps> = (props) => {
|
||||
const {
|
||||
open,
|
||||
dataSource = [],
|
||||
checkedKeys,
|
||||
onItemDelete,
|
||||
onOk,
|
||||
onCancel,
|
||||
onReset,
|
||||
onRadioChange,
|
||||
onTreeCheck,
|
||||
targetItems,
|
||||
modalProps,
|
||||
} = props
|
||||
|
||||
const [type, setType] = useState('box')
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
destroyOnClose
|
||||
title="统计点位"
|
||||
width="948px"
|
||||
footer={null}
|
||||
onCancel={onCancel}
|
||||
{...modalProps}
|
||||
>
|
||||
<div>
|
||||
<Radio.Group
|
||||
onChange={e => {
|
||||
setType(e.target.value)
|
||||
onRadioChange?.(e)
|
||||
}}
|
||||
style={{ marginLeft: '24px', padding: '20px 0' }}
|
||||
value={type}
|
||||
>
|
||||
<Radio value={'box'}>盒子</Radio>
|
||||
<Radio value={'boxGroup'}>盒子组</Radio>
|
||||
</Radio.Group>
|
||||
{type === 'box' ?
|
||||
(
|
||||
<TreeTransfer
|
||||
dataSource={dataSource}
|
||||
targetItems={targetItems}
|
||||
checkedKeys={checkedKeys}
|
||||
onTreeCheck={onTreeCheck}
|
||||
onItemDelete={onItemDelete}
|
||||
onOk={onOk}
|
||||
onReset={onReset}
|
||||
/>
|
||||
) : (
|
||||
<TreeTransfer
|
||||
dataSource={dataSource}
|
||||
targetItems={targetItems}
|
||||
checkedKeys={checkedKeys}
|
||||
onTreeCheck={onTreeCheck}
|
||||
onItemDelete={onItemDelete}
|
||||
onOk={onOk}
|
||||
onReset={onReset}
|
||||
/>
|
||||
)}
|
||||
<p style={{ textAlign: 'right' }} >已选中{targetItems.length}个点位</p>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
};
|
||||
|
||||
export default TreeTransferModal;
|
||||
|
63
packages/biz/src/treeTransferModal/demo/basic.tsx
Normal file
63
packages/biz/src/treeTransferModal/demo/basic.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import React, { useState } from 'react';
|
||||
import { TreeTransferModal } from '@zhst/biz';
|
||||
import { Button, TreeDataNode } from 'antd';
|
||||
import { TreeProps } from 'antd/lib';
|
||||
import { boxDataSource } from './mock'
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
|
||||
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
|
||||
let _targetItems: TreeDataNode[] = []
|
||||
setCheckedKeys(keys)
|
||||
info.checkedNodes.forEach(o => {
|
||||
o.isLeaf && _targetItems.push(o)
|
||||
})
|
||||
setTargetItems(_targetItems)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param key
|
||||
* @param param1
|
||||
*/
|
||||
const onItemDelete = (key: any, { keys }: any) => {
|
||||
setCheckedKeys(pre => {
|
||||
const newKeys = pre.filter(_key => !keys.includes(_key))
|
||||
console.log('newKeys', newKeys, keys)
|
||||
return newKeys
|
||||
})
|
||||
setTargetItems(pre => pre.filter(o => o.key !== key))
|
||||
}
|
||||
|
||||
const onOk = (data: any) => {
|
||||
console.log('data', data)
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
setCheckedKeys([])
|
||||
setTargetItems([])
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button type="primary" onClick={() => setOpen(true)} >选择盒子</Button>
|
||||
<TreeTransferModal
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
onRadioChange={() => setOpen(false)} // 顶部 radio 事件
|
||||
dataSource={boxDataSource} // 数据源
|
||||
targetItems={targetItems} // 右侧选中项
|
||||
checkedKeys={checkedKeys} // 左侧选中
|
||||
onReset={onReset} // 重置按钮事件
|
||||
onOk={onOk} // 确定按钮事件
|
||||
onTreeCheck={onTreeCheck} // 树check选中事件
|
||||
onItemDelete={onItemDelete} // 右侧点击删除事件
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default App;
|
35
packages/biz/src/treeTransferModal/demo/mock.ts
Normal file
35
packages/biz/src/treeTransferModal/demo/mock.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { TreeDataNode } from "antd";
|
||||
|
||||
export const boxDataSource: TreeDataNode[] = [
|
||||
{
|
||||
key: '0-0',
|
||||
title: '分组0-0',
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
},
|
||||
{
|
||||
key: '0-1',
|
||||
title: '分组0-1',
|
||||
isLeaf: false,
|
||||
children: [
|
||||
{ key: '0-1-0', title: '分组0-1-0', isLeaf: true, checkable: false },
|
||||
{ key: '0-1-1', title: '分组0-1-1', isLeaf: true, checkable: false },
|
||||
{ key: '0-1-2', title: '分组0-1-2', isLeaf: true, checkable: false },
|
||||
{
|
||||
key: '0-1-3',
|
||||
title: '分组0-1-3',
|
||||
isLeaf: false,
|
||||
children: [
|
||||
{ key: '0-1-3-1', title: '分组0-1-3-1', isLeaf: true },
|
||||
{ key: '0-1-3-2', title: '分组0-1-3-2', isLeaf: true },
|
||||
{ key: '0-1-3-3', title: '分组0-1-3-3', isLeaf: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ key: '0-2', title: '分组0-2', isLeaf: false, checkable: false, },
|
||||
{ key: '0-3', title: '分组0-3', isLeaf: false, checkable: false, },
|
||||
{ key: '0-4', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
{ key: '0-5', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
{ key: '0-6', title: '分组0-4', isLeaf: false, checkable: false, },
|
||||
];
|
17
packages/biz/src/treeTransferModal/index.md
Normal file
17
packages/biz/src/treeTransferModal/index.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
category: Components
|
||||
title: TreeTransferModal 树穿梭框弹框
|
||||
group:
|
||||
title: 数据展示
|
||||
order: 2
|
||||
---
|
||||
|
||||
树穿梭框弹框
|
||||
|
||||
## 代码演示
|
||||
|
||||
<code src="./demo/basic.tsx">基本用法</code>
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| data | 数据源 | Array[] | [] | - |
|
3
packages/biz/src/treeTransferModal/index.tsx
Normal file
3
packages/biz/src/treeTransferModal/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import TreeTransferModal from './TreeTransferModal'
|
||||
|
||||
export default TreeTransferModal
|
@ -4,4 +4,5 @@ export default defineConfig({
|
||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
||||
esm: { output: 'es' },
|
||||
cjs: { output: 'lib' },
|
||||
// umd: { output: 'dist' }
|
||||
});
|
||||
|
@ -1,5 +1,16 @@
|
||||
# @zhst/utils
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 新增业务组件 Tree、TreeTransfer、TreeTransferModal、BoxSelectTree
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/request@0.7.0
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
1
packages/func/dist/func.min.js
vendored
Normal file
1
packages/func/dist/func.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
packages/func/dist/umd/func.min.js
vendored
1
packages/func/dist/umd/func.min.js
vendored
File diff suppressed because one or more lines are too long
@ -8,6 +8,3 @@ export * from "./time";
|
||||
export * from "./utils";
|
||||
export * from "./camera";
|
||||
export * from "./math";
|
||||
export var a = function a(data) {
|
||||
console.log('data', data);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/func",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"description": "函数合集",
|
||||
"keywords": [
|
||||
"hooks"
|
||||
|
@ -8,7 +8,3 @@ export * from './time'
|
||||
export * from './utils'
|
||||
export * from './camera'
|
||||
export * from './math'
|
||||
|
||||
export const a = (data: string) => {
|
||||
console.log('data', data)
|
||||
}
|
@ -4,4 +4,5 @@ export default defineConfig({
|
||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
||||
esm: { output: 'es' },
|
||||
cjs: { output: 'lib' },
|
||||
// umd: { output: 'dist' }
|
||||
});
|
||||
|
@ -1,5 +1,16 @@
|
||||
# @zhst/hooks
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 新增业务组件 Tree、TreeTransfer、TreeTransferModal、BoxSelectTree
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/func@0.7.0
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/hooks",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"description": "hooks合集",
|
||||
"keywords": [
|
||||
"hooks"
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { defineConfig } from 'father';
|
||||
|
||||
export default defineConfig({
|
||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
||||
esm: { output: 'es' },
|
||||
cjs: { output: 'lib' },
|
||||
});
|
@ -1,140 +0,0 @@
|
||||
# @zhst/biz
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.6.0
|
||||
- @zhst/func@0.6.0
|
||||
- @zhst/meta@0.7.0
|
||||
- @zhst/biz@0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- feat: 初版发布
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.5.0
|
||||
- @zhst/func@0.5.0
|
||||
- @zhst/meta@0.6.0
|
||||
- @zhst/biz@0.5.0
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/meta@0.5.2
|
||||
- @zhst/biz@0.4.2
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- feat: 初始化
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.4.1
|
||||
- @zhst/func@0.4.1
|
||||
- @zhst/meta@0.5.1
|
||||
- @zhst/biz@0.4.1
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- fix: 修改 pkg
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.4.0
|
||||
- @zhst/func@0.4.0
|
||||
- @zhst/meta@0.5.0
|
||||
- @zhst/biz@0.4.0
|
||||
|
||||
## 0.3.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/meta@0.4.4
|
||||
- @zhst/biz@0.3.6
|
||||
|
||||
## 0.3.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/meta@0.4.3
|
||||
- @zhst/biz@0.3.5
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/meta@0.4.2
|
||||
- @zhst/biz@0.3.4
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/func@0.3.1
|
||||
- @zhst/biz@0.3.3
|
||||
- @zhst/hooks@0.3.1
|
||||
- @zhst/meta@0.4.1
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/meta@0.4.0
|
||||
- @zhst/biz@0.3.2
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/meta@0.3.1
|
||||
- @zhst/biz@0.3.1
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- feat: 初始化项目包
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.3.0
|
||||
- @zhst/func@0.3.0
|
||||
- @zhst/meta@0.3.0
|
||||
- @zhst/biz@0.3.0
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- feat: 新增 meta 包
|
||||
- Updated dependencies
|
||||
- @zhst/func@0.2.4
|
||||
- @zhst/hooks@0.2.4
|
||||
- @zhst/meta@0.2.4
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fix: 调试包 link
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.2.3
|
@ -1,20 +0,0 @@
|
||||
:::warning{title=待开始}
|
||||
目前项目正在如火如荼的筹备中...
|
||||
:::
|
||||
|
||||
## 介绍
|
||||
|
||||
物料库,每一个物料都是单独存在的个体,不需要任何依赖,就能形成一个页面,甚至一个项目
|
||||
|
||||
## 安装
|
||||
|
||||
> pnpm install @zhst/material
|
||||
|
||||
## 使用
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import { Demo } from '@zhst/material'
|
||||
|
||||
export default () => <Demo />
|
||||
```
|
3
packages/material/es/Demo/index.d.ts
vendored
3
packages/material/es/Demo/index.d.ts
vendored
@ -1,3 +0,0 @@
|
||||
import React from 'react';
|
||||
declare const _default: () => React.JSX.Element;
|
||||
export default _default;
|
@ -1,4 +0,0 @@
|
||||
import React from 'react';
|
||||
export default (function () {
|
||||
return /*#__PURE__*/React.createElement("div", null, "hi");
|
||||
});
|
1
packages/material/es/index.d.ts
vendored
1
packages/material/es/index.d.ts
vendored
@ -1 +0,0 @@
|
||||
export { default as Demo } from './Demo';
|
@ -1 +0,0 @@
|
||||
export { default as Demo } from "./Demo";
|
3
packages/material/lib/Demo/index.d.ts
vendored
3
packages/material/lib/Demo/index.d.ts
vendored
@ -1,3 +0,0 @@
|
||||
import React from 'react';
|
||||
declare const _default: () => React.JSX.Element;
|
||||
export default _default;
|
@ -1,38 +0,0 @@
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/Demo/index.tsx
|
||||
var Demo_exports = {};
|
||||
__export(Demo_exports, {
|
||||
default: () => Demo_default
|
||||
});
|
||||
module.exports = __toCommonJS(Demo_exports);
|
||||
var import_react = __toESM(require("react"));
|
||||
var Demo_default = () => {
|
||||
return /* @__PURE__ */ import_react.default.createElement("div", null, "hi");
|
||||
};
|
1
packages/material/lib/index.d.ts
vendored
1
packages/material/lib/index.d.ts
vendored
@ -1 +0,0 @@
|
||||
export { default as Demo } from './Demo';
|
@ -1,39 +0,0 @@
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.tsx
|
||||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
Demo: () => import_Demo.default
|
||||
});
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
var import_Demo = __toESM(require("./Demo"));
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Demo
|
||||
});
|
@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "@zhst/material",
|
||||
"version": "0.5.1",
|
||||
"description": "物料库",
|
||||
"keywords": [
|
||||
"business",
|
||||
"biz",
|
||||
"zhst",
|
||||
"material"
|
||||
],
|
||||
"license": "ISC",
|
||||
"author": "dev",
|
||||
"sideEffects": [
|
||||
"dist/*",
|
||||
"es/**/style/*",
|
||||
"lib/**/style/*",
|
||||
"*.less"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
"typings": "es/index.d.ts",
|
||||
"files": [
|
||||
"es",
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "father build"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "http://10.0.0.77:4874"
|
||||
},
|
||||
"dependencies": {
|
||||
"@zhst/hooks": "workspace:^",
|
||||
"@zhst/meta": "workspace:^",
|
||||
"@zhst/func": "workspace:^",
|
||||
"@zhst/biz": "workspace:^"
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default () => {
|
||||
|
||||
return (
|
||||
<div>hi</div>
|
||||
)
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
nav:
|
||||
title: 元组件
|
||||
title: 版本更新日志
|
||||
order: 99
|
||||
---
|
||||
|
||||
<embed src="../../CHANGELOG.md" ></embed>
|
@ -1 +0,0 @@
|
||||
export { default as Demo } from './Demo';
|
@ -1,5 +1,18 @@
|
||||
# @zhst/utils
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 新增业务组件 Tree、TreeTransfer、TreeTransferModal、BoxSelectTree
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.7.0
|
||||
- @zhst/func@0.7.0
|
||||
- @zhst/meta@0.8.0
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
@ -16,23 +16,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
||||
import React, { useEffect, useState, useCallback, useRef, useImperativeHandle } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useLatest } from '@zhst/hooks';
|
||||
import { get, pick, isNull,
|
||||
// @ts-ignore
|
||||
generateImg,
|
||||
// @ts-ignore
|
||||
dataURLToBlob,
|
||||
// @ts-ignore
|
||||
getOdRect,
|
||||
// @ts-ignore
|
||||
getExtendRect,
|
||||
// @ts-ignore
|
||||
getTransformRect,
|
||||
// @ts-ignore
|
||||
getRotateImg, getTransforms, addEventListenerWrapper,
|
||||
// @ts-ignore
|
||||
getFileByRect
|
||||
// @ts-ignore
|
||||
} from '@zhst/func';
|
||||
import { get, pick, isNull, generateImg, dataURLToBlob, getOdRect, getExtendRect, getTransformRect, getRotateImg, getTransforms, addEventListenerWrapper, getFileByRect } from '@zhst/func';
|
||||
import Align from 'rc-align';
|
||||
import { Button, Empty } from '..';
|
||||
import Icon from "../iconfont";
|
||||
|
@ -14,11 +14,7 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
|
||||
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
||||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { noop, get, addEventListenerWrapper,
|
||||
// @ts-ignore
|
||||
dataURLToBlob, nextTick, toRealNumber, getTransforms, formatDurationTime
|
||||
// @ts-ignore
|
||||
} from '@zhst/func';
|
||||
import { noop, get, addEventListenerWrapper, dataURLToBlob, nextTick, toRealNumber, getTransforms, formatDurationTime } from '@zhst/func';
|
||||
import Align from 'rc-align';
|
||||
import { useLatest, useUpdateEffect, useFullscreen, useUnmount } from '@zhst/hooks';
|
||||
import classNames from 'classnames';
|
||||
|
@ -19,7 +19,6 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
||||
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
||||
import React, { Component } from 'react';
|
||||
import flvjs from 'flv.js';
|
||||
// @ts-ignore
|
||||
import { isEqual } from '@zhst/func';
|
||||
export var FLV_EVENT = flvjs.Events;
|
||||
var VideoPlayer = /*#__PURE__*/function (_Component) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/meta",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.0",
|
||||
"description": "原子组件",
|
||||
"keywords": [
|
||||
"meta",
|
||||
@ -67,7 +67,7 @@
|
||||
"@types/tar": "^6.1.10",
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@types/warning": "^3.0.3",
|
||||
"@types/zhst": "workspace:^",
|
||||
"@zhst/types": "workspace:^",
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
"@typescript-eslint/parser": "^6.17.0",
|
||||
"react": "^18.2.0",
|
||||
|
@ -5,23 +5,15 @@ import {
|
||||
get,
|
||||
pick,
|
||||
isNull,
|
||||
// @ts-ignore
|
||||
generateImg,
|
||||
// @ts-ignore
|
||||
dataURLToBlob,
|
||||
// @ts-ignore
|
||||
getOdRect,
|
||||
// @ts-ignore
|
||||
getExtendRect,
|
||||
// @ts-ignore
|
||||
getTransformRect,
|
||||
// @ts-ignore
|
||||
getRotateImg,
|
||||
getTransforms,
|
||||
addEventListenerWrapper,
|
||||
// @ts-ignore
|
||||
getFileByRect
|
||||
// @ts-ignore
|
||||
} from '@zhst/func';
|
||||
import Align from 'rc-align';
|
||||
import { Button, Empty } from '..';
|
||||
|
0
packages/meta/src/BigImagePreview/interface.ts
Normal file
0
packages/meta/src/BigImagePreview/interface.ts
Normal file
@ -3,13 +3,11 @@ import {
|
||||
noop,
|
||||
get,
|
||||
addEventListenerWrapper,
|
||||
// @ts-ignore
|
||||
dataURLToBlob,
|
||||
nextTick,
|
||||
toRealNumber,
|
||||
getTransforms,
|
||||
formatDurationTime
|
||||
// @ts-ignore
|
||||
} from '@zhst/func';
|
||||
import Align from 'rc-align';
|
||||
import { Rect, IScreenshotButtonProp, AlignType } from '@zhst/types'
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Component, CSSProperties } from 'react';
|
||||
import flvjs from 'flv.js';
|
||||
// @ts-ignore
|
||||
import { isEqual } from '@zhst/func';
|
||||
|
||||
export const FLV_EVENT = flvjs.Events;
|
||||
|
@ -190,12 +190,6 @@ export interface ConfigConsumerProps {
|
||||
warning?: WarningContextProps;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param suffixCls
|
||||
* @param customizePrefixCls
|
||||
* @returns
|
||||
*/
|
||||
const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => {
|
||||
if (customizePrefixCls) {
|
||||
return customizePrefixCls;
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
SmileOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import React, { useState } from 'react';
|
||||
import type { RadioChangeEvent } from '@zhst/meta';
|
||||
import type { RadioChangeEvent } from 'antd';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@ -29,7 +29,7 @@ import {
|
||||
Switch,
|
||||
Tree,
|
||||
TreeSelect,
|
||||
} from '@zhst/meta';
|
||||
} from 'antd';
|
||||
import type { DirectionType } from 'antd/es/config-provider';
|
||||
|
||||
const InputGroup = Input.Group;
|
||||
|
@ -5,7 +5,7 @@
|
||||
import { EllipsisOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useState } from 'react';
|
||||
import type { RadioChangeEvent, TourProps, UploadFile } from '@zhst/meta';
|
||||
import type { RadioChangeEvent, TourProps, UploadFile } from 'antd';
|
||||
import {
|
||||
Upload,
|
||||
Tour,
|
||||
@ -29,7 +29,7 @@ import {
|
||||
Image,
|
||||
InputNumber,
|
||||
Divider,
|
||||
} from '@zhst/meta';
|
||||
} from 'antd';
|
||||
import type { Locale } from 'antd/es/locale';
|
||||
import enUS from 'antd/locale/en_US';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
|
@ -1,16 +1,11 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
ColorPicker,
|
||||
ConfigProvider,
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Space,
|
||||
Switch,
|
||||
} from 'antd';
|
||||
import type { Color } from 'antd/es/color-picker';
|
||||
ConfigProvider,
|
||||
} from '@zhst/meta';
|
||||
|
||||
type ThemeData = {
|
||||
borderRadius: number;
|
||||
@ -30,8 +25,6 @@ const defaultData: ThemeData = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [data, setData] = React.useState<ThemeData>(defaultData);
|
||||
|
||||
return (
|
||||
@ -56,46 +49,6 @@ export default () => {
|
||||
</Space>
|
||||
</ConfigProvider>
|
||||
<Divider />
|
||||
<Form
|
||||
form={form}
|
||||
onValuesChange={(_, allValues) => {
|
||||
setData({
|
||||
...allValues,
|
||||
});
|
||||
}}
|
||||
name="theme"
|
||||
initialValues={defaultData}
|
||||
labelCol={{ span: 4 }}
|
||||
wrapperCol={{ span: 20 }}
|
||||
>
|
||||
<Form.Item
|
||||
name="colorPrimary"
|
||||
label="Primary Color"
|
||||
trigger="onChangeComplete"
|
||||
getValueFromEvent={(color: Color) => color.toHexString()}
|
||||
>
|
||||
<ColorPicker />
|
||||
</Form.Item>
|
||||
<Form.Item name="borderRadius" label="Border Radius">
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item label="Button">
|
||||
<Form.Item name={['Button', 'algorithm']} valuePropName="checked" label="algorithm">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['Button', 'colorPrimary']}
|
||||
label="Primary Color"
|
||||
trigger="onChangeComplete"
|
||||
getValueFromEvent={(color: Color) => color.toHexString()}
|
||||
>
|
||||
<ColorPicker />
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
<Form.Item name="submit" wrapperCol={{ offset: 4, span: 20 }}>
|
||||
<Button type="primary">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -212,7 +212,7 @@ interface ProviderChildrenProps extends ConfigProviderProps {
|
||||
legacyLocale: Locale;
|
||||
}
|
||||
|
||||
export const defaultPrefixCls = 'zhst';
|
||||
export const defaultPrefixCls = 'ant';
|
||||
let globalPrefixCls: string;
|
||||
let globalIconPrefixCls: string;
|
||||
let globalTheme: ThemeConfig;
|
||||
|
201
packages/meta/src/config-provider/index.zh-CN.md
Normal file
201
packages/meta/src/config-provider/index.zh-CN.md
Normal file
@ -0,0 +1,201 @@
|
||||
---
|
||||
category: Components
|
||||
subtitle: 全局化配置
|
||||
group: 其他
|
||||
title: ConfigProvider 全局化配置
|
||||
---
|
||||
|
||||
为组件提供统一的全局化配置。
|
||||
|
||||
## 使用
|
||||
|
||||
ConfigProvider 使用 React 的 [context](https://facebook.github.io/react/docs/context.html) 特性,只需在应用外围包裹一次即可全局生效。
|
||||
|
||||
```ts
|
||||
import React from 'react';
|
||||
import { ConfigProvider } from 'antd';
|
||||
|
||||
// ...
|
||||
const Demo: React.FC = () => (
|
||||
<ConfigProvider direction="rtl">
|
||||
<App />
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
export default Demo;
|
||||
```
|
||||
|
||||
### Content Security Policy
|
||||
|
||||
部分组件为了支持波纹效果,使用了动态样式。如果开启了 Content Security Policy (CSP),你可以通过 `csp` 属性来进行配置:
|
||||
|
||||
```ts
|
||||
<ConfigProvider csp={{ nonce: 'YourNonceCode' }}>
|
||||
<Button>My Button</Button>
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<!-- <code src="./demo/locale.tsx">国际化</code>
|
||||
<code src="./demo/direction.tsx">方向</code>
|
||||
<code src="./demo/size.tsx">组件尺寸</code> -->
|
||||
<code src="./demo/theme.tsx">主题</code>
|
||||
<!-- <code src="./demo/wave.tsx">自定义波纹</code>
|
||||
<code src="./demo/prefixCls.tsx" debug>前缀</code>
|
||||
<code src="./demo/useConfig.tsx" debug>获取配置</code>
|
||||
<code src="./demo/warning.tsx" debug>警告</code> -->
|
||||
|
||||
## API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| autoInsertSpaceInButton | 设置为 `false` 时,移除按钮中 2 个汉字之间的空格 | boolean | true | |
|
||||
| componentDisabled | 设置 antd 组件禁用状态 | boolean | - | 4.21.0 |
|
||||
| componentSize | 设置 antd 组件大小 | `small` \| `middle` \| `large` | - | |
|
||||
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
|
||||
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
|
||||
| getPopupContainer | 弹出框(Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | |
|
||||
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 4.2.0 |
|
||||
| iconPrefixCls | 设置图标统一样式前缀 | string | `anticon` | 4.11.0 |
|
||||
| locale | 语言包配置,语言包可到 [antd/locale](http://unpkg.com/antd/locale/) 目录下寻找 | object | - | |
|
||||
| popupMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | 5.5.0 |
|
||||
| popupOverflow | Select 类组件弹层展示逻辑,默认为可视区域滚动,可配置成滚动区域滚动 | 'viewport' \| 'scroll' | 'viewport' | 5.5.0 |
|
||||
| prefixCls | 设置统一样式前缀 | string | `ant` | |
|
||||
| renderEmpty | 自定义组件空状态。参考 [空状态](/components/empty-cn) | function(componentName: string): ReactNode | - | |
|
||||
| theme | 设置主题,参考 [定制主题](/docs/react/customize-theme-cn) | [Theme](/docs/react/customize-theme-cn#theme) | - | 5.0.0 |
|
||||
| virtual | 设置 `false` 时关闭虚拟滚动 | boolean | - | 4.3.0 |
|
||||
| warning | 设置警告等级,`strict` 为 `false` 时会将废弃相关信息聚合为单条信息 | { strict: boolean } | - | 5.10.0 |
|
||||
|
||||
### ConfigProvider.config()
|
||||
|
||||
设置 `Modal`、`Message`、`Notification` 静态方法配置,只会对非 hooks 的静态方法调用生效。
|
||||
|
||||
```ts
|
||||
ConfigProvider.config({
|
||||
prefixCls: 'ant',
|
||||
iconPrefixCls: 'anticon',
|
||||
|
||||
// 5.6.0+
|
||||
// 请优先考虑使用 hooks 版本
|
||||
theme: { token: { colorPrimary: 'red' } },
|
||||
});
|
||||
```
|
||||
|
||||
### ConfigProvider.useConfig() `5.3.0+`
|
||||
|
||||
`5.2.0` 版本后可用。获取父级 `Provider` 的值。如 `DisabledContextProvider`、`SizeContextProvider`。
|
||||
|
||||
```js
|
||||
const {
|
||||
componentDisabled, // 5.3.0+
|
||||
componentSize, // 5.3.0+
|
||||
} = ConfigProvider.useConfig();
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
| 返回值 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| componentDisabled | antd 组件禁用状态 | boolean | - | 5.3.0 |
|
||||
| componentSize | antd 组件大小状态 | `small` \| `middle` \| `large` | - | 5.3.0 |
|
||||
|
||||
### 组件配置
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| alert | 设置 Alert 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| anchor | 设置 Anchor 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| avatar | 设置 Avatar 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| badge | 设置 Badge 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: { count?: string, indicator?: string }, styles?: { count?: React.CSSProperties, indicator?: React.CSSProperties } } | - | 5.7.0 |
|
||||
| breadcrumb | 设置 Breadcrumb 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| button | 设置 Button 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: { icon: string }, styles?: { icon: React.CSSProperties } } | - | 5.6.0 |
|
||||
| calendar | 设置 Calendar 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| card | 设置 Card 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| carousel | 设置 Carousel 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| cascader | 设置 Cascader 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| checkbox | 设置 Checkbox 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| collapse | 设置 Collapse 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| colorPicker | 设置 ColorPicker 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| datePicker | 设置 DatePicker 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| rangePicker | 设置 RangePicker 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.11.0 |
|
||||
| descriptions | 设置 Descriptions 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| divider | 设置 Divider 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| drawer | 设置 Drawer 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [DrawerProps\["classNames"\]](/components/drawer-cn#api), styles?: [DrawerProps\["styles"\]](/components/drawer-cn#api) } | - | 5.7.0, `classNames` 和 `styles`: 5.10.0 |
|
||||
| dropdown | 设置 Dropdown 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.11.0 |
|
||||
| empty | 设置 Empty 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| flex | 设置 Flex 组件的通用属性 | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 |
|
||||
| form | 设置 Form 组件的通用属性 | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)} | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 |
|
||||
| image | 设置 Image 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| input | 设置 Input 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| layout | 设置 Layout 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| list | 设置 List 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| menu | 设置 Menu 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| mentions | 设置 Mentions 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| message | 设置 Message 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| modal | 设置 Modal 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [ModalProps\["classNames"\]](/components/modal-cn#api), styles?: [ModalProps\["styles"\]](/components/modal-cn#api) } | - | 5.7.0, `classNames` 和 `styles`: 5.10.0 |
|
||||
| notification | 设置 Notification 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| pagination | 设置 Pagination 组件的通用属性 | { showSizeChanger?: boolean, className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| progress | 设置 Progress 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| radio | 设置 Radio 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| rate | 设置 Rate 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| result | 设置 Result 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| skeleton | 设置 Skeleton 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| segmented | 设置 Segmented 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| select | 设置 Select 组件的通用属性 | { className?: string, showSearch?: boolean, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| slider | 设置 Slider 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| switch | 设置 Switch 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| space | 设置 Space 的通用属性,参考 [Space](/components/space-cn) | { size: `small` \| `middle` \| `large` \| `number`, className?: string, style?: React.CSSProperties, classNames?: { item: string }, styles?: { item: React.CSSProperties } } | - | 5.6.0 |
|
||||
| spin | 设置 Spin 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| statistic | 设置 Statistic 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| steps | 设置 Steps 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| table | 设置 Table 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| tabs | 设置 Tabs 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| tag | 设置 Tag 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| timeline | 设置 Timeline 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| timePicker | 设置 TimePicker 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| transfer | 设置 Transfer 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| tree | 设置 Tree 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| typography | 设置 Typography 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| upload | 设置 Upload 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| wave | 设置水波纹特效 | { disabled?: boolean, showEffect?: (node: HTMLElement, info: { className, token, component }) => void } | - | 5.8.0 |
|
||||
|
||||
## FAQ
|
||||
|
||||
#### 如何增加一个新的语言包?
|
||||
|
||||
参考[《增加语言包》](/docs/react/i18n#%E5%A2%9E%E5%8A%A0%E8%AF%AD%E8%A8%80%E5%8C%85)。
|
||||
|
||||
#### 为什么时间类组件的国际化 locale 设置不生效?
|
||||
|
||||
参考 FAQ [为什么时间类组件的国际化 locale 设置不生效?](/docs/react/faq#为什么时间类组件的国际化-locale-设置不生效)。
|
||||
|
||||
#### 配置 `getPopupContainer` 导致 Modal 报错?
|
||||
|
||||
相关 issue:<https://github.com/ant-design/ant-design/issues/19974>
|
||||
|
||||
当如下全局设置 `getPopupContainer` 为触发节点的 parentNode 时,由于 Modal 的用法不存在 `triggerNode`,这样会导致 `triggerNode is undefined` 的报错,需要增加一个[判断条件](https://github.com/afc163/feedback-antd/commit/3e4d1ad1bc1a38460dc3bf3c56517f737fe7d44a)。
|
||||
|
||||
```diff
|
||||
<ConfigProvider
|
||||
- getPopupContainer={triggerNode => triggerNode.parentNode}
|
||||
+ getPopupContainer={node => {
|
||||
+ if (node) {
|
||||
+ return node.parentNode;
|
||||
+ }
|
||||
+ return document.body;
|
||||
+ }}
|
||||
>
|
||||
<App />
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
#### 为什么 message.info、notification.open 或 Modal.confirm 等方法内的 ReactNode 无法继承 ConfigProvider 的属性?比如 `prefixCls` 和 `theme`。
|
||||
|
||||
静态方法是使用 ReactDOM.render 重新渲染一个 React 根节点上,和主应用的 React 节点是脱离的。我们建议使用 useMessage、useNotification 和 useModal 来使用相关方法。原先的静态方法在 5.0 中已被废弃。
|
||||
|
||||
#### Vite 生产模式打包后国际化 locale 设置不生效?
|
||||
|
||||
相关 issue:[#39045](https://github.com/ant-design/ant-design/issues/39045)
|
||||
|
||||
由于 Vite 生产模式下打包与开发模式不同,cjs 格式的文件会多一层,需要 `zhCN.default` 来获取。推荐 Vite 用户直接从 `antd/es/locale` 目录下引入 esm 格式的 locale 文件。
|
@ -302,7 +302,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
||||
validateTrigger={mergedValidateTrigger}
|
||||
onMetaChange={onMetaChange}
|
||||
>
|
||||
{/* @ts-ignore */}
|
||||
{/* @ts-ignore */}
|
||||
{(control, renderMeta, context: FormInstance<Values>) => {
|
||||
const mergedName = toArray(name).length && renderMeta ? renderMeta.name : [];
|
||||
const fieldId = getFieldId(mergedName, formName);
|
||||
|
5
packages/meta/src/form/changelog.md
Normal file
5
packages/meta/src/form/changelog.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Form Dom 变化
|
||||
|
||||
- 状态 className 现在移动到顶层,不再是 input only
|
||||
- 去除 `ant-form-item-control-wrapper` 一层 div
|
||||
- `.has-success` 等状态样式添加 `ant-form-item` 前缀
|
@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import { Button, Col, Form, Input, Row, Select, Space, theme } from 'antd';
|
||||
import { Button, Col, Form, Input, Row, Select, Space, theme } from '@zhst/meta';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { AlertFilled, CloseSquareFilled } from '@ant-design/icons';
|
||||
import { Button, Form, Input, Tooltip } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
import uniqueId from 'lodash/uniqueId';
|
||||
import { uniqueId } from '@zhst/func';
|
||||
|
||||
const useStyle = createStyles(() => ({
|
||||
'custom-feedback-icons': css`
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import type { CascaderProps } from '@zhst/meta';
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
Col,
|
||||
Form,
|
||||
@ -11,7 +9,7 @@ import {
|
||||
InputNumber,
|
||||
Row,
|
||||
Select,
|
||||
} from 'antd';
|
||||
} from '@zhst/meta';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@ -193,16 +191,6 @@ const App: React.FC = () => {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="residence"
|
||||
label="Habitual Residence"
|
||||
rules={[
|
||||
{ type: 'array', required: true, message: 'Please select your habitual residence!' },
|
||||
]}
|
||||
>
|
||||
<Cascader options={residences} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="phone"
|
||||
label="Phone Number"
|
||||
@ -218,17 +206,6 @@ const App: React.FC = () => {
|
||||
>
|
||||
<InputNumber addonAfter={suffixSelector} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="website"
|
||||
label="Website"
|
||||
rules={[{ required: true, message: 'Please input website!' }]}
|
||||
>
|
||||
<AutoComplete options={websiteOptions} onChange={onWebsiteChange} placeholder="website">
|
||||
<Input />
|
||||
</AutoComplete>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="intro"
|
||||
label="Intro"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Button, DatePicker, Form, TimePicker } from 'antd';
|
||||
import { Button, DatePicker, Form, TimePicker } from '@zhst/meta';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
|
711
packages/meta/src/form/index.zh-CN.md
Normal file
711
packages/meta/src/form/index.zh-CN.md
Normal file
@ -0,0 +1,711 @@
|
||||
---
|
||||
category: Components
|
||||
subtitle: 表单
|
||||
group: 数据录入
|
||||
title: Form 表单
|
||||
---
|
||||
|
||||
高性能表单控件,自带数据域管理。包含数据录入、校验以及对应样式。
|
||||
|
||||
## 何时使用
|
||||
|
||||
- 用于创建一个实体或收集信息。
|
||||
- 需要对输入的数据类型进行校验时。
|
||||
|
||||
## 代码演示
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic.tsx">基本使用</code>
|
||||
<code src="./demo/control-hooks.tsx">表单方法调用</code>
|
||||
<code src="./demo/layout.tsx">表单布局</code>
|
||||
<code src="./demo/disabled.tsx">表单禁用</code>
|
||||
<code src="./demo/required-mark.tsx">必选样式</code>
|
||||
<code src="./demo/size.tsx">表单尺寸</code>
|
||||
<code src="./demo/layout-can-wrap.tsx">表单标签可换行</code>
|
||||
<code src="./demo/warning-only.tsx">非阻塞校验</code>
|
||||
<code src="./demo/useWatch.tsx">字段监听 Hooks</code>
|
||||
<code src="./demo/validate-trigger.tsx">校验时机</code>
|
||||
<code src="./demo/validate-only.tsx">仅校验</code>
|
||||
<code src="./demo/form-item-path.tsx">字段路径前缀</code>
|
||||
<code src="./demo/dynamic-form-item.tsx">动态增减表单项</code>
|
||||
<code src="./demo/dynamic-form-items.tsx">动态增减嵌套字段</code>
|
||||
<code src="./demo/dynamic-form-items-no-style.tsx" debug>动态增减嵌套纯字段</code>
|
||||
<code src="./demo/dynamic-form-items-complex.tsx">复杂的动态增减表单项</code>
|
||||
<code src="./demo/nest-messages.tsx">嵌套结构与校验信息</code>
|
||||
<code src="./demo/complex-form-control.tsx">复杂一点的控件</code>
|
||||
<code src="./demo/customized-form-controls.tsx">自定义表单控件</code>
|
||||
<code src="./demo/global-state.tsx">表单数据存储于上层组件</code>
|
||||
<code src="./demo/form-context.tsx">多表单联动</code>
|
||||
<code src="./demo/inline-login.tsx">内联登录栏</code>
|
||||
<code src="./demo/normal-login.tsx">登录框</code>
|
||||
<code src="./demo/register.tsx">注册新用户</code>
|
||||
<code src="./demo/advanced-search.tsx">高级搜索</code>
|
||||
<code src="./demo/form-in-modal.tsx">弹出层中的新建表单</code>
|
||||
<code src="./demo/time-related-controls.tsx">时间类控件</code>
|
||||
<code src="./demo/without-form-create.tsx">自行处理表单数据</code>
|
||||
<!-- <code src="./demo/validate-static.tsx">自定义校验</code> -->
|
||||
<code src="./demo/dynamic-rule.tsx">动态校验规则</code>
|
||||
<code src="./demo/dependencies.tsx">校验与更新依赖</code>
|
||||
<code src="./demo/validate-other.tsx">校验其他组件</code>
|
||||
<code src="./demo/disabled-input-debug.tsx" debug>Disabled Input Debug</code>
|
||||
<code src="./demo/label-debug.tsx" debug>测试 label 省略</code>
|
||||
<code src="./demo/col-24-debug.tsx" debug>测试特殊 col 24 用法</code>
|
||||
<code src="./demo/ref-item.tsx" debug>引用字段</code>
|
||||
<code src="./demo/custom-feedback-icons.tsx" debug>Custom feedback icons</code>
|
||||
<code src="./demo/component-token.tsx" debug>组件 Token</code>
|
||||
|
||||
## API
|
||||
|
||||
通用属性参考:[通用属性](/docs/react/common-props)
|
||||
|
||||
### Form
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| colon | 配置 Form.Item 的 `colon` 的默认值。表示是否显示 label 后面的冒号 (只有在属性 layout 为 horizontal 时有效) | boolean | true | |
|
||||
| disabled | 设置表单组件禁用,仅对 antd 组件有效 | boolean | false | 4.21.0 |
|
||||
| component | 设置 Form 渲染元素,为 `false` 则不创建 DOM 节点 | ComponentType \| false | form | |
|
||||
| fields | 通过状态管理(如 redux)控制表单字段,如非强需求不推荐使用。查看[示例](#components-form-demo-global-state) | [FieldData](#fielddata)\[] | - | |
|
||||
| form | 经 `Form.useForm()` 创建的 form 控制实例,不提供时会自动创建 | [FormInstance](#forminstance) | - | |
|
||||
| feedbackIcons | 当 `Form.Item` 有 `hasFeedback` 属性时可以自定义图标 | [FeedbackIcons](#feedbackicons) | - | 5.9.0 |
|
||||
| initialValues | 表单默认值,只有初始化以及重置时生效 | object | - | |
|
||||
| labelAlign | label 标签的文本对齐方式 | `left` \| `right` | `right` | |
|
||||
| labelWrap | label 标签的文本换行方式 | boolean | false | 4.18.0 |
|
||||
| labelCol | label 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}` 或 `sm: {span: 3, offset: 12}` | [object](/components/grid-cn#col) | - | |
|
||||
| layout | 表单布局 | `horizontal` \| `vertical` \| `inline` | `horizontal` | |
|
||||
| name | 表单名称,会作为表单字段 `id` 前缀使用 | string | - | |
|
||||
| preserve | 当字段被删除时保留字段值。你可以通过 `getFieldsValue(true)` 来获取保留字段值 | boolean | true | 4.4.0 |
|
||||
| requiredMark | 必选样式,可以切换为必选或者可选展示样式。此为 Form 配置,Form.Item 无法单独配置 | boolean \| `optional` \| ((label: ReactNode, info: { required: boolean }) => ReactNode) | true | `renderProps`: 5.9.0 |
|
||||
| scrollToFirstError | 提交失败自动滚动到第一个错误字段 | boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) | false | |
|
||||
| size | 设置字段组件的尺寸(仅限 antd 组件) | `small` \| `middle` \| `large` | - | |
|
||||
| validateMessages | 验证提示模板,说明[见下](#validatemessages) | [ValidateMessages](https://github.com/ant-design/ant-design/blob/6234509d18bac1ac60fbb3f92a5b2c6a6361295a/components/locale/en_US.ts#L88-L134) | - | |
|
||||
| validateTrigger | 统一设置字段触发验证的时机 | string \| string\[] | `onChange` | 4.3.0 |
|
||||
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](/components/grid-cn#col) | - | |
|
||||
| onFieldsChange | 字段更新时触发回调事件 | function(changedFields, allFields) | - | |
|
||||
| onFinish | 提交表单且数据验证成功后回调事件 | function(values) | - | |
|
||||
| onFinishFailed | 提交表单且数据验证失败后回调事件 | function({ values, errorFields, outOfDate }) | - | |
|
||||
| onValuesChange | 字段值更新时触发回调事件 | function(changedValues, allValues) | - | |
|
||||
|
||||
### validateMessages
|
||||
|
||||
Form 为验证提供了[默认的错误提示信息](https://github.com/ant-design/ant-design/blob/6234509d18bac1ac60fbb3f92a5b2c6a6361295a/components/locale/en_US.ts#L88-L134),你可以通过配置 `validateMessages` 属性,修改对应的提示模板。一种常见的使用方式,是配置国际化提示信息:
|
||||
|
||||
```js
|
||||
const validateMessages = {
|
||||
required: "'${name}' 是必选字段",
|
||||
// ...
|
||||
};
|
||||
|
||||
<Form validateMessages={validateMessages} />;
|
||||
```
|
||||
|
||||
此外,[ConfigProvider](/components/config-provider-cn) 也提供了全局化配置方案,允许统一配置错误提示模板:
|
||||
|
||||
```js
|
||||
const validateMessages = {
|
||||
required: "'${name}' 是必选字段",
|
||||
// ...
|
||||
};
|
||||
|
||||
<ConfigProvider form={{ validateMessages }}>
|
||||
<Form />
|
||||
</ConfigProvider>;
|
||||
```
|
||||
|
||||
## Form.Item
|
||||
|
||||
表单字段组件,用于数据双向绑定、校验、布局等。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| colon | 配合 `label` 属性使用,表示是否显示 `label` 后面的冒号 | boolean | true | |
|
||||
| dependencies | 设置依赖字段,说明[见下](#dependencies) | [NamePath](#namepath)\[] | - | |
|
||||
| extra | 额外的提示信息,和 `help` 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 | ReactNode | - | |
|
||||
| getValueFromEvent | 设置如何将 event 的值转换成字段值 | (..args: any\[]) => any | - | |
|
||||
| getValueProps | 为子元素添加额外的属性 (不建议通过 `getValueProps` 生成动态函数 prop,请直接将其传递给子组件) | (value: any) => Record<string, any> | - | 4.2.0 |
|
||||
| hasFeedback | 配合 `validateStatus` 属性使用,展示校验状态图标,建议只配合 Input 组件使用 此外,它还可以通过 Icons 属性获取反馈图标。 | boolean \| { icons: [FeedbackIcons](#feedbackicons) } | false | icons: 5.9.0 |
|
||||
| help | 提示信息,如不设置,则会根据校验规则自动生成 | ReactNode | - | |
|
||||
| hidden | 是否隐藏字段(依然会收集和校验字段) | boolean | false | 4.4.0 |
|
||||
| htmlFor | 设置子元素 label `htmlFor` 属性 | string | - | |
|
||||
| initialValue | 设置子元素默认值,如果与 Form 的 `initialValues` 冲突则以 Form 为准 | string | - | 4.2.0 |
|
||||
| label | `label` 标签的文本 | ReactNode | - | |
|
||||
| labelAlign | 标签文本对齐方式 | `left` \| `right` | `right` | |
|
||||
| labelCol | `label` 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}` 或 `sm: {span: 3, offset: 12}`。你可以通过 Form 的 `labelCol` 进行统一设置,不会作用于嵌套 Item。当和 Form 同时设置时,以 Item 为准 | [object](/components/grid-cn#col) | - | |
|
||||
| messageVariables | 默认验证字段的信息 | Record<string, string> | - | 4.7.0 |
|
||||
| name | 字段名,支持数组 | [NamePath](#namepath) | - | |
|
||||
| normalize | 组件获取值后进行转换,再放入 Form 中。不支持异步 | (value, prevValue, prevValues) => any | - | |
|
||||
| noStyle | 为 `true` 时不带样式,作为纯字段控件使用。当自身没有 `validateStatus` 而父元素存在有 `validateStatus` 的 Form.Item 会继承父元素的 `validateStatus` | boolean | false | |
|
||||
| preserve | 当字段被删除时保留字段值 | boolean | true | 4.4.0 |
|
||||
| required | 必填样式设置。如不设置,则会根据校验规则自动生成 | boolean | false | |
|
||||
| rules | 校验规则,设置字段的校验逻辑。点击[此处](#components-form-demo-basic)查看示例 | [Rule](#rule)\[] | - | |
|
||||
| shouldUpdate | 自定义字段更新逻辑,说明[见下](#shouldupdate) | boolean \| (prevValue, curValue) => boolean | false | |
|
||||
| tooltip | 配置提示信息 | ReactNode \| [TooltipProps & { icon: ReactNode }](/components/tooltip-cn#api) | - | 4.7.0 |
|
||||
| trigger | 设置收集字段值变更的时机。点击[此处](#components-form-demo-customized-form-controls)查看示例 | string | `onChange` | |
|
||||
| validateFirst | 当某一规则校验不通过时,是否停止剩下的规则的校验。设置 `parallel` 时会并行校验 | boolean \| `parallel` | false | `parallel`: 4.5.0 |
|
||||
| validateDebounce | 设置防抖,延迟毫秒数后进行校验 | number | - | 5.9.0 |
|
||||
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | - | |
|
||||
| validateTrigger | 设置字段校验的时机 | string \| string\[] | `onChange` | |
|
||||
| valuePropName | 子节点的值的属性,如 Switch、Checkbox 的是 `checked`。该属性为 `getValueProps` 的封装,自定义 `getValueProps` 后会失效 | string | `value` | |
|
||||
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 `labelCol`。你可以通过 Form 的 `wrapperCol` 进行统一设置,不会作用于嵌套 Item。当和 Form 同时设置时,以 Item 为准 | [object](/components/grid-cn#col) | - | |
|
||||
|
||||
被设置了 `name` 属性的 `Form.Item` 包装的控件,表单控件会自动添加 `value`(或 `valuePropName` 指定的其他属性) `onChange`(或 `trigger` 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:
|
||||
|
||||
1. 你**不再需要也不应该**用 `onChange` 来做数据收集同步(你可以使用 Form 的 `onValuesChange`),但还是可以继续监听 `onChange` 事件。
|
||||
2. 你不能用控件的 `value` 或 `defaultValue` 等属性来设置表单域的值,默认值可以用 Form 里的 `initialValues` 来设置。注意 `initialValues` 不能被 `setState` 动态更新,你需要用 `setFieldsValue` 来更新。
|
||||
3. 你不应该用 `setState`,可以使用 `form.setFieldsValue` 来动态改变表单值。
|
||||
|
||||
### dependencies
|
||||
|
||||
当字段间存在依赖关系时使用。如果一个字段设置了 `dependencies` 属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 `dependencies` 后,“密码”字段更新会重新触发“校验密码”的校验逻辑。你可以参考[具体例子](#components-form-demo-dependencies)。
|
||||
|
||||
`dependencies` 不应和 `shouldUpdate` 一起使用,因为这可能带来更新逻辑的混乱。
|
||||
|
||||
### FeedbackIcons
|
||||
|
||||
`({ status: ValidateStatus, errors: ReactNode, warnings: ReactNode }) => Record<ValidateStatus, ReactNode>`
|
||||
|
||||
### shouldUpdate
|
||||
|
||||
Form 通过增量更新方式,只更新被修改的字段相关组件以达到性能优化目的。大部分场景下,你只需要编写代码或者与 [`dependencies`](#dependencies) 属性配合校验即可。而在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过 `shouldUpdate` 修改 Form.Item 的更新逻辑。
|
||||
|
||||
当 `shouldUpdate` 为 `true` 时,Form 的任意变化都会使该 Form.Item 重新渲染。这对于自定义渲染一些区域十分有帮助,要注意 Form.Item 里包裹的子组件必须由函数返回,否则 `shouldUpdate` 不会起作用:
|
||||
|
||||
相关issue:[#34500](https://github.com/ant-design/ant-design/issues/34500)
|
||||
|
||||
```js
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>;
|
||||
}}
|
||||
</Form.Item>
|
||||
```
|
||||
|
||||
你可以参考[示例](#components-form-demo-horizontal-login)查看具体使用场景。
|
||||
|
||||
当 `shouldUpdate` 为方法时,表单的每次数值更新都会调用该方法,提供原先的值与当前的值以供你比较是否需要更新。这对于是否根据值来渲染额外字段十分有帮助:
|
||||
|
||||
```js
|
||||
<Form.Item shouldUpdate={(prevValues, curValues) => prevValues.additional !== curValues.additional}>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item name="other">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
```
|
||||
|
||||
你可以参考[示例](#components-form-demo-control-hooks)查看具体使用场景。
|
||||
|
||||
### messageVariables
|
||||
|
||||
你可以通过 `messageVariables` 修改 Form.Item 的默认验证信息。
|
||||
|
||||
```js
|
||||
<Form>
|
||||
<Form.Item
|
||||
messageVariables={{ another: 'good' }}
|
||||
label="user"
|
||||
rules={[{ required: true, message: '${another} is required' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
messageVariables={{ label: 'good' }}
|
||||
label={<span>user</span>}
|
||||
rules={[{ required: true, message: '${label} is required' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
```
|
||||
|
||||
## Form.List
|
||||
|
||||
为字段提供数组化管理。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| children | 渲染函数 | (fields: Field\[], operation: { add, remove, move }, meta: { errors }) => React.ReactNode | - | |
|
||||
| initialValue | 设置子元素默认值,如果与 Form 的 `initialValues` 冲突则以 Form 为准 | any\[] | - | 4.9.0 |
|
||||
| name | 字段名,支持数组。List 本身也是字段,因而 `getFieldsValue()` 默认会返回 List 下所有值,你可以通过[参数](#getfieldsvalue)改变这一行为 | [NamePath](#namepath) | - | |
|
||||
| rules | 校验规则,仅支持自定义规则。需要配合 [ErrorList](#formerrorlist) 一同使用。 | { validator, message }\[] | - | 4.7.0 |
|
||||
|
||||
```ts
|
||||
<Form.List>
|
||||
{(fields) =>
|
||||
fields.map((field) => (
|
||||
<Form.Item {...field}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
))
|
||||
}
|
||||
</Form.List>
|
||||
```
|
||||
|
||||
注意:Form.List 下的字段不应该配置 `initialValue`,你始终应该通过 Form.List 的 `initialValue` 或者 Form 的 `initialValues` 来配置。
|
||||
|
||||
## operation
|
||||
|
||||
Form.List 渲染表单相关操作函数。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| ------ | ---------- | -------------------------------------------------- | ----------- | ----- |
|
||||
| add | 新增表单项 | (defaultValue?: any, insertIndex?: number) => void | insertIndex | 4.6.0 |
|
||||
| move | 移动表单项 | (from: number, to: number) => void | - | |
|
||||
| remove | 删除表单项 | (index: number \| number\[]) => void | number\[] | 4.5.0 |
|
||||
|
||||
## Form.ErrorList
|
||||
|
||||
4.7.0 新增。错误展示组件,仅限配合 Form.List 的 rules 一同使用。参考[示例](#components-form-demo-dynamic-form-item)。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ------ | -------- | ------------ | ------ |
|
||||
| errors | 错误列表 | ReactNode\[] | - |
|
||||
|
||||
## Form.Provider
|
||||
|
||||
提供表单间联动功能,其下设置 `name` 的 Form 更新时,会自动触发对应事件。查看[示例](#components-form-demo-form-context)。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| onFormChange | 子表单字段更新时触发 | function(formName: string, info: { changedFields, forms }) | - |
|
||||
| onFormFinish | 子表单提交时触发 | function(formName: string, info: { values, forms }) | - |
|
||||
|
||||
```js
|
||||
<Form.Provider
|
||||
onFormFinish={(name) => {
|
||||
if (name === 'form1') {
|
||||
// Do something...
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form name="form1">...</Form>
|
||||
<Form name="form2">...</Form>
|
||||
</Form.Provider>
|
||||
```
|
||||
|
||||
### FormInstance
|
||||
|
||||
| 名称 | 说明 | 类型 | 版本 |
|
||||
| --- | --- | --- | --- |
|
||||
| getFieldError | 获取对应字段名的错误信息 | (name: [NamePath](#namepath)) => string\[] | |
|
||||
| getFieldInstance | 获取对应字段实例 | (name: [NamePath](#namepath)) => any | 4.4.0 |
|
||||
| getFieldsError | 获取一组字段名对应的错误信息,返回为数组形式 | (nameList?: [NamePath](#namepath)\[]) => FieldError\[] | |
|
||||
| getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值,当调用 `getFieldsValue(true)` 时返回所有值 | [GetFieldsValue](#getfieldsvalue) | |
|
||||
| getFieldValue | 获取对应字段名的值 | (name: [NamePath](#namepath)) => any | |
|
||||
| isFieldsTouched | 检查一组字段是否被用户操作过,`allTouched` 为 `true` 时检查是否所有字段都被操作过 | (nameList?: [NamePath](#namepath)\[], allTouched?: boolean) => boolean | |
|
||||
| isFieldTouched | 检查对应字段是否被用户操作过 | (name: [NamePath](#namepath)) => boolean | |
|
||||
| isFieldValidating | 检查对应字段是否正在校验 | (name: [NamePath](#namepath)) => boolean | |
|
||||
| resetFields | 重置一组字段到 `initialValues` | (fields?: [NamePath](#namepath)\[]) => void | |
|
||||
| scrollToField | 滚动到对应字段位置 | (name: [NamePath](#namepath), options: [ScrollOptions](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)) => void | |
|
||||
| setFields | 设置一组字段状态 | (fields: [FieldData](#fielddata)\[]) => void | |
|
||||
| setFieldValue | 设置表单的值(该值将直接传入 form store 中并且**重置错误信息**。如果你不希望传入对象被修改,请克隆后传入) | (name: [NamePath](#namepath), value: any) => void | 4.22.0 |
|
||||
| setFieldsValue | 设置表单的值(该值将直接传入 form store 中并且**重置错误信息**。如果你不希望传入对象被修改,请克隆后传入)。如果你只想修改 Form.List 中单项值,请通过 `setFieldValue` 进行指定 | (values) => void | |
|
||||
| submit | 提交表单,与点击 `submit` 按钮效果相同 | () => void | |
|
||||
| validateFields | 触发表单验证,设置 `recursive` 时会递归校验所有包含的路径 | (nameList?: [NamePath](#namepath)\[], config?: [ValidateConfig](#validateFields)) => Promise | |
|
||||
|
||||
#### validateFields
|
||||
|
||||
```ts
|
||||
export interface ValidateConfig {
|
||||
// 5.5.0 新增。仅校验内容而不会将错误信息展示到 UI 上。
|
||||
validateOnly?: boolean;
|
||||
// 5.9.0 新增。对提供的 `nameList` 与其子路径进行递归校验。
|
||||
recursive?: boolean;
|
||||
// 5.11.0 新增。校验 dirty 的字段(touched + validated)。
|
||||
// 使用 `dirty` 可以很方便的仅校验用户操作过和被校验过的字段。
|
||||
dirty?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
返回示例:
|
||||
|
||||
```js
|
||||
validateFields()
|
||||
.then((values) => {
|
||||
/*
|
||||
values:
|
||||
{
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
}
|
||||
*/
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
/*
|
||||
errorInfo:
|
||||
{
|
||||
values: {
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
},
|
||||
errorFields: [
|
||||
{ name: ['password'], errors: ['Please input your Password!'] },
|
||||
],
|
||||
outOfDate: false,
|
||||
}
|
||||
*/
|
||||
});
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
### Form.useForm
|
||||
|
||||
`type Form.useForm = (): [FormInstance]`
|
||||
|
||||
创建 Form 实例,用于管理所有数据状态。
|
||||
|
||||
### Form.useFormInstance
|
||||
|
||||
`type Form.useFormInstance = (): FormInstance`
|
||||
|
||||
`4.20.0` 新增,获取当前上下文正在使用的 Form 实例,常见于封装子组件消费无需透传 Form 实例:
|
||||
|
||||
```ts
|
||||
const Sub = () => {
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
return <Button onClick={() => form.setFieldsValue({})} />;
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<Sub />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Form.useWatch
|
||||
|
||||
`type Form.useWatch = (namePath: NamePath | (selector: (values: Store)) => any, formInstance?: FormInstance | WatchOptions): Value`
|
||||
|
||||
`5.12.0` 新增 `selector`
|
||||
|
||||
用于直接获取 form 中字段对应的值。通过该 Hooks 可以与诸如 `useSWR` 进行联动从而降低维护成本:
|
||||
|
||||
```ts
|
||||
const Demo = () => {
|
||||
const [form] = Form.useForm();
|
||||
const userName = Form.useWatch('username', form);
|
||||
|
||||
const { data: options } = useSWR(`/api/user/${userName}`, fetcher);
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<Form.Item name="username">
|
||||
<AutoComplete options={options} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
如果你的组件被包裹在 `Form.Item` 内部,你可以省略第二个参数,`Form.useWatch` 会自动找到上层最近的 `FormInstance`。
|
||||
|
||||
`useWatch` 默认只监听在 Form 中注册的字段,如果需要监听非注册字段,可以通过配置 `preserve` 进行监听:
|
||||
|
||||
```ts
|
||||
const Demo = () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const age = Form.useWatch('age', { form, preserve: true });
|
||||
console.log(age);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => form.setFieldValue('age', 2)}>Update</Button>
|
||||
<Form form={form}>
|
||||
<Form.Item name="name">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Form.Item.useStatus
|
||||
|
||||
`type Form.Item.useStatus = (): { status: ValidateStatus | undefined, errors: ReactNode[], warnings: ReactNode[] }`
|
||||
|
||||
`4.22.0` 新增,可用于获取当前 Form.Item 的校验状态,如果上层没有 Form.Item,`status` 将会返回 `undefined`。`5.4.0` 新增 `errors` 和 `warnings`,可用于获取当前 Form.Item 的错误信息和警告信息:
|
||||
|
||||
```ts
|
||||
const CustomInput = ({ value, onChange }) => {
|
||||
const { status, errors } = Form.Item.useStatus();
|
||||
return (
|
||||
<input
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={`custom-input-${status}`}
|
||||
placeholder={(errors.length && errors[0]) || ''}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default () => (
|
||||
<Form>
|
||||
<Form.Item name="username">
|
||||
<CustomInput />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
```
|
||||
|
||||
#### 与其他获取数据的方式的区别
|
||||
|
||||
Form 仅会对变更的 Field 进行刷新,从而避免完整的组件刷新可能引发的性能问题。因而你无法在 render 阶段通过 `form.getFieldsValue` 来实时获取字段值,而 `useWatch` 提供了一种特定字段访问的方式,从而使得在当前组件中可以直接消费字段的值。同时,如果为了更好的渲染性能,你可以通过 Field 的 renderProps 仅更新需要更新的部分。而当当前组件更新或者 effect 都不需要消费字段值时,则可以通过 `onValuesChange` 将数据抛出,从而避免组件更新。
|
||||
|
||||
### Interface
|
||||
|
||||
#### NamePath
|
||||
|
||||
`string | number | (string | number)[]`
|
||||
|
||||
#### GetFieldsValue
|
||||
|
||||
`getFieldsValue` 提供了多种重载方法:
|
||||
|
||||
##### getFieldsValue(nameList?: true | [NamePath](#namepath)\[], filterFunc?: FilterFunc)
|
||||
|
||||
当不提供 `nameList` 时,返回所有注册字段,这也包含 List 下所有的值(即便 List 下没有绑定 Item)。
|
||||
|
||||
当 `nameList` 为 `true` 时,返回 store 中所有的值,包含未注册字段。例如通过 `setFieldsValue` 设置了不存在的 Item 的值,也可以通过 `true` 全部获取。
|
||||
|
||||
当 `nameList` 为数组时,返回规定路径的值。需要注意的是,`nameList` 为嵌套数组。例如你需要某路径值应该如下:
|
||||
|
||||
```ts
|
||||
// 单个路径
|
||||
form.getFieldsValue([['user', 'age']]);
|
||||
|
||||
// 多个路径
|
||||
form.getFieldsValue([
|
||||
['user', 'age'],
|
||||
['preset', 'account'],
|
||||
]);
|
||||
```
|
||||
|
||||
##### getFieldsValue({ strict?: boolean, filter?: FilterFunc })
|
||||
|
||||
`5.8.0` 新增接受配置参数。当 `strict` 为 `true` 时会仅匹配 Item 的值。例如 `{ list: [{ bamboo: 1, little: 2 }] }` 中,如果 List 仅绑定了 `bamboo` 字段,那么 `getFieldsValue({ strict: true })` 会只获得 `{ list: [{ bamboo: 1 }] }`。
|
||||
|
||||
#### FilterFunc
|
||||
|
||||
用于过滤一些字段值,`meta` 会返回字段相关信息。例如可以用来获取仅被用户修改过的值等等。
|
||||
|
||||
```ts
|
||||
type FilterFunc = (meta: { touched: boolean; validating: boolean }) => boolean;
|
||||
```
|
||||
|
||||
#### FieldData
|
||||
|
||||
| 名称 | 说明 | 类型 |
|
||||
| ---------- | ---------------- | ------------------------ |
|
||||
| errors | 错误信息 | string\[] |
|
||||
| warnings | 警告信息 | string\[] |
|
||||
| name | 字段名称 | [NamePath](#namepath)\[] |
|
||||
| touched | 是否被用户操作过 | boolean |
|
||||
| validating | 是否正在校验 | boolean |
|
||||
| value | 字段对应值 | any |
|
||||
|
||||
#### Rule
|
||||
|
||||
Rule 支持接收 object 进行配置,也支持 function 来动态获取 form 的数据:
|
||||
|
||||
```ts
|
||||
type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
|
||||
```
|
||||
|
||||
| 名称 | 说明 | 类型 | 版本 |
|
||||
| --- | --- | --- | --- |
|
||||
| defaultField | 仅在 `type` 为 `array` 类型时有效,用于指定数组元素的校验规则 | [rule](#rule) | |
|
||||
| enum | 是否匹配枚举中的值(需要将 `type` 设置为 `enum`) | any\[] | |
|
||||
| fields | 仅在 `type` 为 `array` 或 `object` 类型时有效,用于指定子元素的校验规则 | Record<string, [rule](#rule)> | |
|
||||
| len | string 类型时为字符串长度;number 类型时为确定数字; array 类型时为数组长度 | number | |
|
||||
| max | 必须设置 `type`:string 类型为字符串最大长度;number 类型时为最大值;array 类型时为数组最大长度 | number | |
|
||||
| message | 错误信息,不设置时会通过[模板](#validatemessages)自动生成 | string | |
|
||||
| min | 必须设置 `type`:string 类型为字符串最小长度;number 类型时为最小值;array 类型时为数组最小长度 | number | |
|
||||
| pattern | 正则表达式匹配 | RegExp | |
|
||||
| required | 是否为必选字段 | boolean | |
|
||||
| transform | 将字段值转换成目标值后进行校验 | (value) => any | |
|
||||
| type | 类型,常见有 `string` \|`number` \|`boolean` \|`url` \| `email`。更多请参考[此处](https://github.com/yiminghe/async-validator#type) | string | |
|
||||
| validateTrigger | 设置触发验证时机,必须是 Form.Item 的 `validateTrigger` 的子集 | string \| string\[] | |
|
||||
| validator | 自定义校验,接收 Promise 作为返回值。[示例](#components-form-demo-register)参考 | ([rule](#rule), value) => Promise | |
|
||||
| warningOnly | 仅警告,不阻塞表单提交 | boolean | 4.17.0 |
|
||||
| whitespace | 如果字段仅包含空格则校验不通过,只在 `type: 'string'` 时生效 | boolean | |
|
||||
|
||||
#### WatchOptions
|
||||
|
||||
| 名称 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| -------- | ------------------------------------- | ------------ | ---------------------- | ----- |
|
||||
| form | 指定 Form 实例 | FormInstance | 当前 context 中的 Form | 5.4.0 |
|
||||
| preserve | 是否监视没有对应的 `Form.Item` 的字段 | boolean | false | 5.4.0 |
|
||||
|
||||
## 主题变量(Design Token)
|
||||
|
||||
<ComponentTokenTable component="Form"></ComponentTokenTable>
|
||||
|
||||
## FAQ
|
||||
|
||||
### Switch、Checkbox 为什么不能绑定数据?
|
||||
|
||||
Form.Item 默认绑定值属性到 `value` 上,而 Switch、Checkbox 等组件的值属性为 `checked`。你可以通过 `valuePropName` 来修改绑定的值属性。
|
||||
|
||||
```ts | pure
|
||||
<Form.Item name="fieldA" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
```
|
||||
|
||||
### name 为数组时的转换规则?
|
||||
|
||||
当 `name` 为数组时,会按照顺序填充路径。当存在数字且 form store 中没有该字段时会自动转变成数组。因而如果需要数组为 key 时请使用 string 如:`['1', 'name']`。
|
||||
|
||||
### 为何在 Modal 中调用 form 控制台会报错?
|
||||
|
||||
> Warning: Instance created by `useForm` is not connect to any Form element. Forget to pass `form` prop?
|
||||
|
||||
这是因为你在调用 form 方法时,Modal 还未初始化导致 form 没有关联任何 Form 组件。你可以通过给 Modal 设置 `forceRender` 将其预渲染。示例点击[此处](https://codesandbox.io/s/antd-reproduction-template-ibu5c)。
|
||||
|
||||
### 为什么 Form.Item 下的子组件 `defaultValue` 不生效?
|
||||
|
||||
当你为 Form.Item 设置 `name` 属性后,子组件会转为受控模式。因而 `defaultValue` 不会生效。你需要在 Form 上通过 `initialValues` 设置默认值。
|
||||
|
||||
### 为什么第一次调用 `ref` 的 Form 为空?
|
||||
|
||||
`ref` 仅在节点被加载时才会被赋值,请参考 React 官方文档:<https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs>
|
||||
|
||||
### 为什么 `resetFields` 会重新 mount 组件?
|
||||
|
||||
`resetFields` 会重置整个 Field,因而其子组件也会重新 mount 从而消除自定义组件可能存在的副作用(例如异步数据、状态等等)。
|
||||
|
||||
### Form 的 initialValues 与 Item 的 initialValue 区别?
|
||||
|
||||
在大部分场景下,我们总是推荐优先使用 Form 的 `initialValues`。只有存在动态字段时你才应该使用 Item 的 `initialValue`。默认值遵循以下规则:
|
||||
|
||||
1. Form 的 `initialValues` 拥有最高优先级
|
||||
2. Field 的 `initialValue` 次之 \*. 多个同 `name` Item 都设置 `initialValue` 时,则 Item 的 `initialValue` 不生效
|
||||
|
||||
### 为什么 `getFieldsValue` 在初次渲染的时候拿不到值?
|
||||
|
||||
`getFieldsValue` 默认返回收集的字段数据,而在初次渲染时 Form.Item 节点尚未渲染,因而无法收集到数据。你可以通过 `getFieldsValue(true)` 来获取所有字段数据。
|
||||
|
||||
### 为什么 `setFieldsValue` 设置字段为 `undefined` 时,有的组件不会重置为空?
|
||||
|
||||
在 React 中,`value` 从确定值改为 `undefined` 表示从受控变为非受控,因而不会重置展示值(但是 Form 中的值确实已经改变)。你可以通过 HOC 改变这一逻辑:
|
||||
|
||||
```js
|
||||
const MyInput = ({
|
||||
// 强制保持受控逻辑
|
||||
value = '',
|
||||
...rest
|
||||
}) => <input value={value} {...rest} />;
|
||||
|
||||
<Form.Item name="my">
|
||||
<MyInput />
|
||||
</Form.Item>;
|
||||
```
|
||||
|
||||
### 为什么字段设置 `rules` 后更改值 `onFieldsChange` 会触发三次?
|
||||
|
||||
字段除了本身的值变化外,校验也是其状态之一。因而在触发字段变化会经历以下几个阶段:
|
||||
|
||||
1. Trigger value change
|
||||
2. Rule validating
|
||||
3. Rule validated
|
||||
|
||||
在触发过程中,调用 `isFieldValidating` 会经历 `false` > `true` > `false` 的变化过程。
|
||||
|
||||
### 为什么 Form.List 不支持 `label` 还需要使用 ErrorList 展示错误?
|
||||
|
||||
Form.List 本身是 renderProps,内部样式非常自由。因而默认配置 `label` 和 `error` 节点很难与之配合。如果你需要 antd 样式的 `label`,可以通过外部包裹 Form.Item 来实现。
|
||||
|
||||
### 为什么 Form.Item 的 `dependencies` 对 Form.List 下的字段没有效果?
|
||||
|
||||
Form.List 下的字段需要包裹 Form.List 本身的 `name`,比如:
|
||||
|
||||
```ts
|
||||
<Form.List name="users">
|
||||
{(fields) =>
|
||||
fields.map((field) => (
|
||||
<React.Fragment key={field.key}>
|
||||
<Form.Item name={[field.name, 'name']} {...someRest1} />
|
||||
<Form.Item name={[field.name, 'age']} {...someRest1} />
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
</Form.List>
|
||||
```
|
||||
|
||||
依赖则是:`['users', 0, 'name']`
|
||||
|
||||
### 为什么 `normalize` 不能是异步方法?
|
||||
|
||||
React 中异步更新会导致受控组件交互行为异常。当用户交互触发 `onChange` 后,通过异步改变值会导致组件 `value` 不会立刻更新,使得组件呈现假死状态。如果你需要异步触发变更,请通过自定义组件实现内部异步状态。
|
||||
|
||||
<style>
|
||||
.site-form-item-icon {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
</style>
|
||||
|
||||
### `scrollToFirstError` 和 `scrollToField` 失效?
|
||||
|
||||
1. 使用了自定义表单控件
|
||||
|
||||
类似问题:[#28370](https://github.com/ant-design/ant-design/issues/28370) [#27994](https://github.com/ant-design/ant-design/issues/27994)
|
||||
|
||||
滚动依赖于表单控件元素上绑定的 `id` 字段,如果自定义控件没有将 `id` 赋到正确的元素上,这个功能将失效。你可以参考这个 [codesandbox](https://codesandbox.io/s/antd-reproduction-template-forked-25nul?file=/index.js)。
|
||||
|
||||
2. 页面内有多个表单
|
||||
|
||||
页面内如果有多个表单,且存在表单项 `name` 重复,表单滚动定位可能会查找到另一个表单的同名表单项上。需要给表单 `Form` 组件设置不同的 `name` 以区分。
|
||||
|
||||
### 继上,为何不通过 `ref` 绑定元素?
|
||||
|
||||
当自定义组件不支持 `ref` 时,Form 无法获取子元素真实 DOM 节点,而通过包裹 Class Component 调用 `findDOMNode` 会在 React Strict Mode 下触发警告。因而我们使用 id 来进行元素定位。
|
||||
|
||||
### `setFieldsValue` 不会触发 `onFieldsChange` 和 `onValuesChange`?
|
||||
|
||||
是的,change 事件仅当用户交互才会触发。该设计是为了防止在 change 事件中调用 `setFieldsValue` 导致的循环问题。如果仅仅需要组件内消费,可以通过 `useWatch` 或者 `Field.renderProps` 来实现。
|
||||
|
||||
### 为什么 Form.Item 嵌套子组件后,不更新表单值?
|
||||
|
||||
Form.Item 在渲染时会注入 `value` 与 `onChange` 事件给子元素,当你的字段组件被包裹时属性将无法传递。所以以下代码是不会生效的:
|
||||
|
||||
```js
|
||||
<Form.Item name="input">
|
||||
<div>
|
||||
<h3>I am a wrapped Input</h3>
|
||||
<Input />
|
||||
</div>
|
||||
</Form.Item>
|
||||
```
|
||||
|
||||
你可以通过 HOC 自定义组件形式来解决这个问题:
|
||||
|
||||
```js
|
||||
const MyInput = (props) => (
|
||||
<div>
|
||||
<h3>I am a wrapped Input</h3>
|
||||
<Input {...props} />
|
||||
</div>
|
||||
);
|
||||
|
||||
<Form.Item name="input">
|
||||
<MyInput />
|
||||
</Form.Item>;
|
||||
```
|
||||
|
||||
### 有更多参考文档吗?
|
||||
|
||||
- 你可以阅读[《antd v4 Form 使用心得》](https://zhuanlan.zhihu.com/p/375753910)获得一些使用帮助以及建议。
|
||||
- 想在 DatePicker、Switch 也使用 before、after?可以参考[《如何优雅的对 Form.Item 的 children 增加 before、after》](https://zhuanlan.zhihu.com/p/422752055)。
|
||||
- 优雅的 Form + Modal 结合使用方案[《如何优雅的使用 Form + Modal》](https://zhuanlan.zhihu.com/p/388222294)。
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user