Skip to content

jimmyarea 留言(前端)

Jimmy edited this page Jul 8, 2021 · 1 revision
import React, { useEffect, useState, useRef } from 'react';
import {
  Card,
  Form,
  Input,
  Button,
  message,
  List,
  Avatar,
  Row,
  Col,
  Modal,
  Comment,
  Tooltip,
} from 'antd';
import { postMessage, getMessageList, deleteMessageItem } from '@/services/dashboard/index';
import { deviceEnquire, DEVICE_TYPE } from '@/utils/device';
import { getStore } from '@/utils/storage';
import { logout } from '@/utils/utils';
import Captcha from './components/Captcha';
import {
  CloudUploadOutlined,
  CommentOutlined,
  DeleteOutlined,
  MessageOutlined,
} from '@ant-design/icons';

const moment = require('moment');

const Message: React.FC = () => {
  const [userInfo, setUserInfo] = useState({ username: '' });
  const [loading, setLoading] = useState(false);
  const [innerLoading, setInnerLoading] = useState(false);
  const [form] = Form.useForm();
  const [replyForm] = Form.useForm();
  const [isMobile, setIsMobile] = useState(false);
  const [hasToken, setHasToken] = useState(false);
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [list, setList] = useState([]);
  const [count, setCount] = useState(0);
  const [loadingMsg, setLoadingMsg] = useState(false);
  const [activePage, setActivePage] = useState(1); // 当前页码
  const [pageSize] = useState(10); // 每页的条数
  const [type, setType] = useState(1); // 是留言还是回复(1是留言,2是回复)
  const [replyPlaceholder, setReplyPlaceholder] = useState(''); // 回复文本的placeholder
  const [replyObj, setReplyObj] = useState({ _id: '', pid: '-1' }); // 回复的文本的对象信息
  const replyArea = useRef(null);

  const layout = {
    labelCol: { span: 6 },
    wrapperCol: { span: 12 },
  };

  const tailLayout = {
    wrapperCol: isMobile ? { span: 24 } : { offset: 6, span: 12 },
  };

  const replyMsg = (item: any) => {
    replyForm.resetFields();
    if (!hasToken) {
      message.warning('请先登陆~');
      return;
    }
    setReplyObj(item);
    setReplyPlaceholder(`${userInfo.username}@${item.userId?.username} ${item?.content}`);
    if (replyArea) {
      setTimeout(() => {
        replyArea?.current?.scrollIntoView({
          behavior: 'smooth',
          block: 'center',
          inline: 'center',
        });
      }, 100);
    }
  };

  const cancelReply = () => {
    setReplyObj({ _id: '', pid: '-1' });
  };

  const onFinish = () => {
    setIsModalVisible(true);
  };

  const onFinishReply = () => {
    setType(2);
    setIsModalVisible(true);
  };

  const onFinishFailed = (errorInfo: any) => {
    if (errorInfo) {
      message.warning('请完成必填字段信息');
    }
  };

  const judgeDevice = () => {
    deviceEnquire((deviceType: any) => {
      switch (deviceType) {
        case DEVICE_TYPE.MOBILE:
          setIsMobile(true);
          break;
        default:
          setIsMobile(false);
          break;
      }
    });
  };

  const judgeHasToken = () => {
    const token = getStore('token', false) || undefined;
    if (token) {
      setHasToken(true);
    }
  };

  const getLoginUserInfo = () => {
    const loginUserInfo = getStore('userInfo', true) || {};
    setUserInfo(loginUserInfo);
  };

  const fetchMessageList = () => {
    setLoadingMsg(true);
    getMessageList(
      {
        current: activePage,
        pageSize,
      },
      {},
    )
      .then((res: any) => {
        if (res.code === 0) {
          setList(res.data?.results || []);
          setCount(res.data?.count || 0);
        }
      })
      .finally(() => {
        setLoadingMsg(false);
      });
  };

  const passCaptcha = () => {
    setIsModalVisible(false);
    let subject = '';
    let content = '';
    if (type === 1) {
      subject = form.getFieldValue('subject');
      content = form.getFieldValue('content');
      setLoading(true);
    }
    if (type === 2) {
      subject = replyPlaceholder;
      content = replyForm.getFieldValue('reply');
      setInnerLoading(true);
    }
    postMessage(
      {},
      {
        subject: subject?.trim(),
        content: content?.trim(),
        type,
        pid: replyObj.pid === '-1' ? replyObj._id : replyObj.pid,
        replyTargetId: replyObj._id || '-1',
      },
    )
      .then((res) => {
        if (res.code === 0) {
          setTimeout(() => {
            message.success(res.msg || '提交成功!');
            if (type === 1) {
              setLoading(false);
              form.resetFields();
            }
            if (type === 2) {
              setInnerLoading(false);
              setReplyObj({ _id: '', pid: '-1' });
              replyForm.resetFields();
            }
            fetchMessageList();
          }, 2000);
        } else {
          message.error(res.msg || '留言失败,请稍后再试!');
          setLoading(false);
          setInnerLoading(false);
          c({ _id: '', pid: '-1' });
          if (res.code === 10004) {
            let timer: any = null;
            let time = 3;
            message.info(`${time} 后跳转到登录页面!`);
            timer = setInterval(() => {
              time -= 1;
              if (time <= 0) {
                clearInterval(timer);
                logout();
              }
            }, 1000);
          }
        }
      })
      .catch(() => {
        setLoading(false);
        setInnerLoading(false);
      })
      .finally(() => {
        setType(1);
      });
  };

  const handleCancel = () => {
    setIsModalVisible(false);
  };

  const changePage = (page: any) => {
    setActivePage(page);
  };

  const removeMsg = (item: any) => {
    Modal.confirm({
      title: '温馨提示',
      content:
        item?.children?.length > 0 ? '你确定删除此条留言及其子留言?' : '你确定删除此条留言?',
      onOk: async () => {
        const result = await deleteMessageItem(item._id);
        if (result.code === 0) {
          message.success(result.msg || '删除成功!');
          fetchMessageList();
        } else {
          message.error(result.msg || '删除失败!');
        }
      },
    });
  };

  useEffect(() => {
    judgeDevice();
    judgeHasToken();
    getLoginUserInfo();
    fetchMessageList();
  }, [activePage]);

  return (
    <>
      <Card style={{ width: '100%', marginTop: '36px' }}>
        <div
          style={{
            textAlign: 'center',
            fontSize: '16px',
            fontWeight: 'bolder',
            marginBottom: '24px',
            position: 'relative',
          }}
        >
          留言
          <span
            style={{
              display: 'inline-block',
              fontSize: '12px',
              position: 'absolute',
              left: '50%',
              marginLeft: '36px',
            }}
          >
            {hasToken ? null : <a onClick={logout}>登录后可留言</a>}
          </span>
        </div>
        <Form
          {...layout}
          form={form}
          name="basic"
          onFinish={onFinish}
          onFinishFailed={onFinishFailed}
        >
          <Form.Item
            label="主题"
            name="subject"
            rules={[
              { required: true, message: '请输入你的主题' },
              { whitespace: true, message: '输入不能为空' },
              { min: 6, message: '主题不能小于6个字符' },
              { max: 30, message: '主题不能大于30个字符' },
            ]}
          >
            <Input maxLength={30} placeholder="请输入你的主题(最少6字符,最多30字符)" />
          </Form.Item>

          <Form.Item
            label="内容"
            name="content"
            rules={[
              { required: true, message: '请输入你的内容' },
              { whitespace: true, message: '输入不能为空' },
              { min: 30, message: '内容不能小于30个字符' },
            ]}
          >
            <Input.TextArea
              placeholder="请输入你的内容(最少30字符)"
              autoSize={{
                minRows: 6,
                maxRows: 12,
              }}
              showCount
              maxLength={300}
            />
          </Form.Item>
          <Form.Item {...tailLayout}>
            <Button
              type="primary"
              htmlType="submit"
              style={{ width: '100%' }}
              loading={loading}
              disabled={loading}
            >
              <CloudUploadOutlined />
              &nbsp;Submit
            </Button>
          </Form.Item>
        </Form>
        <Row style={{ marginTop: '36px' }}>
          <Col {...tailLayout.wrapperCol}>
            <b style={{ marginBottom: '24px' }}>
              留言展示&nbsp;
              <CommentOutlined />
            </b>
            <List
              loading={loadingMsg}
              itemLayout="horizontal"
              pagination={{
                size: 'small',
                total: count,
                showTotal: () => `共 ${count} 条`,
                pageSize,
                current: activePage,
                onChange: changePage,
              }}
              dataSource={list}
              renderItem={(item: any, index: any) => (
                <List.Item actions={[]} key={index}>
                  <List.Item.Meta
                    avatar={
                      <Avatar style={{ backgroundColor: '#1890ff' }}>
                        {item.userId?.username?.slice(0, 1)?.toUpperCase()}
                      </Avatar>
                    }
                    title={<b>{item.subject}</b>}
                    description={
                      <>
                        {item.content}
                        {/* 子留言 */}
                        <div
                          style={{
                            fontSize: '12px',
                            marginTop: '8px',
                            marginBottom: '16px',
                            alignItems: 'center',
                            display: 'flex',
                            flexWrap: 'wrap',
                            justifyContent: 'space-between',
                          }}
                        >
                          <span>
                            用户&nbsp;{item.userId?.username}&nbsp;&nbsp;发表于&nbsp;
                            {moment(item.meta?.createAt).format('YYYY-MM-DD HH:mm:ss')}
                          </span>
                          <span>
                            {item.canDel ? (
                              <a
                                style={{ color: 'red', fontSize: '12px', marginRight: '12px' }}
                                onClick={() => removeMsg(item)}
                              >
                                <DeleteOutlined />
                                &nbsp; Delete
                              </a>
                            ) : null}
                            <a
                              style={{ fontSize: '12px', marginRight: '12px' }}
                              onClick={() => replyMsg(item)}
                            >
                              <MessageOutlined />
                              &nbsp; Reply
                            </a>
                          </span>
                        </div>
                        {/* 回复的内容 */}
                        {item.children && item.children.length ? (
                          <>
                            {item.children.map((innerItem: any, innerIndex: any) => (
                              <Comment
                                key={innerIndex}
                                author={<span>{innerItem.subject}</span>}
                                avatar={
                                  <Avatar style={{ backgroundColor: '#1890ff' }}>
                                    {innerItem.userId?.username?.slice(0, 1)?.toUpperCase()}
                                  </Avatar>
                                }
                                content={<p>{innerItem.content}</p>}
                                datetime={
                                  <Tooltip
                                    title={moment(innerItem.meta?.createAt).format(
                                      'YYYY-MM-DD HH:mm:ss',
                                    )}
                                  >
                                    <span>{moment(innerItem.meta?.createAt).fromNow()}</span>
                                  </Tooltip>
                                }
                                actions={[
                                  <>
                                    {innerItem.canDel ? (
                                      <a
                                        style={{
                                          color: 'red',
                                          fontSize: '12px',
                                          marginRight: '12px',
                                        }}
                                        onClick={() => removeMsg(innerItem)}
                                      >
                                        <DeleteOutlined />
                                        &nbsp; Delete
                                      </a>
                                    ) : null}
                                  </>,
                                  <a
                                    style={{ fontSize: '12px', marginRight: '12px' }}
                                    onClick={() => replyMsg(innerItem)}
                                  >
                                    <MessageOutlined />
                                    &nbsp; Reply
                                  </a>,
                                ]}
                              />
                            ))}
                          </>
                        ) : null}

                        {/* 回复的表单 */}
                        {replyObj._id === item._id || replyObj.pid === item._id ? (
                          <div style={{ marginTop: '12px' }} ref={replyArea}>
                            <Form
                              form={replyForm}
                              name="reply"
                              onFinish={onFinishReply}
                              onFinishFailed={onFinishFailed}
                            >
                              <Form.Item
                                name="reply"
                                rules={[
                                  { required: true, message: '请输入你的内容' },
                                  { whitespace: true, message: '输入不能为空' },
                                  { min: 2, message: '内容不能小于2个字符' },
                                ]}
                              >
                                <Input.TextArea
                                  placeholder={replyPlaceholder}
                                  autoSize={{
                                    minRows: 6,
                                    maxRows: 12,
                                  }}
                                  showCount
                                  maxLength={300}
                                />
                              </Form.Item>

                              <Form.Item>
                                <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
                                  <Button
                                    style={{ marginRight: '12px' }}
                                    onClick={() => cancelReply()}
                                  >
                                    Dismiss
                                  </Button>
                                  <Button
                                    type="primary"
                                    htmlType="submit"
                                    loading={innerLoading}
                                    disabled={innerLoading}
                                  >
                                    Submit
                                  </Button>
                                </div>
                              </Form.Item>
                            </Form>
                          </div>
                        ) : null}
                      </>
                    }
                  />
                </List.Item>
              )}
            />
          </Col>
        </Row>
      </Card>
      <Modal
        title="验证"
        destroyOnClose={true}
        visible={isModalVisible}
        footer={null}
        maskClosable={false}
        onCancel={handleCancel}
      >
        {isModalVisible ? <Captcha onSuccess={passCaptcha} /> : null}
      </Modal>
    </>
  );
};

export default Message;
Clone this wiki locally