折扣价和折扣实时转换

背景 : react 项目
问题 : 在折扣数中输入折扣2.333333,中间会多很多0,输入2.222,不能正常输入到第三位

如下图


原因 : oFixed()数字转字符串时可能会导致精度问题 
解决思路 : parseFloat来解析浮点数
原代码

  const calculateDiscountVal = (price, vip_price) => {
    if (Number(price) > 0 && Number(vip_price) > 0) {
      return (Number(vip_price) / Number(price)).toFixed(2) * 10;
    } else {
      return 0;
    }
  };

  const updateVipPriceFromDiscountRate = (price, discountRate) => {
    if (Number(price) > 0 && Number(discountRate) >= 0 && Number(discountRate) <= 10) {
      const vip_price = price * (discountRate / 10).toFixed(2);
      form.setFieldsValue({ vip_price: vip_price });
    }
  };

  useEffect(() => {
    // 只在零售价改变时更新折扣率
    let price = form.getFieldValue('price');
    let vip_price = form.getFieldValue('vip_price');
    setDiscountVal(calculateDiscountVal(price, vip_price));
  }, [form.getFieldValue('price'), form.getFieldValue('vip_price')]);

  // 问题:输入3.3折后,输入框变成3.30000 ; 原因:toFixed()数字转字符串时可能会导致精度问题 ; 解决:parseFloat来解析浮点数
  const handleDiscountRateChange = (ev) => {
    const newVal = ev.target.value;
    if (Number(newVal) >= 0 && Number(newVal) <= 10) {
      setDiscountVal(newVal);
      const price = form.getFieldValue('price');
      updateVipPriceFromDiscountRate(price, newVal);
    }
  };



  return (
   <Form >
     <Form.Item label='零售价(元):' name='price'>
              <Input
                value={price}
                onChange={(e) => setprice(e.target.value)}
                autoComplete='off'
                allowClear
                style={{ width: 80 }}
              ></Input>
            </Form.Item>
            <Form.Item label='折扣价(元):'>
              <Form.Item name='vip_price' style={{ display: 'inline-block' }}>
                <Input
                  value={vip_price}
                  onChange={(e) => setvip_price(e.target.value)}
                  autoComplete='off'
                  allowClear
                  style={{ width: 80 }}
                ></Input>
              </Form.Item>
              <Form.Item style={{ display: 'inline-block', margin: '0 8px' }}>
                <Input
                  value={discountVal}
                  allowClear
                  onChange={handleDiscountRateChange}
                  autoComplete='off'
                  style={{ width: 80 }}
                ></Input>
                &nbsp;&nbsp;折
              </Form.Item>
            </Form.Item>
          </>
        )}

        <Form.Item wrapperCol={{ offset: 2 }}>
          <Space className='footer' size={20}>
            <Button
              onClick={() => {
                navigate(-1);
              }}
            >
              取消
            </Button>
            <Button type='primary' htmlType='submit'>
              确认
            </Button>
          </Space>
        </Form.Item>
  <<Form />

解决方法 1 :  上述其他代码不变 , 只在handleDiscountRateChange中加入parseFloat , 不可行 , 还会出现NaN , 如图

   // 在onChange中用parseFloat来解析浮点数,输入折扣数后,就触发折扣价改变,发现还是不太行,因为浮点数的精度问题可能会导致在 onChange 事件中出现意外行为。目前会更报错出现NaN,当在用户输入小数点和小数部分时,浮点数可能会被不完整地解析,导致计算错误,出现NaN
  const handleDiscountRateChange = (ev) => {
    const newVal = ev.target.value;
    if (Number(newVal) >= 0 && Number(newVal) <= 10) {
      const roundedVal = parseFloat(newVal).toFixed(2); // 对输入的值进行四舍五入处理
      setDiscountVal(roundedVal);

      const price = form.getFieldValue('price');

      // 添加计算并更新折扣金额的逻辑
      if (Number(price) > 0) {
        const vip_price = price * (roundedVal / 10);
        form.setFieldsValue({ vip_price: vip_price.toFixed(2) });
      }
    }
  };

