react 自定义日历 手把手教你

最近遇到了项目中写日历的形式
看了看react 和 antd ,用的时候发现了好大的坑。建议看看antd 的 API 。一定要看看,不然你就崩溃了。

好了先看看布局后的样式图展
在这里插入图片描述
数据怎么返回你要和后端协定好。
不然数据有问题,前端就要麻烦处理了。
上图可见,这是详情数据,通过id 拿回的。接口的话自己写吧,我这里就不展示了。
协定的是: 数据创建的时候肯定是要传一个时间和具体的事项。但是你要想在日历中展示成一条条的形式,那可定是数组最好处理。
我们模拟一下情景
创建数据的时候:给后端传的数据(前端怎么传,后端怎么存,需要的时候就怎么返回来就行)
我们模拟一下后端返回的数据:

parasm = [ 
	{
		fitTime: '2022-09-01 00:00:00' // 这个时间最好是带着 时分秒,但是不用实时获取当前时间的时分秒,你可以去看 days 或者	moment  当然也可以只到年月日就行,看你们需求。
		data : [
			{工序:1.....}
			{工序:2.....}
			{工序:3.....}
			{工序:4.....}
		]
	},
	{
		fitTime: '2022-09-02 00:00:00' // 这个时间最好是带着 时分秒,但是不用实时获取当前时间的时分秒,你可以去看 days 或者	moment  当然也可以只到年月日就行,看你们需求。
		data : [
			{工序:1.....}
			{工序:2.....}
		]
	},
	{
		fitTime: '2022-09-03 00:00:00' // 这个时间最好是带着 时分秒,但是不用实时获取当前时间的时分秒,你可以去看 days 或者	moment  当然也可以只到年月日就行,看你们需求。
		data : [
			{工序:1.....}
			{工序:2.....}
			{工序:3.....} 
		]
	}
]

好了。这是我们约定的数据样式
,剩下的交给我吧。
看一下antd

在这里插入图片描述
在这里插入图片描述

注意:
dateFullCellRender 自定义渲染日期单元格,返回内容覆盖单元格 function(date: Moment): ReactNode。
headerRender 自定义头部内容 function(object:{value: Moment, type: string, onChange: f(), onTypeChange: f()})

当你在代码中布局后,在页面上展示,通过log 发现打印了好多的时间,就是因为antd 组件本身已经封装过了。不能在时间上去改变什么。
唯一不同的是,你获取后端返回的时间和antd 的时间做比较然后渲染页面就行了。。
看代码操作。解析放在代码里了

除了做个日历后边加了个echart 图
如果你不需要可以不用看 this.complete(); // echart 展示图方法
在这里插入图片描述

import React from 'react';
import { ElNotification, ElCard } from '@/components/el';
import { Spin, Button } from 'antd';
import dayjs from 'dayjs';
import { ElRowContainer } from '@/components/el';
import * as service from './service';  // 接口封装的文件 ,这里不做展示,一般项目里都会有自己的接口封装方法,直接用你们的接口替换就行
import MultiTabMobx from '@/store/multiTab';
import { Badge, Calendar, Select, Row, Col } from 'antd'; // antd 里面的 图标和组件引用
import type { Moment } from 'moment'; // 这个是要用的,可以直接用,一般项目里面都会有
import 'moment/locale/zh-cn'; // 这个也是必须的,不然你会发现引用了之后转义的都是英文时间
import './index.less'; // 样式
import moment from 'moment'; // 也必须的,直接用
import EditModal from './EditModal/index';
import * as echarts from 'echarts'; // echarts 的引用 
import { statusMap } from '@/utils'; // 这个是展示出日历后,对每一条 li 做的颜色展示,不同的需求展示不同的颜色

interface Props {
  match: any;
  push?: any;
  location: any; // 这个是其他页面传回的数据 这里用了 react 中state 的路由传参方式
}
interface State {
  loading: boolean;
  RenderData: any;
  modalVisible: Boolean;
  mark: String;
  data: any;
  formRef: any; //表单的ref
  locationState: any; // 定义的接收传参的处理
}

class PurcBuyerLook extends React.Component<Props, State> {
  modalRef: any;
  multiTabStore: any;
  constructor(props) {
    super(props);
    this.multiTabStore = MultiTabMobx;
    this.state = {
      loading: false,
      RenderData: [], // 前端定义的接收后端数据做展示的
      modalVisible: false,
      formRef: null, //表单的ref
      mark: 'create',
      data: null, // 这个是弹框要用的,需要就用
      locationState: this.props.location  // 接收传参
    };
  }
  async componentDidMount() {
    let params = this.state?.locationState?.state.params; // 接收state传参 的处理。
    if (params) {
      this.getCalendDetail(params); // 日历接口方法
      this.complete(); // echart 展示图方法
    }
  }
  // 获取详情 日历看板数据
  getCalendDetail = async (params) => {
    this.setState({
      loading: true
    });
    const res = await service.QueryCalendar(params); // 接口调取处理结果
    this.setState({
      loading: false
    });
    if (res.success) {
      // 请求成功的返回的数据;
      let details = res.data.map((item) => {
        return item;
      });
      this.setState({
        RenderData: details // 直接赋值,
      });
    }
  };

// 这个是改变年 月 的事件操作。(需要当前接收的id,因为我们看的是特定某条的明细,所以主单的id是唯一的,直接用就行。
这里的时间是你实时变化的,不管是点击年的时候还是月的时候,这里展示的是选中年或月的当前月的第一天,因为要展示一月,所以不用管哪天。)
  getData = async (data) => {
    const capaBoardId = this.state?.locationState?.state?.params?.capaBoardId;
    const calendarDate = dayjs(data)
      .startOf('month')
      .format('YYYY-MM-DD 00:00:00');
    let params = {
      capaBoardId,
      calendarDate // // 选中时间的当前月第一天
    };
    this.getCalendDetail(params);
  };

// 返回到主单,直接放路由就行了
  onBack = () => {
    const { push } = this.props;
    push('/Capacity/manage/Capacitykan/list', () => false);
  };

  // antd 自定义渲染日期单元格,返回内容会被追加到单元格 dateCellRender
  dateFullCellRender = (value: Moment) => { // 这里的 value 就是好多的时间,antd 返的
    const listData = this.getListData(value); // 这里就是数据的处理展示  ,因为是数组,展示的话用 li 做合适。
    return (
      <ul className='events'>
        {listData.map((item) => (
          <li
            key={item.itemId}
            onClick={() => this.handleClick(item)} // 每一个 li 的点击事件
            style={
   
   {  // 么个数据的样式肯定要加的 ,不然放不下怎么办,所以 溢出隐藏
              width: '100px',
              marginLeft: '-30px',
              whiteSpace: 'nowrap',
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              // 报工时间 reportTime 大于排产时间 scheduleTime 已逾期-红色, 已报工数量 reportQty 为0  未开始-黄色 ,已报工数量小于任务数量 scheduleQty 已开始-蓝色 ,已报工数量大于等于任务数量已完成-绿色,
              color: statusMap.get(
                `QTY_${
                  item.reportTime > item.scheduleTime
                    ? 3
                    : item.reportQty == 0
                    ? 0
                    : item.reportQty < item.scheduleQty
                    ? 1
                    : item.reportQty > item.scheduleQty
                    ? 2
                    : 0 || item.reportQty == item.scheduleQty
                    ? 2
                    : 0
                }`
              )
            }}
          >
            {item.itemName}&nbsp;
            {item.itemCode}&nbsp;
            {item.equiProcName}&nbsp;
            {item.techName}
          </li>
        ))}
      </ul>
    );
  };
  // 数据的展示处理,主要的就是这里了
	//  解析 这里的value 还是 antd 的时间 ,
	我们要做的就是拿接口返回的数据时间和这个时间做对比。只要有时间数据一样的就把数据展示到对应的那一天
  getListData = (value: Moment) => {
    let listData;
    this.state.RenderData.map((item) => { // 拿到处理后的后端数据,前边已经state了
   	// 判断一下  后端返回的数据时间 == 通过moment处理的value 时间 (我们是到年月日时分秒了。如果你们不用时分秒就不用处理 直接 [value] 或者 [value.data()] 或者 value )
      if (item.scheduleTime == moment(value).format('YYYY-MM-DD 00:00:00')) {
        listData = [...item.details];
      }
    });
    // 这里把对应的时间数据返回后return 出去。上边有接收处理的 ul --li 
    return listData || [];
  };

// 这是点击每一个li 的事件弹框处理,你们怎么用就怎么改
  handleClick = async (item) => {
    const dataForm = {
      ...item
    };
    this.setState({
      modalVisible: true,
      mark: 'edit',
      data: dataForm
    });
  };

  // 关闭弹窗
  closeModal = () => {
    this.setState({ modalVisible: false });
    this.state.formRef.resetFields(); // 清空数据
  };
  // form表单ref
  formRef = (ref) => {
    this.setState({
      formRef: ref
    });
  };
  // 弹框保存 有新增有编辑 if判断
  save = async () => {
    const { formRef } = this.state;
    // 表单验证
    await formRef.validateFields().catch(() => {
      ElNotification({
        type: 'warning',
        message: '请检查基础信息'
      });
      return Promise.reject();
    });
    const fieldsValue = formRef.getFieldsValue();
    let reportId = this.state.data.id;
    let reportQty = fieldsValue.EditReportQty;
    const res = await service.saveEdit(reportId, reportQty);
    if (res.success) {
      ElNotification({ type: 'success', message: res.msg });
      this.setState({
        modalVisible: false
      });
      formRef.resetFields(); // 清空数据
      let params = this.state?.locationState?.state.params;
      this.getCalendDetail(params); // 日历接口方法
    } else {
      ElNotification({
        type: 'error',
        message: res.msg || res.data || '操作失败!'
      });
    }
  };

  // 完成情况 echart
  complete = async () => {
    var chartDom = document.getElementById('mainStatus');
    var myChart = echarts.init(chartDom);
    var option;
    let id = this.state?.locationState?.state.params?.capaBoardId;
    const echartData = await service.echartDetail(id);
    if (echartData.success) {
      let data1 = []; // 完成工序占比
      let data2 = []; // 未完成工序占比
      // 展示n个工序名称
      let equiProcName = echartData.data.map((item) => {
        return item.equiProcName; // 工序名称
      });
      let itemEquiProc = echartData.data.map((item) => {
        return {
          totalQty: item.totalQty, // 总数
          finishQty: item.finishQty, // 完成数量
          Quantity: item.totalQty - item.finishQty // 未完成数量
        };
      });
      for (let i = 0; i < echartData.data.length; i++) {
        itemEquiProc.forEach((item) => {
          data1.push(item.finishQty); // 完成工序占比
          data2.push(item.Quantity); // 未完成工序占比
        });
      }
      var emphasisStyle = {
        itemStyle: {
          shadowBlur: 10,
          shadowColor: 'rgba(0,0,0,0.3)'
        }
      };
      option = {
        legend: {
          data: ['完成', '未完成'], // 头长度
          left: '10%'
        },
        tooltip: {},
        xAxis: {
          data: equiProcName,
          name: '工序',
          axisLine: { onZero: true },
          splitLine: { show: false },
          splitArea: { show: false }
        },
        yAxis: {},
        grid: {
          bottom: 100
        },
        series: [
          {
            name: '完成',
            type: 'bar',
            stack: 'one',
            emphasis: emphasisStyle,
            data: data1
          },
          {
            name: '未完成',
            type: 'bar',
            stack: 'one',
            emphasis: emphasisStyle,
            data: data2
          }
        ]
      };
      myChart.on('brushSelected', function (params) {
        var brushed = [];
        myChart.setOption({
          title: {
            backgroundColor: '#333',
            text: 'SELECTED DATA INDICES: \n' + brushed.join('\n'),
            bottom: 0,
            right: '10%',
            width: 100,
            textStyle: {
              fontSize: 12,
              color: '#fff'
            }
          }
        });
      });
      option && myChart.setOption(option);
    } else {
      ElNotification({
        type: 'error',
        message: echartData.msg || '操作失败'
      });
    }
  };

  // 页面的渲染
  render() {
    const { modalVisible, loading, data, mark } = this.state;
    return (
      <div>
        <Spin spinning={loading}>
          <ElRowContainer blocks={[]} position='top' onBack={this.onBack} />
          <ElCard title='产能看板详情'>
            <Calendar
              dateCellRender={this.dateFullCellRender}
              headerRender={({ value, type, onChange, onTypeChange }) => {
                const start = 0;
                const end = 12;
                const monthOptions = [];
                const current = value.clone();
                const localeData = value.localeData();
                const months = [];
                for (let i = 0; i < 12; i++) {
                  current.month(i);
                  months.push(localeData.monthsShort(current));
                }
                for (let i = start; i < end; i++) {
                  monthOptions.push(
                    <Select.Option key={i} value={i} className='month-item'>
                      {months[i]}
                    </Select.Option>
                  );
                }
                const year = value.year();
                const month = value.month();
                const options = [];
                for (let i = year - 10; i < year + 10; i += 1) {
                  options.push(
                    <Select.Option key={i} value={i} className='year-item'>
                      {i}
                    </Select.Option>
                  );
                }
                return (
                  <div style={
   
   { padding: 8 }}>
                    <Row gutter={8}>
                      <Col>
                        <Select
                          size='small'
                          dropdownMatchSelectWidth={false}
                          className='my-year-select'
                          value={year}
                          onChange={(newYear) => {
                            const now = value.clone().year(newYear);
                            onChange(now);
                            // 可以在这里打印一下 now  看你需要什么样的时间格式就处理什么格式,然后传到方法里面
                            this.getData(now.format('YYYY-MM-DD'));
                          }}
                        >
                          {options}
                        </Select>
                      </Col>
                      <Col>
                        <Select
                          size='small'
                          dropdownMatchSelectWidth={false}
                          value={month}
                          onChange={(newMonth) => {
                            const now = value.clone().month(newMonth);
                            onChange(now);
                            this.getData(now.format('YYYY-MM-DD'));
                          }}
                        >
                          {monthOptions}
                        </Select>
                      </Col>
                    </Row>
                  </div>
                );
              }}
            />
          </ElCard>
          // echarts 展示图
          <ElCard title='完成情况'>
            <div
              id='mainStatus'
              style={
   
   { height: '300px', marginTop: '30px' }}
            ></div>
          </ElCard>
        </Spin>

        <EditModal
          title={mark === 'create' ? '新增报工' : '编辑报工'}
          modalVisible={modalVisible}
          closeModal={this.closeModal}
          save={this.save}
          onRef={this.formRef}
          data={data}
          mark={mark}
        ></EditModal>
      </div>
    );
  }
}

export default PurcBuyerLook;

到此就结束了。 是不是可简单

猜你喜欢

转载自blog.csdn.net/lzfengquan/article/details/126677110