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
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
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": {
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"postcss": "^8.4.14",
|
||||
"qs": "6.9.7"
|
||||
"qs": "6.9.7",
|
||||
"smooth-signature": "^1.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/generator": "^7.22.9",
|
||||
|
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/overflow-tpl';
|
||||
@import '../components/pdf_viewer';
|
||||
@import '../components/signature';
|
||||
|
||||
@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 type CodeMirror from 'codemirror';
|
||||
import OverflowTpl from './OverflowTpl';
|
||||
import Signature from './Signature';
|
||||
|
||||
export {
|
||||
NotFound,
|
||||
@ -263,5 +264,6 @@ export {
|
||||
Menu,
|
||||
CodeMirror,
|
||||
CodeMirrorEditor,
|
||||
OverflowTpl
|
||||
OverflowTpl,
|
||||
Signature
|
||||
};
|
||||
|
@ -430,5 +430,8 @@ register('de-DE', {
|
||||
'TimeNow': 'Jetzt',
|
||||
'Steps.step': 'Schritt {{index}}',
|
||||
'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',
|
||||
'Steps.step': 'Step {{index}}',
|
||||
'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': '图标选择',
|
||||
'Steps.step': '第 {{index}} 步',
|
||||
'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 {UserSelectControlSchema} from './renderers/Form/UserSelect';
|
||||
import {JSONSchemaEditorControlSchema} from './renderers/Form/JSONSchemaEditor';
|
||||
import {InputSignatureSchema} from './renderers/Form/InputSignature';
|
||||
import {TableSchema2} from './renderers/Table2';
|
||||
import {
|
||||
BaseSchemaWithoutType,
|
||||
@ -254,6 +255,7 @@ export type SchemaType =
|
||||
| 'diff-editor'
|
||||
| 'office-viewer'
|
||||
| 'pdf-viewer'
|
||||
| 'input-signature'
|
||||
|
||||
// editor 系列
|
||||
| 'editor'
|
||||
@ -461,6 +463,7 @@ export type SchemaObject =
|
||||
| InputGroupControlSchema
|
||||
| ListControlSchema
|
||||
| JSONSchemaEditorControlSchema
|
||||
| InputSignatureSchema
|
||||
| LocationControlSchema
|
||||
| UUIDControlSchema
|
||||
| MatrixControlSchema
|
||||
|
@ -94,6 +94,7 @@ import './renderers/Form/TabsTransferPicker';
|
||||
import './renderers/Form/Group';
|
||||
import './renderers/Form/InputGroup';
|
||||
import './renderers/Form/UserSelect';
|
||||
import './renderers/Form/InputSignature';
|
||||
import './renderers/Grid';
|
||||
import './renderers/Grid2D';
|
||||
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