feat():首页闸门控制替换
parent
7d67c27718
commit
2a31f17099
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -0,0 +1,152 @@
|
||||||
|
# 闸门标注功能使用说明
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
本功能在水闸大屏上实现了五个闸门的标注显示,每个标注包含工作闸和检修闸的开关状态。标注使用百分比定位,确保在不同屏幕分辨率下位置保持不变。
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
```
|
||||||
|
src/views/Home/
|
||||||
|
├── index.js # 主组件文件
|
||||||
|
├── index.less # 样式文件
|
||||||
|
└── gateMarkersConfig.js # 闸门标注配置文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何调整闸门位置
|
||||||
|
|
||||||
|
### 1. 打开配置文件
|
||||||
|
编辑 `src/views/Home/gateMarkersConfig.js` 文件
|
||||||
|
|
||||||
|
### 2. 修改位置参数
|
||||||
|
每个闸门对象都有一个 `position` 属性,包含两个百分比值:
|
||||||
|
- `left`: 从左到右的百分比位置 (0-100)
|
||||||
|
- `top`: 从上到下的百分比位置 (0-100)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '1号闸门',
|
||||||
|
workGate: '开启',
|
||||||
|
repairGate: '关闭',
|
||||||
|
position: { left: 15, top: 45 } // 调整这两个值
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 调整建议
|
||||||
|
- **left值**: 从左到右依次增加,建议间隔 15% 左右
|
||||||
|
- 1号闸门: left: 15
|
||||||
|
- 2号闸门: left: 30
|
||||||
|
- 3号闸门: left: 45
|
||||||
|
- 4号闸门: left: 60
|
||||||
|
- 5号闸门: left: 75
|
||||||
|
|
||||||
|
- **top值**: 根据实际图片中闸门的位置调整
|
||||||
|
- 如果闸门在图片上方,使用较小的值(如 35-40)
|
||||||
|
- 如果闸门在图片中间,使用中等值(如 45-55)
|
||||||
|
- 如果闸门在图片下方,使用较大的值(如 60-70)
|
||||||
|
|
||||||
|
### 4. 精确定位方法
|
||||||
|
1. 打开浏览器开发者工具 (F12)
|
||||||
|
2. 选择元素选择工具
|
||||||
|
3. 点击大屏区域,查看容器的实际尺寸
|
||||||
|
4. 计算闸门在图片中的相对位置:
|
||||||
|
```
|
||||||
|
left百分比 = (闸门X坐标 / 容器宽度) × 100
|
||||||
|
top百分比 = (闸门Y坐标 / 容器高度) × 100
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何修改闸门状态
|
||||||
|
|
||||||
|
在 `gateMarkersConfig.js` 中修改每个闸门的 `workGate` 和 `repairGate` 属性:
|
||||||
|
- `'开启'`: 显示为绿色
|
||||||
|
- `'关闭'`: 显示为红色
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '1号闸门',
|
||||||
|
workGate: '开启', // 修改为 '开启' 或 '关闭'
|
||||||
|
repairGate: '关闭', // 修改为 '开启' 或 '关闭'
|
||||||
|
position: { left: 15, top: 45 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 样式自定义
|
||||||
|
|
||||||
|
如需修改标注样式,编辑 `src/views/Home/index.less` 文件中的以下部分:
|
||||||
|
|
||||||
|
### 标注点样式
|
||||||
|
```less
|
||||||
|
.marker-dot {
|
||||||
|
width: 12px; // 点的大小
|
||||||
|
height: 12px;
|
||||||
|
background: #ff4d4f; // 点的颜色
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 信息框样式
|
||||||
|
```less
|
||||||
|
.marker-info {
|
||||||
|
background: rgba(0, 0, 0, 0.75); // 背景颜色和透明度
|
||||||
|
border: 1px solid #ff4d4f; // 边框颜色
|
||||||
|
min-width: 120px; // 最小宽度
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 动画效果
|
||||||
|
```less
|
||||||
|
@keyframes pulse {
|
||||||
|
// 修改脉冲动画效果
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 响应式特性
|
||||||
|
|
||||||
|
- 标注使用百分比定位,自动适应不同屏幕尺寸
|
||||||
|
- 配合 `autofit.js` 实现整体大屏的缩放适配
|
||||||
|
- 标注信息框会自动调整位置,避免超出屏幕边界
|
||||||
|
|
||||||
|
## 交互功能
|
||||||
|
|
||||||
|
- **悬停效果**: 鼠标悬停时标注会放大 1.05 倍
|
||||||
|
- **点击事件**: 点击标注会触发 `handleGateClick` 函数,可在控制台查看点击的闸门信息
|
||||||
|
- **脉冲动画**: 标注点有持续的脉冲动画效果,吸引注意力
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 修改配置文件后需要刷新页面才能看到效果
|
||||||
|
2. 确保百分比值的总和不超过 100%
|
||||||
|
3. 如果标注信息框超出屏幕边界,可以调整 `left` 值或修改样式中的 `left` 偏移量
|
||||||
|
4. 在生产环境中,建议将闸门状态改为从后端 API 获取
|
||||||
|
|
||||||
|
## 示例:从API获取闸门状态
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 在 index.js 中添加获取闸门数据的函数
|
||||||
|
const getGateData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await httppost2(apiurl.gate.getGateStatus)
|
||||||
|
if (res.code == 200) {
|
||||||
|
// 更新闸门状态
|
||||||
|
const updatedGateData = gateMarkersConfig.map(gate => {
|
||||||
|
const apiData = res.data.find(item => item.id === gate.id)
|
||||||
|
return {
|
||||||
|
...gate,
|
||||||
|
workGate: apiData?.workGate || '关闭',
|
||||||
|
repairGate: apiData?.repairGate || '关闭'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setGateData(updatedGateData)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 useEffect 中调用
|
||||||
|
useEffect(() => {
|
||||||
|
// ... 其他初始化代码
|
||||||
|
getGateData()
|
||||||
|
}, [])
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// 闸门标注配置文件
|
||||||
|
// 使用百分比定位,确保在不同分辨率下标注位置保持不变
|
||||||
|
// left: 从左到右的百分比位置 (0-100)
|
||||||
|
// top: 从上到下的百分比位置 (0-100)
|
||||||
|
|
||||||
|
export const gateMarkersConfig = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '1号闸门',
|
||||||
|
workGate: '-',
|
||||||
|
repairGate: '-',
|
||||||
|
position: { left: 33, top: 45 } // 根据实际图片调整这些值
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '2号闸门',
|
||||||
|
workGate: '-',
|
||||||
|
repairGate: '-',
|
||||||
|
position: { left: 40, top: 45 } // 根据实际图片调整这些值
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '3号闸门',
|
||||||
|
workGate: '-',
|
||||||
|
repairGate: '-',
|
||||||
|
position: { left: 47, top: 45 } // 根据实际图片调整这些值
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '4号闸门',
|
||||||
|
workGate: '-',
|
||||||
|
repairGate: '-',
|
||||||
|
position: { left: 54, top: 45 } // 根据实际图片调整这些值
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: '5号闸门',
|
||||||
|
workGate: '-',
|
||||||
|
repairGate: '-',
|
||||||
|
position: { left: 61, top: 45 } // 根据实际图片调整这些值
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 标注样式配置
|
||||||
|
export const markerStyleConfig = {
|
||||||
|
// 标注点大小
|
||||||
|
dotSize: 12,
|
||||||
|
// 标注线高度
|
||||||
|
lineHeight: 30,
|
||||||
|
// 信息框最小宽度
|
||||||
|
infoBoxMinWidth: 120,
|
||||||
|
// 信息框内边距
|
||||||
|
infoBoxPadding: '8px 12px',
|
||||||
|
// 开启状态颜色
|
||||||
|
openColor: '#52c41a',
|
||||||
|
// 关闭状态颜色
|
||||||
|
closedColor: '#ff4d4f',
|
||||||
|
// 标注点颜色
|
||||||
|
dotColor: '#ff4d4f',
|
||||||
|
// 边框颜色
|
||||||
|
borderColor: '#ff4d4f',
|
||||||
|
// 背景透明度
|
||||||
|
backgroundOpacity: 0.75
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,43 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||||
import HFivePlayer from '../../components/video1Plary';
|
import HFivePlayer from '../../components/video1Plary';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { loadMenu, loadRole } from '../../models/auth/_'
|
import { loadMenu, loadRole } from '../../models/auth/_'
|
||||||
|
import { gateMarkersConfig } from './gateMarkersConfig'
|
||||||
|
|
||||||
|
// 闸门标注组件
|
||||||
|
const GateMarker = ({ gateInfo, position, onClick }) => {
|
||||||
|
const getStatusColor = (status) => {
|
||||||
|
return status === '开启' ? '#52c41a' : '#ff4d4f'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="gate-marker"
|
||||||
|
style={{
|
||||||
|
left: `${position.left}%`,
|
||||||
|
top: `${position.top}%`
|
||||||
|
}}
|
||||||
|
onClick={() => onClick && onClick(gateInfo)}
|
||||||
|
>
|
||||||
|
{/* <div className="marker-dot"></div>
|
||||||
|
<div className="marker-line"></div> */}
|
||||||
|
<div className="marker-info">
|
||||||
|
<div className="marker-title">{gateInfo.name}</div>
|
||||||
|
<div className="marker-item">
|
||||||
|
<span className="marker-label">工作闸开度:</span>
|
||||||
|
<span className="marker-value" style={{ color: getStatusColor(gateInfo.workGate) }}>
|
||||||
|
{gateInfo.workGate}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="marker-item">
|
||||||
|
<span className="marker-label">检修闸开度:</span>
|
||||||
|
<span className="marker-value" style={{ color: getStatusColor(gateInfo.repairGate) }}>
|
||||||
|
{gateInfo.repairGate}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
const MenuTitleCard = ({ key, title }) => {
|
const MenuTitleCard = ({ key, title }) => {
|
||||||
return (
|
return (
|
||||||
<div className='menuItem_style' key={key} title={title}>
|
<div className='menuItem_style' key={key} title={title}>
|
||||||
|
|
@ -223,12 +260,12 @@ export default function Home() {
|
||||||
const delClick = () => {
|
const delClick = () => {
|
||||||
let idx = index - 1
|
let idx = index - 1
|
||||||
setIndex(idx)
|
setIndex(idx)
|
||||||
getVideoSrc(videoList[idx].indexCode)
|
getVideoSrc(videoList[idx]?.indexCode)
|
||||||
}
|
}
|
||||||
const addClick = () => {
|
const addClick = () => {
|
||||||
let idx = index + 1
|
let idx = index + 1
|
||||||
setIndex(idx)
|
setIndex(idx)
|
||||||
getVideoSrc(videoList[idx].indexCode)
|
getVideoSrc(videoList[idx]?.indexCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -242,6 +279,15 @@ export default function Home() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [activeOne, setActiveOne] = useState(0)
|
const [activeOne, setActiveOne] = useState(0)
|
||||||
|
|
||||||
|
// 闸门数据 - 使用配置文件
|
||||||
|
const [gateData, setGateData] = useState(gateMarkersConfig)
|
||||||
|
|
||||||
|
// 处理闸门标注点击
|
||||||
|
const handleGateClick = (gateInfo) => {
|
||||||
|
console.log('点击闸门:', gateInfo)
|
||||||
|
// 这里可以添加点击后的逻辑,比如显示详细信息等
|
||||||
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
autofit.init({
|
autofit.init({
|
||||||
dh: 1080,
|
dh: 1080,
|
||||||
|
|
@ -261,7 +307,7 @@ export default function Home() {
|
||||||
<div className='daping-body' id='daping-body'>
|
<div className='daping-body' id='daping-body'>
|
||||||
<div className='topMenu'>
|
<div className='topMenu'>
|
||||||
<div className='title'></div>
|
<div className='title'></div>
|
||||||
<div className='title_name'></div>
|
{/* <div className='title_name'></div> */}
|
||||||
{title.map((item, i) => (
|
{title.map((item, i) => (
|
||||||
<div key={item.key} className={'styles' + i} onClick={() => jumpMenu(item)} style={{ cursor: 'pointer' }}>
|
<div key={item.key} className={'styles' + i} onClick={() => jumpMenu(item)} style={{ cursor: 'pointer' }}>
|
||||||
<MenuTitleCard title={item.title} />
|
<MenuTitleCard title={item.title} />
|
||||||
|
|
@ -269,7 +315,21 @@ export default function Home() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className='content-box'>
|
<div className='content-box'>
|
||||||
<Zmjk water={ RealData.find(item => item?.type == 2)} />
|
{/* <Zmjk water={ RealData.find(item => item?.type == 2)} /> */}
|
||||||
|
<div className='content-center'>
|
||||||
|
{/* <img alt='' src={`${process.env.PUBLIC_URL}/assets/centerZm.png`} /> */}
|
||||||
|
{/* 闸门标注 */}
|
||||||
|
<div className="gate-markers-container">
|
||||||
|
{gateData.map((gate) => (
|
||||||
|
<GateMarker
|
||||||
|
key={gate.id}
|
||||||
|
gateInfo={gate}
|
||||||
|
position={gate.position}
|
||||||
|
onClick={handleGateClick}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{showTabLeft &&
|
{showTabLeft &&
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background:url(../../assets/ykzImg/bp.png) no-repeat center;
|
// background:url(../../assets/ykzImg/bp.png) no-repeat center;
|
||||||
|
background:url(../../assets/ykzImg/ykbg.png) no-repeat center;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
.topMenu{
|
.topMenu{
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -73,7 +74,119 @@
|
||||||
.content-box{
|
.content-box{
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.content-center{
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 闸门标注容器
|
||||||
|
.gate-markers-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 闸门标注
|
||||||
|
.gate-marker {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 100;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-dot {
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: #ff4d4f;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 10px rgba(255, 77, 79, 0.8);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
left: -6px;
|
||||||
|
top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-line {
|
||||||
|
position: absolute;
|
||||||
|
width: 2px;
|
||||||
|
height: 30px;
|
||||||
|
background: linear-gradient(to bottom, #ff4d4f, transparent);
|
||||||
|
left: -1px;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-info {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: -40px;
|
||||||
|
// background: rgba(0, 0, 0, 0.65);
|
||||||
|
// border: 1px solid #ff4d4f;
|
||||||
|
// border-radius: 4px;
|
||||||
|
background:url(../../assets/ykzImg/zmCard.png) no-repeat center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
min-width: 120px;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
|
||||||
|
.marker-title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
padding-bottom: 4px;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
.marker-label {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-value {
|
||||||
|
color: #52c41a;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&.closed {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 0 rgba(255, 77, 79, 0.7);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 0 0 10px rgba(255, 77, 79, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 0 rgba(255, 77, 79, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.content-left{
|
.content-left{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue