You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
630 lines
22 KiB
630 lines
22 KiB
import React, { useEffect } from "react";
|
|
import {
|
|
Card,
|
|
Input,
|
|
Slider,
|
|
InputNumber,
|
|
Switch,
|
|
Select,
|
|
Row,
|
|
Col,
|
|
Button,
|
|
message,
|
|
Space,
|
|
Typography,
|
|
Spin,
|
|
Alert,
|
|
} from "antd";
|
|
import {
|
|
SettingOutlined,
|
|
SaveOutlined,
|
|
DatabaseOutlined,
|
|
ClockCircleOutlined,
|
|
BellOutlined,
|
|
FilterOutlined,
|
|
CloudUploadOutlined,
|
|
SyncOutlined,
|
|
} from "@ant-design/icons";
|
|
import useAdvancedSettings from "../hooks/useAdvancedSettings";
|
|
|
|
const { Option } = Select;
|
|
const { Title, Text } = Typography;
|
|
|
|
const AdvancedSettings = ({ onLogout }) => {
|
|
// 使用高级配置 Hook
|
|
const {
|
|
settings,
|
|
loading,
|
|
isConnected,
|
|
isReady,
|
|
fetchAllSettings,
|
|
saveAllSettings,
|
|
resetSettings,
|
|
updateLocalSettings,
|
|
} = useAdvancedSettings();
|
|
|
|
// 只在首次 isReady 变为 true 时自动拉取配置
|
|
const hasFetchedRef = React.useRef(false);
|
|
useEffect(() => {
|
|
if (isReady && !hasFetchedRef.current) {
|
|
fetchAllSettings();
|
|
hasFetchedRef.current = true;
|
|
}
|
|
}, [isReady, fetchAllSettings]);
|
|
|
|
// 从 settings 中提取配置
|
|
const deviceId = settings.deviceId;
|
|
const fps = settings.dataFps;
|
|
const enableOfflineAlert = settings.alertConfig?.enable ?? false;
|
|
const offlineThreshold = settings.alertConfig?.intervalSec ?? 60;
|
|
const enableFiltering = settings.filterConfig?.enable ?? true;
|
|
const filterMethod = settings.filterConfig?.method ?? 'median';
|
|
const windowSize = settings.filterConfig?.size ?? 5;
|
|
const filterThreshold = settings.filterConfig?.threshold ?? 0.1;
|
|
const flowThreshold = settings.filterConfig?.imgThreshold ?? 10.0;
|
|
const enableMqtt = settings.mqttConfig?.enable ?? true;
|
|
const brokerAddress = settings.mqttConfig?.mqtt?.broker ?? '';
|
|
const mqttPort = settings.mqttConfig?.mqtt?.port ?? 1883;
|
|
const mqttTopic = settings.mqttConfig?.mqtt?.topic ?? '';
|
|
const mqttClientId = settings.mqttConfig?.mqtt?.client_id ?? '';
|
|
const mqttUsername = settings.mqttConfig?.mqtt?.username ?? '';
|
|
const mqttPassword = settings.mqttConfig?.mqtt?.password ?? '';
|
|
|
|
// 更新本地状态的辅助函数
|
|
const setDeviceId = (value) => updateLocalSettings({ deviceId: value });
|
|
const setFps = (value) => updateLocalSettings({ dataFps: value });
|
|
const setEnableOfflineAlert = (value) =>
|
|
updateLocalSettings({
|
|
alertConfig: {
|
|
enable: value,
|
|
intervalSec: settings.alertConfig?.intervalSec ?? 60
|
|
},
|
|
});
|
|
const setOfflineThreshold = (value) =>
|
|
updateLocalSettings({
|
|
alertConfig: {
|
|
enable: settings.alertConfig?.enable ?? false,
|
|
intervalSec: value
|
|
},
|
|
});
|
|
const setEnableFiltering = (value) =>
|
|
updateLocalSettings({
|
|
filterConfig: { ...settings.filterConfig, enable: value },
|
|
});
|
|
const setFilterMethod = (value) =>
|
|
updateLocalSettings({
|
|
filterConfig: { ...settings.filterConfig, method: value },
|
|
});
|
|
const setWindowSize = (value) =>
|
|
updateLocalSettings({
|
|
filterConfig: { ...settings.filterConfig, size: value },
|
|
});
|
|
const setFilterThreshold = (value) =>
|
|
updateLocalSettings({
|
|
filterConfig: { ...settings.filterConfig, threshold: -Math.abs(value) },
|
|
});
|
|
const setFlowThreshold = (value) =>
|
|
updateLocalSettings({
|
|
filterConfig: { ...settings.filterConfig, imgThreshold: value },
|
|
});
|
|
const setEnableMqtt = (value) =>
|
|
updateLocalSettings({
|
|
mqttConfig: {
|
|
enable: value,
|
|
mqtt: settings.mqttConfig?.mqtt ?? {
|
|
broker: '',
|
|
port: 1883,
|
|
topic: '',
|
|
username: '',
|
|
password: '',
|
|
client_id: ''
|
|
}
|
|
},
|
|
});
|
|
const setBrokerAddress = (value) =>
|
|
updateLocalSettings({
|
|
mqttConfig: {
|
|
...settings.mqttConfig,
|
|
mqtt: {
|
|
...(settings.mqttConfig?.mqtt ?? {}),
|
|
broker: value
|
|
},
|
|
},
|
|
});
|
|
const setMqttPort = (value) =>
|
|
updateLocalSettings({
|
|
mqttConfig: {
|
|
...settings.mqttConfig,
|
|
mqtt: {
|
|
...(settings.mqttConfig?.mqtt ?? {}),
|
|
port: value
|
|
},
|
|
},
|
|
});
|
|
const setMqttTopic = (value) =>
|
|
updateLocalSettings({
|
|
mqttConfig: {
|
|
...settings.mqttConfig,
|
|
mqtt: {
|
|
...(settings.mqttConfig?.mqtt ?? {}),
|
|
topic: value
|
|
},
|
|
},
|
|
});
|
|
const setMqttClientId = (value) =>
|
|
updateLocalSettings({
|
|
mqttConfig: {
|
|
...settings.mqttConfig,
|
|
mqtt: {
|
|
...(settings.mqttConfig?.mqtt ?? {}),
|
|
client_id: value
|
|
},
|
|
},
|
|
});
|
|
const setMqttUsername = (value) =>
|
|
updateLocalSettings({
|
|
mqttConfig: {
|
|
...settings.mqttConfig,
|
|
mqtt: {
|
|
...(settings.mqttConfig?.mqtt ?? {}),
|
|
username: value
|
|
},
|
|
},
|
|
});
|
|
const setMqttPassword = (value) =>
|
|
updateLocalSettings({
|
|
mqttConfig: {
|
|
...settings.mqttConfig,
|
|
mqtt: {
|
|
...(settings.mqttConfig?.mqtt ?? {}),
|
|
password: value
|
|
},
|
|
},
|
|
});
|
|
|
|
const handleSave = () => {
|
|
saveAllSettings();
|
|
};
|
|
|
|
const handleReset = () => {
|
|
fetchAllSettings();
|
|
};
|
|
|
|
return (
|
|
<Spin spinning={loading} tip="正在加载配置...">
|
|
<div
|
|
style={{
|
|
padding: "12px",
|
|
backgroundColor: "#f5f7fa",
|
|
minHeight: "calc(100vh - 92px)",
|
|
}}
|
|
>
|
|
{/* WebSocket 连接状态提示 */}
|
|
{!isConnected && (
|
|
<Alert
|
|
message="WebSocket 未连接"
|
|
description="当前未连接到设备,无法读取或保存配置。请检查网络连接。"
|
|
type="warning"
|
|
showIcon
|
|
style={{ marginBottom: 12, borderRadius: 8 }}
|
|
/>
|
|
)}
|
|
|
|
<Card
|
|
style={{
|
|
marginBottom: 12,
|
|
background:
|
|
"linear-gradient(135deg, #01152cff 0%, #063b77ff 100%)",
|
|
borderRadius: 12,
|
|
}}
|
|
styles={{ padding: 12 }}
|
|
>
|
|
<Row justify="space-between" align="middle">
|
|
<Col>
|
|
<Space direction="vertical" size={4}>
|
|
<Title
|
|
level={2}
|
|
style={{
|
|
margin: 0,
|
|
color: "white",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 12,
|
|
}}
|
|
>
|
|
<SettingOutlined />
|
|
高级参数配置
|
|
</Title>
|
|
<Text
|
|
style={{
|
|
color: "rgba(255,255,255,0.85)",
|
|
fontSize: 14,
|
|
}}
|
|
>
|
|
针对下位机运行的参数配置,修改后请及时保存(慎用)
|
|
</Text>
|
|
|
|
</Space>
|
|
</Col>
|
|
<Col>
|
|
<Space size="middle">
|
|
<Button
|
|
icon={<SyncOutlined spin={loading} />}
|
|
onClick={handleReset}
|
|
size="large"
|
|
disabled={!isConnected || loading}
|
|
style={{ height: 42, borderRadius: 8 }}
|
|
>
|
|
刷新配置
|
|
</Button>
|
|
<Button
|
|
type="primary"
|
|
icon={<SaveOutlined />}
|
|
onClick={handleSave}
|
|
size="large"
|
|
disabled={!isConnected || loading}
|
|
style={{
|
|
height: 42,
|
|
borderRadius: 8,
|
|
background: "white",
|
|
color: "#667eea",
|
|
borderColor: "white",
|
|
}}
|
|
>
|
|
保存更改
|
|
</Button>
|
|
</Space>
|
|
</Col>
|
|
</Row>
|
|
</Card>
|
|
|
|
<Row gutter={12}>
|
|
<Col xs={12} lg={12}>
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<DatabaseOutlined style={{ color: "#667eea" }} />
|
|
<span>基础设备信息</span>
|
|
</Space>
|
|
}
|
|
|
|
style={{
|
|
marginBottom: 12,
|
|
borderRadius: 12,
|
|
paddingLeft: 24,
|
|
paddingRight: 24,
|
|
}}
|
|
>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
设备编码
|
|
</div>
|
|
<Input
|
|
value={deviceId}
|
|
onChange={(e) => setDeviceId(e.target.value)}
|
|
placeholder="请输入设备编码"
|
|
size="large"
|
|
style={{ borderRadius: 8 }}
|
|
/>
|
|
</Card>
|
|
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<ClockCircleOutlined style={{ color: "#667eea" }} />
|
|
<span>数据帧率 (FPS)</span>
|
|
</Space>
|
|
}
|
|
|
|
style={{
|
|
marginBottom: 12,
|
|
borderRadius: 12,
|
|
paddingLeft: 24,
|
|
paddingRight: 24,
|
|
}}
|
|
>
|
|
<div style={{ marginBottom: 12, fontWeight: 500 }}>
|
|
数据帧率
|
|
</div>
|
|
<Row gutter={12} align="middle">
|
|
<Col span={18}>
|
|
<Slider
|
|
min={1}
|
|
max={30}
|
|
value={fps}
|
|
onChange={setFps}
|
|
marks={{ 1: "1 Hz", 30: "30 Hz" }}
|
|
/>
|
|
</Col>
|
|
<Col span={6}>
|
|
<InputNumber
|
|
min={1}
|
|
max={30}
|
|
value={fps}
|
|
onChange={setFps}
|
|
style={{ width: "100%", borderRadius: 8 }}
|
|
size="large"
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
|
有效范围 1~30 Hz
|
|
</Text>
|
|
</Card>
|
|
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<BellOutlined style={{ color: "#667eea" }} />
|
|
<span>异常监控与报警</span>
|
|
</Space>
|
|
}
|
|
|
|
style={{
|
|
marginBottom: 12,
|
|
borderRadius: 12,
|
|
paddingLeft: 24,
|
|
paddingRight: 24,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
background:
|
|
"linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%)",
|
|
padding: 20,
|
|
borderRadius: 12,
|
|
marginBottom: 12,
|
|
}}
|
|
>
|
|
<Row justify="space-between" align="middle">
|
|
<Col>
|
|
<Text strong>启用离线超时告警</Text>
|
|
<br />
|
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
|
超过此时长未接收到新数据则触发报警
|
|
</Text>
|
|
</Col>
|
|
<Col>
|
|
<Switch
|
|
checked={enableOfflineAlert}
|
|
onChange={setEnableOfflineAlert}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
超时阈值
|
|
</div>
|
|
<InputNumber
|
|
min={10}
|
|
max={300}
|
|
value={offlineThreshold}
|
|
onChange={setOfflineThreshold}
|
|
size="large"
|
|
addonAfter="秒"
|
|
style={{ width: "100%", borderRadius: 8 }}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col xs={12} lg={12}>
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<FilterOutlined style={{ color: "#667eea" }} />
|
|
<span>数据滤波与异常记录</span>
|
|
</Space>
|
|
}
|
|
|
|
style={{
|
|
marginBottom: 12,
|
|
borderRadius: 12,
|
|
paddingLeft: 24,
|
|
paddingRight: 24,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
background:
|
|
"linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%)",
|
|
padding: 20,
|
|
borderRadius: 12,
|
|
marginBottom: 20,
|
|
}}
|
|
>
|
|
<Row justify="space-between" align="middle">
|
|
<Col>
|
|
<Text strong>滤波配置</Text>
|
|
<br />
|
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
|
启用数据滤波算法
|
|
</Text>
|
|
</Col>
|
|
<Col>
|
|
<Switch
|
|
checked={enableFiltering}
|
|
onChange={setEnableFiltering}
|
|
checkedChildren="已启用"
|
|
unCheckedChildren="已停用"
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
<Row gutter={[12, 12]}>
|
|
<Col span={12}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
滤波方法
|
|
</div>
|
|
<Select
|
|
value={filterMethod}
|
|
onChange={setFilterMethod}
|
|
size="large"
|
|
disabled={!enableFiltering}
|
|
style={{ width: "100%", borderRadius: 8 }}
|
|
>
|
|
<Option value="median">中值滤波</Option>
|
|
</Select>
|
|
</Col>
|
|
<Col span={12}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
窗口大小
|
|
</div>
|
|
<InputNumber
|
|
min={3}
|
|
max={21}
|
|
step={2}
|
|
value={windowSize}
|
|
onChange={setWindowSize}
|
|
size="large"
|
|
disabled={!enableFiltering}
|
|
style={{ width: "100%", borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
<Col span={12}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
滤波阈值
|
|
</div>
|
|
<InputNumber
|
|
value={filterThreshold}
|
|
onChange={setFilterThreshold}
|
|
addonAfter="mm"
|
|
size="large"
|
|
disabled={!enableFiltering}
|
|
style={{ width: "100%", borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
<Col span={12}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
波动阈值
|
|
</div>
|
|
<InputNumber
|
|
min={0}
|
|
max={100}
|
|
step={0.1}
|
|
value={flowThreshold}
|
|
onChange={setFlowThreshold}
|
|
addonAfter="mm"
|
|
size="large"
|
|
disabled={!enableFiltering}
|
|
style={{ width: "100%", borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</Card>
|
|
|
|
<Card
|
|
title={
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<Space>
|
|
<CloudUploadOutlined style={{ color: "#667eea" }} />
|
|
<span>数据上报 (MQTT)</span>
|
|
</Space>
|
|
<Switch
|
|
checked={enableMqtt}
|
|
onChange={setEnableMqtt}
|
|
checkedChildren="启用"
|
|
unCheckedChildren="禁用"
|
|
/>
|
|
</div>
|
|
}
|
|
|
|
style={{
|
|
borderRadius: 12,
|
|
paddingLeft: 24,
|
|
paddingRight: 24,
|
|
}}
|
|
>
|
|
<Row gutter={[12, 12]}>
|
|
<Col span={12}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
Broker Address
|
|
</div>
|
|
<Input
|
|
value={brokerAddress}
|
|
onChange={(e) => setBrokerAddress(e.target.value)}
|
|
placeholder="例如: 218.3.126.49"
|
|
size="large"
|
|
disabled={!enableMqtt}
|
|
style={{ borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
<Col span={8}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
Port
|
|
</div>
|
|
<Input
|
|
value={mqttPort}
|
|
onChange={(e) => setMqttPort(e.target.value)}
|
|
placeholder="1883"
|
|
size="large"
|
|
disabled={!enableMqtt}
|
|
style={{ borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
<Col span={12}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
Topic
|
|
</div>
|
|
<Input
|
|
value={mqttTopic}
|
|
onChange={(e) => setMqttTopic(e.target.value)}
|
|
placeholder="例如: wybb/z/mqtt179"
|
|
size="large"
|
|
disabled={!enableMqtt}
|
|
style={{ borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
<Col span={8}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
Client ID
|
|
</div>
|
|
<Input
|
|
value={mqttClientId}
|
|
onChange={(e) => setMqttClientId(e.target.value)}
|
|
placeholder="wybb_z1_123"
|
|
size="large"
|
|
disabled={!enableMqtt}
|
|
style={{ borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
<Col span={8}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
Username
|
|
</div>
|
|
<Input
|
|
value={mqttUsername}
|
|
onChange={(e) => setMqttUsername(e.target.value)}
|
|
placeholder="用户名"
|
|
size="large"
|
|
disabled={!enableMqtt}
|
|
style={{ borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
<Col span={8}>
|
|
<div style={{ marginBottom: 4, fontWeight: 500 }}>
|
|
Password
|
|
</div>
|
|
<Input.Password
|
|
value={mqttPassword}
|
|
onChange={(e) => setMqttPassword(e.target.value)}
|
|
placeholder="密码"
|
|
size="large"
|
|
disabled={!enableMqtt}
|
|
style={{ borderRadius: 8 }}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
</Spin>
|
|
);
|
|
};
|
|
|
|
export default AdvancedSettings;
|
|
|