react实现虚拟键盘支持Ant design Input和普通input Dom元素-升级篇

本文基于上一篇文章进行优化,不需要关注业务input怎么封装,不涉及业务改动。通用自定义数组键盘输入功能如下:直接贴代码

javascript 复制代码
import type { FC } from 'react';
import React, { useEffect } from 'react';
import { useRecoilValue } from 'recoil';

import { EPanelKeyModeType, EPanelNumCommand } from '@/pages/editor1/common/enum';
import ConfigManage from '@/pages/global/ConfigManage';
import { panelCommandStore } from '@/stores';
import { updateKeyMode } from '@/utils/panel';
export type AnyObjectImple = Record<string, string | number>;
const INPUTTAGNAME = 'INPUT';
const INPUTTEXT = 'text';
const INPUTTEL = 'tel';

/**
 * 通用业务数字键盘计算
 * @type cueMode: 文本模式,  cueMode: 数字模式两种
 * @waring 计算文本之前需要将文本现有数据 set 到panelNumberStore的originalVal上
 * @desc 考虑到这里业务的逻辑较复杂,将业务提取为公用业务组件
 * @desc 任何地方都可以使用该组件进行数字键盘的计算、文本的计算
 * @desc 追加文本框光标位置的计算
 * @returns
 */
const NumberKeyboard: FC = () => {
  // 指令
  const panelCommand = useRecoilValue(panelCommandStore);

  const IsNormalScreen = ConfigManage.getExpandCfg().IsNormalScreen;

  useEffect(() => {
    // 文字输入框添加onfocus, onblur监听,调起小控台数字按键
    if (IsNormalScreen) return;
    const isNeedBlur = (relateDM) => {
      if (relateDM?.tagName !== INPUTTAGNAME) {
        return true;
      } else {
        return relateDM?.type !== INPUTTEXT && relateDM?.type !== INPUTTEL;
      }
    };
    const isTargetInput = (targetDMParam: HTMLInputElement) => {
      return (
        targetDMParam?.tagName === INPUTTAGNAME &&
        (targetDMParam?.type === INPUTTEXT || targetDMParam?.type === INPUTTEL)
      );
    };
    const onFocus = (e: FocusEvent) => {
      if (isTargetInput(e.target as HTMLInputElement)) {
        updateKeyMode(EPanelKeyModeType.numberKeyMode);
      }
    };
    const onBlur = (e: FocusEvent) => {
      const relateDOM = e.relatedTarget as HTMLInputElement;
      if (isTargetInput(e.target as HTMLInputElement) && isNeedBlur(relateDOM)) {
        updateKeyMode(EPanelKeyModeType.restoreLastMode);
      }
    };
    document.addEventListener('focusin', onFocus);
    document.addEventListener('focusout', onBlur);
    return () => {
      document.removeEventListener('focusin', onFocus);
      document.removeEventListener('focusout', onBlur);
    };
  }, []);
  /**
   * 数字按键相关指令
   * @desc 1. 处理普通输入框
   * @desc 2. 处理纯数字按键模式
   * @return input 结果
   */
  const changeNumberCommand = (commandData) => {
    // 指令
    const { command } = commandData;
    try {
      // 获取选定对象
      const selection = window.getSelection();
      if (selection) {
        // 如果是文本节点则先获取光标对象
        const range = selection?.getRangeAt(0);
        // 获取光标对象的范围界定对象,一般就是textNode对象
        const textNode: any = range.startContainer;
        // 获取输入框input Dom元素, 使用的是And design Input,textNode是div,div里面且套input等子元素
        const inputNode = textNode?.getElementsByTagName('input')[0];
        // 光标开始
        const selectionStart = inputNode.selectionStart;
        // 结束位置
        const selectionEnd = inputNode.selectionEnd;
        console.info('selectionStart', selectionStart, 'selectionEnd', selectionEnd);
        // 处理指令运算: 数字
        handleNumberCommand(command, inputNode);
      }
    } catch (e) {}
  };
  /**
   * 数字指令处理
   * @param command
   * @param inputNode
   */
  const handleNumberCommand = (command, inputNode) => {
    // 业务指令转对应文本
    let pressFpgaVal: number | string = command - EPanelNumCommand.num0;
    if (command === EPanelNumCommand.point) {
      pressFpgaVal = '.';
    }
    /**
     * 1. 处理键盘指令,分数字模式和非数字模式,非数字模式直接追加内容,数字模式计算内容
     * 2. 判断指令范围
     * 3. 判断指令模式,处理对应模式数据
     */
    if (command >= EPanelNumCommand.num0 && command <= EPanelNumCommand.enter) {
      if (command <= EPanelNumCommand.point) {
        // 0-9数字处理指令
        doNumberCommand(inputNode, pressFpgaVal);
      } else if (command === EPanelNumCommand.point) {
        // 小数点操作
        // equalTen();
      } else if ([EPanelNumCommand.add, EPanelNumCommand.minus].includes(command)) {
        // 运算指令暂时不支持
        // opertBtn(command);
      } else if (command === EPanelNumCommand.back) {
        // 控台Back删除处理指令
        doBackCommand(inputNode);
      } else if (command === EPanelNumCommand.enter) {
        // 控台Enter处理指令
        doEnterCommand(inputNode);
      }
    }
  };
  /**
   * 0-9数字处理指令
   * @param command
   */
  const doNumberCommand = (inputNode, pressFpgaVal) => {
    // 转换成字符串
    const value = inputNode.value + '';
    // 光标开始、结束位置
    const selectionStart = inputNode.selectionStart;
    const selectionEnd = inputNode.selectionEnd;
    // 计算新值
    const newValue =
      value.substr(0, selectionStart) + pressFpgaVal + value.substring(selectionEnd, value.length);
    console.info('value', value, newValue);

    // 事件发布到组件中input
    const evt = new UIEvent('inputchange', {
      bubbles: false,
      cancelable: false,
    });
    const keyName = 'data';
    evt[keyName] = {
      value: newValue,
      selectionStart: selectionStart + pressFpgaVal.toString().length,
      selectionEnd: selectionStart + pressFpgaVal.toString().length,
    };
    // 触发onChange事件
    inputNode.dispatchEvent(evt, newValue);
  };
  /**
   * 控台Back删除处理指令
   * @param command
   */
  const doBackCommand = (inputNode) => {
    // 转换成字符串
    const value = inputNode.value + '';
    // 光标开始、结束位置
    const selectionStart = inputNode.selectionStart;
    const selectionEnd = inputNode.selectionEnd;
    // 计算新值
    const newValue =
      value.substr(0, selectionStart - 1) + value.substring(selectionEnd, value.length);
    console.info('value', value, newValue);

    // 事件发布到组件中input
    const evt = new UIEvent('inputchange', {
      bubbles: false,
      cancelable: false,
    });
    const keyName = 'data';
    evt[keyName] = {
      value: newValue,
      selectionStart: selectionStart - 1,
      selectionEnd: selectionStart - 1,
    };
    // 触发onChange事件
    inputNode.dispatchEvent(evt);
  };
  /**
   * 控台Enter处理指令
   * 触发失去焦点指令
   * @param command
   */
  const doEnterCommand = (inputNode) => {
    // 失焦事件发布到组件中input
    const evt = new UIEvent('enter', {
      bubbles: false,
      cancelable: false,
    });
    // 触发onChange事件
    inputNode.dispatchEvent(evt);
  };
  /**
   * 初始化订阅数字键盘指令
   */
  useEffect(() => {
    if (panelCommand) {
      changeNumberCommand(panelCommand);
    }
  }, [panelCommand]);

  return <div />;
};

export default NumberKeyboard;
相关推荐
qq_381454995 小时前
设计模式详解:代码架构的艺术
开发语言·javascript·ecmascript
半桶水专家5 小时前
vue3中v-model 用法详解
前端·javascript·vue.js
行走的陀螺仪5 小时前
Vue3 项目单元测试全指南:价值、Vitest 落地与提效方案
开发语言·前端·单元测试·html5·vitest
AI浩13 小时前
【Labelme数据操作】LabelMe标注批量复制工具 - 完整教程
运维·服务器·前端
涔溪14 小时前
CSS 网格布局(Grid Layout)核心概念、基础语法、常用属性、实战示例和进阶技巧全面讲解
前端·css
2401_8784545314 小时前
浏览器工作原理
前端·javascript
西陵14 小时前
为什么说 AI 赋能前端开发,已经不是选择题,而是必然趋势?
前端·架构·ai编程
by__csdn15 小时前
Vue3 setup()函数终极攻略:从入门到精通
开发语言·前端·javascript·vue.js·性能优化·typescript·ecmascript
天天扭码16 小时前
前端如何实现RAG?一文带你速通,使用RAG实现长期记忆
前端·node.js·ai编程