feat(机柜可视化): 添加设备搜索功能并优化高亮效果
- 新增全局设备搜索功能,支持跨机柜搜索和定位 - 添加搜索结果显示面板,包含设备位置和机柜信息 - 实现设备高亮动画效果,增强视觉引导 - 优化UI交互,添加搜索状态提示和清除功能
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "gradient",
|
"type": "image",
|
||||||
"image": "/uploads/1766291127756_çææ
ä¾£å¾ç.png",
|
"image": null,
|
||||||
"size": "contain"
|
"size": "contain"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||||
import { Card, Select, Button, Space, message, Tooltip, Modal, Form, Switch, Checkbox } from 'antd';
|
import { Card, Select, Button, Space, message, Tooltip, Modal, Form, Switch, Checkbox, Input, Badge, Typography } from 'antd';
|
||||||
import {
|
import {
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
ZoomInOutlined,
|
ZoomInOutlined,
|
||||||
@@ -12,11 +12,15 @@ import {
|
|||||||
LaptopOutlined,
|
LaptopOutlined,
|
||||||
MobileOutlined,
|
MobileOutlined,
|
||||||
PrinterOutlined,
|
PrinterOutlined,
|
||||||
SettingOutlined
|
SettingOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
ClearOutlined,
|
||||||
|
EnvironmentOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
// 工具函数提取到组件外部,避免每次渲染重复创建
|
// 工具函数提取到组件外部,避免每次渲染重复创建
|
||||||
const getDeviceIcon = (deviceType) => {
|
const getDeviceIcon = (deviceType) => {
|
||||||
@@ -177,6 +181,27 @@ const initAnimationStyles = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes highlightPulse {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 20px rgba(56, 189, 248, 0.6),
|
||||||
|
0 4px 12px rgba(56, 189, 248, 0.4),
|
||||||
|
0 1px 2px rgba(0,0,0,0.3),
|
||||||
|
inset 0 1px 0 rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 35px rgba(56, 189, 248, 0.9),
|
||||||
|
0 6px 20px rgba(56, 189, 248, 0.6),
|
||||||
|
0 1px 2px rgba(0,0,0,0.3),
|
||||||
|
inset 0 1px 0 rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-highlighted {
|
||||||
|
animation: highlightPulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.metal-texture {
|
.metal-texture {
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(90deg,
|
linear-gradient(90deg,
|
||||||
@@ -312,6 +337,13 @@ function RackVisualization() {
|
|||||||
const [loadingTooltipFields, setLoadingTooltipFields] = useState(false); // 加载字段配置状态
|
const [loadingTooltipFields, setLoadingTooltipFields] = useState(false); // 加载字段配置状态
|
||||||
const [savingTooltipConfig, setSavingTooltipConfig] = useState(false); // 保存配置状态
|
const [savingTooltipConfig, setSavingTooltipConfig] = useState(false); // 保存配置状态
|
||||||
|
|
||||||
|
// 设备搜索功能
|
||||||
|
const [searchKeyword, setSearchKeyword] = useState(''); // 搜索关键词
|
||||||
|
const [searchResults, setSearchResults] = useState([]); // 搜索结果
|
||||||
|
const [highlightedDevice, setHighlightedDevice] = useState(null); // 高亮的设备
|
||||||
|
const [searchMatchCount, setSearchMatchCount] = useState(0); // 匹配数量
|
||||||
|
const [searching, setSearching] = useState(false); // 搜索中状态
|
||||||
|
|
||||||
// 设备详情tooltip字段配置
|
// 设备详情tooltip字段配置
|
||||||
const [tooltipFields, setTooltipFields] = useState({});
|
const [tooltipFields, setTooltipFields] = useState({});
|
||||||
|
|
||||||
@@ -374,6 +406,11 @@ function RackVisualization() {
|
|||||||
}
|
}
|
||||||
}, [defaultTooltipFields]);
|
}, [defaultTooltipFields]);
|
||||||
|
|
||||||
|
// 初始化获取保存的字段配置
|
||||||
|
useEffect(() => {
|
||||||
|
fetchTooltipDeviceFields();
|
||||||
|
}, [fetchTooltipDeviceFields]);
|
||||||
|
|
||||||
// 保存tooltip字段配置
|
// 保存tooltip字段配置
|
||||||
const saveTooltipConfig = useCallback(async () => {
|
const saveTooltipConfig = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -400,55 +437,6 @@ function RackVisualization() {
|
|||||||
}
|
}
|
||||||
}, [tooltipFields, fetchTooltipDeviceFields]);
|
}, [tooltipFields, fetchTooltipDeviceFields]);
|
||||||
|
|
||||||
// 获取所有机柜 - 使用 useCallback 避免重复创建
|
|
||||||
const fetchRacks = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
// 为机柜可视化页面传递较大的pageSize以获取所有机柜
|
|
||||||
const response = await axios.get('/api/racks', { params: { pageSize: 1000 } });
|
|
||||||
|
|
||||||
// 验证数据结构并过滤有效机柜
|
|
||||||
// 机柜API返回的是包含total和racks数组的对象
|
|
||||||
const racksArray = response.data.racks || [];
|
|
||||||
const validRacks = racksArray.filter(rack =>
|
|
||||||
rack &&
|
|
||||||
typeof rack === 'object' &&
|
|
||||||
rack.rackId &&
|
|
||||||
typeof rack.height === 'number'
|
|
||||||
);
|
|
||||||
|
|
||||||
setRacks(validRacks);
|
|
||||||
if (validRacks.length > 0) {
|
|
||||||
// 自动选择第一个机柜
|
|
||||||
const firstRack = validRacks[0];
|
|
||||||
setSelectedRack(firstRack);
|
|
||||||
|
|
||||||
// 设置对应的机房
|
|
||||||
if (firstRack?.Room) {
|
|
||||||
const roomKey = firstRack.Room.roomId || firstRack.Room.id || firstRack.Room.name;
|
|
||||||
setSelectedRoom(roomKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchDevices(firstRack.rackId);
|
|
||||||
} else {
|
|
||||||
setSelectedRack(null);
|
|
||||||
setSelectedRoom(null);
|
|
||||||
setDevices([]);
|
|
||||||
message.warning('未找到有效的机柜数据');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error('获取机柜列表失败');
|
|
||||||
console.error('获取机柜列表失败:', error);
|
|
||||||
setError(error);
|
|
||||||
setRacks([]);
|
|
||||||
setSelectedRack(null);
|
|
||||||
setDevices([]);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 获取机柜内的设备 - 使用 useCallback 避免重复创建
|
// 获取机柜内的设备 - 使用 useCallback 避免重复创建
|
||||||
const fetchDevices = useCallback(async (rackId) => {
|
const fetchDevices = useCallback(async (rackId) => {
|
||||||
try {
|
try {
|
||||||
@@ -594,6 +582,144 @@ function RackVisualization() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 搜索设备 - 全局搜索,支持跨机柜
|
||||||
|
const handleSearch = useCallback(async (keyword) => {
|
||||||
|
setSearchKeyword(keyword);
|
||||||
|
|
||||||
|
if (!keyword || !keyword.trim()) {
|
||||||
|
setSearchResults([]);
|
||||||
|
setHighlightedDevice(null);
|
||||||
|
setSearchMatchCount(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchLower = keyword.toLowerCase().trim();
|
||||||
|
setSearching(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用全局搜索API,搜索所有机柜的设备
|
||||||
|
const response = await axios.get('/api/devices', {
|
||||||
|
params: { keyword: searchLower, pageSize: 100 }
|
||||||
|
});
|
||||||
|
|
||||||
|
const devicesData = response.data.devices || [];
|
||||||
|
|
||||||
|
// 构建搜索结果,包含机柜信息
|
||||||
|
const results = devicesData.map(device => ({
|
||||||
|
deviceId: device.deviceId || device.id,
|
||||||
|
name: device.name,
|
||||||
|
type: device.type,
|
||||||
|
rackId: device.rackId,
|
||||||
|
rackName: device.Rack?.name || '未知机柜',
|
||||||
|
roomName: device.Rack?.Room?.name || '未知机房',
|
||||||
|
position: device.position
|
||||||
|
}));
|
||||||
|
|
||||||
|
setSearchResults(results);
|
||||||
|
setSearchMatchCount(results.length);
|
||||||
|
|
||||||
|
// 如果有搜索结果,自动定位到第一个设备所在的机柜
|
||||||
|
if (results.length > 0) {
|
||||||
|
const firstResult = results[0];
|
||||||
|
setHighlightedDevice(firstResult.deviceId);
|
||||||
|
|
||||||
|
// 如果设备不在当前机柜,自动切换机柜
|
||||||
|
if (selectedRack && firstResult.rackId !== selectedRack.rackId) {
|
||||||
|
const targetRack = racks.find(r => r.rackId === firstResult.rackId);
|
||||||
|
if (targetRack) {
|
||||||
|
// 找到设备所在的机房
|
||||||
|
if (targetRack?.Room) {
|
||||||
|
const roomKey = targetRack.Room.roomId || targetRack.Room.id || targetRack.Room.name;
|
||||||
|
if (roomKey !== selectedRoom) {
|
||||||
|
setSelectedRoom(roomKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换到设备所在的机柜
|
||||||
|
setSelectedRack(targetRack);
|
||||||
|
fetchDevices(targetRack.rackId);
|
||||||
|
message.info(`已跳转到机柜: ${firstResult.rackName}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.info(`已定位到设备: ${firstResult.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setHighlightedDevice(null);
|
||||||
|
message.warning('未找到匹配的设备');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('全局搜索失败:', error);
|
||||||
|
message.error('搜索失败,请重试');
|
||||||
|
setSearchResults([]);
|
||||||
|
setSearchMatchCount(0);
|
||||||
|
} finally {
|
||||||
|
setSearching(false);
|
||||||
|
}
|
||||||
|
}, [selectedRack, selectedRoom, racks, fetchDevices]);
|
||||||
|
|
||||||
|
// 清除搜索
|
||||||
|
const clearSearch = useCallback(() => {
|
||||||
|
setSearchKeyword('');
|
||||||
|
setSearchResults([]);
|
||||||
|
setHighlightedDevice(null);
|
||||||
|
setSearchMatchCount(0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 跳转到指定设备
|
||||||
|
const jumpToDevice = useCallback((deviceId) => {
|
||||||
|
setHighlightedDevice(deviceId);
|
||||||
|
message.info(`已定位到设备: ${deviceId}`);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 获取所有机柜 - 使用 useCallback 避免重复创建
|
||||||
|
const fetchRacks = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
// 为机柜可视化页面传递较大的pageSize以获取所有机柜
|
||||||
|
const response = await axios.get('/api/racks', { params: { pageSize: 1000 } });
|
||||||
|
|
||||||
|
// 验证数据结构并过滤有效机柜
|
||||||
|
// 机柜API返回的是包含total和racks数组的对象
|
||||||
|
const racksArray = response.data.racks || [];
|
||||||
|
const validRacks = racksArray.filter(rack =>
|
||||||
|
rack &&
|
||||||
|
typeof rack === 'object' &&
|
||||||
|
rack.rackId &&
|
||||||
|
typeof rack.height === 'number'
|
||||||
|
);
|
||||||
|
|
||||||
|
setRacks(validRacks);
|
||||||
|
if (validRacks.length > 0) {
|
||||||
|
// 自动选择第一个机柜
|
||||||
|
const firstRack = validRacks[0];
|
||||||
|
setSelectedRack(firstRack);
|
||||||
|
|
||||||
|
// 设置对应的机房
|
||||||
|
if (firstRack?.Room) {
|
||||||
|
const roomKey = firstRack.Room.roomId || firstRack.Room.id || firstRack.Room.name;
|
||||||
|
setSelectedRoom(roomKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchDevices(firstRack.rackId);
|
||||||
|
} else {
|
||||||
|
setSelectedRack(null);
|
||||||
|
setSelectedRoom(null);
|
||||||
|
setDevices([]);
|
||||||
|
message.warning('未找到有效的机柜数据');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取机柜列表失败');
|
||||||
|
console.error('获取机柜列表失败:', error);
|
||||||
|
setError(error);
|
||||||
|
setRacks([]);
|
||||||
|
setSelectedRack(null);
|
||||||
|
setDevices([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRacks();
|
fetchRacks();
|
||||||
}, [fetchRacks]);
|
}, [fetchRacks]);
|
||||||
@@ -914,6 +1040,27 @@ function RackVisualization() {
|
|||||||
title="机柜可视化"
|
title="机柜可视化"
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
|
<Input
|
||||||
|
placeholder="搜索设备名称、ID、IP..."
|
||||||
|
value={searchKeyword}
|
||||||
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
|
onPressEnter={(e) => handleSearch(e.target.value)}
|
||||||
|
style={{ width: 220 }}
|
||||||
|
allowClear
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
suffix={
|
||||||
|
searchMatchCount > 0 ? (
|
||||||
|
<Badge count={searchMatchCount} size="small" style={{ backgroundColor: '#52c41a' }} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{searchKeyword && (
|
||||||
|
<Button
|
||||||
|
icon={<ClearOutlined />}
|
||||||
|
onClick={clearSearch}
|
||||||
|
title="清除搜索"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Select
|
<Select
|
||||||
placeholder="选择机房"
|
placeholder="选择机房"
|
||||||
style={{ width: 180 }}
|
style={{ width: 180 }}
|
||||||
@@ -962,6 +1109,83 @@ function RackVisualization() {
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{searchKeyword && (
|
||||||
|
<div style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
padding: '12px 16px',
|
||||||
|
background: searchResults.length > 0 ? 'linear-gradient(135deg, #f6ffed 0%, #d9f7be 100%)' : '#fff2f0',
|
||||||
|
border: `1px solid ${searchResults.length > 0 ? '#b7eb8f' : '#ffccc7'}`,
|
||||||
|
borderRadius: '6px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '12px'
|
||||||
|
}}>
|
||||||
|
<SearchOutlined style={{
|
||||||
|
fontSize: 18,
|
||||||
|
color: searchResults.length > 0 ? '#52c41a' : '#ff4d4f'
|
||||||
|
}} />
|
||||||
|
<Text strong style={{ color: searchResults.length > 0 ? '#135200' : '#cf1322' }}>
|
||||||
|
{searchResults.length > 0
|
||||||
|
? `找到 ${searchResults.length} 个设备`
|
||||||
|
: searching ? '搜索中...' : '未找到匹配的设备'}
|
||||||
|
</Text>
|
||||||
|
{searching && (
|
||||||
|
<Badge status="processing" text="正在搜索所有机柜..." />
|
||||||
|
)}
|
||||||
|
{searchResults.length > 0 && (
|
||||||
|
<Space wrap size={4}>
|
||||||
|
{searchResults.slice(0, 5).map(result => {
|
||||||
|
const isCurrentRack = selectedRack && result.rackId === selectedRack.rackId;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={result.deviceId}
|
||||||
|
size="small"
|
||||||
|
type={highlightedDevice === result.deviceId ? 'primary' : 'default'}
|
||||||
|
icon={<EnvironmentOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
// 如果设备不在当前机柜,跳转到对应机柜
|
||||||
|
if (selectedRack && result.rackId !== selectedRack.rackId) {
|
||||||
|
const targetRack = racks.find(r => r.rackId === result.rackId);
|
||||||
|
if (targetRack) {
|
||||||
|
if (targetRack?.Room) {
|
||||||
|
const roomKey = targetRack.Room.roomId || targetRack.Room.id || targetRack.Room.name;
|
||||||
|
if (roomKey !== selectedRoom) {
|
||||||
|
setSelectedRoom(roomKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSelectedRack(targetRack);
|
||||||
|
fetchDevices(targetRack.rackId);
|
||||||
|
message.info(`已跳转到机柜: ${result.rackName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setHighlightedDevice(result.deviceId);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderColor: highlightedDevice === result.deviceId ? '#1890ff' : undefined,
|
||||||
|
background: highlightedDevice === result.deviceId ? '#1890ff' : undefined
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip title={`机柜: ${result.rackName}${result.roomName ? ` | 机房: ${result.roomName}` : ''} | U${result.position || '?'}`}>
|
||||||
|
<span>
|
||||||
|
{result.name}
|
||||||
|
<Text type="secondary" style={{ fontSize: 10, marginLeft: 4 }}>
|
||||||
|
U{result.position || '?'}
|
||||||
|
</Text>
|
||||||
|
{!isCurrentRack && (
|
||||||
|
<EnvironmentOutlined style={{ marginLeft: 4, color: '#faad14' }} />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{searchResults.length > 5 && (
|
||||||
|
<Text type="secondary">+ 还有 {searchResults.length - 5} 个</Text>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button icon={<ZoomInOutlined />} onClick={handleZoomIn}>放大</Button>
|
<Button icon={<ZoomInOutlined />} onClick={handleZoomIn}>放大</Button>
|
||||||
@@ -1333,10 +1557,13 @@ function RackVisualization() {
|
|||||||
const position = device?.position || 1;
|
const position = device?.position || 1;
|
||||||
const height = device?.height || 1;
|
const height = device?.height || 1;
|
||||||
|
|
||||||
|
// 检查是否是高亮的设备
|
||||||
|
const isHighlighted = highlightedDevice === deviceId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={deviceId}
|
key={deviceId}
|
||||||
className="device"
|
className={`device ${isHighlighted ? 'device-highlighted' : ''}`}
|
||||||
style={{
|
style={{
|
||||||
...getDeviceStyle(device, selectedRack.height),
|
...getDeviceStyle(device, selectedRack.height),
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -1347,20 +1574,34 @@ function RackVisualization() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
boxShadow: `
|
boxShadow: isHighlighted
|
||||||
0 1px 2px rgba(0,0,0,0.3),
|
? `
|
||||||
0 2px 4px rgba(0,0,0,0.2),
|
0 0 20px rgba(56, 189, 248, 0.6),
|
||||||
inset 0 1px 0 rgba(255,255,255,0.1),
|
0 4px 12px rgba(56, 189, 248, 0.4),
|
||||||
inset 0 -1px 0 rgba(0,0,0,0.1)
|
0 1px 2px rgba(0,0,0,0.3),
|
||||||
`,
|
inset 0 1px 0 rgba(255,255,255,0.2)
|
||||||
background: 'linear-gradient(180deg, #3d4451 0%, #2d3139 50%, #252930 100%)',
|
`
|
||||||
border: '1px solid #4a5568',
|
: `
|
||||||
borderTop: '1px solid #565c6b',
|
0 1px 2px rgba(0,0,0,0.3),
|
||||||
|
0 2px 4px rgba(0,0,0,0.2),
|
||||||
|
inset 0 1px 0 rgba(255,255,255,0.1),
|
||||||
|
inset 0 -1px 0 rgba(0,0,0,0.1)
|
||||||
|
`,
|
||||||
|
background: isHighlighted
|
||||||
|
? 'linear-gradient(180deg, #0ea5e9 0%, #0284c7 50%, #0369a1 100%)'
|
||||||
|
: 'linear-gradient(180deg, #3d4451 0%, #2d3139 50%, #252930 100%)',
|
||||||
|
border: isHighlighted
|
||||||
|
? '2px solid #38bdf8'
|
||||||
|
: '1px solid #4a5568',
|
||||||
|
borderTop: isHighlighted
|
||||||
|
? '2px solid #7dd3fc'
|
||||||
|
: '1px solid #565c6b',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
zIndex: 100,
|
zIndex: isHighlighted ? 200 : 100,
|
||||||
pointerEvents: 'auto'
|
pointerEvents: 'auto',
|
||||||
|
animation: isHighlighted ? 'highlightPulse 1.5s ease-in-out infinite' : 'none'
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
const isOneU = (device?.height || 1) === 1;
|
const isOneU = (device?.height || 1) === 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user