先看实现的成果:

次要性能点:反对传入分组的组数,反对传入不同的色彩色条,可拖动左侧滑块扭转占比,可通过输出右侧input框实现更改占比,反对百分比和数值两种显示模式

间接上代码:正文写的很具体了:

import React, { useEffect, useState } from 'react';import styles from './index.less';import { CaretLeftOutlined } from '@ant-design/icons';import { InputNumber } from 'antd';import Slider from 'rc-slider';import 'rc-slider/assets/index.css';import { bignumberFc } from '@/utils/bigNumber';type colorBlockProps = {  groupCount: number; //分的组数  colorGroupArrayHex: string[]; //色彩色块的固定数组  type: 'percent' | 'value'; //百分比或数值的显示模式  min?: number; //数值显示模式的最小值  max?: number; //数值显示模式的最大值  currentColorBlockInfo?: {    //保留的以后的组件的信息,还原时候须要    sliderValues: number[];    numberValues: number[];    groupRangeArrs: number[][];  };  // 最大值最小值变动的flag,因为我组件外层会有多个中央触发最大最小值的变动,避免反复渲染,所以传入一个flag去监听,而不是监听min和max  minmaxChangeFlag?: string;};const ColorBlock: React.FC<colorBlockProps> = (props: colorBlockProps) => {  const { groupCount, colorGroupArrayHex, type, min, max, currentColorBlockInfo, minmaxChangeFlag } = props;  // 滑动条组件的values,实现是利用从从0到1,示意百分比,同时也是百分比模式下input框的显示值  const [sliderValues, setSliderValues] = useState<number[]>([]);  // 数值类型的数组,它的值与sliderValues与min max 是一一对应的,是数值模式下input框的显示值  const [numberValues, setNumberValues] = useState<number[]>([]);  // input输入框的层级数组,聚焦的时候把以后的input框的z值调大,避免被遮挡  const [zIndexArray, setZIndexArray] = useState<number[]>([]);  // 拖动滑块的响应函数  const handleSliderChange = (values: number[] | number) => {    let valueArray = values as number[];    if (valueArray.includes(0) && valueArray.includes(1)) {      setSliderValues(valueArray);    }  };  // 更改inputNumber提早失效定时器  let timer: any = null;  // input输入框手动输出的响应(百分比显示模式)  const handlePercentInputChange = (index: number, value: any) => {    const newSliderValues = [...sliderValues];    if (typeof value === 'number') {      // 解决成整数并且勾销百分比模式      const newValue = Math.round(value) / 100;      if (newValue < newSliderValues[index + 1] && newValue > newSliderValues[index - 1]) {        newSliderValues[index] = newValue;      }      if (timer) clearTimeout(timer);      timer = setTimeout(() => {        setSliderValues(newSliderValues);      }, 500);    }  };  // input输入框手动输出的响应(数值显示模式)  const handleValueInputChange = (index: number, value: any) => {    const newSliderValues = [...sliderValues];    const newNumberValues = [...numberValues];    if (typeof value === 'number' && max !== undefined && min !== undefined) {      // 首先判断输出的范畴是否在相邻两个值的步长范畴内      if (        value < newNumberValues[index + 1] - (max - min) / 100 &&        value > newNumberValues[index - 1] + (max - min) / 100      ) {        // 算出新的占比        newSliderValues[index] = (value - min) / (max - min);      }      if (timer) clearTimeout(timer);      timer = setTimeout(() => {        setSliderValues(newSliderValues);      }, 500);    }  };  // 聚焦的input输入框永远在最上方,避免被遮蔽看不到信息  const handleInputOnFocus = (index: number) => {    let newZIndexArray = [...zIndexArray];    for (let i: number = 0; i < newZIndexArray.length; i++) {      if (i === index) {        newZIndexArray[i] = 9999;      } else newZIndexArray[i] = 1000;    }    setZIndexArray(newZIndexArray);  };  // 初始化函数,若最大值最值,或分组的组数,或显示模式变动,则从新初始化  useEffect(() => {    const initSliderValues: number[] = [];    const initZIndexArray: number[] = [];    for (let i: number = 0; i < groupCount + 1; i++) {      initSliderValues.push(Math.round((1 / groupCount) * i * 100) / 100);      initZIndexArray.push(1000);    }    setSliderValues(initSliderValues);    setZIndexArray(initZIndexArray);  }, [minmaxChangeFlag, groupCount, type]);  // 因数值类型的数组,它的值与sliderValues与min max 是一一对应的,所以sliderValue变动的时候同时更新数值型的数组  useEffect(() => {    if (sliderValues && min !== undefined && max !== undefined) {      const newNumberValues: number[] = [];      for (let i: number = 0; i < groupCount + 1; i++) {        newNumberValues.push(min + (max - min) * sliderValues[i]);      }      setNumberValues(newNumberValues);    }  }, [sliderValues]);  // 还原数据  useEffect(() => {    if (currentColorBlockInfo) {      setSliderValues(currentColorBlockInfo.sliderValues);      setNumberValues(currentColorBlockInfo.numberValues);    }  }, []);  // 组件的要害值的变动,将要害值 交给下层进行存储或者变更解决  useEffect(() => {    const groupRangeArrs: number[][] = [];    const newSliderValues = [...sliderValues];    const newNumberValues = [...numberValues];    // 百分比显示模式    if (type !== 'percent' && newSliderValues.length > 2) {      for (let i: number = 0; i < newSliderValues.length - 1; i++)        groupRangeArrs.push([newSliderValues[i], newSliderValues[i + 1]]);    }    // 数值显示模式    else if (type === 'percent' && newNumberValues.length > 2) {      for (let i: number = 0; i < newNumberValues.length - 1; i++)        groupRangeArrs.push([newNumberValues[i], newNumberValues[i + 1]]);    }    // 上面正文掉的为我本人的更新currentColorBlockInfo 的信息的办法,是交给下层进行解决,可依据理论状况本人去实现属性的存储,只需存储这三个属性即可    //                {    //                      sliderValues: sliderValues,    //                      numberValues: numberValues,    //                      groupRangeArrs: groupRangeArrs,    //                },    // setAttr(    //   {    //     colorBlockSlider: {    //       sliderValues: sliderValues,    //       numberValues: numberValues,    //       groupRangeArrs: groupRangeArrs,    //       // timeFlag:moment().format('YYYYMMDDHHmmss')    //     },    //   },    //   'setOtherAttr',    // );  }, [sliderValues, numberValues]);  return (    <div className={styles.container}>      <div>        percent        <Slider          range          step={0.01}          min={0}          max={1}          count={groupCount}          value={sliderValues}          pushable={0.01}          trackStyle={colorGroupArrayHex.map((item: string) => {            return { width: 30, backgroundColor: item as string };          })}          vertical          reverse          allowCross={true}          onChange={handleSliderChange}        />        <div>          {sliderValues.map((item: number, index: number) => {            return type !== 'percent' ? (              // 百分比显示模式              <div                key={item}                style={{                  border: '0px solid #000',                  marginLeft: 30,                  marginTop: item === 0 ? -11 : 0,                  height:                    sliderValues[index] === 1 ? '1%' : `${(sliderValues[index + 1] - sliderValues[index]) * 100}%`,                }}              >                <CaretLeftOutlined />                <InputNumber                  disabled={item === 0 || item === 1}                  size="small"                  value={item * 100}                  className={zIndexArray[index] === 9999 ? 'onFocusInput' : ''}                  precision={0}                  step={1}                  onChange={(value) => {                    handlePercentInputChange(index, value);                  }}                  onFocus={() => {                    handleInputOnFocus(index);                  }}                ></InputNumber>                <span>%</span>              </div>            ) : (              // 数值显示模式              <div                key={item}                style={{                  border: '0px solid #000',                  marginLeft: 30,                  marginTop: item === 0 ? -11 : 0,                  height:                    sliderValues[index] === 1 ? '1%' : `${(sliderValues[index + 1] - sliderValues[index]) * 100}%`,                }}              >                <CaretLeftOutlined />                <InputNumber                  // addonAfter={<span>%</span>}                  disabled={item === 0 || item === 1}                  size="small"                  style={{ width: 150 }}                  value={numberValues[index]}                  className={zIndexArray[index] === 9999 ? 'onFocusInput' : ''}                  precision={                    numberValues.length > 1                      ? bignumberFc                          .divide(bignumberFc.subtract(numberValues[numberValues.length - 1], numberValues[0]), 100)                          .toString()                          .split('.')[1]                        ? bignumberFc                            .divide(bignumberFc.subtract(numberValues[numberValues.length - 1], numberValues[0]), 100)                            .toString()                            .split('.')[1].length                        : 0                      : 0                  }                  step={                    numberValues.length > 1                      ? bignumberFc.divide(                          bignumberFc.subtract(numberValues[numberValues.length - 1], numberValues[0]),                          100,                        )                      : 1                  }                  onChange={(value) => {                    handleValueInputChange(index, value);                  }}                  onFocus={() => {                    handleInputOnFocus(index);                  }}                ></InputNumber>              </div>            );          })}        </div>      </div>    </div>  );};export default ColorBlock;

ps:bigNumberFc是基于bignumber.js封装的更高精度的计算方法,也贴在上面:

import BigNumber from 'bignumber.js';// 高精度计算的处理函数汇合const bignumberFc = {  add: (a: number, b: number) => {    return new BigNumber(a).plus(b).toNumber();  },  subtract: (a: number, b: number) => {    return new BigNumber(a).minus(b).toNumber();  },  multiply: (a: number, b: number) => {    return new BigNumber(a).multipliedBy(b).toNumber();  },  divide: (a: number, b: number) => {    return new BigNumber(a).dividedBy(b).toNumber();  },  // 均值  avg: (arr: number[]) => {    let total: number = 0;    for (let i: number = 0; i < arr.length; i++) {      total = bignumberFc.add(total, arr[i]);    }    return bignumberFc.divide(total, arr.length);  },  // 方差  variance: (arr: number[]) => {    let total: number = 0;    for (let i: number = 0; i < arr.length; i++) {      total = bignumberFc.add(total, arr[i]);    }    const mean = bignumberFc.divide(total, arr.length);    let totalS: number = 0;    for (let i: number = 0; i < arr.length; i++) {      const tempEverySubtract: number = bignumberFc.subtract(arr[i], mean);      totalS = bignumberFc.add(totalS, bignumberFc.multiply(tempEverySubtract, tempEverySubtract));    }    return bignumberFc.divide(totalS, arr.length);  },  //  线性回归  regression: (list: any[]) => {    if (!list) return '';    if (list.length === 0) return '';    //  x,y总数    let totalX: number = 0,      totalY: number = 0;    //  x,y平均数    let avgX: number = 0,      avgY: number = 0;    //  数组长度    let n: number = list?.length;    //  xy阶乘    let xy2: number = 0;    //  x平方阶乘    let x2: number = 0;    //  斜率b,常量a    let b: number = 0,      a: number = 0;    //  分子、分母    let fz: number = 0,      fm: number = 0;    //  回归线    let fn: string = '';    for (let i = 0; i < n; i++) {      totalX += list[i][0];      totalY += list[i][1];      xy2 += bignumberFc.multiply(list[i][0], list[i][1]);      x2 += bignumberFc.multiply(list[i][0], list[i][0]);    }    //  均值    avgX = bignumberFc.divide(totalX, n);    avgY = bignumberFc.divide(totalY, n);    //  分子分母    let nxy = bignumberFc.multiply(n, bignumberFc.multiply(avgX, avgY));    fz = bignumberFc.subtract(xy2, nxy);    let nx = bignumberFc.multiply(n, bignumberFc.multiply(avgX, avgX));    fm = bignumberFc.subtract(x2, nx);    b = bignumberFc.divide(fz, fm);    a = bignumberFc.subtract(list[0][1], bignumberFc.multiply(b, list[0][0]));    fn = a < 0 ? 'y=' + (b == 0 ? '' : b + 'x') + a : 'y=' + (b == 0 ? '' : b + 'x+') + a;    return fn;    // console.log(avgX,'平均数x');    // console.log(avgY,'平均数y');    // console.log(nxy,'nxy');    // console.log(nx,'nx');    // console.log(fz,'分子');    // console.log(x2,'x2');    // console.log(xy2,'xy2');    // console.log(fm,'分母');    // console.log(fn,'线性回归方程');  },};