天天看点

使用react-selectable-fast实现时间拖动选择的功能

使用到的资源

react-selectable-fast

antd

react

以下为完整代码,有冗余,没有整理

/**
 * @file modules/adlib/plan/EditFormantd
 * @author shj
 */

import {
    Form,
    Input,
    Switch,
    DatePicker,
    message,
    Button
} from 'antd';
import {utils} from 'baidu-acu-react-common';
import React from 'react';
import moment from 'moment';
import {getInitDate} from 'modules/common/helper';
import ScheduleChange from 'containers/adlib/CyclePanel';

const FormItem = Form.Item;
const {timeToUtc} = utils.timeUtil;

const formItemLayout = {
    labelCol: {
        xs: {span: 24},
        sm: {span: 4}
    },
    wrapperCol: {
        xs: {span: 24},
        sm: {span: 16}
    }
};
let data = getInitDate();

class EditFormantd extends React.Component {

    state = {
        confirmDirty: false,
        autoCompleteResult: [],
        payload: {},
        isNewAD: false,
        cycle: 'ffffffffffffffffffffffffffffffffffffffffff'
    }
    componentDidMount() {
        if (this.props.item) {
            this.loadDetail();
        }
        if (this.props.parent) {
            this.setState({isNewAD: false});
        }
        else {
            this.setState({isNewAD: true});

        }
    }
    loadDetail = () => {
        const {actions, item} = this.props;
        actions.getPlanDetail({id: item.planId}).then(res => {
            this.setState({payload: res, cycle: res.planCycle});
        });
    }
    changeFormatPlanCycle(planCycle) {
        // 将数组中的值给读取出来 按照二进制来存储
        let arr = '';
        for (let i = 0; i < 24; i++) {
            if (planCycle.includes(i + '')) {
                arr += 1;
                continue;
            }
            arr += 0;
        }
        // 转为16进制
        const str = parseInt(this.reverseStr(arr), 2).toString(16);
        return this.formatHexData(str).repeat(7);
    }
    reverseStr(str) {
        return str.split('').reverse().join('');
    }
    formatHexData(data) {
        // 判断字符串的长度,然后补0
        return '0'.repeat(6 - data.length) + data;
    }
    decodePlayCycle(cycle) {
        // 将传输的字段给解析
        const subSyc = parseInt(cycle.substring(0, 6), 16).toString(2);
        let arr = this.reverseStr(subSyc).split('');
        let data = [];
        for (let i = 0; i < 24; i++) {
            if (arr[i] === '1') {
                data.push(i + '');
                continue;
            }
        }
        return data.length > 1 ? data : ['0'];
    }
    decodeCycle(cycle) {
        // 将传输的字段给解析
        const subSyc = parseInt(cycle, 16).toString(2);
        let arr = this.reverseStr(subSyc).split('');
        return arr;
    }
    doSubmit = e => {
        e && e.preventDefault();
        const {actions, parent} = this.props;
        const {payload} = this.state;
        return new Promise((resolve, reject) => {
            this.props.form.validateFieldsAndScroll((err, values) => {
                values.startTime = timeToUtc(values.startTime);
                values.endTime = timeToUtc(values.endTime);
                values.budget = values.budget * 100;
                if (!err) {
                    if (payload.planId) {
                        values.cost = payload.cost;
                        values.createTime = payload.createTime;
                        values.planId = payload.planId;
                        values.status = payload.status;
                        values.unitCount = payload.unitCount;
                        values.userId = payload.userId;
                    }
                    const promise = payload.planId ? actions.updatePlan(values)
                        : actions.createPlan(values);
                    promise.then(
                        () => {
                            parent.handleCancel();
                            parent.loadList();
                        },
                        res => {
                            let msg = '';
                            if (res.field) {
                                msg = res.field.planName;
                            }
                            message.error('操作失败, ' + msg);
                            reject();
                        }
                    );
                }
                else {
                    reject();
                }
            });
        });
    }

    compareToStartTime= (rule, value, callback) => {
        const form = this.props.form;
        if (value && (moment(value).isBefore(form.getFieldValue('startTime')))) {
            callback('结束时间必须大于开始时间!');
        }
        else {
            callback();
        }
    }

