# MSignature 手写签名与签批

- 来源：[E10 UI 组件库](https://www.e-cology.com.cn/ui/mobile/signature/freepass)
- 平台：移动端
- 维护者：屈正巍
- Issue：http://gitlab.ecology10.cn/FRONTEND/weapp-ui/-/issues?state=all&search=[MSignature]

## 代码演示

### 签名模式

直接引入组件进行使用（常用表单模式）

```jsx
import { Component } from 'react';
import { MSignature, constants } from '@weapp/ui';
import { withRouter } from 'react-router-dom';
import { img1 } from '../mock';

const prefixCls = `${constants.uiPcClsPrefix}-demo-signature`;

interface SignatureDemoProps {
  history?: any;
}

// @ts-ignore
@withRouter
class SignatureDemo extends Component<SignatureDemoProps> {

  state = {
    value: img1,
    value1: img1,
    value2: img1,
    readonly: false
  }

  onChange = (value: string, menuValue?: string) => {
    console.info('onChange', value, 'menuValue', menuValue);
    this.props.history?.go(-1);
  }
  onValueChange = (value: string, menuValue?: string) => {
    console.info('onValueChange', value, 'menuValue', menuValue);
    this.setState({ value })
    this.props.history?.go(-1)
  }
  onValue2Change = (value: string, menuValue?: string) => {
    console.info('onValueChange', value, 'menuValue', menuValue);
    this.setState({ value2: value, readonly: true })
    this.props.history?.go(-1)
  }

  render() {
    return (
      <div className={prefixCls}>
        <p>基础用法</p>
        <MSignature
          inForm
          onChange={this.onChange}
        />
        <br />
        <p>受控</p>
        <MSignature
          inForm
          value={this.state.value}
          onChange={this.onValueChange}
        />
        <br />
        <p>受控后变为只读</p>
        <MSignature
          inForm
          value={this.state.value2}
          onChange={this.onValue2Change}
          readOnly={this.state.readonly}
        />
        <br />
        <p>带默认值</p>
        <MSignature
          inForm
          defaultValue={img1}
          onChange={this.onChange}
        />
        <br />
        <p>只读</p>
        <MSignature
          inForm
          readOnly
          defaultValue={img1}
        />
        <br />
        <p>禁用</p>
        <MSignature
          inForm
          disabled
          defaultValue={img1}
        />
        <br />
        <p>隐藏图片上传模式</p>
        <MSignature
          inForm
          onChange={this.onChange}
          hideUploadImage
        />
        <br />
      </div>
    )
  }
}

export default SignatureDemo;
```

### 签批模式

直接引入组件进行使用（常用表单模式）

```jsx
import { Component } from 'react';
import { MSignature, constants } from '@weapp/ui';
import { withRouter } from 'react-router-dom';
import { img1 } from '../mock';

const prefixCls = `${constants.uiPcClsPrefix}-demo-signature`;

interface SignatureDemoProps {
  history?: any;
}

// @ts-ignore
@withRouter
class SignatureDemo extends Component<SignatureDemoProps> {

  state = {
    value: img1,
    value1: img1,
    value2: img1,
    readonly: false
  }

  onChange = (value: string, menuValue?: string) => {
    console.info('onChange', value, 'menuValue', menuValue);
    this.props.history?.go(-1);
  }
  onValueChange = (value: string) => {
    console.info('onValueChange', value);
    this.setState({ value })
    this.props.history?.go(-1)
  }
  onValue2Change = (value: string, menuValue?: string) => {
    console.info('onValueChange', value, 'menuValue', menuValue);
    this.setState({ value2: value, readonly: true })
    this.props.history?.go(-1)
  }

  render() {
    return (
      <div className={prefixCls}>
        <p>基础用法</p>
        <MSignature
          mode={'review'}
          inForm
          onChange={this.onChange}
        />
        <br />
        <p>受控</p>
        <MSignature
          mode={'review'}
          inForm
          value={this.state.value}
          onChange={this.onValueChange}
        />
        <br />
        <p>受控后变为只读</p>
        <MSignature
          mode={'review'}
          inForm
          value={this.state.value2}
          onChange={this.onValue2Change}
          readOnly={this.state.readonly}
        />
        <br />
        <p>带默认值</p>
        <MSignature
          mode={'review'}
          inForm
          defaultValue={img1}
          onChange={this.onChange}
        />
        <br />
        <p>只读</p>
        <MSignature
          mode={'review'}
          inForm
          readOnly
          defaultValue={img1}
          onChange={this.onChange}
        />
        <br />
        <p>禁用</p>
        <MSignature
          mode={'review'}
          inForm
          disabled
          defaultValue={img1}
          onChange={this.onChange}
        />
        <br />
      </div>
    )
  }
}

export default SignatureDemo;
```

### 自定义触发器

通过自定义触发器调用，需要自定义配置路由

```jsx
import { Component } from 'react';
import { MSignature, Button, constants, utils } from '@weapp/ui';
import { withRouter } from 'react-router-dom';
import { img1 } from '../mock';
const { formatParentPath } = utils;

const prefixCls = `${constants.uiPcClsPrefix}-demo-signature`;

interface SignatureDemoProps {
  history?: any;
}

// @ts-ignore
@withRouter
class SignatureDemo extends Component<SignatureDemoProps> {

  private path0 = `${formatParentPath(this.props as any)}/mSignature0`
  private path1 = `${formatParentPath(this.props as any)}/mSignature1`

  showMSignatureByRoute = () => this.props.history.push(this.path0);
  showMSignatureByRoute1 = () => this.props.history.push(this.path1);

  onChange = (value: string, menuValue?: string) => {
    console.info('onChange', value, 'menuValue', menuValue);
    this.props.history?.go(-1);
  }
  onCustomCancel = () => {
    console.info('onCustomCancel');
    this.props.history?.go(-1)
  }

  render() {
    return (
      <div className={prefixCls}>
        <p>默认path</p>
        <MSignature
          defaultValue={img1}
          path={this.path0}
          onChange={this.onChange}
          onCustomCancel={this.onCustomCancel}
          imgDialogProps={{
            title: 'mytitle'
          }}
          canvasDialogProps={{
            title: 'title'
          }}
        >
          <Button
            onClick={this.showMSignatureByRoute}
            type="primary"
          >
            手写签名0
          </Button>
        </MSignature>
        <br />
        <p>触发器写到外部</p>
        <MSignature
          defaultValue={img1}
          path={this.path1}
          onChange={this.onChange}
          imgDialogProps={{
            title: 'mytitle'
          }}
          canvasDialogProps={{
            title: 'title'
          }}
          onCustomCancel={this.onCustomCancel}
        >
        </MSignature>
        <Button
          onClick={this.showMSignatureByRoute1}
          type="primary"
        >
          手写签名1
        </Button>
      </div>
    );
  }
}

export default SignatureDemo;
```

### 删除状态

存在服务器上的文件被删除的状态

```jsx
import { Component } from 'react';
import { MSignature, constants } from '@weapp/ui';
import { withRouter } from 'react-router-dom';
import { img1 } from '../mock';

const prefixCls = `${constants.uiPcClsPrefix}-demo-signature`;

interface SignatureDemoProps {
  history?: any;
}

// @ts-ignore
@withRouter
class SignatureDemo extends Component<SignatureDemoProps> {

  state = {
    value: img1,
    value2: img1,
    value3: img1,
    fileStatus: 0,
    fileStatus2: 1,
    fileStatus3: 2
  }

  onValueChange = (value: string, menuValue?: string) => {
    console.info('onValueChange', value, 'menuValue', menuValue);
    this.setState({ value })
    this.props.history?.go(-1)
  }
  onValueChange2 = (value: string, menuValue?: string) => {
    console.info('onValueChange', value, 'menuValue', menuValue);
    this.setState({ value2: value, fileStatus2: 0 })
    this.props.history?.go(-1)
  }
  onValueChange3 = (value: string, menuValue?: string) => {
    console.info('onValueChange', value, 'menuValue', menuValue);
    this.setState({ value3: value, fileStatus3: 0 })
    this.props.history?.go(-1)
  }

  render() {
    const { value, value2, value3, fileStatus, fileStatus2, fileStatus3 } = this.state
    return (
      <div className={prefixCls}>
        <p>基础用法</p>
        <MSignature
          inForm
          value={value}
          onChange={this.onValueChange}
          fileStatus={fileStatus}
        />
        <p>删除状态</p>
        <MSignature
          inForm
          value={value2}
          onChange={this.onValueChange2}
          fileStatus={fileStatus2}
        />
        <p>文件不存在状态</p>
        <MSignature
          inForm
          value={value3}
          onChange={this.onValueChange3}
          fileStatus={fileStatus3}
        />
      </div>
    )
  }
}

export default SignatureDemo;
```

### 电子签名

当传入eSignature时，可以在一级路由快速填入value值

```jsx
import { Component } from 'react';
import { AnyObj, MSignature, constants } from '@weapp/ui';
import { withRouter } from 'react-router-dom';
import { img1 } from '../mock';

const prefixCls = `${constants.uiPcClsPrefix}-demo-signature`;

interface SignatureDemoProps {
  history?: any;
}

// @ts-ignore
@withRouter
class SignatureDemo extends Component<SignatureDemoProps> {

  onChange = (value: string, menuValue?: string) => {
    console.info('onChange', value, 'menuValue', menuValue);
    this.props.history?.go(-1);
  }

  handle = (res: AnyObj) => {
    return img1
  }

  render() {
    return (
      <div className={prefixCls}>
        <p>显示电子签名</p>
        <MSignature
          inForm
          eSignature={img1}
          onChange={this.onChange}
        />
        <p>异步传入电子签名</p>
        <MSignature
          inForm
          getESignature={{
            requestParams: {
              url: '/api/hrm/common/browser/prop/resource',
              params: {
                "browserMultiple": true
              },
              method: 'post'
            },
            handle: this.handle
          }}
          onChange={this.onChange}
        />
        <p>异步传入电子签名(缓存)</p>
        <MSignature
          inForm
          getESignature={{
            requestParams: {
              url: '/api/hrm/common/browser/prop/resource',
              params: {
                "browserMultiple": true
              },
              method: 'post'
            },
            handle: this.handle,
            useCache:true
          }}
          onChange={this.onChange}
        />
      </div>
    );
  }
}

export default SignatureDemo;
```

### 扫码签名签批

自定义二维码

```jsx
import { Component, ReactNode } from 'react';
import { MSignature, constants, MButton, Qrcode } from '@weapp/ui';
import { img1 } from '../mock';
import { handleSiteDomain } from "@weapp/utils";
const qrcodeSettings = {
  src: "/favicon.ico",
  height: 24,
  width: 24,
  excavate: true,
}

interface SignatureDemoProps {
  history?: any;
}

const prefixCls = `${constants.uiPcClsPrefix}-demo-signature`;

class SignatureDemo extends Component<SignatureDemoProps> {

  goBack = () => this.props?.history.go(-1)
  onChange = (value: string) => {
    console.info('onChange', value)
    this.props.history?.go(-1);
  }
  customPreview = (value: string) => (
    <div>
      <p>2019.8.3</p>
      <h5>艾伦</h5>
      <img style={{ opacity: 0.3 }} className="ui-signature-canvas" src={value} alt='' />
    </div>
  )
  qrcodeCustomRender = (node: ReactNode) => {
    return <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' }}>
      <Qrcode
        url={handleSiteDomain("https://weapp.eteams.cn/ui")}
        size={150}
        bgColor={"#ffffff"}
        fgColor={"#000000"}
        level={"L"}
        imageSettings={qrcodeSettings}
      />
      展示用户自定义内容
      <div style={{ height: 50, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>展示用户自定义内容：此处可以采用组件库的二维码组件</div>
      {/* {node} */}
      {<MButton type={'danger'} onClick={this.goBack}>自定义返回按钮</MButton>}
    </div>
  }
  render() {
    return (
      <div className={prefixCls}>
        <h2>扫码签名</h2>
        <p>自定义二维码url</p>
        <MSignature
          inForm
          qrcodeUrl={img1}
          qrcodeStyle={{ maxWidth: '100%' }}
          onChange={this.onChange}
        />
        <br />
        <p>自定义二维码dom</p>
        <MSignature
          inForm
          qrcode={(<div><img src={img1} alt={img1} style={{ maxWidth: '100%' }} /></div>)}
          onChange={this.onChange}
        />
        <br />
        <p>自定义二维码function</p>
        <MSignature
          inForm
          defaultValue={img1}
          qrcodeCustomRender={this.qrcodeCustomRender}
          hideQRcodeBackButton
          onChange={this.onChange}
        />
        <br />
        <h2>扫码签批</h2>
        <p>自定义二维码url</p>
        <MSignature
          inForm
          mode={'review'}
          qrcodeUrl={img1}
          qrcodeStyle={{ maxWidth: '100%' }}
          onChange={this.onChange}
        />
        <br />
        <p>自定义二维码dom</p>
        <MSignature
          inForm
          mode={'review'}
          qrcode={(<div><img src={img1} alt={img1} style={{ maxWidth: '100%' }} /></div>)}
          onChange={this.onChange}
        />
        <br />
        <p>自定义二维码function</p>
        <MSignature
          inForm
          mode={'review'}
          defaultValue={img1}
          qrcodeCustomRender={this.qrcodeCustomRender}
          hideQRcodeBackButton
          onChange={this.onChange}
        />
        <br />
      </div>
    )
  }
}

export default SignatureDemo;
```

### 竖屏模式

部分机型可以开启竖屏模式进行签批

```jsx
import { Component } from 'react';
import { MSignature, constants } from '@weapp/ui';
import { withRouter } from 'react-router-dom';
import { img1 } from '../mock';

const prefixCls = `${constants.uiPcClsPrefix}-demo-signature`;

interface SignatureDemoProps {
  history?: any;
}

// @ts-ignore
@withRouter
class SignatureDemo extends Component<SignatureDemoProps> {

  state = {
    value: img1,
    value1: img1,
    value2: img1,
    readonly: false
  }

  onChange = (value: string, menuValue?: string) => {
    console.info('onChange', value, 'menuValue', menuValue);
    this.props.history?.go(-1);
  }
  onValueChange = (value: string, menuValue?: string) => {
    console.info('onValueChange', value, 'menuValue', menuValue);
    this.setState({ value })
    this.props.history?.go(-1)
  }
  onValue2Change = (value: string, menuValue?: string) => {
    console.info('onValueChange', value, 'menuValue', menuValue);
    this.setState({ value2: value, readonly: true })
    this.props.history?.go(-1)
  }

  render() {
    return (
      <div className={prefixCls}>
        <p>基础用法</p>
        <MSignature
          inForm
          onChange={this.onChange}
          useVerticalMode
        />
        <br />
        <p>受控</p>
        <MSignature
          inForm
          value={this.state.value}
          onChange={this.onValueChange}
          useVerticalMode
        />
        <br />
        <p>受控后变为只读</p>
        <MSignature
          inForm
          value={this.state.value2}
          onChange={this.onValue2Change}
          readOnly={this.state.readonly}
          useVerticalMode
        />
        <br />
        <p>带默认值</p>
        <MSignature
          inForm
          defaultValue={img1}
          onChange={this.onChange}
          useVerticalMode
        />
      </div>
    )
  }
}

export default SignatureDemo;
```

## API

### 注意：

```
签名组件与
1.顶部title以及图标文字不同，默认预览的长宽比例不同
2.签批组件默认进入到签批层，而签名组件默认到中间层（签批组件singleMode默认为true，签名组件默认为false）
3.签批组件没有上传功能以及电子签名功能，签名组件有 
4.两种类型组件都会默认进入横屏模式，可以通过isHorizontal来控制
5.2024.09.18 注意上述2 -> 目前签批与签名模式交互替换，已经取消中间层，singleMode此api废弃
```

### 类型说明：（请从@weapp/ui 中导出）

```typescript
type menuIndexType = '0' | '1' | '2' | '3' // '0':绘制签名,'1':扫码签名,'2':电子签名,'3':上传签名
/**
 * 文件状态
 * 0 或者没有值: 附件正常状态
 * 1: 删除状态【文件和当前业务去已除了关联关系，包括文件已经被彻底删除】
 * 2: 文件不存在状态【跟进此附件id，文件服务也查询不到任何信息】
 */
type fileStatus = number | string
```

### 参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| disabled | 选择器是否禁用 | boolean | 否 | false |
| readOnly | 选择器是否完全只读 | boolean | 否 | false |
| className | 自定义类名 | string | 否 |  |
| style | 自定义样式 | CSSProperties | 否 |  |
| inForm | 是否在表单中 | string | 否 | false |
| value | base64 格式的图片值 | string | 否 |  |
| defaultValue | base64 格式的图片值 | string | 否 |  |
| eSignature | 电子签名值 | string | 否 |  |
| getESignature | 异步获取电子签名值 | GetESignature | 否 |  |
| singleMode | 是否开启简易模式（直接跳转签批层，2024.09.18交互模式替换此api废弃） | boolean | 否 | 签名默认为false，签批默认为true |
| uploadParams | 上传附件参数 | 参考 MUpload 组件参数 | 否 |  |
| confirmDialogProps | 清空确认框的props | 参考 MDialog 组件参数 | 否 |  |
| emptyInfoDialogProps | 空白提示框的props | 参考 MDialog 组件参数 | 否 |  |
| imgDialogProps | 图像路由层整体的props | 参考 MDialog 组件参数 | 否 |  |
| canvasDialogProps | 绘制路由层整体的props | 参考 MDialog 组件参数 | 否 |  |
| canvasDialogFooterCustomRender | 绘制路由层底部按钮的自定义复写 | (props?: any, content?: any, events?: any) | 否 |  |
| onChange | 签批确认后回调 | (value: string, menuIndex?: menuIndexType) => void | 否 |  |
| onCancel | 点击取消时的回调（默认回回退路由） | () => void | 否 |  |
| onCustomCancel | 点击取消时的回调 (自定义取消事件，当存在时优先走此方法而不走onCancel) | () => void | 否 |  |
| routeId | 用作 corsLink，不指定时，组件内部随机生成 | string | 否 |  |
| onUploadChange | 上传成功后的回调 | (data?: UploadData[], uploading?: UploadData[], reUpload?: boolean) => void | 否 |  |
| showScopeControl | 是否需要画布缩放 | boolean | 否 | false |
| showScaleControl | 是否需要画布尺寸控制 | boolean | 否 | true |
| mode | 控制签名模式或签批模式 | 'name' \| 'review' | 否 | 'name' |
| openHorizontal | 是否开启横屏模式 | boolean | 否 | false |
| isHorizontal | 是否默认进入横屏模式 | boolean | 否 | false |
| showHorizontalIcon | 是否展示横屏竖屏切换按钮 | boolean | 否 | false |
| onClickPrevImg | 点击预览图片的回调 | () => void | 否 |  |
| readOnlyPreventDefaultClickEvent | 只读模式是否禁用原生的点击预览事件 | boolean | 否 | false |
| onCanvasClear | 弹框中点击清空的回调 | ()=>void | 否 |  |
| readOnlyImgFileId | 用于只读模式下图片预览的下载功能 | string | 否 |  |
| showHorizontalInitPlaceholder | 是否展示横屏默认placeholder | boolean | 否 | true |
| showHorizontalTitle | 是否展示横屏默认title | boolean | 否 | true |
| showDelete | 是否显示删除预览按钮 | boolean | 否 | true |
| fileStatus | 文件状态 | number \| string | 否 |  |
| hideCorsESignature | 是否隐藏选择模块电子签 | boolean | 否 |  |
| corsESignatureProps | 模块电子签参数 | any | 否 |  |
| corsESignatureDialogProps | 模块电子签dialog参数 | any | 否 |  |
| loginFree | 是否免登录 | boolean | 否 |  |
| hideUploadImage | 隐藏图片上传开关 | boolean | 否 | false |
| hideQRcodeBackButton | 是否隐藏移动端扫码签名或签批的回退按钮 | boolean | 否 | false |
| useVerticalMode | 是否强制开启竖屏模式 | boolean | 否 | false |
### 特殊类型说明

#### getESignature与eSignature 不可同时传入

```typescript
type GetESignature ={
  /**
   * 请求参数与utils中的request一致
   */
  requestParams:RequestOptionConfig,
  /**
   * @param res 
   * @returns eSignature
   * 自定义处理方法，传入异步接口res结果，返回处理后的eSignature
   */
  handle:(res:AnyObj)=>string,
    //是否启用缓存
  useCache?:boolean
}
```
