| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- import React, { useState, useEffect } from 'react';
- import { Tooltip, message } from 'antd';
- import { ScanOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
- import { useIntl } from 'react-intl';
- interface BarcodeScannerIndicatorProps {
- style?: React.CSSProperties;
- className?: string;
- inline?: boolean; // 是否为内联模式(非浮动)
- }
- const BarcodeScannerIndicator: React.FC<BarcodeScannerIndicatorProps> = ({
- style,
- className,
- inline = false
- }) => {
- const intl = useIntl();
- const [hasFocusedInput, setHasFocusedInput] = useState(false);
- // 检测元素是否为可编辑元素(排除隐藏的扫码枪input)
- const isEditableElement = (element: HTMLElement | null): boolean => {
- if (!element) return false;
- // 排除隐藏的扫码枪input(通过样式判断)
- if (element.tagName === 'INPUT') {
- const input = element as HTMLInputElement;
- const style = window.getComputedStyle(input);
- // 如果是隐藏的input(opacity为0或position为fixed且left为负值),则不算作可编辑元素
- if (
- style.opacity === '0' ||
- (style.position === 'fixed' && parseInt(style.left) < -1000)
- ) {
- return false;
- }
- }
- return (
- element.tagName === 'INPUT' ||
- element.tagName === 'TEXTAREA' ||
- element.isContentEditable ||
- element.closest('.ant-select') !== null ||
- element.closest('.ant-picker') !== null ||
- element.closest('.ant-input-number') !== null
- );
- };
- // 监听焦点变化
- useEffect(() => {
- const handleFocusIn = (e: FocusEvent) => {
- const target = e.target as HTMLElement;
- setHasFocusedInput(isEditableElement(target));
- };
- const handleFocusOut = () => {
- // 延迟检查,确保新焦点已设置
- setTimeout(() => {
- const activeElement = document.activeElement as HTMLElement;
- setHasFocusedInput(isEditableElement(activeElement));
- }, 10);
- };
- window.addEventListener('focusin', handleFocusIn);
- window.addEventListener('focusout', handleFocusOut);
- // 初始化检查
- const activeElement = document.activeElement as HTMLElement;
- setHasFocusedInput(isEditableElement(activeElement));
- return () => {
- window.removeEventListener('focusin', handleFocusIn);
- window.removeEventListener('focusout', handleFocusOut);
- };
- }, []);
- // 点击处理:使所有输入框失焦
- const handleClick = () => {
- const activeElement = document.activeElement as HTMLElement;
-
- if (activeElement && activeElement.blur) {
- activeElement.blur();
- }
- // 确保焦点移到 body
- document.body.focus();
- // 显示提示
- message.success(
- intl.formatMessage({
- id: 'scanner.indicator.activated',
- defaultMessage: '扫码枪已就绪,请扫描二维码'
- })
- );
- };
- const isReady = !hasFocusedInput;
- const tooltipText = intl.formatMessage({
- id: isReady ? 'scanner.indicator.tooltip.ready' : 'scanner.indicator.tooltip.notReady',
- defaultMessage: isReady
- ? '扫码枪已就绪,可以扫描二维码'
- : '扫码枪不可用,点击此处激活'
- });
- return (
- <Tooltip
- title={tooltipText}
- placement="top"
- >
- <div
- role="button"
- tabIndex={0}
- onClick={handleClick}
- onKeyDown={(e) => {
- if (e.key === 'Enter' || e.key === ' ') {
- e.preventDefault();
- handleClick();
- }
- }}
- style={{
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- padding: '4px 15px',
- backgroundColor: isReady ? '#52c41a' : '#d9d9d9',
- border: `1px solid ${isReady ? '#52c41a' : '#d9d9d9'}`,
- borderRadius: '6px',
- color: 'white',
- cursor: 'pointer',
- transition: 'all 0.3s ease',
- height: '100%',
- minHeight: '32px',
- outline: 'none',
- userSelect: 'none',
- ...style
- }}
- className={className}
- onMouseEnter={(e) => {
- e.currentTarget.style.opacity = '0.85';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.opacity = '1';
- }}
- >
- <div style={{ position: 'relative', display: 'inline-block' }}>
- <ScanOutlined style={{ fontSize: '18px' }} />
- {isReady ? (
- <CheckCircleOutlined
- style={{
- position: 'absolute',
- bottom: -2,
- right: -2,
- fontSize: '10px',
- color: '#52c41a',
- backgroundColor: 'white',
- borderRadius: '50%'
- }}
- />
- ) : (
- <CloseCircleOutlined
- style={{
- position: 'absolute',
- bottom: -2,
- right: -2,
- fontSize: '10px',
- color: '#ff4d4f',
- backgroundColor: 'white',
- borderRadius: '50%'
- }}
- />
- )}
- </div>
- </div>
- </Tooltip>
- );
- };
- export default BarcodeScannerIndicator;
|