    compareToEndTime= (rule, value, callback) => {
        const form = this.props.form;
        if (value && (moment(value).isAfter(form.getFieldValue('endTime')))) {
            callback('开始时间必须小于结束时间!');
        }
        else {
            callback();
        }
    }
    // 校验开始时间(大于昨天,小于10年)
    disabledStartDate = startTime => {
        return startTime > moment().add(10, 'year') || startTime < moment().subtract(1, 'day');
    }
    // 校验结束时间(小于10年)
    disabledEndDate = endTime => {
        const form = this.props.form;
        return endTime > moment().add(11, 'year') || endTime < form.getFieldValue('startTime');
    }
    render() {
        const {form} = this.props;
        const {payload, isNewAD, cycle} = this.state;
        const startInit = payload.startTime ? moment(payload.startTime) : moment();
        const endInit = payload.endTime ? moment(payload.endTime) : moment().add(10, 'year');
        const getFieldDecorator = form.getFieldDecorator;
        return (
            <Form onSubmit={this.doSubmit}>
                <FormItem {...formItemLayout} label="计划名称" extra='请输入计划名称,不超过100个字符'>
                    {getFieldDecorator('planName', {
                        initialValue: payload.planName || '',
                        rules: [
                            {
                                pattern: /^[0-9a-zA-Z_\u4e00-\u9fa5]+$/,
                                message: '只能输入数字字母下划线和汉字'
                            },
                            {
                                max: 100,
                                message: '最多输入100个字符'
                            },
                            {
                                required: true,
                                message: '计划名称必填'
                            }
                        ]
                    })(<Input placeholder='请输入计划名称' style={{width: 200}} />)}
                </FormItem>
                <FormItem {...formItemLayout} label="每日预算(元)" >
                    {getFieldDecorator('budget', {
                        initialValue: payload.budget ? payload.budget / 100 : undefined,
                        rules: [
                            {pattern: /^[1-9][0-9]{0,6}([.]{1}[0-9]{1,2})?$/,
                            message: '仅可输入正数,整数部分最多7位,小数部分最多2位'
                            },
                            {
                                required: true,
                                message: '每日预算(元)必填'
                            }
                        ]
                    })(<Input placeholder='请输入每日预算' style={{width: 200}} />)}
                </FormItem>
                <FormItem {...formItemLayout} label="匀速消耗" >
                    {getFieldDecorator('enableUniform', {
                        initialValue: payload.enableUniform ? payload.enableUniform : false,
                        rules: [
                            {
                                required: true,
                                message: '匀速消耗必填'
                            }
                        ]
                    })(<Switch checkedChildren="开" unCheckedChildren="关" defaultunChecked />)}
                </FormItem>
                <FormItem {...formItemLayout} label="开始时间" >
                    {getFieldDecorator('startTime', {
                        initialValue: startInit,
                        rules: [
                            {
                                required: true,
                                message: '开始时间必填'
                            },
                            {
                                validator: this.compareToEndTime
                            }
                        ]
                    })(<DatePicker showTime disabledDate={this.disabledStartDate} format='YYYY-MM-DD HH:mm:ss' />)}
                </FormItem>
                <FormItem {...formItemLayout} label="结束时间" >
                    {getFieldDecorator('endTime', {
                        initialValue: endInit,
                        rules: [
                            {
                                required: true,
                                message: '结束时间必填'
                            },
                            {
                                validator: this.compareToStartTime
                            }
                        ]
                    })(<DatePicker showTime disabledDate={this.disabledEndDate} format='YYYY-MM-DD HH:mm:ss' />)}
                </FormItem>
                <FormItem {...formItemLayout} label="投放时间" >
                    {getFieldDecorator('planCycle', {
                        initialValue: cycle,
                        rules: [
                            {
                                required: true,
                                message: '投放时间'
                            }
                        ]
                    })(<ScheduleChange
                        cycle={cycle}
                        parent={this}
                    />)}
                </FormItem>
                {!isNewAD && <FormItem>
                    <Button type="primary" size='large' htmlType="submit" >
                        提交
                    </Button>
                </FormItem>}
            </Form>
        );
    }
}
export default Form.create()(EditFormantd);
           
/**
 * @file CyclePanel
 * @author shj
 */

