mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
commit
c3a7faf3f0
115
docs/zh-CN/components/form/input-signature.md
Normal file
115
docs/zh-CN/components/form/input-signature.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
title: inputSignature 签名面板
|
||||||
|
description:
|
||||||
|
type: 0
|
||||||
|
group: null
|
||||||
|
menuName: inputSignature
|
||||||
|
icon:
|
||||||
|
order: 62
|
||||||
|
---
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"name": "signature",
|
||||||
|
"type": "input-signature",
|
||||||
|
"label": "手写签名",
|
||||||
|
"height": 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 水平展示
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"name": "signature",
|
||||||
|
"type": "input-signature",
|
||||||
|
"horiz": true,
|
||||||
|
"height": 300
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义按钮名称
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"name": "signature",
|
||||||
|
"type": "input-signature",
|
||||||
|
"height": 160,
|
||||||
|
"confirmText": "确定",
|
||||||
|
"undoText": "上一步",
|
||||||
|
"clearText": "重置"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义颜色
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"name": "signature",
|
||||||
|
"type": "input-signature",
|
||||||
|
"label": "手写签名",
|
||||||
|
"height": 200,
|
||||||
|
"color": "#ff0000",
|
||||||
|
"bgColor": "#fff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配合图片组件实现实时预览
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"name": "signature",
|
||||||
|
"type": "input-signature",
|
||||||
|
"label": "手写签名",
|
||||||
|
"height": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"name": "signature"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 属性表
|
||||||
|
|
||||||
|
| 属性名 | 类型 | 默认值 | 说明 |
|
||||||
|
| ----------- | --------- | --------- | ------------------ |
|
||||||
|
| width | `number` | | 组件宽度,最小 300 |
|
||||||
|
| height | `number` | | 组件高度,最小 160 |
|
||||||
|
| color | `string` | `#000` | 手写字体颜色 |
|
||||||
|
| bgColor | `string` | `#EFEFEF` | 面板背景颜色 |
|
||||||
|
| clearText | `string` | `清空` | 清空按钮名称 |
|
||||||
|
| undoText | `string` | `撤销` | 撤销按钮名称 |
|
||||||
|
| confirmText | `string` | `确认` | 确认按钮名称 |
|
||||||
|
| horiz | `boolean` | | 是否水平展示 |
|
@ -785,6 +785,16 @@ export const components = [
|
|||||||
wrapDoc
|
wrapDoc
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: 'InputSignature 签名面板',
|
||||||
|
path: '/zh-CN/components/form/input-signature',
|
||||||
|
component: React.lazy(() =>
|
||||||
|
import('../../docs/zh-CN/components/form/input-signature.md').then(
|
||||||
|
wrapDoc
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -43,7 +43,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-to-regexp": "^6.2.0",
|
"path-to-regexp": "^6.2.0",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"qs": "6.9.7"
|
"qs": "6.9.7",
|
||||||
|
"smooth-signature": "^1.0.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/generator": "^7.22.9",
|
"@babel/generator": "^7.22.9",
|
||||||
@ -149,4 +150,4 @@
|
|||||||
"printBasicPrototype": false
|
"printBasicPrototype": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
packages/amis-ui/scss/components/_signature.scss
Normal file
35
packages/amis-ui/scss/components/_signature.scss
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
.#{$ns}Signature {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
&-canvas {
|
||||||
|
border: 1px dashed #aaa;
|
||||||
|
}
|
||||||
|
&-Tool {
|
||||||
|
text-align: center;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 8px;
|
||||||
|
button {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-horizontal {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
.#{$ns}Signature-Tool {
|
||||||
|
width: 32px;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
.actions {
|
||||||
|
white-space: nowrap;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -143,5 +143,6 @@
|
|||||||
@import '../components/menu';
|
@import '../components/menu';
|
||||||
@import '../components/overflow-tpl';
|
@import '../components/overflow-tpl';
|
||||||
@import '../components/pdf_viewer';
|
@import '../components/pdf_viewer';
|
||||||
|
@import '../components/signature';
|
||||||
|
|
||||||
@import '../components/print';
|
@import '../components/print';
|
||||||
|
113
packages/amis-ui/src/components/Signature.tsx
Normal file
113
packages/amis-ui/src/components/Signature.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* @file Signature.tsx 签名组件
|
||||||
|
*
|
||||||
|
* @created: 2024/03/04
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {themeable, ThemeProps} from 'amis-core';
|
||||||
|
import {LocaleProps, localeable} from 'amis-core';
|
||||||
|
import {resizeSensor} from 'amis-core';
|
||||||
|
import SmoothSignature from 'smooth-signature';
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
|
export interface ISignatureProps extends LocaleProps, ThemeProps {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
color?: string;
|
||||||
|
bgColor?: string;
|
||||||
|
clearText?: string;
|
||||||
|
undoText?: string;
|
||||||
|
confirmText?: string;
|
||||||
|
horizontal?: boolean;
|
||||||
|
onChange?: (value?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Signature: React.FC<ISignatureProps> = props => {
|
||||||
|
const {classnames: cx, horizontal, width, height} = props;
|
||||||
|
const [sign, setSign] = React.useState<SmoothSignature | null>(null);
|
||||||
|
const wrapper = React.useRef<HTMLDivElement>(null);
|
||||||
|
const canvas = React.useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!wrapper.current || !canvas.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initCanvas(canvas.current);
|
||||||
|
const unSensor = resizeSensor(wrapper.current, resize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setSign(null);
|
||||||
|
unSensor();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clear = React.useCallback(() => {
|
||||||
|
if (sign) {
|
||||||
|
sign.clear();
|
||||||
|
props.onChange?.(undefined);
|
||||||
|
}
|
||||||
|
}, [sign]);
|
||||||
|
const undo = React.useCallback(() => {
|
||||||
|
if (sign) {
|
||||||
|
sign.undo();
|
||||||
|
}
|
||||||
|
}, [sign]);
|
||||||
|
const confirm = React.useCallback(() => {
|
||||||
|
if (sign) {
|
||||||
|
const base64 = sign.toDataURL();
|
||||||
|
props.onChange?.(base64);
|
||||||
|
}
|
||||||
|
}, [sign]);
|
||||||
|
const resize = React.useCallback(() => {
|
||||||
|
setSign(null);
|
||||||
|
initCanvas(canvas.current!);
|
||||||
|
}, []);
|
||||||
|
const initCanvas = React.useCallback(
|
||||||
|
(element: HTMLCanvasElement) => {
|
||||||
|
const {width, height} = props;
|
||||||
|
const rect = element.parentElement!.getBoundingClientRect();
|
||||||
|
const clientWidth = Math.floor(rect.width);
|
||||||
|
const defaultWidth = width || clientWidth - (horizontal ? 40 : 0);
|
||||||
|
const defaultHeight = height || clientWidth / 2 - (horizontal ? 0 : 40);
|
||||||
|
const signature = new SmoothSignature(element, {
|
||||||
|
width: Math.max(defaultWidth, 300),
|
||||||
|
height: Math.max(defaultHeight, 160),
|
||||||
|
color: props.color || '#000',
|
||||||
|
bgColor: props.bgColor || '#efefef'
|
||||||
|
});
|
||||||
|
setSign(signature);
|
||||||
|
},
|
||||||
|
[width, height, horizontal]
|
||||||
|
);
|
||||||
|
|
||||||
|
function renderTool() {
|
||||||
|
const {translate: __, clearText, undoText, confirmText} = props;
|
||||||
|
return (
|
||||||
|
<div className={cx('Signature-Tool')}>
|
||||||
|
<div className="actions">
|
||||||
|
<Button onClick={clear}>{clearText || __('Signature.clear')}</Button>
|
||||||
|
<Button onClick={undo}>{undoText || __('Signature.undo')}</Button>
|
||||||
|
<Button onClick={confirm}>
|
||||||
|
{confirmText || __('Signature.confirm')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx('Signature', {
|
||||||
|
'is-horizontal': horizontal
|
||||||
|
})}
|
||||||
|
ref={wrapper}
|
||||||
|
>
|
||||||
|
<canvas className={cx('Signature-canvas')} ref={canvas} />
|
||||||
|
{renderTool()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default themeable(localeable(Signature));
|
@ -130,6 +130,7 @@ import InputBoxWithSuggestion from './InputBoxWithSuggestion';
|
|||||||
import {CodeMirrorEditor} from './CodeMirror';
|
import {CodeMirrorEditor} from './CodeMirror';
|
||||||
import type CodeMirror from 'codemirror';
|
import type CodeMirror from 'codemirror';
|
||||||
import OverflowTpl from './OverflowTpl';
|
import OverflowTpl from './OverflowTpl';
|
||||||
|
import Signature from './Signature';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
NotFound,
|
NotFound,
|
||||||
@ -263,5 +264,6 @@ export {
|
|||||||
Menu,
|
Menu,
|
||||||
CodeMirror,
|
CodeMirror,
|
||||||
CodeMirrorEditor,
|
CodeMirrorEditor,
|
||||||
OverflowTpl
|
OverflowTpl,
|
||||||
|
Signature
|
||||||
};
|
};
|
||||||
|
@ -430,5 +430,8 @@ register('de-DE', {
|
|||||||
'TimeNow': 'Jetzt',
|
'TimeNow': 'Jetzt',
|
||||||
'Steps.step': 'Schritt {{index}}',
|
'Steps.step': 'Schritt {{index}}',
|
||||||
'FormulaInput.True': 'Treu',
|
'FormulaInput.True': 'Treu',
|
||||||
'FormulaInput.False': 'Falsch'
|
'FormulaInput.False': 'Falsch',
|
||||||
|
'Signature.clear': 'leer',
|
||||||
|
'Signature.undo': 'widerrufen',
|
||||||
|
'Signature.confirm': 'bestätigen'
|
||||||
});
|
});
|
||||||
|
@ -414,5 +414,8 @@ register('en-US', {
|
|||||||
'IconSelect.choice': 'Icon selection',
|
'IconSelect.choice': 'Icon selection',
|
||||||
'Steps.step': 'Step {{index}}',
|
'Steps.step': 'Step {{index}}',
|
||||||
'FormulaInput.True': 'True',
|
'FormulaInput.True': 'True',
|
||||||
'FormulaInput.False': 'False'
|
'FormulaInput.False': 'False',
|
||||||
|
'Signature.clear': 'clear',
|
||||||
|
'Signature.undo': 'undo',
|
||||||
|
'Signature.confirm': 'confirm'
|
||||||
});
|
});
|
||||||
|
@ -409,5 +409,8 @@ register('zh-CN', {
|
|||||||
'IconSelect.choice': '图标选择',
|
'IconSelect.choice': '图标选择',
|
||||||
'Steps.step': '第 {{index}} 步',
|
'Steps.step': '第 {{index}} 步',
|
||||||
'FormulaInput.True': '真',
|
'FormulaInput.True': '真',
|
||||||
'FormulaInput.False': '假'
|
'FormulaInput.False': '假',
|
||||||
|
'Signature.clear': '清空',
|
||||||
|
'Signature.undo': '撤销',
|
||||||
|
'Signature.confirm': '确认'
|
||||||
});
|
});
|
||||||
|
@ -121,6 +121,7 @@ import {TransferPickerControlSchema} from './renderers/Form/TransferPicker';
|
|||||||
import {TabsTransferPickerControlSchema} from './renderers/Form/TabsTransferPicker';
|
import {TabsTransferPickerControlSchema} from './renderers/Form/TabsTransferPicker';
|
||||||
import {UserSelectControlSchema} from './renderers/Form/UserSelect';
|
import {UserSelectControlSchema} from './renderers/Form/UserSelect';
|
||||||
import {JSONSchemaEditorControlSchema} from './renderers/Form/JSONSchemaEditor';
|
import {JSONSchemaEditorControlSchema} from './renderers/Form/JSONSchemaEditor';
|
||||||
|
import {InputSignatureSchema} from './renderers/Form/InputSignature';
|
||||||
import {TableSchema2} from './renderers/Table2';
|
import {TableSchema2} from './renderers/Table2';
|
||||||
import {
|
import {
|
||||||
BaseSchemaWithoutType,
|
BaseSchemaWithoutType,
|
||||||
@ -254,6 +255,7 @@ export type SchemaType =
|
|||||||
| 'diff-editor'
|
| 'diff-editor'
|
||||||
| 'office-viewer'
|
| 'office-viewer'
|
||||||
| 'pdf-viewer'
|
| 'pdf-viewer'
|
||||||
|
| 'input-signature'
|
||||||
|
|
||||||
// editor 系列
|
// editor 系列
|
||||||
| 'editor'
|
| 'editor'
|
||||||
@ -461,6 +463,7 @@ export type SchemaObject =
|
|||||||
| InputGroupControlSchema
|
| InputGroupControlSchema
|
||||||
| ListControlSchema
|
| ListControlSchema
|
||||||
| JSONSchemaEditorControlSchema
|
| JSONSchemaEditorControlSchema
|
||||||
|
| InputSignatureSchema
|
||||||
| LocationControlSchema
|
| LocationControlSchema
|
||||||
| UUIDControlSchema
|
| UUIDControlSchema
|
||||||
| MatrixControlSchema
|
| MatrixControlSchema
|
||||||
|
@ -94,6 +94,7 @@ import './renderers/Form/TabsTransferPicker';
|
|||||||
import './renderers/Form/Group';
|
import './renderers/Form/Group';
|
||||||
import './renderers/Form/InputGroup';
|
import './renderers/Form/InputGroup';
|
||||||
import './renderers/Form/UserSelect';
|
import './renderers/Form/UserSelect';
|
||||||
|
import './renderers/Form/InputSignature';
|
||||||
import './renderers/Grid';
|
import './renderers/Grid';
|
||||||
import './renderers/Grid2D';
|
import './renderers/Grid2D';
|
||||||
import './renderers/HBox';
|
import './renderers/HBox';
|
||||||
|
112
packages/amis/src/renderers/Form/InputSignature.tsx
Normal file
112
packages/amis/src/renderers/Form/InputSignature.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* @file Signature.tsx 签名组件
|
||||||
|
*
|
||||||
|
* @created: 2024/03/04
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
IScopedContext,
|
||||||
|
FormItem,
|
||||||
|
FormControlProps,
|
||||||
|
ScopedContext
|
||||||
|
} from 'amis-core';
|
||||||
|
import {Signature} from 'amis-ui';
|
||||||
|
import pick from 'lodash/pick';
|
||||||
|
import {FormBaseControlSchema} from '../../Schema';
|
||||||
|
|
||||||
|
export interface InputSignatureSchema extends FormBaseControlSchema {
|
||||||
|
type: 'input-signature';
|
||||||
|
/**
|
||||||
|
* 组件宽度,默认占满父容器
|
||||||
|
*/
|
||||||
|
width?: number;
|
||||||
|
/**
|
||||||
|
* 组件高度,默认占满父容器
|
||||||
|
*/
|
||||||
|
height?: number;
|
||||||
|
/**
|
||||||
|
* 组件字段颜色
|
||||||
|
* @default #000
|
||||||
|
*/
|
||||||
|
color?: string;
|
||||||
|
/**
|
||||||
|
* 组件背景颜色
|
||||||
|
* @default #efefef
|
||||||
|
*/
|
||||||
|
bgColor?: string;
|
||||||
|
/**
|
||||||
|
* 清空按钮名称
|
||||||
|
* @default 清空
|
||||||
|
*/
|
||||||
|
clearText?: string;
|
||||||
|
/**
|
||||||
|
* 撤销按钮名称
|
||||||
|
* @default 撤销
|
||||||
|
*/
|
||||||
|
undoText?: string;
|
||||||
|
/**
|
||||||
|
* 确认按钮名称
|
||||||
|
* @default 确认
|
||||||
|
*/
|
||||||
|
confirmText?: string;
|
||||||
|
/**
|
||||||
|
* 是否水平展示
|
||||||
|
*/
|
||||||
|
horiz?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInputSignatureProps extends FormControlProps {}
|
||||||
|
|
||||||
|
interface IInputSignatureState {
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class InputSignatureComp extends React.Component<
|
||||||
|
IInputSignatureProps,
|
||||||
|
IInputSignatureState
|
||||||
|
> {
|
||||||
|
render() {
|
||||||
|
const {classnames: cx, horiz: horizontal, onChange} = this.props;
|
||||||
|
const props = pick(this.props, [
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'mobileUI',
|
||||||
|
'color',
|
||||||
|
'bgColor',
|
||||||
|
'clearText',
|
||||||
|
'undoText',
|
||||||
|
'confirmText'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Signature
|
||||||
|
classnames={cx}
|
||||||
|
horizontal={horizontal}
|
||||||
|
onChange={onChange}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FormItem({
|
||||||
|
type: 'input-signature',
|
||||||
|
sizeMutable: false
|
||||||
|
})
|
||||||
|
export class InputSignatureRenderer extends InputSignatureComp {
|
||||||
|
static contextType = ScopedContext;
|
||||||
|
|
||||||
|
constructor(props: IInputSignatureProps, context: IScopedContext) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const scoped = context;
|
||||||
|
scoped.registerComponent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
super.componentWillUnmount?.();
|
||||||
|
const scoped = this.context as IScopedContext;
|
||||||
|
scoped.unRegisterComponent(this);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user