最终解决方案 : 将parseFloat放在失去焦点(onBlur)中处理,更靠谱 , 如图

 

  // 将parseFloat放在失去焦点(onBlur)中处理,更靠谱
  const handleDiscountRateChange = (ev) => {
    const newVal = ev.target.value;
    if (Number(newVal) >= 0 && Number(newVal) <= 10) {
      // 不在这里进行四舍五入处理,而是在失去焦点时处理
      setDiscountVal(newVal);
    }
  };

  const handleDiscountRateBlur = () => {
    const newVal = parseFloat(discountVal);
    if (Number.isFinite(newVal) && newVal >= 0 && newVal <= 10) {
      const roundedVal = newVal.toFixed(2);
      setDiscountVal(roundedVal);
      const price = form.getFieldValue('price');

      // 添加计算并更新折扣元的逻辑
      if (Number(price) > 0) {
        const vip_price = price * (roundedVal / 10);
        form.setFieldsValue({ vip_price: vip_price.toFixed(2) });
      }
    }
  };


  <Form.Item style={{ display: 'inline-block', margin: '0 8px' }}>
                <Input
                  value={discountVal}
                  allowClear
                  onChange={handleDiscountRateChange}
                  onBlur={handleDiscountRateBlur} // 添加失去焦点事件处理函数
                  autoComplete='off'
                  style={{ width: 80 }}
                ></Input>
                &nbsp;&nbsp;折
   </Form.Item>

----------------------上述原代码不完整,以提供思路解决完问题为主,以下是完整的代码---------------------

import React, { useEffect, useState, useRef } from 'react';
import {
  Form,
  Input,
  Select,
  Button,
  Radio,
  Space,
  Image,
  Spin,
  Tree,
  Drawer,
  Row,
  Col,
} from 'antd';
import { getInfoById, publisherList, publisherAdd, getListAll, editBook } from './request';
import { getList } from '../classify/request';
import { useLocation } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { convertToAntdTreeData } from '@/utils/common.js';
const SaleEdit = () => {
  const {
    state: { id },
  } = useLocation();
  const [initialValues, setinitialValues] = useState({
    name: '',
    authors: '',
    category_id: null,
    producers_id: null,
    press_id: null,
    is_free: null,
  });
  const [form] = Form.useForm();
  const navigate = useNavigate();
  const [showAddCategory, setShowAddCategory] = useState(false); //弹出添加出品方
  const [showAddCategory1, setShowAddCategory1] = useState(false);
  const [showDrawer, setShowDrawer] = useState(false);
  const [publisher_name, setPublisherName] = useState(''); //出品方
  const [publishers, setPublishers] = useState([]);
  const [publisher_name1, setPublisherName1] = useState(''); //出版社
  const [publishers1, setPublishers1] = useState([]);
  const [treeData, setTreeData] = useState([]);

  // 在组件中添加一个状态来保存书籍类型的值
  const [bookType, setBookType] = useState(0); // 默认值为免费
  const [producersConfig, setproducersConfig] = useState({
    validateStatus: '',
    help: '',
    noStyle: false,
    style: {},
  });
  const [pressConfig, setpressConfig] = useState({
    validateStatus: '',
    help: '',
    noStyle: false,
    style: {},
  });
  const addproducers = async (obj, callbackFn) => {
    const { types } = obj;
    let flag = true;
    if (types == 1) {
      if (!publisher_name) {
        flag = false;
        setproducersConfig({
          ...producersConfig,
          validateStatus: 'error',
          help: '请输入新的出品方',
          noStyle: false,
        });
      }
    } else if (types == 2) {
      if (!publisher_name1) {
        flag = false;
        setpressConfig({
          ...pressConfig,
          validateStatus: 'error',
          help: '请输入新的出版社',
          noStyle: false,
        });
      }
    }
    if (!flag) return;
    // 上面代码是校验
    const bool = await publisherAdd(obj);
    if (!bool) return;
    callbackFn();
    setproducersConfig({ validateStatus: '', help: '', noStyle: false, style: {} });
    setpressConfig({ validateStatus: '', help: '', noStyle: false, style: {} });
    getProducers();
  };
  const [price, setprice] = useState(0);
  const [vip_price, setvip_price] = useState(0);
  const [discountVal, setDiscountVal] = useState(0);

  const calculateDiscountVal = (price, vip_price) => {
    if (Number(price) > 0 && Number(vip_price) > 0) {
      return (Number(vip_price) / Number(price)).toFixed(2) * 10;
    } else {
      return 0;
    }
  };

  const updateVipPriceFromDiscountRate = (price, discountRate) => {
    if (Number(price) > 0 && Number(discountRate) >= 0 && Number(discountRate) <= 10) {
      const vip_price = price * (discountRate / 10).toFixed(2);
      form.setFieldsValue({ vip_price: vip_price });
    }
  };

  useEffect(() => {
    // 只在零售价改变时更新折扣率
    let price = form.getFieldValue('price');
    let vip_price = form.getFieldValue('vip_price');
    setDiscountVal(calculateDiscountVal(price, vip_price));
  }, [form.getFieldValue('price'), form.getFieldValue('vip_price')]);

  // // 问题:输入3.3折后,输入框变成3.30000 ; 原因:toFixed()数字转字符串时可能会导致精度问题 ; 解决:parseFloat来解析浮点数
  // const handleDiscountRateChange = (ev) => {
  //   const newVal = ev.target.value;
  //   if (Number(newVal) >= 0 && Number(newVal) <= 10) {
  //     setDiscountVal(newVal);
  //     const price = form.getFieldValue('price');
  //     updateVipPriceFromDiscountRate(price, newVal);
  //   }
  // };

  // 在onChange中用parseFloat来解析浮点数,输入折扣数后,就触发折扣价改变,发现还是不太行,因为浮点数的精度问题可能会导致在 onChange 事件中出现意外行为。目前会更报错出现NaN,当在用户输入小数点和小数部分时,浮点数可能会被不完整地解析,导致计算错误,出现NaN
  // const handleDiscountRateChange = (ev) => {
  //   const newVal = ev.target.value;
  //   if (Number(newVal) >= 0 && Number(newVal) <= 10) {
  //     const roundedVal = parseFloat(newVal).toFixed(2); // 对输入的值进行四舍五入处理
  //     setDiscountVal(roundedVal);

  //     const price = form.getFieldValue('price');

  //     // 添加计算并更新折扣金额的逻辑
  //     if (Number(price) > 0) {
  //       const vip_price = price * (roundedVal / 10);
  //       form.setFieldsValue({ vip_price: vip_price.toFixed(2) });
  //     }
  //   }
  // };

  // 将parseFloat放在失去焦点(onBlur)中处理,更靠谱
  const handleDiscountRateChange = (ev) => {
    const newVal = ev.target.value;
    if (Number(newVal) >= 0 && Number(newVal) <= 10) {
      // 不在这里进行四舍五入处理,而是在失去焦点时处理
      setDiscountVal(newVal);
    }
  };

  const handleDiscountRateBlur = () => {
    const newVal = parseFloat(discountVal);
    if (Number.isFinite(newVal) && newVal >= 0 && newVal <= 10) {
      const roundedVal = newVal.toFixed(2);
      setDiscountVal(roundedVal);
      const price = form.getFieldValue('price');

      // 添加计算并更新折扣元的逻辑
      if (Number(price) > 0) {
        const vip_price = price * (roundedVal / 10);
        form.setFieldsValue({ vip_price: vip_price.toFixed(2) });
      }
    }
  };

  useEffect(() => {
    if (!showAddCategory) {
      setPublisherName('');
    }
    if (!showAddCategory1) {
      setPublisherName1('');
    }
  }, [showAddCategory, showAddCategory1]);

  const handleOpenDrawer = async () => {
    setShowDrawer(true);
    const data = await getListAll({ book_id: id });
    let arr = convertToAntdTreeData(data, 'name');
    setTreeData(arr);
  };

  const handleCloseDrawer = () => {
    setShowDrawer(false);
  };
  const [classData, setClassData] = useState([]);
  const [producersData, setproducersData] = useState([]);
  const [pressData, setpressData] = useState([]);
  // 出品方/出版社
  const getProducers = async () => {
    const producersList = await publisherList({ types: 1 });
    const press = await publisherList({ types: 2 });
    setproducersData(producersList);
    setpressData(press);
  };
  const [FormLoad, setFormLoad] = useState(true);

  const [checkedKeys, setcheckedKeys] = useState([]);

  const submitTree = (obj) => {
    form.setFieldValue('trials', checkedKeys);
    handleCloseDrawer(false);
  };

  const ontreeCheck = (keys, { checked, checkedNodes, node, halfCheckedKeys }) => {
    setcheckedKeys(keys);
  };
  const getinfo = async () => {
    setFormLoad(true);
    // 详情
    const data = await getInfoById({ id });
    const { category_id, producers_id, press_id } = data;
    let obj = { category_id, producers_id, press_id };
    setBookType(data.is_free);
    for (let key in obj) {
      if (obj[key] === 0) {
        obj[key] = null;
      }
    }
    form.setFieldsValue({ ...data, ...obj });
    setFormLoad(false);
  };
  const getInfo = async () => {
    // 分类
    const { list } = await getList({ page: 1, page_size: 999 });
    setClassData(list);
    getProducers();
    getinfo();
  };
  const onFinish = async (obj) => {
    const bool = await editBook({ ...obj, id });
    if (!bool) return;
    navigate(-1);
  };
  useEffect(() => {
    getInfo();
  }, []);

  return (
    <Spin spinning={FormLoad} style={{ padding: 20 }}>
      <Form labelCol={{ span: 2 }} form={form} initialValues={initialValues} onFinish={onFinish}>
        <Form.Item label='书籍名称' name='name'>
          <Input
            disabled
            allowClear
            autoComplete='off'
            placeholder='请输入书籍名称'
            style={{ width: 400 }}
          ></Input>
        </Form.Item>
        <Form.Item label='书籍作者' name='authors'>
          <Input autoComplete='off' disabled style={{ width: 260 }}></Input>
        </Form.Item>
        <Form.Item
          label='分类'
          name='category_id'
          rules={[{ required: true, message: '请选择分类' }]}
        >
          <Select placeholder='请选择分类' style={{ width: 260 }}>
            {classData &&
              classData.map((item) => (
                <Select.Option value={item.id} key={item.id}>
                  {item.name}
                </Select.Option>
              ))}
          </Select>
        </Form.Item>
        <Form.Item label='出品方' required style={producersConfig.style}>
          <Space>
            <Form.Item
              noStyle
              name='producers_id'
              rules={[{ required: true, message: '请选择出品方' }]}
            >
              <Select placeholder='请选择出品方' style={{ width: 260 }}>
                {producersData?.map((item) => (
                  <Select.Option key={item.id} value={item.id}>
                    {item.publisher_name}
                  </Select.Option>
                ))}
              </Select>
            </Form.Item>
            <a
              style={{
                textDecoration: 'underline',
                color: '#1672EC',
                cursor: 'pointer',
              }}
              onClick={() => {
                setproducersConfig({ ...producersConfig, style: { marginBottom: 0 } });
                setShowAddCategory(true);
              }}
            >
              添加出品方
            </a>
          </Space>
          {showAddCategory && (
            <div style={{ marginTop: '10px' }}>
              <Space>
                <Form.Item
                  noStyle={producersConfig.noStyle}
                  validateStatus={producersConfig.validateStatus}
                  help={producersConfig.help}
                >
                  <Input
                    style={{ width: 260 }}
                    autoComplete='off'
                    placeholder='请输入新的出品方'
                    value={publisher_name}
                    allowClear
                    onChange={(e) => {
                      let val = e.target.value;
                      if (val) {
                        setproducersConfig({ ...producersConfig, help: '', validateStatus: '' });
                      }
                      setPublisherName(val);
                    }}
                  />
                </Form.Item>
                <a
                  type='link'
                  style={{ textDecoration: 'underline', color: '#1672EC' }}
                  onClick={() =>
                    addproducers({ types: 1, publisher_name }, () => setShowAddCategory(false))
                  }
                >
                  添加
                </a>
                <a
                  type='link'
                  style={{ textDecoration: 'underline', color: '#AA1941' }}
                  onClick={() => {
                    setproducersConfig({ validateStatus: '', help: '', noStyle: false, style: {} });
                    setShowAddCategory(false); // 可选:添加后关闭输入框
                  }}
                >
                  删除
                </a>
              </Space>
            </div>
          )}
        </Form.Item>
        <Form.Item label='出版社' required style={pressConfig.style}>
          <Space>
            <Form.Item
              noStyle
              name='press_id'
              rules={[{ required: true, message: '请选择出版社' }]}
            >
              <Select placeholder='请选择出品方' style={{ width: 260 }}>
                {pressData?.map((item) => {
                  return (
                    <Select.Option key={item.id} value={item.id}>
                      {item.publisher_name}
                    </Select.Option>
                  );
                })}
              </Select>
            </Form.Item>
            <a
              style={{
                textDecoration: 'underline',
                color: '#1672EC',
                cursor: 'pointer',
              }}
              onClick={() => {
                setpressConfig({ ...pressConfig, style: { marginBottom: 0 } });
                setShowAddCategory1(true);
              }}
            >
              添加出版社
            </a>
          </Space>
          {showAddCategory1 && (
            <div style={{ marginTop: '10px' }}>
              <Space>
                <Form.Item
                  noStyle={pressConfig.noStyle}
                  validateStatus={pressConfig.validateStatus}
                  help={pressConfig.help}
                >
                  <Input
                    style={{ width: 260 }}
                    autoComplete='off'
                    placeholder='请输入新的出版社'
                    allowClear
                    value={publisher_name1}
                    onChange={(e) => {
                      let val = e.target.value;
                      if (val) {
                        setpressConfig({ ...pressConfig, help: '', validateStatus: '' });
                      }
                      setPublisherName1(e.target.value);
                    }}
                  />
                </Form.Item>
                <a
                  type='link'
                  style={{ textDecoration: 'underline', color: '#1672EC' }}
                  onClick={() =>
                    addproducers({ types: 2, publisher_name: publisher_name1 }, () =>
                      setShowAddCategory1(false),
                    )
                  }
                >
                  添加
                </a>
                <a
                  type='link'
                  style={{ textDecoration: 'underline', color: '#AA1941' }}
                  onClick={() => {
                    setpressConfig({ validateStatus: '', help: '', noStyle: false, style: {} });
                    setShowAddCategory1(false);
                  }}
                >
                  删除
                </a>
              </Space>
            </div>
          )}
        </Form.Item>
        <Form.Item label='书籍类型' name='is_free'>
          <Radio.Group
            onChange={(e) => {
              setBookType(e.target.value);
            }}
          >
            <Radio value={1}>免费</Radio>
            <Radio value={0}>付费</Radio>
          </Radio.Group>
        </Form.Item>
        {bookType === 0 && (
          <Form.Item label='试读章节' name='trials'>
            <a type='link' style={{ textDecoration: 'underline' }} onClick={handleOpenDrawer}>
              选择试读章节
            </a>
          </Form.Item>
        )}

        {bookType === 0 && (
          <>
            <Form.Item label='零售价(元):' name='price'>
              <Input
                value={price}
                onChange={(e) => setprice(e.target.value)}
                autoComplete='off'
                allowClear
                style={{ width: 80 }}
              ></Input>
            </Form.Item>
            <Form.Item label='折扣价(元):'>
              <Form.Item name='vip_price' style={{ display: 'inline-block' }}>
                <Input
                  value={vip_price}
                  onChange={(e) => setvip_price(e.target.value)}
                  autoComplete='off'
                  allowClear
                  style={{ width: 80 }}
                ></Input>
              </Form.Item>
              <Form.Item style={{ display: 'inline-block', margin: '0 8px' }}>
                <Input
                  value={discountVal}
                  allowClear
                  onChange={handleDiscountRateChange}
                  onBlur={handleDiscountRateBlur} // 添加失去焦点事件处理函数
                  autoComplete='off'
                  style={{ width: 80 }}
                ></Input>
                &nbsp;&nbsp;折
              </Form.Item>
            </Form.Item>
          </>
        )}
        <Form.Item wrapperCol={{ offset: 2 }}>
          <Space className='footer' size={20}>
            <Button
              onClick={() => {
                navigate(-1);
              }}
            >
              取消
            </Button>
            <Button type='primary' htmlType='submit'>
              确认
            </Button>
          </Space>
        </Form.Item>
      </Form>

      <Drawer
        placement='right'
        onClose={handleCloseDrawer}
        open={showDrawer}
        labelCol={{ span: 7 }}
        mask={false}
      >
        <Form onFinish={submitTree}>
          <Form.Item name='power_list' style={{ padding: 10 }}>
            <Tree
              checkable
              checkedKeys={checkedKeys}
              defaultExpandAll={false} //让授权后的弹窗只展示根标签
              treeData={treeData}
              // showLine //删除这里,树形结构左侧的下拉线消失,图标从+-更改为默认的△
              // checkStrictly
              onCheck={ontreeCheck}
            />
          </Form.Item>
          <Form.Item wrapperCol={{ offset: 10, span: 16 }}>
            <Space size={20}>
              <Button className='cancel' onClick={() => handleCloseDrawer(false)}>
                取消
              </Button>
              <Button className='submit' htmlType='submit'>
                提交
              </Button>
            </Space>
          </Form.Item>
        </Form>
      </Drawer>
    </Spin>
  );
};

export default SaleEdit;

 

相关推荐

  1. python图书自动折扣系统

    2024-03-10 19:02:04       152 阅读
  2. c++ 判断点折线 距离

    2024-03-10 19:02:04       32 阅读
  3. 查找——顺序查找折半查找

    2024-03-10 19:02:04       33 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-10 19:02:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-10 19:02:04       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-10 19:02:04       82 阅读
  4. Python语言-面向对象

    2024-03-10 19:02:04       91 阅读

热门阅读

  1. 主流开发语言和开发环境介绍

    2024-03-10 19:02:04       39 阅读
  2. nextTick原理

    2024-03-10 19:02:04       41 阅读
  3. prometheus配置grafana看板及alert告警文档

    2024-03-10 19:02:04       44 阅读
  4. B树、B+树及B*树的原理、作用及区别

    2024-03-10 19:02:04       40 阅读
  5. json-server 快速搭建本地服务器

    2024-03-10 19:02:04       50 阅读
  6. LeetCode111 二叉树的最小深度

    2024-03-10 19:02:04       48 阅读
  7. flask流式响应

    2024-03-10 19:02:04       48 阅读
  8. Flask从入门到精通

    2024-03-10 19:02:04       34 阅读
  9. Python Flask 打包成exe 心得体会

    2024-03-10 19:02:04       41 阅读
  10. 5.49 BCC工具之rdmaucma.py解读

    2024-03-10 19:02:04       42 阅读