# MCardDetailForm 事项卡片

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

## 代码演示

### 基础用法

直接引入组件进行使用

```jsx
import { Component } from 'react';
import {
  MCardDetailForm, constants, CardDetailFormItemProps, CardDetailFormStore,
  FormDatas, FormLayoutType, MDialog, MDialogButtonType
} from '@weapp/ui';
import { observer } from 'mobx-react';

const { uiPcClsPrefix } = constants;
const { ButtonGroup } = MDialog;
interface MCardDetailFormDemoProps {
}

const prefixCls = `${uiPcClsPrefix}-demo-card-detail-form`;

const items: CardDetailFormItemProps = {
  'normal': {
    itemType: 'INPUT',
  },
  'alwaysShow': {
    itemType: 'INPUT',
  },
  'collapsible': {
    itemType: 'INPUT',
    required: true,
  },
  'tags_alwaysShow': {
    itemType: 'INPUT',
    placeholder: 'task',
    showType: 'alwaysShow',
  },
  'tags_notEmpty': {
    itemType: 'INPUT',
    placeholder: 'task',
    showType: 'notEmpty',
  },
  'tags_collapsible': {
    itemType: 'INPUT',
    placeholder: 'task',
    showType: 'collapsible',
    autoFocus: true,
    otherParams: {
    },
  },
  'follow': {
    itemType: 'INPUT',
    value: '1',
  },
}

let layout: FormLayoutType[] = [
  [
    { id: '1', label: 'A区', items: ['normal'], labelSpan: 6, hide: false, groupId: 'normal' },
  ],
  [
    { id: '3', label: 'B区', items: ['alwaysShow'], labelSpan: 6, hide: false, groupId: 'alwaysShow' },
  ],
  [
    { id: '5', label: 'C区', items: ['collapsible'], labelSpan: 6, hide: false, groupId: 'collapsible' },
  ],
  [
    { id: '7', label: 'D区-始终展示', items: ['tags_alwaysShow'], icon: 'Icon-Associated-tasks', labelSpan: 6, hide: false, groupId: 'tags' },
  ],
  [
    { id: '9', label: 'D区-有值时展示', items: ['tags_notEmpty'], icon: 'Icon-Associated-tasks', labelSpan: 6, hide: false, groupId: 'tags' },
  ],
  [
    { id: '10', label: 'D区-跟随展示', items: ['tags_collapsible'], icon: 'Icon-Associated-tasks', labelSpan: 6, hide: false, groupId: 'tags' },
  ],
  [
    { id: '12', label: 'E区', items: ['follow'], labelSpan: 6, hide: false, groupId: 'follow' },
  ]
]

const cardDetailFormStore = new CardDetailFormStore();

@observer
class CardDetailFormDemo extends Component<MCardDetailFormDemoProps> {
  componentDidMount() {
    this.initCardDetailForm().then(() => {
      cardDetailFormStore.updateDatas({
        tags_notEmpty_normal: '1'
      });
    })
  }
  initCardDetailForm = () => {
    return new Promise((resolve) => { // 模拟接口前后顺序
      cardDetailFormStore.init({ items, layout, data: {}, isMobile: true }); // 先初始化
      resolve({});
    })
  }
  onChange = (groupId: string, value?: FormDatas) => {
    console.log('onChange', groupId, value);
  }
  onBlur = (groupId: string, value?: FormDatas) => {
    console.log('onBlur', groupId, value);
  }
  getData = () => console.log(cardDetailFormStore.getFormDatas());
  validate = () => cardDetailFormStore.validate();
  resetForm = () => cardDetailFormStore.reset();
  updateDatas = () => {
    cardDetailFormStore.updateDatasNotReFormat('tags', {
      'tags_alwaysShow': 'tags_alwaysShow'
    })
    cardDetailFormStore.updateDatasNotReFormat('normal', {
      'normal': 'normal'
    })
  }
  render() {
    const typePrimary: MDialogButtonType = 'primary';
    const btns = [
      { id: 'getData', content: '获取数据', type: typePrimary, onClick: this.getData },
      { id: 'validate', content: '校验', onClick: this.validate },
      { id: 'reset', content: '重置', onClick: this.resetForm },
      { id: 'update', content: '更新数据', onClick: this.updateDatas },
    ]
    const formProps = {
      normal: { isHoldRight: true },
      alwaysShow: { isHoldRight: true },
      collapsible: { isHoldRight: true },
      tags: { isHoldRight: true },
      follow: { isHoldRight: true },
    };
    return (
      <div className={prefixCls}>
        <MCardDetailForm
          store={cardDetailFormStore}
          onChange={this.onChange}
          onBlur={this.onBlur}
          formProps={formProps}
        />
        <div className={`${prefixCls}-footer`}>
          <ButtonGroup datas={btns} />
        </div>
      </div>
    );
  }
}

export default CardDetailFormDemo
```

### 设置url模式

设置url模式

```jsx
import { Component } from 'react';
import {
  MCardDetailForm, constants, CardDetailFormItemProps, CardDetailFormStore,
  FormDatas, FormLayoutType, MDialog, MDialogButtonType, CardDetailFormAllDatas
} from '@weapp/ui';
import { observer } from 'mobx-react';

const { uiPcClsPrefix } = constants;
const { ButtonGroup } = MDialog;
interface MCardDetailFormDemoProps {
}

const prefixCls = `${uiPcClsPrefix}-demo-card-detail-form`;

const cardDetailFormStore = new CardDetailFormStore();

@observer
class CardDetailFormDemo extends Component<MCardDetailFormDemoProps> {
  getData = () => console.log(cardDetailFormStore.getFormDatas());
  validate = () => cardDetailFormStore.validate();
  resetForm = () => cardDetailFormStore.reset();
  beforeInit = (datas: CardDetailFormAllDatas) => {
    datas?.items && Object.keys(datas?.items).forEach((itemkey) => {
      datas.items[itemkey] = {
        ...datas.items[itemkey],
        required: true,
      }
    })
    return datas;
  }
  render() {
    const typePrimary: MDialogButtonType = 'primary';
    const btns = [
      { id: 'getData', content: '获取数据', type: typePrimary, onClick: this.getData },
      { id: 'validate', content: '校验', onClick: this.validate },
      { id: 'reset', content: '重置', onClick: this.resetForm },
    ]
    return (
      <div className={prefixCls}>
        <MCardDetailForm
          className="testCardDetailForm"
         
          store={cardDetailFormStore}
          mode={true}
          url={'/api/app/bcw/form/defaultCardDetailForm'}
          module={'bcw'}
          saveDataParams={{
            objectId: "4032202098417870868",
            objectModule: "workflow",
            objectModuleId: 0,
          }}
          saveParams={{
            module: 'workflow',
            objectName: 'test',
          }}
          dataParams={{
            module: 'workflow',
            objectId: "4032202098417870868",
          }}
          // prohibitRealTimeSaving
          cardDetailDialogParams={{
            objectId: "4032202098417870868",
            objectModule: "workflow",
          }}
          beforeInit={this.beforeInit}
        />
        <div className={`${prefixCls}-footer`}>
          <ButtonGroup datas={btns} />
        </div>
      </div>
    );
  }
}

export default CardDetailFormDemo
```

### 非url模式(公共关联项目)

非url模式(公共关联项目)

```jsx
import { Component } from 'react';
import {
  MCardDetailForm, constants, CardDetailFormStore, MDialog, MDialogButtonType
} from '@weapp/ui';
import { observer } from 'mobx-react';

const { uiPcClsPrefix } = constants;
const { ButtonGroup } = MDialog;
interface MCardDetailFormDemoProps {
}

const prefixCls = `${uiPcClsPrefix}-demo-card-detail-form`;

const cardDetailFormStore = new CardDetailFormStore();

@observer
class CardDetailFormDemo extends Component<MCardDetailFormDemoProps> {
  componentDidMount() {
    cardDetailFormStore.doRequest('/api/app/bcw/form/defaultCardDetailForm', true, {
			store: cardDetailFormStore,
			module: 'bcw',
			saveDataParams: {
        objectId: "4032202098417870868",
        objectModule: "workflow",
        objectModuleId: 0,
			},
			saveParams: {
        module: 'workflow',
        objectName: 'test',
			},
			dataParams: {
        module: 'workflow',
        objectId: "4032202098417870868",
			},
			cardDetailDialogParams: {
        objectId: "4032202098417870868",
        objectModule: "workflow",
			}
		}).then(() => {
			console.log('afterDoRequest');
		})
  }
  getData = () => console.log(cardDetailFormStore.getFormDatas());
  validate = () => cardDetailFormStore.validate();
  resetForm = () => cardDetailFormStore.reset();
  render() {
    const typePrimary: MDialogButtonType = 'primary';
    const btns = [
      { id: 'getData', content: '获取数据', type: typePrimary, onClick: this.getData },
      { id: 'validate', content: '校验', onClick: this.validate },
      { id: 'reset', content: '重置', onClick: this.resetForm },
    ]
    return (
      <div className={prefixCls}>
        <MCardDetailForm
          className="testCardDetailForm"
         
          store={cardDetailFormStore}
          module={'bcw'}
        />
        <div className={`${prefixCls}-footer`}>
          <ButtonGroup datas={btns} />
        </div>
      </div>
    );
  }
}

export default CardDetailFormDemo
```

### 自定义请求

自定义请求

```jsx
import { Component } from 'react';
import {
  MCardDetailForm, CardDetailFormStore,
} from '@weapp/ui';
import { observer } from 'mobx-react';
import { request, RequestOptionConfig } from '@weapp/utils';

interface MCardDetailFormDemoProps {
}

const cardDetailFormStore = new CardDetailFormStore();

@observer
class CardDetailFormDemo extends Component<MCardDetailFormDemoProps> {
  fetchData = (options: RequestOptionConfig) => {
    // options内输出的url内容在network对应可抓到
    return request({
      ...options, headers: {
        ...options.headers,
        testHeaderId: '1',
      }
    });
  }
  render() {
    return (
        <MCardDetailForm
          className="testCardDetailForm"
         
          store={cardDetailFormStore}
          mode={true}
          url={'/api/app/bcw/form/defaultCardDetailForm'}
          module={'bcw'}
          saveDataParams={{
            objectId: "4032202098417870868",
            objectModule: "workflow",
            objectModuleId: 0,
          }}
          saveParams={{
            module: 'workflow',
            objectName: 'test',
          }}
          dataParams={{
            module: 'workflow',
            objectId: "4032202098417870868",
          }}
          // prohibitRealTimeSaving
          cardDetailDialogParams={{
            objectId: "4032202098417870868",
            objectModule: "workflow",
          }}
          fetchData={this.fetchData}
        />
    );
  }
}

export default CardDetailFormDemo
```

### 只读模式

只读模式

```jsx
import { Component } from 'react';
import {
  MCardDetailForm, constants, CardDetailFormItemProps, CardDetailFormStore,
  FormDatas, FormLayoutType, MDialog, MDialogButtonType, CardDetailFormAllDatas
} from '@weapp/ui';
import { observer } from 'mobx-react';

const { uiPcClsPrefix } = constants;
const { ButtonGroup } = MDialog;
interface MCardDetailFormDemoProps {
}

const prefixCls = `${uiPcClsPrefix}-demo-card-detail-form`;

const cardDetailFormStore = new CardDetailFormStore();

@observer
class CardDetailFormDemo extends Component<MCardDetailFormDemoProps> {
  getData = () => console.log(cardDetailFormStore.getFormDatas());
  validate = () => cardDetailFormStore.validate();
  resetForm = () => cardDetailFormStore.reset();
  render() {
    const typePrimary: MDialogButtonType = 'primary';
    const btns = [
      { id: 'getData', content: '获取数据', type: typePrimary, onClick: this.getData },
      { id: 'validate', content: '校验', onClick: this.validate },
      { id: 'reset', content: '重置', onClick: this.resetForm },
    ]
    return (
      <div className={prefixCls}>
        <MCardDetailForm
          className="testCardDetailForm"
         
          store={cardDetailFormStore}
          mode={true}
          url={'/api/app/bcw/form/defaultCardDetailForm'}
          module={'bcw'}
          saveDataParams={{
            objectId: "4032202098417870868",
            objectModule: "workflow",
            objectModuleId: 0,
          }}
          saveParams={{
            module: 'workflow',
            objectName: 'test',
          }}
          dataParams={{
            module: 'workflow',
            objectId: "4032202098417870868",
          }}
          // prohibitRealTimeSaving
          cardDetailDialogParams={{
            objectId: "4032202098417870868",
            objectModule: "workflow",
          }}
          globalReadOnly
          hideSubBtn
        />
        <div className={`${prefixCls}-footer`}>
          <ButtonGroup datas={btns} />
        </div>
      </div>
    );
  }
}

export default CardDetailFormDemo
```

## API

### 参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |  |
| --- | --- | --- | --- | --- | --- |
| store | 事项卡片组件实例 | CardDetailFormStore | 是 |  |  |
| onChange | 表单字段值改变事件 | (groupId: string, value?: FormDatas, otherParams?: any) => void, | 否 |  |  |
| onBlur | 表单字段失去焦点事件 | (groupId: string, value?: FormDatas, otherParams?: any) => void, | 否 |  |  |
| formProps | 表单自定义设置 | CardDetailFormCustomProps | 否 |  |  |
| showBtnContent | 自定义折叠面板显示按钮内容 | string \| ReactNode | 否 |  |  |
| hideBtnContent | 自定义折叠面板隐藏按钮内容 | string \| ReactNode | 否 |  |  |
| showSubBtnContent | 自定义内部折叠面板显示按钮内容 | string \| ReactNode | 否 |  |  |
| hideSubBtnContent | 自定义内部折叠面板隐藏按钮内容 | string \| ReactNode | 否 |  |  |
| url | 支持 url 配置模式 | string | 否 |  |  |
| method | 支持 method 配置(url 模式) | string | 否 |  |  |
| params | 支持接口传参配置(url 模式) | any | 否 |  |  |
| prohibitRealTimeSaving | 禁止实时保存关联数据 | boolean | 否 |  |  |
| customGrouping | 开启自定义分组(开启后，布局分组按照 init 方法传给组件的 groups 数据分布，url 模式，通过 props 设置，非 url 模式，在 init 方法处理) | boolean | 否 |  |  |
| cardDetailDialogParams | 打开事项卡片弹框附加参数(同Browser中的cardDetailDialogParams配置) | object | 否 |  |  |
| afterInit | 初始化后置事件(仅mode=true模式) | () => void | 否 |  |  |
| beforeInit | 初始化Form前置事件(仅mode=true模式或doRequest使用) | (datas: CardDetailFormAllDatas) => Promise<CardDetailFormAllDatas> \| CardDetailFormAllDatas | 否 |  |  |
| openLoading | 开启 CardDetailForm 的loading效果 | boolean | 否 |  |  |
| afterUpdateRelevanceDatas | 关联数据更新后回调事件 | (datas: AnyObj) => void | 否 |  |  |
| fetchData | 自定义请求数据(mode=true模式时设置, 非mode=true，在doRequest方法内传入) | (options: RequestOptionConfig) => RequestPromise | 否 |  |  |
#### CardDetailForm.getShowGroupId API说明

```
CardDetailForm 公共规则：获取字段的groupId
getShowGroupId: (item: FormItemProps) => string;
```

#### cardDetailDialogParams例子

```
  {
    objectId: "4032202098417870868", // 卡片id
    objectModule: "document", // 模块
  }
```

#### CardDetailFormCustomProps 参数说明如下：

```
CardDetailForm组件内部共四个分组，分别为 normal、alwaysShow、collapsible、tags、follow

formProps参数支持设置以上三个分组的Form组件自定义参数，即normal、alwaysShow、collapsible、follow，tags分组目前不支持

formProps: CardDetailFormCustomProps = {
  normal: { onChange: this.onChange }, // onChange仅为参考，实际单个formProps支持配置除store之外的所有Form组件参数
  alwaysShow: {},
  collapsible: {},
}
```

#### layout 及 items 配置说明

```
该组件共涉及五个Form组件，即五个分组，groupId分别为normal(A区)、alwaysShow(B区)、collapsible(C区)、tags(D区)、follow(E区)。
移动端无D区字段，联动显示至其他区域的需求，因此规则与PC端不一致
移动端A、B、C、D、E各分组各自独立，且包含两层折叠面板，其中C、D区跟随外部折叠面板显示或隐藏，其中D区的有数据时展示及始终折叠展示类型的字段还跟随内部折叠区域显示或隐藏

一、items配置说明
items配置基本与Form组件一致，另新增以下参数：
1. showType，配置项如下，且仅在D区，也就是groupId = tags分组有效
showType：可配置为'alwaysShow' | 'notEmpty' | 'collapsible' 分别为，始终展示，有数据时展示、始终折叠
2.otherParams内新增cardDetailFormProps配置，用于自定义权限校验
```

cardDetailFormProps: {

```
targetModule: ik, // 保存数据时，浏览框数据内存储的targetModule

module: 'ebuilder', //模块标识，用于判断是否有license权限、是否未免费版本等
```

}

```
二、layout配置说明
layout配置与Form组件一致

三、注意：layout中hide字段，设置默认的字段显示或者隐藏。当该字段不需要展示时，请在初始化组件的时候将hide设置为true，或者不传该字段给组件；需要默认显示该字段时，需设置hide为false

四、一般需求，D区一行仅包含一种showType字段类型。但Form支持一行配置多个字段，则如果一行包含多种showType，则规则为collapsible > notEmpty > alwaysShow, 即一行内包含三种类型字段时，以优先级最高的类型为准。

五、init方法说明
init方法参数配置：
1.必须有layout、items、data，用于表单布局，group按照组件内部处理即可
2.组件分为自行调用初始化及通过url配置模式，若为自定调用初始化的使用方式，请在init的时候加上hoverEdit及isMoble参数配置。其中hoverEdit用于控制是否默认开始数据移入显示的效果，
isMobile用于区分是pc端还是移动端
3.isMobile说明：若未使用url模式，且D区有自定义的关联字段，则必须配置该参数，用于判断pc端非url模式，默认初始化有值才显示的自定义关联字段布局。
4.若满足第三点情况，但不配置isMobile参数为false，也可单独使用CardDetailFormStore的updateDatas方法自行处理。
5.customGrouping说明：开启后，布局分组按照init方法传给组件的groups数据分布，url模式，通过props设置，非url模式，在init方法处理

六、自定义分组说明（customGrouping = true）
默认groups数据如下，id、custom、visible参数与布局联动相关，不可修改。
因此，开启自定义分组后，组件仅支持修改分组标题，即给到组件的groups数据中仅对应分组的title参数生效，其他参数不会生效
const groups = [
  { id: 'normal', title: '', custom: false, visible: true },
  { id: 'alwaysShow', title: '', custom: true, visible: true },
  { id: 'collapsible', title: '', custom: true, visible: true },
  { id: 'follow', title: '', custom: true, visible: true },
  { id: 'tags', title: '', custom: true, visible: true },
]
```

#### CardDetailFormStore API 说明

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| init | 初始化任务卡片(仅初始化Form布局) | (allDatas: CardDetailFormAllDatas) => void | 否 |  |
| getFormDatas | 获取任务卡片表单数据 | () => FormDatas | 否 |  |
| updateDatasNotReFormat | 更新任务卡片表单数据(不包含重新排版功能) | (datas: FormDatas) => void | 否 |  |
| reset | 重置任务卡片表单数据 | () => void | 否 |  |
| validate | 校验任务卡片表单数据 | () => void | 否 |  |
| doSave | 保存关联字段数据 | () => Promise |  |  |
| doRequest | 初始化任务卡片(包含请求接口及公共关联浏览框字段处理逻辑) | (url: string, isMobile: boolean, props: CardDetailFormProps & MCardDetailFormProps) => Promise\ | 否 |  |
| validateByGroupId | 校验任务卡片表单数据(按照分组校验、按照自定义校验配置校验) | (groupId: string, customRules?: AnyObj) => boolean | 否 |  |
| secretLevelStore | 密级数据过滤store | SecretLevelStoreType | 否 |  |
#### SecretLevelStore API 说明

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| validateSecretLevel | 密级校验方法 | (secretLevel: number, secretLevelValidity: string, prohibitRealTimeSaving?: boolean) => void | 否 |  |
#### CardDetailFormAllDatas 说明

```
interface CardDetailFormAllDatas {
  items: CardDetailFormItemProps,
  layout: FormLayoutType[],
  data: FormDatas,
  isMobile?: boolean,
  groups?: Array<FormGroupProps>,
  module?: string,
  saveDataParams?: any,
  dataParams?: any,
  customGrouping?: boolean,
  /* 打开事项卡片附加的参数 */
  cardDetailDialogParams?: AnyObj;
  defaultValidateGroupIds?: Array<string>;
}
```
