mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat: 补充 ConfirmBox ui 控件, 并将 PickerContainer 改成 ConfirmBox 实现 (#5708)
* publish beta * feat: 添加 ui ConfirmBox * feat: 补充 confirmBox ui 控件, 并将 pickerContainer 改成 confirmBox 实现 * PickerContainer title 逻辑不变动 * 暴露 InputTableColumnProps * 调整 ts 定义 * 升级 react-hook-form * inputTable 补充数组本身的验证 * Combo 也支持内部数组的验证 * 调整内部验证 * 调整目录
This commit is contained in:
parent
860c57eb0e
commit
723c6bf4eb
@ -463,6 +463,8 @@ export const validateMessages: {
|
|||||||
matchRegexp: 'validate.matchRegexp',
|
matchRegexp: 'validate.matchRegexp',
|
||||||
minLength: 'validate.minLength',
|
minLength: 'validate.minLength',
|
||||||
maxLength: 'validate.maxLength',
|
maxLength: 'validate.maxLength',
|
||||||
|
minLengthArray: 'validate.array.minLength',
|
||||||
|
maxLengthArray: 'validate.array.maxLength',
|
||||||
maximum: 'validate.maximum',
|
maximum: 'validate.maximum',
|
||||||
lt: 'validate.lt',
|
lt: 'validate.lt',
|
||||||
minimum: 'validate.minimum',
|
minimum: 'validate.minimum',
|
||||||
@ -525,10 +527,19 @@ export function validate(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!fn(values, value, ...args)) {
|
if (!fn(values, value, ...args)) {
|
||||||
|
let msgRuleName = ruleName;
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
msgRuleName = `${ruleName}Array`;
|
||||||
|
}
|
||||||
|
|
||||||
errors.push({
|
errors.push({
|
||||||
rule: ruleName,
|
rule: ruleName,
|
||||||
msg: filter(
|
msg: filter(
|
||||||
__((messages && messages[ruleName]) || validateMessages[ruleName]),
|
__(
|
||||||
|
(messages && messages[ruleName]) ||
|
||||||
|
validateMessages[msgRuleName] ||
|
||||||
|
validateMessages[ruleName]
|
||||||
|
),
|
||||||
{
|
{
|
||||||
...[''].concat(args)
|
...[''].concat(args)
|
||||||
}
|
}
|
||||||
|
206
packages/amis-ui/examples/App.tsx
Normal file
206
packages/amis-ui/examples/App.tsx
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Layout, AsideNav, Spinner, NotFound} from 'amis-ui';
|
||||||
|
import {eachTree, TreeArray, TreeItem} from 'amis-core';
|
||||||
|
import {
|
||||||
|
HashRouter as Router,
|
||||||
|
Route,
|
||||||
|
Redirect,
|
||||||
|
Switch,
|
||||||
|
Link,
|
||||||
|
NavLink
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
const pages: TreeArray = [
|
||||||
|
{
|
||||||
|
label: '常规',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: '按钮',
|
||||||
|
path: '/basic/button',
|
||||||
|
component: React.lazy(() => import('./basic/Button'))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: '表单',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'InputTable',
|
||||||
|
path: '/form/input-table',
|
||||||
|
component: React.lazy(() => import('./form/InputTable'))
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: 'Combo',
|
||||||
|
path: '/form/combo',
|
||||||
|
component: React.lazy(() => import('./form/Combo'))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: '弹框',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'PickContainer',
|
||||||
|
path: '/modal/pick-conatiner',
|
||||||
|
component: React.lazy(() => import('./modal/PickerContainer'))
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: 'ConfirmBox',
|
||||||
|
path: '/modal/confirm-box',
|
||||||
|
component: React.lazy(() => import('./modal/ConfirmBox'))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function getPath(path: string) {
|
||||||
|
return path ? (path[0] === '/' ? path : `/${path}`) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActive(link: any, location: any) {
|
||||||
|
return !!(link.path && getPath(link.path) === location.pathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function navigations2route(
|
||||||
|
navigations: any,
|
||||||
|
additionalProperties?: any
|
||||||
|
) {
|
||||||
|
let routes: any = [];
|
||||||
|
|
||||||
|
navigations.forEach((root: any) => {
|
||||||
|
root.children &&
|
||||||
|
eachTree(root.children, (item: any) => {
|
||||||
|
if (item.path && item.component) {
|
||||||
|
routes.push(
|
||||||
|
additionalProperties ? (
|
||||||
|
<Route
|
||||||
|
key={routes.length + 1}
|
||||||
|
path={item.path[0] === '/' ? item.path : `/${item.path}`}
|
||||||
|
render={(props: any) => (
|
||||||
|
<item.component {...additionalProperties} {...props} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Route
|
||||||
|
key={routes.length + 1}
|
||||||
|
path={item.path[0] === '/' ? item.path : `/${item.path}`}
|
||||||
|
component={item.component}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
function renderAside() {
|
||||||
|
return (
|
||||||
|
<AsideNav
|
||||||
|
navigations={pages.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
children: item.children
|
||||||
|
? item.children.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
className: 'is-top'
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}))}
|
||||||
|
renderLink={({
|
||||||
|
link,
|
||||||
|
active,
|
||||||
|
toggleExpand,
|
||||||
|
classnames: cx,
|
||||||
|
depth
|
||||||
|
}: any) => {
|
||||||
|
let children = [];
|
||||||
|
|
||||||
|
if (link.children && link.children.length) {
|
||||||
|
children.push(
|
||||||
|
<span
|
||||||
|
key="expand-toggle"
|
||||||
|
className={cx('AsideNav-itemArrow')}
|
||||||
|
onClick={e => toggleExpand(link, e)}
|
||||||
|
></span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
link.badge &&
|
||||||
|
children.push(
|
||||||
|
<b
|
||||||
|
key="badge"
|
||||||
|
className={cx(
|
||||||
|
`AsideNav-itemBadge`,
|
||||||
|
link.badgeClassName || 'bg-info'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{link.badge}
|
||||||
|
</b>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (link.icon) {
|
||||||
|
children.push(
|
||||||
|
<i key="icon" className={cx(`AsideNav-itemIcon`, link.icon)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
children.push(
|
||||||
|
<span className={cx('AsideNav-itemLabel')} key="label">
|
||||||
|
{link.label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
return link.path ? (
|
||||||
|
/^https?\:/.test(link.path) ? (
|
||||||
|
<a target="_blank" href={link.path} rel="noopener">
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
to={
|
||||||
|
getPath(link.path) ||
|
||||||
|
(link.children && getPath(link.children[0].path))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<a onClick={link.children ? () => toggleExpand(link) : undefined}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isActive={(link: any) => isActive(link, location)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<Layout
|
||||||
|
header={
|
||||||
|
<div id="headerBar" className="box-shadow bg-dark">
|
||||||
|
<div className={`cxd-Layout-brand`}>amis-ui 示例</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
aside={renderAside()}
|
||||||
|
>
|
||||||
|
<React.Suspense
|
||||||
|
fallback={<Spinner overlay spinnerClassName="m-t-lg" size="lg" />}
|
||||||
|
>
|
||||||
|
<Switch>
|
||||||
|
{navigations2route(pages)}
|
||||||
|
<Route render={() => <NotFound description="Not found" />} />
|
||||||
|
</Switch>
|
||||||
|
</React.Suspense>
|
||||||
|
</Layout>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
11
packages/amis-ui/examples/basic/Button.tsx
Normal file
11
packages/amis-ui/examples/basic/Button.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Button} from 'amis-ui';
|
||||||
|
|
||||||
|
export default function ButtonExamples() {
|
||||||
|
return (
|
||||||
|
<div className="wrapper">
|
||||||
|
<p>示例: </p>
|
||||||
|
<Button>Button</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
59
packages/amis-ui/examples/form/Combo.tsx
Normal file
59
packages/amis-ui/examples/form/Combo.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Button, Combo, Form, Controller, InputBox} from 'amis-ui';
|
||||||
|
|
||||||
|
export default function ButtonExamples() {
|
||||||
|
const handleSubmit = React.useCallback((data: any) => {
|
||||||
|
console.log('submit', data);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="wrapper">
|
||||||
|
<Form defaultValues={{items: [{a: 1, b: 2}]}} onSubmit={handleSubmit}>
|
||||||
|
{({control, onSubmit}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Combo
|
||||||
|
name="items"
|
||||||
|
control={control}
|
||||||
|
minLength={2}
|
||||||
|
maxLength={5}
|
||||||
|
itemRender={({control}) => (
|
||||||
|
<>
|
||||||
|
<Controller
|
||||||
|
name="key"
|
||||||
|
control={control}
|
||||||
|
isRequired
|
||||||
|
render={({field, fieldState}) => (
|
||||||
|
<InputBox
|
||||||
|
{...field}
|
||||||
|
placeholder="Key"
|
||||||
|
hasError={!!fieldState.error}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="title"
|
||||||
|
control={control}
|
||||||
|
isRequired
|
||||||
|
render={({field, fieldState}) => (
|
||||||
|
<InputBox
|
||||||
|
{...field}
|
||||||
|
placeholder="Title"
|
||||||
|
hasError={!!fieldState.error}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button onClick={onSubmit}>Submit</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
50
packages/amis-ui/examples/form/InputTable.tsx
Normal file
50
packages/amis-ui/examples/form/InputTable.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Button, InputTable, Form, Controller, InputBox} from 'amis-ui';
|
||||||
|
|
||||||
|
export default function ButtonExamples() {
|
||||||
|
const handleSubmit = React.useCallback((data: any) => {
|
||||||
|
console.log('submit', data);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="wrapper">
|
||||||
|
<Form defaultValues={{items: [{a: 1, b: 2}]}} onSubmit={handleSubmit}>
|
||||||
|
{({control, onSubmit}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InputTable
|
||||||
|
name="items"
|
||||||
|
control={control}
|
||||||
|
minLength={2}
|
||||||
|
maxLength={5}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
tdRender: ({control}) => {
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name="key"
|
||||||
|
control={control}
|
||||||
|
isRequired
|
||||||
|
render={({field, fieldState}) => (
|
||||||
|
<InputBox
|
||||||
|
{...field}
|
||||||
|
placeholder="Key"
|
||||||
|
hasError={!!fieldState.error}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Button onClick={onSubmit}>Submit</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
73
packages/amis-ui/examples/modal/ConfirmBox.tsx
Normal file
73
packages/amis-ui/examples/modal/ConfirmBox.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Button, ConfirmBox, Controller, Form, InputBox} from 'amis-ui';
|
||||||
|
|
||||||
|
export default function ButtonExamples() {
|
||||||
|
const [isShow, setIsShow] = React.useState(false);
|
||||||
|
const handleClick = React.useCallback(() => {
|
||||||
|
setIsShow(!isShow);
|
||||||
|
}, [isShow]);
|
||||||
|
const handleCancel = React.useCallback(() => {
|
||||||
|
setIsShow(false);
|
||||||
|
}, []);
|
||||||
|
// const beforeConfirm = React.useCallback(async () => {
|
||||||
|
// return false;
|
||||||
|
// }, []);
|
||||||
|
const handleConfirm = React.useCallback((data: any) => {
|
||||||
|
console.log('confirmed', data);
|
||||||
|
setIsShow(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="wrapper">
|
||||||
|
<Button onClick={handleClick}>Open</Button>
|
||||||
|
<ConfirmBox
|
||||||
|
type="drawer"
|
||||||
|
size="md"
|
||||||
|
position="bottom"
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
show={isShow}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
>
|
||||||
|
{({bodyRef}) => (
|
||||||
|
<Form ref={bodyRef}>
|
||||||
|
{({control}) => (
|
||||||
|
<>
|
||||||
|
<Controller
|
||||||
|
mode="horizontal"
|
||||||
|
label="A"
|
||||||
|
name="a"
|
||||||
|
control={control}
|
||||||
|
rules={{maxLength: 20}}
|
||||||
|
isRequired
|
||||||
|
render={({field, fieldState}) => (
|
||||||
|
<InputBox
|
||||||
|
{...field}
|
||||||
|
hasError={!!fieldState.error}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
mode="horizontal"
|
||||||
|
label="B"
|
||||||
|
name="b"
|
||||||
|
control={control}
|
||||||
|
rules={{maxLength: 20}}
|
||||||
|
isRequired
|
||||||
|
render={({field, fieldState}) => (
|
||||||
|
<InputBox
|
||||||
|
{...field}
|
||||||
|
hasError={!!fieldState.error}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</ConfirmBox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
66
packages/amis-ui/examples/modal/PickerContainer.tsx
Normal file
66
packages/amis-ui/examples/modal/PickerContainer.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {PickerContainer, Button, Form, Controller, InputBox} from 'amis-ui';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const body = React.createRef<any>();
|
||||||
|
const beforeConfirm = React.useCallback(() => {
|
||||||
|
return body.current?.submit();
|
||||||
|
}, []);
|
||||||
|
const handleConfirm = React.useCallback((data: any) => {
|
||||||
|
console.log('confirmed', data);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="wrapper">
|
||||||
|
<PickerContainer
|
||||||
|
beforeConfirm={beforeConfirm}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
bodyRender={() => (
|
||||||
|
<Form ref={body}>
|
||||||
|
{({control}) => (
|
||||||
|
<>
|
||||||
|
<Controller
|
||||||
|
mode="horizontal"
|
||||||
|
label="A"
|
||||||
|
name="a"
|
||||||
|
control={control}
|
||||||
|
rules={{maxLength: 20}}
|
||||||
|
isRequired
|
||||||
|
render={({field, fieldState}) => (
|
||||||
|
<InputBox
|
||||||
|
{...field}
|
||||||
|
hasError={!!fieldState.error}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
mode="horizontal"
|
||||||
|
label="B"
|
||||||
|
name="b"
|
||||||
|
control={control}
|
||||||
|
rules={{maxLength: 20}}
|
||||||
|
isRequired
|
||||||
|
render={({field, fieldState}) => (
|
||||||
|
<InputBox
|
||||||
|
{...field}
|
||||||
|
hasError={!!fieldState.error}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{({isOpened, onClick}) => (
|
||||||
|
<Button active={isOpened} onClick={onClick}>
|
||||||
|
Open
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</PickerContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
74
packages/amis-ui/index.html
Normal file
74
packages/amis-ui/index.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>amis-ui</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||||
|
/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||||
|
<link rel="stylesheet" href="../../examples/static/iconfont.css" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="../../node_modules/@fortawesome/fontawesome-free/css/all.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="../../node_modules/@fortawesome/fontawesome-free/css/v4-shims.css"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" href="./scss/themes/cxd.scss" />
|
||||||
|
<link rel="stylesheet" href="./scss/helper.scss" />
|
||||||
|
<style>
|
||||||
|
.app-wrapper,
|
||||||
|
.schema-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root" class="app-wrapper"></div>
|
||||||
|
<script type="module">
|
||||||
|
import React from 'react';
|
||||||
|
import {createRoot} from 'react-dom/client';
|
||||||
|
import App from './examples/App';
|
||||||
|
|
||||||
|
export function bootstrap(mountTo, initalState) {
|
||||||
|
const root = createRoot(mountTo);
|
||||||
|
root.render(React.createElement(App));
|
||||||
|
}
|
||||||
|
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||||
|
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||||
|
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
|
||||||
|
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
|
||||||
|
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
||||||
|
|
||||||
|
self.MonacoEnvironment = {
|
||||||
|
getWorker(_, label) {
|
||||||
|
if (label === 'json') {
|
||||||
|
return new jsonWorker();
|
||||||
|
}
|
||||||
|
if (label === 'css' || label === 'scss' || label === 'less') {
|
||||||
|
return new cssWorker();
|
||||||
|
}
|
||||||
|
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||||
|
return new htmlWorker();
|
||||||
|
}
|
||||||
|
if (label === 'typescript' || label === 'javascript') {
|
||||||
|
return new tsWorker();
|
||||||
|
}
|
||||||
|
return new editorWorker();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState = {};
|
||||||
|
bootstrap(document.getElementById('root'), initialState);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -57,7 +57,7 @@
|
|||||||
"rc-input-number": "^7.3.9",
|
"rc-input-number": "^7.3.9",
|
||||||
"rc-progress": "^3.1.4",
|
"rc-progress": "^3.1.4",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-hook-form": "7.30.0",
|
"react-hook-form": "7.39.0",
|
||||||
"react-json-view": "1.21.3",
|
"react-json-view": "1.21.3",
|
||||||
"react-overlays": "5.1.1",
|
"react-overlays": "5.1.1",
|
||||||
"react-textarea-autosize": "8.3.3",
|
"react-textarea-autosize": "8.3.3",
|
||||||
|
@ -73,7 +73,8 @@ import {
|
|||||||
RegisterOptions,
|
RegisterOptions,
|
||||||
useFieldArray,
|
useFieldArray,
|
||||||
UseFieldArrayProps,
|
UseFieldArrayProps,
|
||||||
UseFormReturn
|
UseFormReturn,
|
||||||
|
useFormState
|
||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
import useSubForm from '../hooks/use-sub-form';
|
import useSubForm from '../hooks/use-sub-form';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
@ -147,17 +148,64 @@ export function Combo({
|
|||||||
minLength,
|
minLength,
|
||||||
maxLength
|
maxLength
|
||||||
}: ComboProps) {
|
}: ComboProps) {
|
||||||
// 看文档是支持的,但是传入报错,后面看看
|
const subForms = React.useRef<Record<any, UseFormReturn>>({});
|
||||||
// let rules2: any = {...rules};
|
const subFormRef = React.useCallback(
|
||||||
|
(subform: UseFormReturn | null, index: number) => {
|
||||||
|
if (subform) {
|
||||||
|
subForms.current[index] = subform;
|
||||||
|
} else {
|
||||||
|
delete subForms.current[index];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[subForms]
|
||||||
|
);
|
||||||
|
let finalRules: any = {...rules};
|
||||||
|
|
||||||
// if (isRequired) {
|
if (isRequired) {
|
||||||
// rules2.required = true;
|
finalRules.required = true;
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
if (minLength) {
|
||||||
|
finalRules.minLength = minLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxLength) {
|
||||||
|
finalRules.maxLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalRules.validate = React.useCallback(
|
||||||
|
async (items: Array<any>) => {
|
||||||
|
const map = subForms.current;
|
||||||
|
|
||||||
|
if (typeof rules?.validate === 'function') {
|
||||||
|
const result = await rules.validate(items);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key of Object.keys(map)) {
|
||||||
|
const valid = await (function (methods) {
|
||||||
|
return new Promise<boolean>(resolve => {
|
||||||
|
methods.handleSubmit(
|
||||||
|
() => resolve(true),
|
||||||
|
() => resolve(false)
|
||||||
|
)();
|
||||||
|
});
|
||||||
|
})(map[key]);
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
return __('validateFailed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[subForms]
|
||||||
|
);
|
||||||
const {fields, append, update, remove} = useFieldArray({
|
const {fields, append, update, remove} = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: name,
|
name: name,
|
||||||
shouldUnregister: true
|
shouldUnregister: true,
|
||||||
// rules: rules2
|
rules: finalRules
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderBody() {
|
function renderBody() {
|
||||||
@ -180,6 +228,7 @@ export function Combo({
|
|||||||
itemRender={itemRender}
|
itemRender={itemRender}
|
||||||
translate={__}
|
translate={__}
|
||||||
classnames={cx}
|
classnames={cx}
|
||||||
|
formRef={subFormRef}
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
onClick={() => remove(index)}
|
onClick={() => remove(index)}
|
||||||
@ -215,6 +264,10 @@ export function Combo({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {errors} = useFormState({
|
||||||
|
control
|
||||||
|
});
|
||||||
|
|
||||||
return wrap === false ? (
|
return wrap === false ? (
|
||||||
renderBody()
|
renderBody()
|
||||||
) : (
|
) : (
|
||||||
@ -226,8 +279,8 @@ export function Combo({
|
|||||||
description={description}
|
description={description}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
isRequired={isRequired}
|
isRequired={isRequired}
|
||||||
hasError={false /*目前看来不支持,后续研究一下 */}
|
hasError={!!errors[name]?.message}
|
||||||
errors={undefined /*目前看来不支持,后续研究一下 */}
|
errors={errors[name]?.message as any}
|
||||||
>
|
>
|
||||||
{renderBody()}
|
{renderBody()}
|
||||||
</FormField>
|
</FormField>
|
||||||
@ -242,6 +295,7 @@ export interface ComboItemProps {
|
|||||||
index: number;
|
index: number;
|
||||||
translate: TranslateFn;
|
translate: TranslateFn;
|
||||||
classnames: ClassNamesFn;
|
classnames: ClassNamesFn;
|
||||||
|
formRef: (form: UseFormReturn | null, index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ComboItem({
|
export function ComboItem({
|
||||||
@ -250,9 +304,16 @@ export function ComboItem({
|
|||||||
index,
|
index,
|
||||||
translate,
|
translate,
|
||||||
update,
|
update,
|
||||||
classnames: cx
|
classnames: cx,
|
||||||
|
formRef
|
||||||
}: ComboItemProps) {
|
}: ComboItemProps) {
|
||||||
const methods = useSubForm(value, translate, data => update(index, data));
|
const methods = useSubForm(value, translate, data => update(index, data));
|
||||||
|
React.useEffect(() => {
|
||||||
|
formRef?.(methods, index);
|
||||||
|
return () => {
|
||||||
|
formRef?.(null, index);
|
||||||
|
};
|
||||||
|
}, [methods]);
|
||||||
|
|
||||||
let child: any = itemRender(methods, index);
|
let child: any = itemRender(methods, index);
|
||||||
if (child?.type === React.Fragment) {
|
if (child?.type === React.Fragment) {
|
||||||
|
145
packages/amis-ui/src/components/ConfirmBox.tsx
Normal file
145
packages/amis-ui/src/components/ConfirmBox.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from './Modal';
|
||||||
|
import Button from './Button';
|
||||||
|
import Drawer from './Drawer';
|
||||||
|
import {localeable, LocaleProps, themeable, ThemeProps} from 'amis-core';
|
||||||
|
|
||||||
|
export interface ConfirmBoxProps extends LocaleProps, ThemeProps {
|
||||||
|
show?: boolean;
|
||||||
|
closeOnEsc?: boolean;
|
||||||
|
beforeConfirm?: (bodyRef?: any) => any;
|
||||||
|
onConfirm?: (data: any) => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
title?: string;
|
||||||
|
showTitle?: boolean;
|
||||||
|
showFooter?: boolean;
|
||||||
|
headerClassName?: string;
|
||||||
|
children?:
|
||||||
|
| JSX.Element
|
||||||
|
| ((methods: {
|
||||||
|
bodyRef: React.MutableRefObject<
|
||||||
|
| {
|
||||||
|
submit: () => Promise<Record<string, any>>;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>;
|
||||||
|
}) => JSX.Element);
|
||||||
|
popOverContainer?: any;
|
||||||
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||||
|
position?: 'top' | 'right' | 'bottom' | 'left';
|
||||||
|
resizable?: boolean;
|
||||||
|
type: 'dialog' | 'drawer';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConfirmBox({
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
closeOnEsc,
|
||||||
|
show,
|
||||||
|
onCancel,
|
||||||
|
title,
|
||||||
|
showTitle,
|
||||||
|
headerClassName,
|
||||||
|
translate: __,
|
||||||
|
children,
|
||||||
|
showFooter,
|
||||||
|
onConfirm,
|
||||||
|
beforeConfirm,
|
||||||
|
popOverContainer,
|
||||||
|
position,
|
||||||
|
resizable,
|
||||||
|
classnames: cx
|
||||||
|
}: ConfirmBoxProps) {
|
||||||
|
const bodyRef = React.useRef<
|
||||||
|
{submit: () => Promise<Record<string, any>>} | undefined
|
||||||
|
>();
|
||||||
|
const handleConfirm = React.useCallback(async () => {
|
||||||
|
const ret = beforeConfirm
|
||||||
|
? await beforeConfirm?.(bodyRef.current)
|
||||||
|
: await bodyRef.current?.submit?.();
|
||||||
|
|
||||||
|
if (ret === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfirm?.(ret);
|
||||||
|
}, [onConfirm, beforeConfirm]);
|
||||||
|
|
||||||
|
function renderDialog() {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size={size}
|
||||||
|
closeOnEsc={closeOnEsc}
|
||||||
|
show={show}
|
||||||
|
onHide={onCancel!}
|
||||||
|
container={popOverContainer}
|
||||||
|
>
|
||||||
|
{showTitle !== false && title ? (
|
||||||
|
<Modal.Header onClose={onCancel} className={headerClassName}>
|
||||||
|
{title}
|
||||||
|
</Modal.Header>
|
||||||
|
) : null}
|
||||||
|
<Modal.Body>
|
||||||
|
{typeof children === 'function'
|
||||||
|
? children({
|
||||||
|
bodyRef: bodyRef
|
||||||
|
})
|
||||||
|
: children}
|
||||||
|
</Modal.Body>
|
||||||
|
{showFooter ?? true ? (
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button onClick={onCancel}>{__('cancel')}</Button>
|
||||||
|
<Button onClick={handleConfirm} level="primary">
|
||||||
|
{__('confirm')}
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
) : null}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDrawer() {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
size={size}
|
||||||
|
closeOnEsc={closeOnEsc}
|
||||||
|
show={show}
|
||||||
|
onHide={onCancel!}
|
||||||
|
container={popOverContainer}
|
||||||
|
position={position}
|
||||||
|
resizable={resizable}
|
||||||
|
showCloseButton={false}
|
||||||
|
>
|
||||||
|
{showTitle !== false && title ? (
|
||||||
|
<div className={cx('Drawer-header', headerClassName)}>
|
||||||
|
<div className={cx('Drawer-title')}>{title}</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className={cx('Drawer-body')}>
|
||||||
|
{typeof children === 'function'
|
||||||
|
? children({
|
||||||
|
bodyRef: bodyRef
|
||||||
|
})
|
||||||
|
: children}
|
||||||
|
</div>
|
||||||
|
{showFooter ?? true ? (
|
||||||
|
<div className={cx('Drawer-footer')}>
|
||||||
|
<Button onClick={handleConfirm} level="primary">
|
||||||
|
{__('confirm')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onCancel}>{__('cancel')}</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return type === 'drawer' ? renderDrawer() : renderDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmBox.defaultProps = {
|
||||||
|
type: 'dialog' as 'dialog',
|
||||||
|
position: 'right' as 'right'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default localeable(themeable(ConfirmBox));
|
@ -2,7 +2,7 @@
|
|||||||
* @file 给组件用的,渲染器里面不要用这个
|
* @file 给组件用的,渲染器里面不要用这个
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {themeable, ThemeProps} from 'amis-core';
|
import {noop, themeable, ThemeProps} from 'amis-core';
|
||||||
import {useForm, UseFormReturn} from 'react-hook-form';
|
import {useForm, UseFormReturn} from 'react-hook-form';
|
||||||
import {useValidationResolver} from '../hooks/use-validation-resolver';
|
import {useValidationResolver} from '../hooks/use-validation-resolver';
|
||||||
import {localeable, LocaleProps} from 'amis-core';
|
import {localeable, LocaleProps} from 'amis-core';
|
||||||
@ -16,9 +16,9 @@ export type FormRef = React.MutableRefObject<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export interface FormProps extends ThemeProps, LocaleProps {
|
export interface FormProps extends ThemeProps, LocaleProps {
|
||||||
defaultValues: any;
|
defaultValues?: any;
|
||||||
autoSubmit?: boolean;
|
autoSubmit?: boolean;
|
||||||
onSubmit: (value: any) => void;
|
onSubmit?: (value: any) => void;
|
||||||
forwardRef?: FormRef;
|
forwardRef?: FormRef;
|
||||||
children?: (
|
children?: (
|
||||||
methods: UseFormReturn & {
|
methods: UseFormReturn & {
|
||||||
@ -35,11 +35,11 @@ export function Form(props: FormProps) {
|
|||||||
resolver: useValidationResolver(props.translate)
|
resolver: useValidationResolver(props.translate)
|
||||||
});
|
});
|
||||||
let onSubmit = React.useRef<(data: any) => void>(
|
let onSubmit = React.useRef<(data: any) => void>(
|
||||||
methods.handleSubmit(props.onSubmit)
|
methods.handleSubmit(props.onSubmit || noop)
|
||||||
);
|
);
|
||||||
if (autoSubmit) {
|
if (autoSubmit) {
|
||||||
onSubmit = React.useRef(
|
onSubmit = React.useRef(
|
||||||
debounce(methods.handleSubmit(props.onSubmit), 250, {
|
debounce(methods.handleSubmit(props.onSubmit || noop), 250, {
|
||||||
leading: false,
|
leading: false,
|
||||||
trailing: true
|
trailing: true
|
||||||
})
|
})
|
||||||
@ -60,9 +60,12 @@ export function Form(props: FormProps) {
|
|||||||
// 这个模式别的组件没见到过不知道后续会不会不允许
|
// 这个模式别的组件没见到过不知道后续会不会不允许
|
||||||
props.forwardRef.current = {
|
props.forwardRef.current = {
|
||||||
submit: () =>
|
submit: () =>
|
||||||
new Promise<any>((resolve, reject) => {
|
new Promise<any>(resolve => {
|
||||||
methods.handleSubmit(
|
methods.handleSubmit(
|
||||||
values => resolve(values),
|
values => {
|
||||||
|
props.onSubmit?.(values);
|
||||||
|
resolve(values);
|
||||||
|
},
|
||||||
() => resolve(false)
|
() => resolve(false)
|
||||||
)();
|
)();
|
||||||
})
|
})
|
||||||
@ -93,7 +96,14 @@ export function Form(props: FormProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ThemedForm = themeable(localeable(Form));
|
const ThemedForm = themeable(localeable(Form));
|
||||||
type ThemedFormProps = Omit<FormProps, keyof ThemeProps | keyof LocaleProps>;
|
type ThemedFormProps = Omit<
|
||||||
|
JSX.LibraryManagedAttributes<
|
||||||
|
typeof ThemedForm,
|
||||||
|
React.ComponentProps<typeof ThemedForm>
|
||||||
|
>,
|
||||||
|
'children'
|
||||||
|
> &
|
||||||
|
Pick<FormProps, 'children'>;
|
||||||
|
|
||||||
export default React.forwardRef((props: ThemedFormProps, ref: FormRef) => (
|
export default React.forwardRef((props: ThemedFormProps, ref: FormRef) => (
|
||||||
<ThemedForm {...props} forwardRef={ref} />
|
<ThemedForm {...props} forwardRef={ref} />
|
||||||
|
@ -11,30 +11,29 @@ import {
|
|||||||
Control,
|
Control,
|
||||||
useFieldArray,
|
useFieldArray,
|
||||||
UseFieldArrayProps,
|
UseFieldArrayProps,
|
||||||
UseFormReturn
|
UseFormReturn,
|
||||||
|
useFormState
|
||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
import useSubForm from '../hooks/use-sub-form';
|
import useSubForm from '../hooks/use-sub-form';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import FormField, {FormFieldProps} from './FormField';
|
import FormField, {FormFieldProps} from './FormField';
|
||||||
import {Icon} from './icons';
|
import {Icon} from './icons';
|
||||||
|
|
||||||
export interface InputTabbleProps<T = any>
|
export interface InputTableColumnProps {
|
||||||
extends ThemeProps,
|
|
||||||
LocaleProps,
|
|
||||||
Omit<
|
|
||||||
FormFieldProps,
|
|
||||||
'children' | 'errors' | 'hasError' | 'isRequired' | 'className'
|
|
||||||
>,
|
|
||||||
UseFieldArrayProps {
|
|
||||||
control: Control<any>;
|
|
||||||
fieldClassName?: string;
|
|
||||||
|
|
||||||
columns: Array<{
|
|
||||||
title?: string;
|
title?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
thRender?: () => JSX.Element;
|
thRender?: () => JSX.Element;
|
||||||
tdRender: (methods: UseFormReturn, index: number) => JSX.Element | null;
|
tdRender: (methods: UseFormReturn, index: number) => JSX.Element | null;
|
||||||
}>;
|
}
|
||||||
|
|
||||||
|
export interface InputTabbleProps<T = any>
|
||||||
|
extends ThemeProps,
|
||||||
|
LocaleProps,
|
||||||
|
Omit<FormFieldProps, 'children' | 'errors' | 'hasError' | 'className'>,
|
||||||
|
UseFieldArrayProps {
|
||||||
|
control: Control<any>;
|
||||||
|
fieldClassName?: string;
|
||||||
|
columns: Array<InputTableColumnProps>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 要不要包裹 label 之类的
|
* 要不要包裹 label 之类的
|
||||||
@ -71,17 +70,78 @@ export function InputTable({
|
|||||||
addButtonClassName,
|
addButtonClassName,
|
||||||
scaffold,
|
scaffold,
|
||||||
minLength,
|
minLength,
|
||||||
maxLength
|
maxLength,
|
||||||
|
isRequired,
|
||||||
|
rules
|
||||||
}: InputTabbleProps) {
|
}: InputTabbleProps) {
|
||||||
|
const subForms = React.useRef<Record<any, UseFormReturn>>({});
|
||||||
|
const subFormRef = React.useCallback(
|
||||||
|
(subform: UseFormReturn | null, index: number) => {
|
||||||
|
if (subform) {
|
||||||
|
subForms.current[index] = subform;
|
||||||
|
} else {
|
||||||
|
delete subForms.current[index];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[subForms]
|
||||||
|
);
|
||||||
|
let finalRules: any = {...rules};
|
||||||
|
|
||||||
|
if (isRequired) {
|
||||||
|
finalRules.required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minLength) {
|
||||||
|
finalRules.minLength = minLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxLength) {
|
||||||
|
finalRules.maxLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalRules.validate = React.useCallback(
|
||||||
|
async (items: Array<any>) => {
|
||||||
|
const map = subForms.current;
|
||||||
|
|
||||||
|
if (typeof rules?.validate === 'function') {
|
||||||
|
const result = await rules.validate(items);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key of Object.keys(map)) {
|
||||||
|
const valid = await (function (methods) {
|
||||||
|
return new Promise<boolean>(resolve => {
|
||||||
|
methods.handleSubmit(
|
||||||
|
() => resolve(true),
|
||||||
|
() => resolve(false)
|
||||||
|
)();
|
||||||
|
});
|
||||||
|
})(map[key]);
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
return __('validateFailed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[subForms]
|
||||||
|
);
|
||||||
|
|
||||||
const {fields, append, update, remove} = useFieldArray({
|
const {fields, append, update, remove} = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: name
|
name: name,
|
||||||
|
rules: finalRules
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Array.isArray(columns)) {
|
if (!Array.isArray(columns)) {
|
||||||
columns = [];
|
columns = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {errors} = useFormState({
|
||||||
|
control
|
||||||
|
});
|
||||||
|
|
||||||
function renderBody() {
|
function renderBody() {
|
||||||
return (
|
return (
|
||||||
<div className={cx(`Table`, className)}>
|
<div className={cx(`Table`, className)}>
|
||||||
@ -110,19 +170,17 @@ export function InputTable({
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
translate={__}
|
translate={__}
|
||||||
classnames={cx}
|
classnames={cx}
|
||||||
|
formRef={subFormRef}
|
||||||
/>
|
/>
|
||||||
<td key="operation">
|
<td key="operation">
|
||||||
<Button
|
<Button
|
||||||
level="link"
|
level="link"
|
||||||
key="delete"
|
key="delete"
|
||||||
className={cx(
|
disabled={
|
||||||
`Table-delBtn ${
|
|
||||||
removable === false ||
|
removable === false ||
|
||||||
(minLength && fields.length <= minLength)
|
!!(minLength && fields.length <= minLength)
|
||||||
? 'is-disabled'
|
}
|
||||||
: ''
|
className={cx('Table-delBtn')}
|
||||||
}`
|
|
||||||
)}
|
|
||||||
onClick={() => remove(index)}
|
onClick={() => remove(index)}
|
||||||
>
|
>
|
||||||
{__('delete')}
|
{__('delete')}
|
||||||
@ -170,8 +228,8 @@ export function InputTable({
|
|||||||
labelClassName={labelClassName}
|
labelClassName={labelClassName}
|
||||||
description={description}
|
description={description}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
hasError={false /*目前看来不支持,后续研究一下 */}
|
hasError={!!errors[name]?.message}
|
||||||
errors={undefined /*目前看来不支持,后续研究一下 */}
|
errors={errors[name]?.message as any}
|
||||||
>
|
>
|
||||||
{renderBody()}
|
{renderBody()}
|
||||||
</FormField>
|
</FormField>
|
||||||
@ -189,6 +247,7 @@ export interface InputTableRowProps {
|
|||||||
index: number;
|
index: number;
|
||||||
translate: TranslateFn;
|
translate: TranslateFn;
|
||||||
classnames: ClassNamesFn;
|
classnames: ClassNamesFn;
|
||||||
|
formRef: (form: UseFormReturn | null, index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InputTableRow({
|
export function InputTableRow({
|
||||||
@ -197,9 +256,16 @@ export function InputTableRow({
|
|||||||
index,
|
index,
|
||||||
translate,
|
translate,
|
||||||
update,
|
update,
|
||||||
|
formRef,
|
||||||
classnames: cx
|
classnames: cx
|
||||||
}: InputTableRowProps) {
|
}: InputTableRowProps) {
|
||||||
const methods = useSubForm(value, translate, data => update(index, data));
|
const methods = useSubForm(value, translate, data => update(index, data));
|
||||||
|
React.useEffect(() => {
|
||||||
|
formRef?.(methods, index);
|
||||||
|
return () => {
|
||||||
|
formRef?.(null, index);
|
||||||
|
};
|
||||||
|
}, [methods]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -9,12 +9,12 @@ import {
|
|||||||
} from 'amis-core';
|
} from 'amis-core';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
import ConfirmBox, {ConfirmBoxProps} from './ConfirmBox';
|
||||||
|
|
||||||
export interface PickerContainerProps extends ThemeProps, LocaleProps {
|
export interface PickerContainerProps
|
||||||
title?: string;
|
extends ThemeProps,
|
||||||
showTitle?: boolean;
|
LocaleProps,
|
||||||
showFooter?: boolean;
|
Omit<ConfirmBoxProps, 'children' | 'type'> {
|
||||||
headerClassName?: string;
|
|
||||||
children: (props: {
|
children: (props: {
|
||||||
onClick: (e: React.MouseEvent) => void;
|
onClick: (e: React.MouseEvent) => void;
|
||||||
setState: (state: any) => void;
|
setState: (state: any) => void;
|
||||||
@ -28,12 +28,6 @@ export interface PickerContainerProps extends ThemeProps, LocaleProps {
|
|||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
}) => JSX.Element | null;
|
}) => JSX.Element | null;
|
||||||
value?: any;
|
value?: any;
|
||||||
beforeConfirm?: (bodyRef: any) => any;
|
|
||||||
onConfirm?: (value?: any) => void;
|
|
||||||
onCancel?: () => void;
|
|
||||||
popOverContainer?: any;
|
|
||||||
popOverClassName?: string;
|
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
|
||||||
@ -99,7 +93,7 @@ export class PickerContainer extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
async confirm() {
|
async confirm(): Promise<any> {
|
||||||
const {onConfirm, beforeConfirm} = this.props;
|
const {onConfirm, beforeConfirm} = this.props;
|
||||||
|
|
||||||
const ret = await beforeConfirm?.(this.bodyRef.current);
|
const ret = await beforeConfirm?.(this.bodyRef.current);
|
||||||
@ -109,7 +103,7 @@ export class PickerContainer extends React.Component<
|
|||||||
|
|
||||||
// beforeConfirm 返回 false 则阻止后续动作
|
// beforeConfirm 返回 false 则阻止后续动作
|
||||||
if (ret === false) {
|
if (ret === false) {
|
||||||
return;
|
return false;
|
||||||
} else if (isObject(ret)) {
|
} else if (isObject(ret)) {
|
||||||
state.value = ret;
|
state.value = ret;
|
||||||
}
|
}
|
||||||
@ -135,7 +129,8 @@ export class PickerContainer extends React.Component<
|
|||||||
headerClassName,
|
headerClassName,
|
||||||
translate: __,
|
translate: __,
|
||||||
size,
|
size,
|
||||||
showFooter
|
showFooter,
|
||||||
|
closeOnEsc
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -145,36 +140,29 @@ export class PickerContainer extends React.Component<
|
|||||||
setState: this.updateState
|
setState: this.updateState
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<Modal
|
<ConfirmBox
|
||||||
|
type="dialog"
|
||||||
size={size}
|
size={size}
|
||||||
closeOnEsc
|
closeOnEsc={closeOnEsc}
|
||||||
show={this.state.isOpened}
|
show={this.state.isOpened}
|
||||||
onHide={this.close}
|
onCancel={this.close}
|
||||||
|
title={title || __('Select.placeholder')}
|
||||||
|
showTitle={showTitle}
|
||||||
|
headerClassName={headerClassName}
|
||||||
|
showFooter={showFooter}
|
||||||
|
beforeConfirm={this.confirm}
|
||||||
>
|
>
|
||||||
{showTitle !== false ? (
|
{() =>
|
||||||
<Modal.Header onClose={this.close} className={headerClassName}>
|
popOverRender({
|
||||||
{__(title || 'Select.placeholder')}
|
|
||||||
</Modal.Header>
|
|
||||||
) : null}
|
|
||||||
<Modal.Body>
|
|
||||||
{popOverRender({
|
|
||||||
...(this.state as any),
|
...(this.state as any),
|
||||||
ref: this.bodyRef,
|
ref: this.bodyRef,
|
||||||
setState: this.updateState,
|
setState: this.updateState,
|
||||||
onClose: this.close,
|
onClose: this.close,
|
||||||
onChange: this.handleChange,
|
onChange: this.handleChange,
|
||||||
onConfirm: this.confirm
|
onConfirm: this.confirm
|
||||||
})}
|
})!
|
||||||
</Modal.Body>
|
}
|
||||||
{showFooter ?? true ? (
|
</ConfirmBox>
|
||||||
<Modal.Footer>
|
|
||||||
<Button onClick={this.close}>{__('cancel')}</Button>
|
|
||||||
<Button onClick={this.confirm} level="primary">
|
|
||||||
{__('confirm')}
|
|
||||||
</Button>
|
|
||||||
</Modal.Footer>
|
|
||||||
) : null}
|
|
||||||
</Modal>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,8 @@ import Form from './Form';
|
|||||||
import FormField, {Controller} from './FormField';
|
import FormField, {Controller} from './FormField';
|
||||||
import Combo from './Combo';
|
import Combo from './Combo';
|
||||||
import InputTable from './InputTable';
|
import InputTable from './InputTable';
|
||||||
|
import type {InputTableColumnProps} from './InputTable';
|
||||||
|
import ConfirmBox from './ConfirmBox';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
NotFound,
|
NotFound,
|
||||||
@ -184,6 +186,7 @@ export {
|
|||||||
SchemaVariableList,
|
SchemaVariableList,
|
||||||
VariableList,
|
VariableList,
|
||||||
PickerContainer,
|
PickerContainer,
|
||||||
|
ConfirmBox,
|
||||||
FormulaPicker,
|
FormulaPicker,
|
||||||
InputJSONSchema,
|
InputJSONSchema,
|
||||||
withBadge,
|
withBadge,
|
||||||
@ -236,5 +239,6 @@ export {
|
|||||||
FormField,
|
FormField,
|
||||||
Controller,
|
Controller,
|
||||||
Combo,
|
Combo,
|
||||||
InputTable
|
InputTable,
|
||||||
|
InputTableColumnProps
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,7 @@ export function useValidationResolver(__ = (str: string) => str) {
|
|||||||
return React.useCallback<any>(
|
return React.useCallback<any>(
|
||||||
async (values: any, context: any, config: any) => {
|
async (values: any, context: any, config: any) => {
|
||||||
const rules: any = {};
|
const rules: any = {};
|
||||||
|
const customValidator: any = {};
|
||||||
const ruleKeys = Object.keys(validations);
|
const ruleKeys = Object.keys(validations);
|
||||||
for (let key of Object.keys(config.fields)) {
|
for (let key of Object.keys(config.fields)) {
|
||||||
const field = config.fields[key];
|
const field = config.fields[key];
|
||||||
@ -31,10 +32,27 @@ export function useValidationResolver(__ = (str: string) => str) {
|
|||||||
if (field.required) {
|
if (field.required) {
|
||||||
rules[key].isRequired = true;
|
rules[key].isRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof field.validate === 'function') {
|
||||||
|
customValidator[key] = field.validate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = validateObject(values, rules, undefined, __);
|
const errors = validateObject(values, rules, undefined, __);
|
||||||
|
|
||||||
|
for (let key of Object.keys(customValidator)) {
|
||||||
|
const validate = customValidator[key];
|
||||||
|
const result = await validate(values[key]);
|
||||||
|
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
errors[key] = errors[key] || [];
|
||||||
|
errors[key].push({
|
||||||
|
rule: 'custom',
|
||||||
|
msg: result
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
values,
|
values,
|
||||||
errors: formatErrors(errors)
|
errors: formatErrors(errors)
|
||||||
|
@ -286,6 +286,10 @@ register('de-DE', {
|
|||||||
'Kontrollieren Sie die Länge des Inhalts. Geben Sie nicht mehr als $1 Buchstaben ein.',
|
'Kontrollieren Sie die Länge des Inhalts. Geben Sie nicht mehr als $1 Buchstaben ein.',
|
||||||
'validate.minimum': 'Der Eingabewert ist kleiner als der Mindestwert von $1.',
|
'validate.minimum': 'Der Eingabewert ist kleiner als der Mindestwert von $1.',
|
||||||
'validate.minLength': 'Geben Sie weitere Zeichen ein, mindestens $1.',
|
'validate.minLength': 'Geben Sie weitere Zeichen ein, mindestens $1.',
|
||||||
|
'validate.array.minLength':
|
||||||
|
'Bitte fügen Sie weitere Mitglieder hinzu, mindestens $1 Mitglieder',
|
||||||
|
'validate.array.maxLength':
|
||||||
|
'Bitte kontrollieren Sie die Anzahl der Mitglieder, die $1 nicht überschreiten darf',
|
||||||
'validate.notEmptyString': 'Geben Sie nicht nur Leerzeichen ein.',
|
'validate.notEmptyString': 'Geben Sie nicht nur Leerzeichen ein.',
|
||||||
'validate.isDateTimeSame':
|
'validate.isDateTimeSame':
|
||||||
'Der aktuelle Datumswert ist ungültig, bitte geben Sie denselben Datumswert wie $1 ein',
|
'Der aktuelle Datumswert ist ungültig, bitte geben Sie denselben Datumswert wie $1 ein',
|
||||||
|
@ -274,6 +274,9 @@ register('en-US', {
|
|||||||
'Please control the content length, do not enter more than $1 letters',
|
'Please control the content length, do not enter more than $1 letters',
|
||||||
'validate.minimum': 'The input value is lower than the minimum value of $1',
|
'validate.minimum': 'The input value is lower than the minimum value of $1',
|
||||||
'validate.minLength': 'Please enter more, at least $1 characters.',
|
'validate.minLength': 'Please enter more, at least $1 characters.',
|
||||||
|
'validate.array.minLength': 'Please add more members, at least $1 members',
|
||||||
|
'validate.array.maxLength':
|
||||||
|
'Please control the number of members, which cannot exceed $1',
|
||||||
'validate.notEmptyString': 'Please do not enter all blank characters',
|
'validate.notEmptyString': 'Please do not enter all blank characters',
|
||||||
'validate.isDateTimeSame':
|
'validate.isDateTimeSame':
|
||||||
'The current date value is invalid, please enter the same date value as $1',
|
'The current date value is invalid, please enter the same date value as $1',
|
||||||
|
@ -274,8 +274,10 @@ register('zh-CN', {
|
|||||||
'validate.matchRegexp': '格式不正确, 请输入符合规则为 `${1|raw}` 的内容。',
|
'validate.matchRegexp': '格式不正确, 请输入符合规则为 `${1|raw}` 的内容。',
|
||||||
'validate.maximum': '当前输入值超出最大值 $1',
|
'validate.maximum': '当前输入值超出最大值 $1',
|
||||||
'validate.maxLength': '请控制内容长度, 不要输入 $1 个以上字符',
|
'validate.maxLength': '请控制内容长度, 不要输入 $1 个以上字符',
|
||||||
|
'validate.array.maxLength': '请控制成员个数, 不能超过 $1 个',
|
||||||
'validate.minimum': '当前输入值低于最小值 $1',
|
'validate.minimum': '当前输入值低于最小值 $1',
|
||||||
'validate.minLength': '请输入更多的内容,至少输入 $1 个字符。',
|
'validate.minLength': '请输入更多的内容,至少输入 $1 个字符。',
|
||||||
|
'validate.array.minLength': '请添加更多的成员,成员数至少 $1 个。',
|
||||||
'validate.notEmptyString': '请不要全输入空白字符',
|
'validate.notEmptyString': '请不要全输入空白字符',
|
||||||
'validate.isDateTimeSame': '当前日期值不合法,请输入和 $1 相同的日期值',
|
'validate.isDateTimeSame': '当前日期值不合法,请输入和 $1 相同的日期值',
|
||||||
'validate.isDateTimeBefore': '当前日期值不合法,请输入 $1 之前的日期值',
|
'validate.isDateTimeBefore': '当前日期值不合法,请输入 $1 之前的日期值',
|
||||||
|
@ -9,6 +9,6 @@
|
|||||||
"../../node_modules/@types"
|
"../../node_modules/@types"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "__tests__/**/*", "src/custom.d.ts"],
|
"include": ["src/**/*", "examples/**/*", "__tests__/**/*", "src/custom.d.ts"],
|
||||||
"references": [{"path": "../amis-core"}]
|
"references": [{"path": "../amis-core"}]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user