# Comment 评论

- 来源：[E10 UI 组件库](https://www.e-cology.com.cn/ui/comment/freepass)
- 平台：PC
- 维护者：王斌
- 设计稿：http://pd.e-cology.com.cn/pd/E10/interface/e10xiaoguotu/PC/zms/pinglundianzan/
- Issue：http://gitlab.ecology10.cn/FRONTEND/weapp-ui/-/issues?state=all&search=[Comment]

## 代码演示

### 基础用法

直接引入组件进行使用

```jsx
import React from 'react';
import { Comment } from '@weapp/ui';

export default class Basic extends React.Component<any> {

  render() {
    return (
      <Comment
       
        module={"share"}
        objectModule="bcw"
        targetId="752714034012356609"
      />
    )
  }
}
```

### 使用各种API

API的使用方法

```jsx
import React from 'react';
import { Comment, Icon, Button, ReplyOption, Input, RadioValueType, Avatar, EChart, FormItemProps, FormLayoutType, AnyObj, Switch } from '@weapp/ui';
import { request, RequestOptionConfig } from '@weapp/utils';

interface StateType {
  key: string;
  targetId: string;
  filterActiveKey: string;
  usePastePrompt: boolean;
}

export default class Basic extends React.Component<any, StateType> {

  constructor(props: any) {
    super(props);
    this.state = {
      key: "main",
      targetId: "4341580164279439928",
      filterActiveKey: "",
      usePastePrompt: true
    }
  }

  commentRef = React.createRef<any>();

  onClick = () => {
    this.setState({ key: "main" });
  }

  onChange = (value: string) => {
    this.setState({ key: value });
  }

  onSubmit = (value: string) => {
    console.log(value)
  }

  onReplySubmit = (value: string, data: ReplyOption) => {
    console.log(value)
  }

  changeTargetId = () => {
    this.setState({targetId: this.state.targetId !==  "5584370050882368875" ? "5584370050882368875" : "4341580164279439928"});
  }

  onReplyClick = (data: ReplyOption) => {
    console.log(data)
  }

  onRefresh = () => {
    this.commentRef.current && this.commentRef.current.refresh(true)
  }

  onReplyCancel = (data: ReplyOption, key: string) => {
    return new Promise<any>((resolve) => {
      alert("取消回复");
      resolve({})
    })
  }

  fetchData = (options: RequestOptionConfig) => {
    return request({
      ...options, headers: {
        ...options.headers,
        testHeaderId: '1',
      }
    });
  }
  onPasteChange = (v: boolean) => {
    this.setState({usePastePrompt: v})
  }
  render() {
    const { key, usePastePrompt } = this.state;
    return (
      <div style={{transform: "translate(0px, 0px)"}}>
        <div style={{ marginBottom: '10px' }}>
          开启纯文本粘贴附件提示
          <Switch value={usePastePrompt} onChange={this.onPasteChange} />
        </div>
        <Comment
         
          ref={this.commentRef}
          defaultActiveKey="main"
          activeKey={key}
          onMenuChange={this.onChange}
          commentUrl="/api/doc/common/comment/commentPage"
          createUrl="/api/doc/common/comment/createComment"
          updataUrl="/api/doc/common/comment/updateComment"
          viewUrl="/api/doc/stream/readinfo.json"
          deleteRelevanceUrl="/api/doc/common/comment/deleteCommentRelevance.common"
          commentCommentedUrl="/api/doc/common/comment/commentCommented"          
          module={"document"}
          objectModule="doc"
          targetId={"3573031474186044663"}
          viewParams={{
            id: "1564647864693947289"
          }}
          autoRefresh          
          onReplyCancel={this.onReplyCancel}
          // onBeforeCreate={this.onBeforeCreate}
          optionConfig={[
            "edit",
            {
              id: "reply",
              onClick: this.onReplyClick
            },
            "task",
            "upvote",
            "copy",
          ]}
          atProps={{hasMenu: false}}
          operationLogProps={{
            module: "demo"
          }}
          customFieldProps={{
            module: "demo"
          }}
          createParams={{
            "tel": "",
          }}
          usePosition
          actionConfig={["upload", "emoticon", "|", "associated", "at", "|", "phrase", "signApprove", "signature"]}
          usePrivated
          paginationType="more"
          showEmpty
          customRenderEmpty={
            <div>empty</div>
          }
          fetchData={this.fetchData}
          usePastePrompt={usePastePrompt}
        />
        <br />
        <div>
          <Button onClick={this.onClick}>设置key</Button>
          <Button onClick={this.onRefresh}>刷新</Button>
        </div>
      </div>
    )
  }
}
```

### 渲染额外内容

评论支持各个区域渲染额外内容

```jsx
import React from 'react';
import { Comment, Icon, ReplyOption, Input, Avatar, Dialog } from '@weapp/ui';

const { message } = Dialog;

export default class Basic extends React.Component<any, any> {

  constructor(props: any) {
    super(props);
    this.state = {
      value: "",
    }
  }

  onInputChange = (value: React.ReactText) => {
    this.setState({ value });
  }

  renderExtraConent = () => {
    return (
      <div style={{ padding: "10px 0" }}>
        手机:
        <Input value={this.state.value} style={{ marginLeft: 15, width: "55%" }} onChange={this.onInputChange} />
      </div>
    )
  }

  renderReplyHeader = (data: ReplyOption) => {
    return <span>回复内容</span>
  }

  onIconSearch = () => {
    message({
      type: "success",
      content: "这是自定义Button"
    })
  }

  renderEditOptions = () => {
    return [
      <Icon onClick={this.onIconSearch} name="Icon-global-search" />,
      <Icon onClick={this.onIconSearch} name="Icon-task" />,
    ]
  }

  renderItemFooterInfo = (data: ReplyOption) => {
    return <span style={{ paddingLeft: 10 }}>租户Key:[{data.tenantKey}]</span>
  }

  renderItemHeaderInfo = (data: ReplyOption) => {
    return <span>[{data.id}]</span>
  }

  renderCommentContent = (data: ReplyOption) => {
    return <span>这是添加的comment</span>
  }

  renderContentFooter = (data: any) => {
    return `评论内容底部`
  }

  renderContentHeader = (data: any) => {
    return `评论内容顶部`
  }

  customAvatarUrl = (data: ReplyOption) => {
    // id为3开头的评论替换头像地址
    if (data.id.startsWith("3")) return "/api/file/preview?type=redirect&fileId=7832692480228392907"
    return null;
  }

  customAvatar = (com: React.ReactNode, data: ReplyOption) => {
    // id为8开头的评论替换整个头像
    if (data.id.startsWith("8")) {
      return (
        <Avatar
          size="medium"
          name={"替换头像"}
        />
      )
    }
    return com;
  }

  renderMore = () => {
    return (
      <div>
        这里是添加的更多内容
      </div>
    )
  }

  render() {
    return (
      <div style={{ transform: "translate(0px, 0px)" }}>
        <Comment
         
          module={"document"}
          objectModule="doc"
          targetId={"3573031474186044667"}
          customAvatarUrl={this.customAvatarUrl}
          customAvatar={this.customAvatar}
          renderEditOptions={this.renderEditOptions}
          renderReplyOption={this.renderEditOptions}
          renderReplyHeader={this.renderReplyHeader}
          renderItemFooterInfo={this.renderItemFooterInfo}
          renderItemHeaderInfo={this.renderItemHeaderInfo}
          renderContentFooter={this.renderContentFooter}
          renderContentHeader={this.renderContentHeader}
          renderMore={this.renderMore}
          renderExtraConent={this.renderExtraConent}
          customRenderEmpty={
            <div>empty</div>
          }
        />
      </div>
    )
  }
}
```

### 展示类型

包括横向展示、只读、隐藏头部或输入框

```jsx
import React from 'react';
import { Comment, Radio, RadioValueType, Switch, RadioOptionType, CommentReadOnly } from '@weapp/ui';

type ShowType = "horizontal" | "vertical";

interface StateType {
  showType: ShowType;
  readOnly: string;
  hasEdit: boolean;
  showHeader: boolean;
  showLogType: boolean;
}

export default class Basic extends React.Component<any, StateType> {

  constructor(props: any) {
    super(props);
    this.state = {
      showType: "vertical",
      readOnly: "editable",
      hasEdit: true,
      showHeader: true,
      showLogType: true
    }
  }

  onReadOnlyChange = (value: RadioValueType) => {
    this.setState({ readOnly: value as any });
  }

  getReadOnly = (value: string) => {
    let data: CommentReadOnly = false;
    if (value === "readonly") data = true;
    if (value === "notDelete") data = "notDelete";
    return data;
  }

  onHasEditChange = (checked: boolean) => {
    this.setState({ hasEdit: checked });
  }

  onRadioChange = (value: RadioValueType) => {
    this.setState({ showType: value as ShowType });
  }

  onHeaderChange = (checked: boolean) => {
    this.setState({ showHeader: checked });
  }

  onShowLogTypeChange = (checked: boolean) => {
    this.setState({ showLogType: checked });
  }

  render() {
    const showTypeObj: RadioOptionType[] = [{
      id: "vertical",
      content: "垂直展示",
      color: '#FF4D4F'
    }, {
      id: "horizontal",
      content: "水平展示",
      color: '#5D9CEC'
    }]
    const readOnlyObj: RadioOptionType[] = [{
      id: "editable",
      content: "可编辑",
      color: '#FF4D4F'
    }, {
      id: "readonly",
      content: "只读",
      color: '#5D9CEC'
    }, {
      id: "notDelete",
      content: "只读并隐藏删除按钮",
      color: "#9EEA6A"
    }]
    const { readOnly, hasEdit, showType, showHeader, showLogType } = this.state;
    return (
      <div style={{ transform: "translate(0px, 0px)" }}>
        <Radio optionType='tag' hideCheck data={showTypeObj} value={showType} onChange={this.onRadioChange} />
        <br />
        <br />
        <div>
          <Radio
           
            optionType='tag'
            hideCheck
            data={readOnlyObj}
            value={readOnly} onChange={this.onReadOnlyChange} />
        </div>
        <br />
        <div>
          是否显示评论输入框
          <Switch
            value={hasEdit}
            onChange={this.onHasEditChange}
          />
        </div>
        <br />
        <div>
          是否显示评论头部
          <Switch
            value={showHeader}
            onChange={this.onHeaderChange}
          />
        </div>
        <br />
        <div>
          是否隐藏查阅情况分类
          <Switch 
            value={showLogType}
            onChange={this.onShowLogTypeChange}
          />
        </div>
        <br />
        <Comment
              
          module={"document"}
          objectModule="doc"
          targetId={"3573031474186044666"}
          readOnly={this.getReadOnly(readOnly)}
          isHorizontal={showType === "horizontal"}
          hasEdit={hasEdit}
          showHeader={showHeader}
          showLogType={showLogType}
        />
      </div>
    )
  }
}
```

### 高级搜索

评论使用高级搜索

```jsx
import React from 'react';
import { Comment, Button, FormItemProps, FormLayoutType, AnyObj } from '@weapp/ui';
import { request, RequestOptionConfig } from '@weapp/utils';

const initItem: FormItemProps = {
  'value_subtarget': {
    itemType: "BROWSER",
    type: "1",
    customParameterName: 'value',
    browserBean: {
      module: "auth",
      type: "resource",
      multiple: true,
    },
    customParameters: {
      type: "array",
    }
  },
}

const initLayout: Array<FormLayoutType> = [
  [
    { id: "subtarget_id", label: "联系人", items: ['operate', 'value_subtarget'], labelSpan: 8, groupId: 'g1', hide: false, needQuickSearch: true },
  ]
]

export default class Basic extends React.Component<any, any> {

  commentRef = React.createRef<any>();

  searchInitDatas = {
    data: {},
    layout: initLayout,
    items: initItem,
    groups: [],
  }

  // 高级搜索配置项更新后需要手动初始化高级搜索
  initSearch = () => {
    this.commentRef.current && this.commentRef.current.initSearch()
  }

  // 格式化高级搜索返回数据
  formatSearchValue = (key: string, data: AnyObj) => {
    const { value } = data;
    if(key === "subtarget_id") {
      return {
        value: value instanceof Array ? value.map(({id}: any) => id).join(",") : value,
      }
    }
  }

  fetchData = (options: RequestOptionConfig) => {
    return request({
      ...options, headers: {
        ...options.headers,
        testHeaderId: '1',
      }
    });
  }

  searchAdvancedPanelProps = {
    fetchData: this.fetchData,
  }

  render() {
    return (
      <div style={{transform: "translate(0px, 0px)"}}>
        <Comment
                
          module={"document"}
          ref={this.commentRef}
          objectModule="doc"
          hasCommentSearch
          hasCommentSort
          hasEchart
          targetId={"3573031474186044668"}
          searchInitDatas={this.searchInitDatas}
          searchAdvancedPanelProps={this.searchAdvancedPanelProps}
          getFormatSearchValue={this.formatSearchValue}
          fetchData={this.fetchData}
        />
        <br />
        <div>
          <Button onClick={this.initSearch}>初始化高级搜索</Button>
        </div>
      </div>
    )
  }
}
```

### 自定义筛选

评论添加自定义筛选按钮

```jsx
import React from 'react';
import { Comment, EChart } from '@weapp/ui';

export default class Basic extends React.Component<any, any> {

  constructor(props: any) {
    super(props);
    this.state = {
      filterActiveKey: "",
    }
  }
  onFilterBtnChange = (key: string) => {
    console.log("key:", key)
    this.setState({ filterActiveKey: key });
  }

  renderEchart = () => {
    const option = {
      tooltip: {
        trigger: 'item',
        formatter: '{b}:  {c}次({d}%)'
      },
      legend: {
        top: 10,
        left: 20,
        data: ['西凉', '益州', '兖州', '荆州', '幽州'],
        orient: "vertical"
      },
      series: [
        {
          type: 'pie',
          radius: '65%',
          center: ['400', '50%'],
          selectedMode: 'single',
          data: [
            {
              value: 1548,
              name: '幽州',
            },
            { value: 735, name: '荆州' },
            { value: 510, name: '兖州' },
            { value: 434, name: '益州' },
            { value: 335, name: '西凉' }
          ],
          label: {
            formatter: "{b}:  {c}次({d}%)",
          },
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    };
    return (
      <EChart
       
        option={option}
      />
    )
  }

  render() {
    const { filterActiveKey } = this.state;
    return (
      <div style={{transform: "translate(0px, 0px)"}}>
        <Comment
            
          module={"document"}
          objectModule="doc"
          hasSort
          targetId='3573031474186044669'
          filterActiveKey={filterActiveKey}
          onFilterBtnChange={this.onFilterBtnChange}
          filterBtnOption={[{
            id: "filter",
            icon: "Icon-performance-appraisal-o",
            render: this.renderEchart
          }]}
        />
      </div>
    )
  }
}
```

### 使用富文本

评论输入文本框替换为富文本框

```jsx
import React from 'react';
import { Comment } from '@weapp/ui';

export default class Basic extends React.Component<any> {

  render() {
    return (
      <Comment
       
        module={"cowork"}
        objectModule="cowork"
        targetId="752714034012356609"
        useRichText
        optionConfig={["delete", "reply", "task", "edit", "copy"]}
        richTextProps={{
          ckConfig: {
            uploadParams: {
              module: 'document'
            }
          },
        }}
      />
    )
  }
}
```

### 拦截接口

提交、更新、删除、查询评论接口支持拦截

```jsx
import React from 'react';
import { AnyObj, Comment, Dialog, ReplyOption } from '@weapp/ui';
import { RequestResult } from '@weapp/utils';

const { confirm } = Dialog;

export default class Basic extends React.Component<any> {

  onBeforeCreate = (params?: AnyObj, replyData?: ReplyOption) => {
    if(!replyData) alert("是否确认添加评论");
    return new Promise((resolve, reject) => {
      resolve({
        ...params,
        customKey: 'true',
      })
    })
  }

  onAfterCreate = (result: RequestResult<any>, replyData?: ReplyOption) => {
    return new Promise((resolve, reject) => {
      if(replyData) {
        resolve(result)
        return;
      }
      const { data } = result;
      const customResult = {
        ...result,
        data: {
          ...data,
          data: {
            ...data?.data,
            originContent: "自定义评论内容返回"
          }
        }
      }
      confirm({
        content: "是否添加自定义评论内容",
        onCancel: () => resolve(result),
        onOk: () => resolve(customResult),
      })
      
    })
  }

  render() {
    return (
      <Comment
       
        module={"document"}
        objectModule="doc"
        targetId="8514696681578165565"
        onBeforeCreate={this.onBeforeCreate}
        onAfterCreate={this.onAfterCreate}
      />
    )
  }
}
```

### 菜单中使用

菜单中渲染评论组件

```jsx
import React from 'react';
import { getLabel } from '@weapp/utils';
import { Button, Comment } from '@weapp/ui';
import { AnyObj } from '@weapp/ui';

const { CommentMenu } = Comment;

interface StateType {
  key: string;
  url: string;
  urlParams: AnyObj;
}

export default class Basic extends React.Component<any, StateType> {

  constructor(props: any) {
    super(props);
    this.state = {
      key: "",
      url: "/api/task/common/comment/commentPage",
      urlParams: {},
    }
  }
  
  commentRef = React.createRef<any>();

  onClick = () => {
    this.setState({ key: "main" })
  }

  onChange = () => {
    this.setState({      
      url: "/api/doc/common/comment/commentPage",
      urlParams: {
        targetId: "4341580164279439928"
      },
    })
  }

  renderItemHeaderInfo = () => {
    return "66666"
  }

  customListRenderHeader = () => {
    return "99999"
  }

  onRefresh = () => {
    this.commentRef.current && this.commentRef.current.refresh()
  }

  render() {
    const { url, urlParams } = this.state;
    return (
      <div>
        <Button onClick={this.onRefresh}>刷新</Button>
        <Button onClick={this.onChange}>改变值</Button>
        <br />
        <div style={{border: "1px solid #e5e5e5", height: 800}}>
          <CommentMenu
           
            defaultActiveKey="doc"
            ref={this.commentRef}
            createUrl="/api/bcw/common/comment/createComment"
            updataUrl="/api/bcw/common/comment/updateComment"
            updataParams={{
            }}
            module="share"
            objectModule="task"
            hasSearch
            // renderItemHeaderInfo={this.renderItemHeaderInfo}
            menu={[{
              id: "employeeComment",
              content: "下属的评论",
              url: "/api/bcw/comment/employeeComment",
              isSubordinate: true,
              customListRenderHeader: this.customListRenderHeader,
              urlParams: {
                "module":"task",
              },
              // showType: "card",
            }, {
              id: "task",
              content: "任务评论",
              url: url,
              urlParams,
            }, {
              id: "doc",
              content: "文档评论",
              url: "/api/task/common/comment/others.common",
              urlParams: {
                "pageNo": 1,
                "pageSize": 10,
                "id": "5113593421692880341",
                "module": "task",
                "loadCount": true,
                "method": "unread"
              },
              // showType: "card"
            }, {
              id: "demo",
              content: "demo评论超级长超级长超级长超级长",
              url: "/api/bcw/common/comment/commentPage",
              urlParams: {"targetId":"4341580164279439908","module":"share" },
            }, {
              id: "demo2",
              content: "demo评论2",
              url: "/api/bcw/common/comment/commentPage",
              // urlParams: {"id":"1361715441145981309","module":"task","loadCount":false,"typeFilter":"shareToMe","subordinateType":null,"querySubordinates":[],"pageNo":6},
              showType: "card"
            }]}
            paginationType="more"
          />
        </div>
      </div>
    )
  }
}
```

### 文本输入

添加评论区域单独抽离

```jsx
import React from 'react';
import { Comment, Icon, Button, Dialog, ReplyOption } from '@weapp/ui';

const { message } = Dialog;
const { CommentEdit } = Comment;

interface StateType {
  key: string;
}

export default class Basic extends React.Component<any, StateType> {

  constructor(props: any) {
    super(props);
    this.state = {
      key: "",
    }
  }

  onChange = (value: string) => {
    console.log('onChange', value);
  }

  onSubmit = (value: string) => {
    console.log('onSubmit', value);
  }

  render() {
    return (
      <div>
        <h3>富文本输入框</h3>
        <CommentEdit
          useRichText={true}
          onChange={this.onChange}
          actionConfig={[
            "emoticon",
            "associated",
            "|",
            "phrase",
          ]}
        />
      <h3>纯文本输入框</h3>
      <CommentEdit
        useRichText={false}
        onChange={this.onChange}
        actionConfig={[
          "emoticon",
          "associated",
          "|",
          "phrase",
        ]}
      />
      </div>
    )
  }
}
```

### 文本框

单独使用评论文本框，包含at、添加附件、事项关联、表情功能

```jsx
import React from 'react';
import { Comment, Button, ReplyOption, Dialog } from '@weapp/ui';
import mock from './itemMock.json';
import { dayjs } from '@weapp/utils';

const { CommentItem } = Comment;

interface StateType {
  visible: boolean;
}

export default class Basic extends React.Component<any, StateType> {

  constructor(props: any) {
    super(props);
    this.state = {
      visible: false
    }
  }

  onClick = () => {
    this.setState({ visible: true });
  }

  onClose = () => { 
    this.setState({ visible: false });
  }

  renderFooter = (data: ReplyOption) => {
    return dayjs(data.addTime).format("YYYY-MM-DD HH:mm:ss")
  }

  render() {
    const { visible } = this.state;
    return (
      <>
        <div style={{border: "1px solid #e5e5e5"}}>
          <CommentItem
           
            data={mock as ReplyOption}
            renderFooter={this.renderFooter}
          />
        </div>
        <br />
        <Button onClick={this.onClick}>获取数据</Button>
        <Dialog
          title="CommentItem数据"
          mask
          visible={visible}
          onClose={this.onClose}
          closable
        >
          <pre>
            {JSON.stringify(mock)}
          </pre>
        </Dialog>
      </>
    )
  }
}
```

### 单独显示一条评论

单独渲染一条完整的评论

```jsx
import React from 'react';
import { Comment, getBrowserFromAssociated, getCommentUploadData } from '@weapp/ui';
import { RequestOptionConfig, request } from '@weapp/utils';

const { CommentEditText } = Comment;

interface StateType {
  visible: boolean;
}

const relevanceList = [
  {
    "id": "8514264896184456777",
    "objectId": "684828072958533632",
    "targetModule": "task",
    "targetId": "2224258308996215588",
    "targetName": "333",
    "delete": false,
    "employeeId": "8513694054004321558"
  }
]

const list = [
  {
    "id": "4764273037983858332",
    "name": "20220120101735.zip",
    "refId": "685177760693739529",
    "fileid": "4764273037983858332",
    "type": "multipart/x-zip",
    "size": 9552,
    "md5": "428b4aac1b4c1cf1c09b61347fdf213d",
    "url": "dcdd164e-0c05-4108-b840-4e21102316c9",
    "uploadTime": 1642730380000,
    "module": "document",
    "options": [
      {
        "id": "download",
        "icon": "Icon-download02",
        "content": "下载",
        "moreType": false
      },
      {
        "id": "rename",
        "icon": "Icon-rename",
        "content": "重命名",
        "moreType": true
      },
      {
        "id": "version",
        "icon": "Icon-Unfinished-business-o",
        "content": "历史版本",
        "moreType": true
      },
      {
        "id": "reupload",
        "icon": "Icon-Left-refresh",
        "content": "重新上传",
        "moreType": true
      },
      {
        "id": "delete",
        "icon": "Icon-delete",
        "content": "删除",
        "moreType": false
      }
    ],
    "map": {},
    "ban": false,
    "docId": "3574273037937351515",
    "docType": "other",
    "extName": "zip",
    "image": false,
    "hasRef": true,
    "qysFile": false
  }
]

export default class Basic extends React.Component<any, StateType> {

  constructor(props: any) {
    super(props);
    this.state = {
      visible: false
    }
  }

  onClick = () => {
    this.setState({ visible: true });
  }

  onClose = () => { 
    this.setState({ visible: false });
  }

  fetchData = (options: RequestOptionConfig) => {
    return request({
      ...options, headers: {
        ...options.headers,
        testHeaderId: '1',
      }
    });
  }
  render() {
    return (
      <>
        <CommentEditText
         
          defaultAssociatedData={getBrowserFromAssociated(relevanceList)}
          defaultFileData={getCommentUploadData(list)}
          fetchData={this.fetchData}
        />
      </>
    )
  }
}
```

### 评论内容渲染

只渲染评论内容

```jsx
import React from 'react';
import { Comment } from '@weapp/ui';

const { CommentContent } = Comment;
const { getAtValue } = CommentContent;

interface StateType {
  key: string;
}

const data = "上传了文件@@ :hushed: :kissing_heart: :disappointed:  baidu.com:@6626096193053094293; :@4211695550202213983; :@1562228802271224; ";
const userData = '[{"userId":"6626096193053094293","userName":"aaa-1","userIdIndex":"0","atType":"user"},{"userId":"4211695550202213983","userName":"888888","userIdIndex":"0","atType":"department"},{"userId":"1562228802271224","userName":"开开信心","userIdIndex":"0","atType":"group"}]'

export default class Basic extends React.Component<any, StateType> {

  constructor(props: any) {
    super(props);
    this.state = {
      key: "",
    }
  }

  render() {
    console.log(getAtValue(data, userData))
    return (
      <CommentContent
        value={data}
        userData={userData}
       
      />
    )
  }
}
```

### 点赞按钮

单独使用点赞按钮，包含接口控制

```jsx
import React from 'react';
import { Icon, Upvote } from '@weapp/ui';
import { request, RequestOptionConfig } from '@weapp/utils';

export default class Basic extends React.Component<any, any> {

  constructor(props: any) {
    super(props);
    this.state = {
      upvote: false,
      upvote2: false,
    }
  }

  onUpvotedChange = (upvoted: boolean) => {
    this.setState({ upvote: upvoted });
  }

  onUpvotedChange2 = (upvoted: boolean) => {
    this.setState({ upvote2: upvoted });
  }

  fetchData = (options: RequestOptionConfig) => {
    return request({
      ...options, headers: {
        ...options.headers,
        testHeaderId: '1',
      }
    });
  }

  render() {
    const { upvote, upvote2 } = this.state;
    return (
      <div>
        <h3> 点赞状态需要外部保存并传入 </h3>
        <Upvote
         
          module="doc"
          objectId="1362372473190223857"
          upvoted={upvote}
          onUpvotedChange={this.onUpvotedChange}
          fetchData={this.fetchData}
        />
        <h3> 自定义点赞图标 </h3>
        <Upvote
         
          module="doc"
          objectId="1362372473190223858"
          upvoted={upvote2}
          onUpvotedChange={this.onUpvotedChange2}
          fetchData={this.fetchData}
        >
          <Icon size="sm" name={upvote2 ? "Icon-Collection02" : "Icon-Collection" } />
        </Upvote>
      </div>
    )
  }
}
```

### 自定义评论操作按钮

每条评论操作按钮支持按数据判断，隐藏、显示、自定义添加

```jsx
import React from 'react';
import { Comment, ReplyOption,CommentOptionType } from '@weapp/ui';

export default class Basic extends React.Component<any> {

  onCustomClick = (data: ReplyOption) => {
    console.log(data)
  }

  customTopicConfig = (data: ReplyOption)=>{
    // data 为 当前评论所有数据
    return { data: { mid: '123', type: '123', module: '123', minfo: '123',smid:data?.id },callback:()=>{
      console.log(this)
    }}
  }

  customOptionConfig = (data: ReplyOption, defaultOption: CommentOptionType[] = []): CommentOptionType[] => {
    console.log({ data, defaultOption });
    return defaultOption;
  }
  render() {
    return (
      <Comment
        module={"document"}
        objectModule="doc"
        targetId="1362372473190223857"
          // 方式一：
        // optionConfig={[
        //   "edit",
        //   "reply",
        //   'topic',
        //   "task",
        //   "upvote",
        //   {
        //     id: 'demo',
        //     title: "测试点击",
        //     titleInfo: "点击事件详情",
        //     iconName: "Icon-smiling-face",
        //     onClick: this.onCustomClick
        //   }
        // ]}
          // 方式二：
        optionConfig={this.customOptionConfig}
          // 方式一：
        // topicConfig={
        //   { data: { mid: '123', type: '123', module: '123', minfo: '123' }}
        // }
          // 方式二：
        topicConfig={this.customTopicConfig}
      />
    )
  }
}
```

### 单条评论操作日志

单条评论查看详细操作日志，包括评论、附件、事项关联等的操作

```jsx
import React, { ReactText } from 'react';
import { Button, Comment, CommentOptionType, Input } from '@weapp/ui';

const optionConfig: CommentOptionType[] = ["delete", "reply", "task", "edit", "log"];

export default class Basic extends React.Component<any> {

  state: Readonly<any> = {
    value: "8514696681578165565",
    targetId: "8514696681578165565",
  };

  changeHandler = (value: ReactText) => {
    this.setState({ value })
  }

  clickHandler = () => {
    this.setState({ targetId: this.state.value })
  }

  render() {
    return (
      <>
        <div style={{ display: "flex" }}>
          <Input onChange={this.changeHandler} value={this.state.value} />
          <Button
            onClick={this.clickHandler}
            type="primary"
           
            style={{ flex: "none", marginLeft: 10 }}
          >
            切换id
          </Button>
        </div>
        <br />
        <Comment
         
          module="task"
          objectModule="task"
          targetId={this.state.targetId}
          autoRefresh
          optionConfig={optionConfig}
        />
      </>
    )
  }
}
```

### 自定义评论Item

通过抛出组件配置每条评论展示样式

```jsx
import React from 'react';
import { Comment } from '@weapp/ui';
import { classUseMemo } from '@weapp/utils';

const { Address, Quote, ReplyFloor, CommentAvatar, CommentItemFooter, CommentItemContent } = Comment;

export default class Basic extends React.Component<any> {

  customRenderItem = (rowData: any, rowIndex: any) => {
    return (
      <div>
        <CommentAvatar
         
          data={rowData}
        />
        <CommentItemContent
         
          data={rowData}
        />
        <Address mapId={`custom_map_${rowData.id}`}
         
          data={rowData}
        />
        <ReplyFloor
         
          data={rowData} objectModule="workflow/core"
        />
        <CommentItemFooter
         
          data={rowData}
          objectModule="workflow/core"
          module="workflow"
          optionConfig={["copy", "delete", "edit", "log", "quote", "reply", "task", "upvote"]}
          readOnly
        />
      </div>
      
    )
  }

  render() {

    const listProps = classUseMemo("listProps", this, () => ({ customRenderItem: this.customRenderItem }), [])

    return (
      <Comment
       
        module={"workflow"}
        objectModule="workflow/core"
        targetId="684882614825205883"
        layout="floor"
        listProps={listProps}
      />
    )
  }
}
```

### 脱敏功能

评论组件脱敏设置

```jsx
import React from 'react';
import { Comment } from '@weapp/ui';
import { desensitization } from '../../../../components/encry/utils';

const { CommentEdit, CommentEditText } = Comment;

export default class Basic extends React.Component<any> {

  a = () => {
    return null;
  }

  render() {
    return (
      <Comment
        useDesensitization
        module={"cowork"}
        objectModule="cowork"
        targetId="752714034012356609"
        optionConfig={[

          'topic',
          "task",
        ]}
        topicConfig={{
          data: { name: '123', mid: '123', minfo: '123', module: '123', type: 'w123' },
          icon: 'Icon-mapping'
        }}
      />
    )
  }
}
```

### 按楼层展示

评论按楼层展示，回复作为单独的一级

```jsx
import React from 'react';
import { Comment } from '@weapp/ui';

export default class Basic extends React.Component<any> {
  render() {
    return (
      <Comment
       
        module="bcw"
        objectModule="bcw"
        targetId="752714034012356609"
        layout="floor"
      />
    )
  }
}
```

### 自定义请求

自定义请求

```jsx
import React from 'react';
import { Comment, Button, Dialog } from '@weapp/ui';
import { request, RequestOptionConfig } from '@weapp/utils';

export default class Basic extends React.Component<any, any> {
  constructor(props: any) {
    super(props);
    this.state = {
      visible: false,
    }
  }
  fetchData = (options: RequestOptionConfig) => {
    return request({
      ...options, headers: {
        ...options.headers,
        testHeaderId: '1',
      }
    });
  }
  onClose = () => this.setState({ visible: false })
  onOpen = () => this.setState({ visible: true })
  render() {
    return (
      <div>
        <Button onClick={this.onOpen}>测试fetchData</Button>
        <Dialog
          visible={this.state.visible}
          onClose={this.onClose}
          closable
          title="测试fetchData"
          destroyOnClose
          >
          <Comment
           
            module={"document"}
            objectModule="doc"
            targetId="752714034012356609"
            textToRichText
            fetchData={this.fetchData}
          />
        </Dialog>
      </div>
    )
  }
}
```

### 转为商机

添加转为商机功能

```jsx
import React, { ReactText } from 'react';
import { Button, Comment, CommentOptionType, Input, ReplyOption } from '@weapp/ui';

export default class Basic extends React.Component<any> {
  state = {
    targetId: "752714034012356609",
    inputValue: "752714034012356609",
  }
  customOptionConfig = (data: ReplyOption, defaultOption: CommentOptionType[] = []): CommentOptionType[] => {
    console.log({ data, defaultOption });
    return [...defaultOption, "salechance"];
  }
  handleValueChange = (value: ReactText) => {
    this.setState({ inputValue: value });
  }
  handleSearch = () => {
    this.setState({ targetId: this.state.inputValue });
  }
  render() {
    return (
      <div>
        <Input
         
          value={this.state.inputValue}
          onChange={this.handleValueChange}
        />
        <Button onClick={this.handleSearch}>搜索</Button>
        <br />
        <Comment
         
          module={"customer"}
          key={this.state.targetId}
          objectModule="crm"
          targetId={this.state.targetId}
          optionConfig={this.customOptionConfig}
        />
      </div>
    )
  }
}
```

### 查阅情况自定义类型

```jsx
import React from 'react';
import { Comment, RadioOptionProps } from '@weapp/ui';

export default class Basic extends React.Component<any> {

    viewSelectTypeProps = (_: any, data: any) => {
        return {
            //className: 'custom-select',
            customOptionContenRender: (optionProps: RadioOptionProps, content: any) => {
                let count = data[optionProps.value || '']?.length;
                return <span>{`${optionProps.value} - ${content} - ${count}`}</span>
            }
        }
    }

    render() {
        return (
            <Comment
               
                module={"share"}
                objectModule="bcw"
                targetId="752714034012356609"
                viewSelectTypeProps={this.viewSelectTypeProps}
                paginationProps={{
                    pageSize: 2
                }}
                defaultActiveKey={'view'}
            />
        )
    }
}
```

## API

### 参数说明如下：

#### Comment

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| activeKey | 顶部 Tabs 选中的 key | string | 否 | `` |
| defaultActiveKey | 顶部 Tabs 默认选中的 key | string | 否 | `main` |
| menu | 顶部 Tabs 默认配置项 | CommentTabsDataType | 否 |  |
| extendMenu | 顶部 Tabs 额外配置项（在默认配置项的基础上额外添加） | CommentTabsDataType | 否 |  |
| commentUrl | 评论列表接口 | string | 否 |  |
| commentParams | 评论列表接口参数 | AnyObj | 否 |  |
| createUrl | 创建评论接口 | string | 否 |  |
| createParams | 创建评论接口参数 | AnyObj | 否 |  |
| updataUrl | 更新接口 URL | string | 否 |  |
| updataParams | 更新接口的参数 | AnyObj | 否 |  |
| chartUrl | 统计数据列表接口 | string | 否 |  |
| chartParams | 统计数据接口参数 | AnyObj | 否 |  |
| viewUrl | 查阅情况日志接口 | string | 否 |  |
| viewParams | 查阅情况日志参数 | AnyObj | 否 |  |
| paginationProps | 分页组件 props | PaginationProps \| false | 否 |  |
| paginationType | 分页类型 | 'scroll' \| 'part' \| 'more' | 否 | `more` |
| labelOption | 渲染更多数据配置值 | CommentLabel[] | 否 |  |
| itemClassName | item 样式类名 | string | 否 |  |
| renderEditOptions | 渲染添加评论文本框下方按钮 | () => React.ReactNode[] | 否 |  |
| renderReplyOption | 渲染回复文本框下方按钮 | (data: ReplyOption) => React.ReactNode[] | 否 |  |
| module | 模块名 | string | 否 | `share` |
| hasSearch | 是否使用搜索 | boolean | 否 | true |
| hasCommentSearch | 是否新版使用搜索 | boolean | 否 | true |
| hasEchart | 是否使用图表绘制 | boolean | 否 | true |
| quoteEllipsis | 是否启用引用省略（只显示一行） | boolean | 否 | false |
| listProps | 内置 List 组件 props | ListProps | 否 | {} |
| optionConfig | 每条回复内容下方 option 按钮设置 | CommentOptionType[] \| ((data:ReplyOption,defaultOption?: CommentOptionType[]) => CommentOptionType[]) | 否 | [`delete`, `reply`, `edit`, `task`, `upvote`] |
| renderItemFooterInfo | 回复内容下方添加额外信息 | (data: ReplyOption) => React.ReactNode | 否 |  |
| renderItemHeaderInfo | 回复内容顶部添加额外信息 | (data: ReplyOption) => React.ReactNode | 否 |  |
| onReplySubmit | 回复提交回调事件 | (value: string) => void | 否 |  |
| onEditSubmit | 创建评论提交回调事件 | (data: ReplyOption) => React.ReactNode[] | 否 |  |
| onUpdata | 回复更新回调事件 | (value: string, data: ReplyOption) => Promise \| void | 否 |  |
| onEditSubmitted | 创建评论提交完成回调 | () => void | 否 |  |
| onReplySubmitted | 回复评论提交完成回调 | () => void | 否 |  |
| onUpdated | 更新评论提交完成回调 | () => void | 否 |  |
| renderMore | 渲染创建评论区域下方内容 | (data: ReplyOption) => React.ReactNode[] | 否 |  |
| renderExtraConent | 渲染创建评论区域下方内容 | (data: ReplyOption) => React.ReactNode | 否 |  |
| showText | 是否显示添加评论的文本框（默认为收起） | boolean | 否 | false |
| showTextCache | 是否在有评论缓存时显示添加评论的文本框（默认为收起） | boolean | 否 | false |
| asyncSubmit | 是否使用异步提交（EditText中的startSubmit执行后会返Promise函数，便于外部直接通过ref调用并获取提交状态） | boolean | 否 | false |
| getScrollContainer | 滚动容器定位(组件外部容器滚动时使用，内部 List 滚动请使用 listProps 传递滚动 api，下列数据同) | () => HTMLElement | 否 |  |
| onEndReachedThreshold | 触发滚动事件时距离容器底部的像素的值 | number | 否 |  |
| scrollEventThrottle | 滚动事件触发间隔(毫秒) | number | 否 |  |
| onScroll | 滚动事件回调 | () => void | 否 |  |
| targetId | 评论唯一 id | string | 否 |  |
| onMenuChange | 顶部菜单切换事件 | (activeKey: string, activeItem: object, e: React.MouseEvent<Element, MouseEvent>) => void | 否 |  |
| onPageChange | 分页切换事件 | (current: number, pageSize: number) => void | 否 |  |
| operationLogProps | 操作日志组件 props | 具体类型请查看对应组件文档 | 否 |  |
| customFieldProps | 自定义字段修改日志组件 props | 具体类型请查看对应组件文档 | 否 |  |
| autoRefresh | targetId改变后是否自动刷新 | boolean | 否 | false |
| menuProps | 顶部Menu组件props(参考menu组件) | MenuProps | 否 |  |
| renderReplyHeader | 添加回复标题 | (data: ReplyOption) => React.ReactNode | 否 |  |
| deleteRelevanceUrl | 删除评论事项关联接口 | string | 否 |  |
| actionConfig | 文本框底部事件配置项 | CommentAction[] | 否 | [`upload`, `emoticon`, `associated`, `at`] |
| customAvatarUrl | 自定义头像展示地址(返回null则使用接口默认头像) | string \| null | 否 |  |
| customAvatar | 自定义头像(自定义头像的优先级在自定义头像地址之前) | (com: React.ReactNode, data: ReplyOption) => React.ReactNode | 否 |  |
| commentCommentedUrl | 回复列表接口 | string | 否 |  |
| commentCommentedParams | 回复列表参数 | AnyObj \| ((data?: ReplyOption) => AnyObj) | 否 |  |
| textareaProps | 文本框props | TextareaProps \| (type?: '' \| 'reply') =>  TextareaProps `reply`表示回复输入框 | 否 |  |
| readOnly | 是否只读 | boolean \| `notDelete` | 否 | false |
| hasEdit | 是否显示评论发布输入框 | boolean | 否 | true |
| isHorizontal | 是否水平展示评论 | boolean | 否 | false |
| objectModule | 模块接口路由 | string | 否 | `bcw` |
| showEmpty | 评论为空时是否展示Empty组件 | boolean | 否 | false |
| filterBtnOption | 评论过滤事件按钮添加 | FilterBtnOption[] | 否 | false |
| filterActiveKey | 评论过滤选中的key | string | 否 | false |
| onFilterBtnChange | 评论过滤点击事件 | (selectedKey: string) => void | 否 | false |
| customRenderEmpty | 自定义空数据状态 | (() => ReactNode) \| ReactNode | 否 |  |
| atProps | At组件Props | AtProps | 否 |  |
| searchAdvancedPanelProps | 高级搜索面板Props | Omit<SearchAdvancedPanelProps, "searchAdvancedStore" \| "conditionId"> | 否 |  |
| searchInitDatas | 高级搜索初始化内容 | FormInitAllDatas | 否 |  |
| searchConditionId | 高级搜索conditionId(唯一) | string | 使用高级搜索时必填 |  |
| getFormatSearchValue | 格式化高级搜索数据 | (key: string, data: AnyObj, result: any) => (AnyObj \| void) | 否 |  |
| showHeader | 是否展示评论顶部内容 | boolean | 否 | true |
| onBeforeGetComment | 获取评论数据前触发 | (params?: AnyObj) => Promise<any \| void> | 否 |  |
| onAfterGetComment | 获取评论数据更新数据前触发 | (result: RequestResult) => Promise<any \| void> | 否 |  |
| onBeforeCreate | 创建评论接口 | (params?: AnyObj, replyData?: ReplyOption, fullParams?: AnyObj) => Promise<any \| void> | 否 |  |
| onAfterCreate | 评论提交或回复后更新数据前触发 | (result: RequestResult, replyData?: ReplyOption) => Promise<any \| void> | 否 |  |
| onBeforeUpdate | 评论更新前触发 | (params?: AnyObj, updateData?: ReplyOption, fullParams?: AnyObj) => Promise<any \| void> | 否 |  |
| onAfterUpdate | 评论更新后更新数据前触发 | (result: RequestResult, updateData?: ReplyOption) => Promise<any \| void> | 否 |  |
| onBeforeDelete | 评论删除前触发 | (params?: AnyObj, data?: ReplyOption) => Promise<any \| void> | 否 |  |
| onAfterDelete | 评论删除后更新数据前触发 | (result: RequestResult, data?: ReplyOption) => Promise<any \| void> | 否 |  |
| uploadProps | 评论内部附件上传Props | UploadProps \| ((data: ReplyOption) => UploadProps) | 否 |  |
| editTextProps | 透传给 CommentEditText 组件的额外参数，也支持 `(type, data) => props` 动态返回，不同场景可分别处理新增、回复、编辑、引用 | Partial \| ((type: 'add' \| 'reply' \| 'edit' \| 'quote', data?: ReplyOption) => Partial) | 否 |  |
| onReplyCancel | 评论编辑或回复去掉按钮回调 | (data: ReplyOption, type: string) => Promise | 否 |  |
| useCache | 是否使用评论缓存 | boolean | 否 | true |
| useRichText | 是否使用富文本 | boolean | 否 |  |
| richTextProps | 富文本框Props | RichTextProps | 否 |  |
| onDelete | 评论删除回调 | (data?: ReplyOption) => void | 否 |  |
| onDeleted | 评论删除成功回调 | (data?: ReplyOption) => void | 否 |  |
| renderReplyContent | 渲染评论回复区域内容 | (data: ReplyOption) => React.ReactNode | 否 |  |
| hasSort | 是否使用排序 | boolean | 否 |  |
| hasCommentSort | 是否使用新排序 | boolean | 否 |  |
| sortBy | 排序方式 | `desc` \| `asc` | 否 |  |
| onSortChange | 排序修改事件 | (sortBy: `desc` \| `asc`) => void | 否 |  |
| useSkeleton | 是否开启骨架屏 | boolean | 否 | true(开启) |
| layout | 评论展示类型 | `content` \| `floor`(按内容/楼层展示) | 否 | content(按内容展示) |
| customRenderText | 文本框自定义渲染 | (com: ReactNode, data?: ReplyOption) => React.ReactNode | 否 |  |
| renderContentFooter | 自定义评论回复区域底部内容 | (data: ReplyOption) => ReactNode | 否 |  |
| renderContentHeader | 自定义评论内容区域顶部内容 | (data: ReplyOption) => ReactNode | 否 |  |
| queryChildrenUrl | 查询子回复接口 | string | 否 |  |
| queryChildrenParams | 查询子回复参数 | AnyObj | 否 |  |
| showTime | 是否显示评论底部时间 | boolean | 否 | true |
| quoteRenderProps | 引用区域自定义渲染内容 | ItemRenderObjType | 否 |  |
| customRenderLogName | 自定义渲染查阅情况用户名 | (item: AnyObj) => ReactNode | 否 |  |
| alwaysDisplayReply | 强制展示评论回复内容 | boolean | 否 |  |
| deleteUrl | 删除接口URL | string | 否 |  |
| deleteParams | 删除接口的参数 | AnyObj | 否 |  |
| useDesensitization | 开启脱敏设置 | boolean | 否 | false |
| browserProps | 评论列表中事项关联属性 | BrowserProps \| ((data: SelectedData, commentData: ReplyOption) => BrowserProps) | 否 |  |
| topicConfig | 话题设置参数（需要转话题功能时 必填 ） | TopicConfigParams | 否 |  |
| fetchData | 自定义请求数据 | (options: RequestOptionConfig) => RequestPromise | 否 |  |
| disableAt | 禁用输入框@人员功能 | boolean | 否 |  |
| associativeProps | 创建评论事项关联按钮prosp | BrowserProps | 否 |  |
| getCustomSearchConditions | 获取自定义搜索内容 | (conditions: any[], searchAdvancedStore: SearchAdvancedStore) => any[] | 否 |  |
| fileOptionBlacklist | 评论附件操作按钮黑名单 | string[] | 否 |  |
| customRenderCommentItem | 自定义渲染评论Item | (rowData: ReplyOption, content: ReactNode, index: number, props: AnyObj) => ReactNode | 否 |  |
| showLogType | 显示查阅情况分类 | boolean | 否 | true |
| usePastePrompt | 开启纯文本粘贴附件提示 | boolean | 否 | true |
| renderCustomMoreContent | 渲染评论中间(评论内容和事项关联中间区域，在renderCommentContent之下) | (data: ReplyOption) => React.ReactNode | 否 |  |
| customRenderCommentItemContent | 渲染CommentItem内容 | (data: ReplyOption) => React.ReactNode | 否 |  |
| renderItemHeaderMore | 渲染头部自定义内容(用户名右侧) renderMoreTop之前 | (data: ReplyOption) => React.ReactNode | 否 |  |
| templateId | 模板标识（支持EB评论）(1 - 7) | string | 否 |  |
| splitline | 分割线样式 | `solid` \| `longDashed` \| `dashed` \| `dotted` \| `differDashed` | 否 |  |
| splitlineColor | 分割线颜色 | string | 否 |  |
| sourceConfig | 来源显示设置 | `['PC', 'nonPC']` \| `['PC']` \| `['nonPC']`\| `[]` | 否 |  |
| useFullParams | 接口是否使用完整参数模式（用于onBeforeCreate、onBeforeUpdate口子等） | boolean | 否 |  |
| viewSelectTypeProps | 查阅情况分类Props | RadioProps \| ((_: any, data: Record<ReadType, any>) => RadioProps)  `ReadType` 为 `all`、`empRead`、`empUnread` | 否 |  |
> onBeforeCreate、onAfterCreate、onBeforeUpdate、onAfterUpdate方法可以通过reject传入boolean类型来决定是否调用初始化事件，默认为true(调用)

```ts
type TopicConfigParams = AnyObj | ((data: ReplyOption) => AnyObj)
```

##### CommentTabsDataType

CommentTabsDataType类型

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| sort | 排序字段 | number | 否 | 99 |
| render | () => React.ReactNode | 自定义渲染内容 | 否 |  |
##### FilterBtnOption

FilterBtnOption类型

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| icon | Icon 图标名 | IconNames | 是 |  |
| render | () => React.ReactNode | 自定义渲染内容 | 否 |  |
| onClick | () => void | 按钮点击事件 | 否 |  |
其余属性请参考 menu 组件

menu 属性默认值为

```
[{
  id: `main`,
  content: getLabel('57083','评论'),
  title: getLabel('57083','评论'),
  sort: 99,
}, {
  id: `view`,
  content: getLabel('57084','查阅情况'),
  title: getLabel('57084','查阅情况'),
  sort: 99,
}, {
  id: `operationLog`,
  title: getLabel('57085','操作日志'),
  content: getLabel('57085','操作日志'),
  sort: 99
}, {
  id: `customField`,
  title: getLabel('68980','自定义日志') ,
  content: getLabel('68980','自定义日志') ,
  sort: 99
}]
```

> 需要对顶部默认 tab 进行修改请保留原有 id，组件内部根据 id 渲染内容，当存在 render 属性时，强制渲染成 render 内容

##### 操作日志

- 操作日志前端请参考文档
- 后端接口请参考文档

## Comment.CommentMenu

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| activeKey | 顶部 Tabs 选中的 key | string | 否 | "" |
| defaultActiveKey | 顶部 Tabs 默认选中的 key | string | 否 | `main` |
| menu | 顶部 Tabs 配置项 | CommentMenuDataType | 否 |  |
| paginationProps | 分页组件 props(type=`card`无效) | PaginationProps \| false | 否 |  |
| paginationType | 分页类型 | 'scroll' \| 'part' \| 'more' | 否 | `more` |
| labelOption | 渲染更多数据配置值 | CommentLabel[] | 否 |  |
| itemClassName | item 样式类名 | string | 否 |  |
| hasSearch | 是否显示顶部搜索按钮 | boolean | 否 | true |
| renderEditOptions | 渲染添加评论文本框下方按钮 | () => React.ReactNode[] | 否 |  |
| renderReplyOption | 渲染回复文本框下方按钮 | (data: ReplyOption) => React.ReactNode[] | 否 |  |
| module | 模块名 | string | 否 | `share` |
| listProps | 内置 List 组件 props | ListProps | 否 | {} |
| optionConfig | 每条回复内容下方 option 按钮设置 | CommentOptionType[] \| ((data:ReplyOption, defaultOption?: CommentOptionType[]) => CommentOptionType[]) | 否 | [`delete`, `reply`, `edit`, `task`, `like`] |
| onReplySubmit | 回复提交回调事件 | (value: string) => void | 否 |  |
| renderItemFooterInfo | 回复内容下方添加额外信息 | (data: ReplyOption) => React.ReactNode | 否 |  |
| renderItemHeaderInfo | 回复内容顶部添加额外信息 | (data: ReplyOption) => React.ReactNode | 否 |  |
| onEditSubmit | 创建评论提交回调事件 | (data: ReplyOption) => React.ReactNode[] | 否 |  |
| renderMore | 渲染创建评论区域下方内容 | (data: ReplyOption) => React.ReactNode[] | 否 |  |
| showText | 是否显示添加评论的文本框（默认为收起） | boolean | 否 | false |
| onPageChange | 分页切换事件 | (current: number, pageSize: number) => void | 否 |  |
| menu | 顶部Menu组件props(参考menu组件) | menuProps | 否 |  |
| renderReplyHeader | 添加回复标题 | (data: ReplyOption) => React.ReactNode | 否 |  |
| deleteRelevanceUrl | 删除评论事项关联接口 | string | 否 |  |
| customAvatarUrl | 自定义头像展示地址(返回null则使用接口默认头像) | string \| null | 否 |  |
| customAvatar | 自定义头像(自定义头像的优先级在自定义头像地址之前) | (com: React.ReactNode, data: ReplyOption) => React.ReactNode | 否 |  |
| commentCommentedUrl | 回复列表接口 | string(/api/{module}/common/comment/commentCommented) | 否 |  |
| commentCommentedParams | 回复列表参数 | AnyObj \| ((data?: ReplyOption) => AnyObj) | 否 |  |
| textareaProps | 文本框props | TextareaProps       \| (type?: '' \| 'reply') =>  TextareaProps  `reply`表示回复输入框 | 否 |  |
| onMenuChange | 顶部菜单切换事件 | (activeKey: string, activeItem: object, e: React.MouseEvent<Element, MouseEvent>) => void | 否 |  |
| objectModule | 模块接口路由 | string | 否 | `bcw` |
| showEmpty | 评论为空时是否展示Empty组件 | boolean | 否 | true |
| atProps | At组件Props | AtProps | 否 |  |
| customRenderEmpty | 自定义空数据状态 | (() => ReactNode) \| ReactNode | 否 |  |
| showSource | 是否展示评论来源 | boolean | 否 | true |
| searchAdvancedPanelProps | 高级搜索面板Props | Omit<SearchAdvancedPanelProps, `searchAdvancedStore` \| `conditionId`> | 否 |  |
| searchAdvancedProps | 高级搜索Props | SearchAdvancedProps | 否 |  |
| searchInitDatas | 高级搜索初始化内容 | FormInitAllDatas | 否 |  |
| searchConditionId | 高级搜索conditionId(唯一) | string | 使用高级搜索时必填 |  |
| getFormatSearchValue | 格式化高级搜索数据 | (key: string, data: AnyObj) => (AnyObj \| void) | 否 |  |
| onBeforeGetComment | 获取评论数据前触发 | (params?: AnyObj, activeKey?: string) => Promise<any \| void> | 否 |  |
| onAfterGetComment | 获取评论数据更新数据前触发 | (result: RequestResult, activeKey?: string) => Promise<any \| void> | 否 |  |
| onBeforeCreate | 创建评论接口 | (params?: AnyObj, replyData?: ReplyOption) => Promise<any \| void> | 否 |  |
| onAfterCreate | 评论提交或回复后更新数据前触发 | (result: RequestResult, replyData?: ReplyOption) => Promise<any \| void> | 否 |  |
| onBeforeUpdate | 评论更新前触发 | (params?: AnyObj, updateData?: ReplyOption) => Promise<any \| void> | 否 |  |
| onAfterUpdate | 评论更新后更新数据前触发 | (result: RequestResult, updateData?: ReplyOption) => Promise<any \| void> | 否 |  |
| onBeforeDelete | 评论删除前触发 | (params?: AnyObj, data?: ReplyOption) => Promise<any \| void> | 否 |  |
| onAfterDelete | 评论删除后更新数据前触发 | (result: RequestResult, data?: ReplyOption) => Promise<any \| void> | 否 |  |
| viewPagination | 查阅情况是否添加分页 | boolean | 否 | false |
| uploadProps | 评论内部附件上传Props | UploadProps \| ((data: ReplyOption) => UploadProps) | 否 |  |
| onReplyCancel | 评论编辑或回复去掉按钮回调 | (data: ReplyOption, type: string) => Promise | 否 |  |
| useRichText | 是否使用富文本 | boolean | 否 |  |
| richTextProps | 富文本框Props | RichTextProps | 否 |  |
| onDelete | 评论删除回调 | (data?: ReplyOption) => void | 否 |  |
| onDeleted | 评论删除成功回调 | (data?: ReplyOption) => void | 否 |  |
| renderReplyContent | 渲染评论回复区域内容 | (data: ReplyOption) => React.ReactNode | 否 |  |
| usePrivated | 是否开启私密评论功能 | boolean | 否 |  |
| deleteUrl | 删除接口URL | string | 否 |  |
| deleteParams | 删除接口的参数 | AnyObj | 否 |  |
| fetchData | 自定义请求数据 | (options: RequestOptionConfig) => RequestPromise | 否 |  |
| disableAt | 禁用输入框@人员功能 | boolean | 否 |  |
##### CommentMenuDataType 配置项（其余配置项参考 Menu 组件）

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| url | 是否显示添加评论的文本框（默认为收起） | url | 否 | "" |
| urlParams | 是否显示添加评论的文本框（默认为收起） | any | 否 |  |
| fetchData | 数据类型（url 存在时失效） | FetchDataType | 否 |  |
| showType | 页签展示类型 | `card` \| `list` | 否 | `list` |
| customCardRenderHeader | 自定义渲染卡片头部（showType = `card`时有效） | (data: CommentCardData) => React.ReactNode; | 否 |  |
| customListRenderHeader | 自定义渲染列表头部（showType = `list`时有效） | (data: ReplyOption) => React.ReactNode; | 否 |  |
| render | 自定义渲染内容 | (data: ReplyOption[]) => React.ReactNode; | 否 | false |
| isSubordinate | 是否显示下属查询 | boolean | 否 | false |
| addReplyCount | 回复时是否默认添加回复数量 | boolean | 否 | false |
> 下属评论需要对应接口支持，默认格式为``, module为模块id

#### Commment 和 CommentCommentMenu 组件手动刷新数据需要通过 ref 值然后调用 refresh 方法

## Comment.CommentEdit

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| value | 文本框内容 | string | 否 |  |
| defaultValue | 默认文本框内容 | string | 否 |  |
| submitDisable | 是否禁用submit按钮 | boolean | 否 | false |
| onEditSubmit | 提交按钮事件 | (value: string, params: AnyObj, attachments: string, uploadData: UploadData[], uploadStatus?: boolean, richTextImgUploadComplete?: boolean) => (Promise \| void) | 否 |  |
| onCancel | 取消按钮回调事件 | () => void | 否 |  |
| renderMore | 渲染最下方内容 | () => React.ReactNode | 否 |  |
| renderOptions | 渲染底部配置按钮 | () => React.ReactNode[] | 否 |  |
| showText | 是否显示文本框 | boolean | 否 | false |
| onChange | 文本框内容修改事件 | (value: string) => void | 否 |  |
| textareaProps | 文本框props | TextareaProps  \| (type?: '' \| 'reply') =>  TextareaProps `reply`表示回复输入框 | 否 |  |
| targetId | 评论唯一id（传入非空值后开启使用缓存 | string | 否 |  |
| objectModule | 接口路由拼接(缓存) | string | 否 | bcw |
| useRichText | 是否使用富文本 | boolean | 否 |  |
| richTextProps | 富文本框Props | RichTextProps | 否 |  |
| fetchData | 自定义请求数据 | (options: RequestOptionConfig) => RequestPromise | 否 |  |
## Comment.CommentContent

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| value | 评论内容 | string | 是 |  |
| userData | @人员信息，可以直接传接口值 | string \| object | 否 |  |
另外也提供方法直接将后台纯文本转换为文本框内可以渲染的文本内容

```ts
const { getAtValue } = Comment.CommentContent;

const value = getAtValue(commentValue, userData);
```

##### Comment.CommentEdit this.ref.current

评论编辑框ref支持使用的方法

| 参数 | 说明 | 类型 |  |  |
| --- | --- | --- | --- | --- |
| insertValue | 当前光标位置插入内容 | (value: string) => void |  |  |
| fetchData | 自定义请求数据 | (options: RequestOptionConfig) => RequestPromise | 否 |  |
## Comment.CommentList

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| data | 评论的数据 | ReplyOption[] | 否 |  |
| paginationProps | 分页组件 props | PaginationProps\| false | 否 |  |
| paginationType | 分页类型 | ListPaginationType | 否 |  |
| commentLoading | List 组件 loading 状态 | boolean | 否 |  |
| itemClassName | item 样式类名 | string | 否 |  |
| module | 模块接口标识 | string | 否 | `share` |
| hasMore | 是否存在更多数据 | boolean | 否 |  |
| renderItemHeaderInfo | 渲染列表更多标题内容 | (data: ReplyOption) => React.ReactNode | 否 |  |
| onForward | 转任务回调函数 | (value: string, user: any[], date: string) => void | 否 |  |
| listProps | List 组件 props | ListProps | 否 |  |
| optionConfig | 每条回复内容下方 option 按钮设置 | CommentOptionType[] \| ((data:ReplyOption,defaultOption?: CommentOptionType[]) => CommentOptionType[]) | 否 |  |
| onReplySubmit | 回复提交回调事件 | (value: string, data: ReplyOption) => void | 否 |  |
| onUpdata | 回复更新回调事件 | (value: string, data: ReplyOption) => Promise \| void | 否 |  |
| renderReplyOption | 添加回复文本框下方按钮 | (data: ReplyOption) => React.ReactNode[] | 否 |  |
| renderOtherInput | 添加更多显示内容 | (data: ReplyOption) => React.ReactNode | 否 |  |
| renderItemFooterInfo | 回复内容下方添加额外信息 | (data: ReplyOption) => React.ReactNode | 否 |  |
| getScrollContainer | 滚动容器定位(组件外部容器滚动时使用，内部 List 滚动请使用 listProps 传递滚动 api，下列数据同) | () => HTMLElement | 否 |  |
| onEndReachedThreshold | 触发滚动事件时距离容器底部的像素的值 | number | 否 |  |
| scrollEventThrottle | 滚动事件触发间隔(毫秒) | number | 否 |  |
| onScroll | 滚动事件回调 | () => void | 否 |  |
| topicConfig | 话题设置参数（需要转话题功能时 必填 ） | {data: TopicConfigDataParams, icon: IconNames} | 否 |  |
| fetchData | 自定义请求数据 | (options: RequestOptionConfig) => RequestPromise | 否 |  |
## Comment.CommentItem

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| data | 评论数据 | ReplyOption | 是 |  |
| renderFooter | 渲染底部内容 | (data: ReplyOption) => React.ReactNode | 否 |  |
| renderMoreTop | 渲染头部自定义内容(用户名右侧) | (data: ReplyOption) => React.ReactNode | 否 |  |
| renderCommentContent | 渲染评论中间(评论内容和事项关联中间区域) | (data: ReplyOption) => React.ReactNode | 否 |  |
| onFileDelete | 附件点击删除事件 | (fileId: string, module: string, id: string, listT: UploadData[]) => void | 否 |  |
| renderReplyHeader | 渲染回复人右侧内容 | (data: ReplyOption) => React.ReactNode | 否 |  |
| deleteCommentRelevance | 事项关联点击删除事件 | (id: string, targetId: string, module: string) => void | 否 |  |
| quoteRenderProps | 引用区域自定义渲染内容 | ItemRenderObjType | 否 |  |
## Comment.CommentEditText

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| value | 评论数据 | string | 是 |  |
| onChange | 文本框内容改变回调 | (value: string) => void | 否 |  |
| textareaProps | 文本框 props | TextareaProps    \| (type?: '' \| 'reply') =>  TextareaProps `reply`表示回复输入框 | 否 |  |
| renderOptions | 渲染文本框下方事件右侧区域内容 | (() => React.ReactNode[]) \| React.ReactNode[] | 否 |  |
| renderSubmit | 渲染发表按钮 | (() => React.ReactNode) \| React.ReactNode | 否 |  |
| renderOptionsInfo | 渲染发布按钮左侧内容 | (() => React.ReactNode) \| React.ReactNode | 否 |  |
| renderExtraConent | 渲染创建评论区域额外内容 | (() => React.ReactNode) \| React.ReactNode | 否 |  |
| module | 系统模块 | string | 否 | `share` |
| uploadUrl | 文件上传服务接口 | string | 否 | `/api/file/module/upload` |
| onSubmit | 发布事件 | CommentEditTextSubmitFunc | 否 |  |
| atDataList | 默认 at 人员信息() | AtCommentData[] | 否 |  |
| placeholder | 文本框 placeholder | string | 否 |  |
| associatedData | 默认展示事件关联数据 | SelectedData | 否 |  |
| actionConfig | 底部事件配置项 | CommentAction[] | 否 |  |
| targetId | 评论唯一id（传入非空值后开启使用缓存 | string | 否 |  |
| objectModule | 接口路由拼接(缓存) | string | 否 | `bcw` |
| useRichText | 是否使用富文本 | boolean | 否 |  |
| richTextProps | 富文本框Props | RichTextProps | 否 |  |
| quoteData | 引用数据 | ReplyOption | 否 |  |
| onClearQuote | 删除引用按钮回调 | () => void | 否 |  |
| renderOptionBottom | 位置信息下方自定义渲染内容 | (() => ReactNode) \| ReactNode | 否 |  |
| fetchData | 自定义请求数据 | (options: RequestOptionConfig) => RequestPromise | 否 |  |
##### AtCommentData

评论at数据内容

| 参数 | 说明 | 类型 | 是否必填 | 默认 |
| --- | --- | --- | --- | --- |
| atType | @类型 | string | 是 |  |
| userId | @人员 id | string | 是 |  |
| name | @人员名称 | string | 是 |  |
##### CommentAction

评论文本框底部按钮配置

```ts
export type CommentActionName = "at" | "emoticon" | "associated" | "upload" | "address" | "phrase" | "signature" | "signApprove" | "|";

export type CommentActionObj = {
  id: CommentActionName | string;
  content?: string;
  icon?: IconNames;
  hideTitle?: boolean;
  name?: string;
  onClick?: (id: string) => void;
  customRender?: (_: any, com: ReactNode) => ReactNode;
};

export type CommentAction = CommentActionName | CommentActionObj;
```

> CommentActionName API

| 参数 | 说明 |
| --- | --- |
| at | @人员 |
| emoticon | 表情 |
| associated | 事项关联 |
| upload | 附件上传 |
| address | 获取定位 |
| phrase | 常用短语 |
| signature | 电子签名 |
| signApprove | 手写签批 |
| \| | 分隔线 |
##### CommentEditTextSubmitFunc (value: string, params: AnyObj, attachments: string, uploadData: UploadData[], uploadStatus?: boolean, richTextImgUploadComplete?: boolean) => void;

| 参数 | 说明 | 类型 |
| --- | --- | --- |
| value | 评论输入框的内容 | string |
| params | 评论额外传参,包含 relevanceList(附件关联列表)、imAtContent(@人名转换为@人 id 格式后的内容)、atLinkList(包含@人员名称和@人 id 对应关系)、content(剔除@人 id 后的评论内容) | AnyObj |
| attachments | 所有上传附件 id | string |
| uploadData | 所有上传附件的信息 | UploadData[] |
| uploadStatus | 附件是否全部上传完成 | boolean |
| richTextImgUploadComplete | 富文本中图片是否全部上传完成 | boolean |
> CommentEditTextSubmitFunc 参数说明: 标准评论接口将 params 参数直接传入创建评论接口 comment 属性，不用做额外处理

## Comment.Upvote

| 参数 | 说明 | 类型 |  |  |
| --- | --- | --- | --- | --- |
| module | 模块接口拼接路由 | string |  |  |
| objectId | 每条评论对应 id | string |  |  |
| upvoteCount | 点赞人数 | number |  |  |
| defaultUpvoteCount | 默认点赞人数 | number |  |  |
| defaultUpvoted | 默认点赞状态 | boolean |  |  |
| upvoted | 点赞状态 | boolean |  |  |
| defaultPopupVisible | 点赞人数弹出框默认显示状态 | boolean |  |  |
| popupVisible | 点赞人数弹出框显示状态 | boolean |  |  |
| queryVoterParams | 点赞查询接口传参 | AnyObj |  |  |
| upvoteChangeParams | 点赞状态改变接口传参 | AnyObj |  |  |
| onPopupVisibleChange | 点赞人数弹出框显示改变事件 | (visible: boolean) => void |  |  |
| onUpvotedChange | 点赞状态改变事件 | (upvoted: boolean) => void |  |  |
| onUpvotedCountChange | 点赞数量改变事件 | (upvoteCount: number) => void |  |  |
| onUpvotedClick | 点赞按钮点击事件 | () => void |  |  |
| fetchData | 自定义请求数据 | (options: RequestOptionConfig) => RequestPromise | 否 |  |
