From b7182c7066793a2f38847201bd66979170a4e2e2 Mon Sep 17 00:00:00 2001 From: qinjian Date: Wed, 10 Dec 2025 16:40:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=AB=98=E7=BA=A7?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=9D=83=E9=99=90=E9=AA=8C=E8=AF=81=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=8F=8A=E7=9B=B8=E5=85=B3=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AdvancedConfigAuth.jsx | 114 ++++++++++ .../sections/wuyuanbiaoba/components/index.js | 1 + .../sections/wuyuanbiaoba/container/index.jsx | 210 ++++++++++++------ .../sections/wuyuanbiaoba/hooks/useAuth.js | 1 + config.cjs | 4 + 5 files changed, 262 insertions(+), 68 deletions(-) create mode 100644 client/src/sections/wuyuanbiaoba/components/AdvancedConfigAuth.jsx create mode 100644 client/src/sections/wuyuanbiaoba/hooks/useAuth.js diff --git a/client/src/sections/wuyuanbiaoba/components/AdvancedConfigAuth.jsx b/client/src/sections/wuyuanbiaoba/components/AdvancedConfigAuth.jsx new file mode 100644 index 0000000..d64a25f --- /dev/null +++ b/client/src/sections/wuyuanbiaoba/components/AdvancedConfigAuth.jsx @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import { Card, Input, Button, message } from 'antd'; +import { LockOutlined } from '@ant-design/icons'; + +/** + * 高级配置密码验证组件 + */ +const AdvancedConfigAuth = ({ verifyPassword, onUnlock }) => { + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + + const handleVerify = async () => { + if (!password) { + message.warning('请输入密码'); + return; + } + + setLoading(true); + try { + const isValid = await verifyPassword(password); + + if (isValid) { + message.success('密码正确,已解锁高级配置'); + onUnlock && onUnlock(); + } else { + message.error('密码错误,请重试'); + setPassword(''); + } + } catch (error) { + console.error('密码验证失败:', error); + message.error('验证过程出错,请重试'); + } finally { + setLoading(false); + } + }; + + const handleKeyPress = (e) => { + if (e.key === 'Enter') { + handleVerify(); + } + }; + + return ( +
+ +
+ +
+ +

内部高级配置

+

+ 请输入管理员访问密码以继续 +

+ + setPassword(e.target.value)} + onKeyPress={handleKeyPress} + prefix={} + style={{ marginBottom: 16 }} + /> + + + +
+ 视觉位移计配置工具 v{window.env?.FS_VERSION || ''} Build {new Date().getFullYear()} +
+
+
+ ); +}; + +export default AdvancedConfigAuth; diff --git a/client/src/sections/wuyuanbiaoba/components/index.js b/client/src/sections/wuyuanbiaoba/components/index.js index 072da63..a55d6d2 100644 --- a/client/src/sections/wuyuanbiaoba/components/index.js +++ b/client/src/sections/wuyuanbiaoba/components/index.js @@ -5,3 +5,4 @@ export { default as RealtimeCharts } from './RealtimeCharts'; export { default as RealtimeDataTable } from './RealtimeDataTable'; export { default as TemplateModal } from './TemplateModal'; export { default as TargetDetailModal } from './TargetDetailModal'; +export { default as AdvancedConfigAuth } from './AdvancedConfigAuth'; diff --git a/client/src/sections/wuyuanbiaoba/container/index.jsx b/client/src/sections/wuyuanbiaoba/container/index.jsx index 7934030..ffacc0d 100644 --- a/client/src/sections/wuyuanbiaoba/container/index.jsx +++ b/client/src/sections/wuyuanbiaoba/container/index.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; -import { Tabs, Typography } from "antd"; +import { Tabs, Typography, Menu } from "antd"; +import { MonitorOutlined, LockOutlined } from "@ant-design/icons"; import ExcelJS from "exceljs"; import { @@ -10,6 +11,7 @@ import { RealtimeDataTable, TemplateModal, TargetDetailModal, + AdvancedConfigAuth, } from "../components"; import { WebSocketProvider, @@ -18,6 +20,7 @@ import { } from "../actions/websocket.jsx"; import { useTemplateStorage } from "../hooks/useTemplateStorage.js"; import { useTargetStorage } from "../hooks/useTargetStorage.js"; +import { useAuth } from "../hooks/useAuth.js"; import { useRef } from "react"; const { Title } = Typography; @@ -93,6 +96,12 @@ const WuyuanbiaobaContent = () => { // 添加选中标靶的状态 const [selectedTargetId, setSelectedTargetId] = useState(null); + // 菜单状态 + const [currentMenu, setCurrentMenu] = useState("monitor"); + + // 权限验证 Hook + const { isUnlocked, verifyPassword, logout } = useAuth(); + // 处理实时数据并转换为表格格式 const processRealtimeData = (data) => { if (!data || !data.data || !Array.isArray(data.data)) { @@ -483,91 +492,156 @@ const WuyuanbiaobaContent = () => { backgroundColor: "#f0f2f5", }} > - {/* Header 区域 */} + {/* Header 区域 - 固定在顶部 */}
视觉位移计配置工具 -
- - {/* 中间区域 - 固定视口剩余高度 */} -
- {/* Camera 区域 */} - - - {/* 右侧 Target List / Temp List 区域 */} -
setCurrentMenu(key)} style={{ - flex: 1, - backgroundColor: "white", - display: "flex", - flexDirection: "column", + border: "none", + fontSize: "18px", + justifyContent: "flex-end" }} - > - -
-
- - {/* 底部区域 - 在视口下方,需要滚动查看 */} -
- {/* Charts 区域 */} - , + label: "实时监控", + }, + { + key: "advanced", + icon: , + label: "高级配置", + }, + ]} /> +
- {/* Table 区域 - 使用采样数据显示 */} - + {/* 内容区域 - 添加顶部padding以避免被固定header遮挡 */} +
+ {/* 实时监控页面 */} + {currentMenu === "monitor" && ( + <> + {/* 中间区域 - 固定视口剩余高度 */} +
+ {/* Camera 区域 */} + + + {/* 右侧 Target List / Temp List 区域 */} +
+ +
+
+ + {/* 底部区域 - 在视口下方,需要滚动查看 */} +
+ {/* Charts 区域 */} + + + {/* Table 区域 - 使用采样数据显示 */} + +
+ + )} + + {/* 高级配置页面 */} + {currentMenu === "advanced" && ( +
+ {isUnlocked ? ( +
+
+ +
高级配置功能开发中...
+
+
+ ) : ( + {}} + /> + )} +
+ )}
{/* 模板编辑模态框 */} diff --git a/client/src/sections/wuyuanbiaoba/hooks/useAuth.js b/client/src/sections/wuyuanbiaoba/hooks/useAuth.js new file mode 100644 index 0000000..0779766 --- /dev/null +++ b/client/src/sections/wuyuanbiaoba/hooks/useAuth.js @@ -0,0 +1 @@ +import { useState, useEffect, useCallback } from "react"; const CORRECT_PASSWORD_HASH = "77796ac7e66ecc44954287ed7de7096c4016dd6ffb2763091c4eb3bc4d28b6dc", AUTH_STATUS_KEY = "advanced_config_unlocked"; async function generatePasswordHash(e) { try { var t = (new TextEncoder).encode(e), c = await crypto.subtle.digest("SHA-256", t); return Array.from(new Uint8Array(c)).map(e => e.toString(16).padStart(2, "0")).join("") } catch (e) { throw console.error("密码哈希生成失败:", e), e } } function checkUnlockStatus() { return "true" === sessionStorage.getItem(AUTH_STATUS_KEY) } function setUnlockStatus(e) { e ? sessionStorage.setItem(AUTH_STATUS_KEY, "true") : sessionStorage.removeItem(AUTH_STATUS_KEY) } function useAuth() { let [e, t] = useState(checkUnlockStatus); return useEffect(() => { t(checkUnlockStatus()) }, []), { isUnlocked: e, verifyPassword: useCallback(async e => { try { return e ? await generatePasswordHash(e) === CORRECT_PASSWORD_HASH && (setUnlockStatus(!0), t(!0), !0) : !1 } catch (e) { return console.error("密码验证失败:", e), !1 } }, []), logout: useCallback(() => { setUnlockStatus(!1), t(!1) }, []) } } export { useAuth, generatePasswordHash, checkUnlockStatus }; diff --git a/config.cjs b/config.cjs index dc7d654..825b80a 100644 --- a/config.cjs +++ b/config.cjs @@ -1,8 +1,11 @@ const path = require('path') +const packageJson = require('./package.json') + const flag = process.env.npm_lifecycle_script.includes('--mode localdev') ? 'localdev' : null; if (flag) { process.env.FS_FLAG = flag; } +process.env.FS_VERSION = packageJson.version; module.exports = { env: process.env.NODE_ENV || 'development', // 运行环境 development | production port: process.env.PORT || 5000, // 服务端口 @@ -12,6 +15,7 @@ module.exports = { '/client/assets/script/peace.js' ], flag: flag, // 自定义环境标识 + version: packageJson.version, // 版本号 proxy: [{ // 代理配置 path: '/_api', target: process.env.API,