import _ from 'lodash';
import {Checkbox} from 'antd';
import {SelectableGroup} from 'react-selectable-fast';
import {Component} from 'react';
import {parseHexString, getHexString, getFullArray} from 'modules/common/helper';
import './adlib.less';
import GroupItem from './GroupItem';

const weeksArray = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const quckTips = [{text: '全周投放', id: [0, 1, 2, 3, 4, 5, 6]},
    {text: '周一到周五投放', id: [0, 1, 2, 3, 4]},
    {text: '周末投放', id: [5, 6]}
];

export default class CyclePanel extends Component {
    state = {
        confirmDirty: false,
        autoCompleteResult: [],
        allData: [],
        dayHours: [],
        days: []
    }
    componentDidMount() {
        const {cycle} = this.props;
        let daysAll = [];
        let daysNo = [];
        // 将总的数据转为数组, 每个代表一小时, 1表示选中,0表示不显示
        let data = parseHexString(cycle).reverse().join('');
        let allData = this.getAllData(data);
        const dayHours = [];
        for (let i = 0; i <= 24; i++) {
            dayHours.push(i);
        }
        for (let i = 0; i < 24; i++) {
            daysAll.push(1);
            daysNo.push(0);
        }
        let days = this.checkSelectedAll(allData);
        this.setState({dayHours, allData, daysNo, daysAll, days, data});
    }
    changeradio = e => {
        let {allData, daysNo, daysAll} = this.state;
        // 对应的是周几全选
        for (let i = 0; i < 7; i++) {
            if (e.indexOf(i) > -1) {
                // 全选
                allData[i] = daysAll;
            }
            else {
                // 全不选
                allData[i] = daysNo;
            }
        }
        this.setState({allData, days: e});
    }
    handleSelectionFinish = e => {
        let selected = e.map(d => d.props);
        let cycle = this.getData(selected);
        let allData = this.getAllData(cycle);
        let days = this.checkSelectedAll(allData);
        const {parent} = this.props;
        this.setState({allData, days});
        parent.setState({cycle: getHexString(getFullArray(cycle.split('')).reverse())});
    }
    checkSelectedAll = allData => {
        let days = [];
        _.map(allData, (val, index) => {
            if (val.indexOf('0') === -1) {
                // 全部是选中
                days.push(index);
            }
        });
        return days;
    }
    getData = e => {
        let allArr = [];
        let selectArr = e.map(d => d.id);
        for (let i = 0; i < 168; i++) {
            if (selectArr.indexOf(i) > -1) {
                allArr.push(1);
            }
            else {
                allArr.push(0);
            }
        }
        return allArr.join('');
    }
    getAllData = data => {
        let allData = [];
        for (let i = 0; i < data.length; i = i + 24) {
            allData.push(data.substr(i, 24).split(''));
        }
        return allData;
    }
    render() {
        const {allData, dayHours, days} = this.state;
        return (
            <div className="heatmap">
                <div className="heatmap-body">
                    <div className="heatmap-time-line" >
                        {_.map(dayHours, (val, index) => {
                            if (index % 2 === 0) {
                                return <div className='heatmap-time-head'>{val}:00</div>;
                            }
                        })}
                    </div>
                    <div className="heatmap-time-body">
                        <div className='heatmap-line'>
                            <Checkbox.Group onChange={this.changeradio} value={days}>
                                {_.map(weeksArray, (dayText, index) => (
                                    <Checkbox id={dayText} value={index}>
                                        <span className='week-box'>{dayText}</span>
                                    </Checkbox>
                                    ))}
                            </Checkbox.Group>
                        </div>
                        {allData && <div className='select-tab'>
                            <SelectableGroup
                                className="main"
                                clickClassName="tick"
                                enableDeselect
                                allowClickWithoutSelected={false}
                                onSelectionFinish={this.handleSelectionFinish}
                            >
                                {allData.map((d, index) => (
                                    <div key={d[index] + weeksArray[index]}>
                                        {d.map((val, i) => {
                                            let id = index * 24 + i;
                                            let select = Number(val) === 1;
                                            return (
                                                <GroupItem item={val} key={id} isSelected={select} id={id} />
                                            );
                                        })}
                                    </div>
                                ))}
                            </SelectableGroup>
                        </div>}
                    </div>
                </div>
                <div className='heatmap-time-foot'>
                    快速设定:
                    <span
                        className='slot-btn'
                        title={quckTips[0].text}
                        onClick={() => this.changeradio(quckTips[0].id)}
                    >{quckTips[0].text}</span>
                    <span
                        className='slot-btn'
                        title={quckTips[1].text}
                        onClick={() => this.changeradio(quckTips[1].id)}
                    >{quckTips[1].text}</span>
                    <span
                        className='slot-btn'
                        title={quckTips[2].text}
                        onClick={() => this.changeradio(quckTips[2].id)}
                    >{quckTips[2].text}</span>
                    <div className="ui-schedule-chart-help">
                        <div className="item selected"></div>
                        <div className="text">投放时间段</div>
                        <div className="item"></div>
                        <div className="text">暂停时间段</div>
                    </div>
                </div>
            </div>
        );
    }
}
           
/**
 * @file common/common
 */
import {Select} from 'antd';
import _ from 'lodash';

const {Option} = Select;
export function getInitDate() {
    let data = [];
    for (let i = 0; i < 24; i++) {
        // const text = i + '~' + (i + 1);
        let text;
        if (i < 9) {
            text = '0' + i + ':00 ~ ' + '0' + (i + 1) + ':00';
        }
        else if (i === 9) {
            text = '09:00 ~ ' + '10:00';
        }
        else {
            text = i + ':00 ~ ' + (i + 1) + ':00';
        }
        data.push(<Option key={i}>{text}</Option>);
    }
    return data;
}
export function getCookie(name) {
    let cookies = document.cookie;
    let result = 1;
    if (cookies.indexOf(name) > -1) {
        let cookieArr = cookies.split(';');
        cookieArr.map(d => {
            let index = d.indexOf(name);
            if (index > -1) {
                result = d.split('=')[1];
            }
        });
    }
    return result;
}
export function getPagination(tabData, pageSize) {
    return (tabData ? {
        total: tabData.totalCount,
        pageSize: pageSize,
        pageSizeOptions: ['10', '20', '30', '50', '100'],
        defaultPageSize: pageSize,
        showSizeChanger: true,
        showTotal() {
            return '共 ' + tabData.totalCount + ' 条数据';
        }
    } : {});
}
export function formatRegionconf(param, fun) {
    _.each(param, function (val) {
        val.value = val.id;
        val.key = 'val.id' + val.id + val.text;
        val.title = val.text;
        if (_.has(val, 'children')) {
            formatRegionconf(val.children, fun);
        }
    });
}
export function getRegionIds(regionConf, list, fun) {
    let nameList = [];
    const flag = list.length > 0;
    _.each(regionConf, function (val) {
        if (flag) {
            if (list.indexOf(val.id) > -1) {
                nameList.push(val.id);
                // 遍历children
                let child = getChildren(val.children, fun);
                nameList = nameList.concat(child);
            }
            if (val.children) {
                let child = getRegionIds(val.children, list, fun);
                nameList = nameList.concat(child);
            }
        }
        else {
            nameList.push(val.id);
            let child = getRegionIds(val.children, [], fun);
            nameList = nameList.concat(child);
        }
    });
    return nameList;
}
export function getChildren(param, fun) {
    let idList = [];
    _.each(param, function (val) {
        idList.push(val.id);
        let child = getChildren(val.children, fun);
        idList = idList.concat(child);
    });
    return idList;
}
export function getFullArray(rawValue) {
    return [].concat.apply([], rawValue);
}
export function getRawValue(fullArray) {
    let hourCount = 24;
    let rawValue = [];
    for (let i = 0; i < fullArray.length; i += hourCount) {
        rawValue.push(fullArray.slice(i, i + hourCount));
    }
    return rawValue;
}
export function getHexString(fullArray) {
    let itemLength = 4;
    let hexStr = '';
    for (let i = 0; i < fullArray.length; i += itemLength) {
        hexStr += parseInt(fullArray.slice(i, i + itemLength).join(''), 2).toString(16);
    }
    return hexStr;
}
export function parseHexString(hexStr) {
    let itemLength = 4;
    return hexStr.split('').map(function (hexChar) {
        let binStr = parseInt(hexChar, 16).toString(2);
        return new Array(itemLength - binStr.length + 1).join('0') + binStr;
    }).join('').split('').map(Number);
}
           
.heatmap {
    font-size: 12px;
    width: 700px;
    padding: 6px 0 8px 0;
    background: #fff;
    border: 1px solid #CDCDCD;
    position: relative;
    overflow-x: scroll;
    overflow-y: hidden;

    .heatmap-body {
        height: 200px;
        .heatmap-time-line {
            height: 30px;
            padding: 0 0 0 40px;
            .heatmap-time-head {
                border: 0;
                width: 50px;
                text-align: left;
                position: relative;
                color: #000;
                display: inline-block;
                font-size: 12px;
                height: 23px;
                line-height: 23px;
                -moz-user-select: none;
                -webkit-user-select: none;
            }
            .long {
                width: 125px;
            }
        }

        .heatmap-day-head {
            float: left;
            width: 100%;
            margin: 0;
            .heatmap-day {
                line-height: 25px;
                padding-right: 5px;
                height: 25px;
                text-align: right;
            }
        }
        .heatmap-time-body {
            .heatmap-line {
                height: 25px;
                width: 60px;
                .week-box {
                    padding: 0;
                    line-height: 25px;
                }
                .ant-checkbox-wrapper + .ant-checkbox-wrapper {
                    margin-left: 0;
                }
            }
            .select-tab {
                margin-left: 50px;
                margin-top: -25px;
                width: 600px;
            }
            .selected {
                background: #dcfacf;
                border: 1px solid #c2e8a6;
                color: #fff;
                font-size: 0;
            }
            .noselected {
                background: #fff;
                border: 1px solid #dcdcdc;
                color: #E6E6E6;
                font-size: 0;

            }
        }

    }

    .heatmap-head {
        height: 28px;
        width: 100%;
        line-height: 28px;
        padding-top: 10px;
        position: relative;
        .heatmap-help {
            float: right;
            width: 200px;

            .heatmap-help-text {
                float: left;
                line-height: 16px;
                padding: 5px 8px 0 3px;
            }
        }
    }

    // 配置格子

    .heatmap-time {
        background: #fff;
        border: 1px solid #F7F8FC;
        color: #fff;
        cursor: pointer;
        float: left;
        // display: inline-block;
        font-size: 12px;
        height: 25px;
        line-height: 25px;
        text-align: center;
        width: 25px;
        -moz-user-select: none;
        -webkit-user-select: none;
        position: relative;
    }
    .heatmap-time-day {
        float: left;
        //display: inline-block;
        font-size: 12px;
        height: 23px;
        line-height: 23px;
        text-align: center;
        -moz-user-select: none;
        -webkit-user-select: none;
        position: relative;
    }
    .heatmap-time-foot {
        float: left;
        margin-top: 10px;
        width: 100%;
        //display: inline-block;
        font-size: 12px;
        margin-left: -40px;
        height: 23px;
        line-height: 23px;
        text-align: center;
        -moz-user-select: none;
        -webkit-user-select: none;
        position: relative;
        .slot-btn {
            border: 1px solid #888f9b;
            margin-left: 10px;
            padding: 2px 10px;
        }
        .ui-schedule-chart-help {
            float: right;
            width: 250px;
            & > div {
                float: left;
                margin-left: 10px;
            }
            .item {
                border: 1px solid #E2E5EC;
                width: 25px;
                height: 25px;
                position: relative;
                top: 3px;
            }
            .selected {
                margin-left: 20px;
                background-color: #dcfacf;
            }
        }
    }
    .heatmap-time-selected {
        background: #108cee;
        color: #fff;
    }
}
           
/**
 * @file GroupItem
 * @author shj
 */
import _ from 'lodash';
import {createSelectable} from 'react-selectable-fast';
import {Component} from 'react';
import './adlib.less';
import cs from 'classnames';


class GroupItem extends Component {
    render() {
        const {item} = this.props;
        let {selectableRef, isSelecting, id, isSelected} = this.props;
        let title = item.title + ':00-' + (item.title + 1) + ':00\n点击/拖动鼠标选择';
        return (
            <div
                ref={selectableRef}
                className={cs('heatmap-time', {selected: isSelected, selecting: isSelecting})}
                title={title}
            >
                {item.id}
            </div>
        );
    }
}
export default createSelectable(GroupItem);
           

效果

使用react-selectable-fast实现时间拖动选择的功能

可以通过鼠标拖动,实现数据的加载