Реализовал формирование меню через json файлы, реализовал визуализацию меню в виде графа
parent
13399945be
commit
f8eab83bb4
|
|
@ -10,6 +10,8 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chartjs-adapter-date-fns": "^3.0.0",
|
||||||
|
"d3": "^7.9.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"chart.js": "^4.0.0",
|
"chart.js": "^4.0.0",
|
||||||
|
|
|
||||||
31
src/App.jsx
31
src/App.jsx
|
|
@ -5,26 +5,25 @@ import NetworkSpeedChart2 from './Charts/TestCharts2'
|
||||||
import NetworkSpeedChart3 from './Charts/TestCharts3'
|
import NetworkSpeedChart3 from './Charts/TestCharts3'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
/*return (
|
return (
|
||||||
<div style={{ display: "flex", height: "100vh", overflow: "hidden" }}>
|
<div style={{ display: "flex", height: "100vh", overflow: "hidden" }}>
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
<h1>График</h1>
|
|
||||||
<CpuChart />
|
|
||||||
</div>
|
|
||||||
); */
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "20px" }}>
|
|
||||||
<h1>Dashboard</h1>
|
|
||||||
<Dashboard />
|
|
||||||
<div style={{ marginBottom: "40px" }}>
|
|
||||||
<h2>Примеры импорта данных</h2>
|
|
||||||
<NetworkSpeedChart />
|
|
||||||
<NetworkSpeedChart2 />
|
|
||||||
<NetworkSpeedChart3 />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
/*
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "20px" }}>
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
<Dashboard />
|
||||||
|
<div style={{ marginBottom: "40px" }}>
|
||||||
|
<h2>Примеры импорта данных</h2>
|
||||||
|
<NetworkSpeedChart />
|
||||||
|
<NetworkSpeedChart2 />
|
||||||
|
<NetworkSpeedChart3 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import SidebarMenu from "./SidebarMenu";
|
import SidebarMenu from "./SidebarMenu";
|
||||||
import SystemStatusTable from "../Charts/SystemStatusTable";
|
import SystemStatusTable from "../Charts/SystemStatusTable";
|
||||||
import SystemStatusTableSoftware from "../Charts/SystemStatusTableSoftware";
|
import SystemStatusTableSoftware from "../Charts/SystemStatusTableSoftware";
|
||||||
|
import TreeChart from "./TreeChart"; // Подключаем граф
|
||||||
import "../Style/Dashboard.css";
|
import "../Style/Dashboard.css";
|
||||||
import ErrorIndicator from "./ErrorIndicator";
|
import ErrorIndicator from "./ErrorIndicator";
|
||||||
|
import tabContentData from "./tabContent";
|
||||||
const tabContent = {
|
import menuData from "./menuData.json"; // Загружаем меню
|
||||||
"Сервис ВКС": <div><h2>Сервис 1</h2></div>,
|
|
||||||
"Сервис 2": <div><h2>Сервис 2</h2></div>,
|
|
||||||
"Сервис 3": <div><h2>Сервис 3</h2></div>,
|
|
||||||
"Контроль системы": <div><h2>Контроль системы</h2><p>Описание контроля.</p></div>,
|
|
||||||
"Система управления": <div><h2>Система управления</h2><p>Описание системы управления.</p></div>,
|
|
||||||
"Проведение ВКС": <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div>,
|
|
||||||
"Резервное копирование": <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div>,
|
|
||||||
"Ретрансляция информации": <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div>,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [tabs, setTabs] = useState([]); // Открытые вкладки
|
const [tabs, setTabs] = useState([]);
|
||||||
const [activeTab, setActiveTab] = useState("Главная"); // Текущая активная вкладка
|
const [activeTab, setActiveTab] = useState("Главная");
|
||||||
|
const [tabContent, setTabContent] = useState({});
|
||||||
|
const [treeData, setTreeData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTabContent(tabContentData);
|
||||||
|
setTreeData({ title: "Меню", items: menuData }); // Передаём данные в граф
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleOpenTab = (tabName) => {
|
const handleOpenTab = (tabName) => {
|
||||||
if (!tabs.includes(tabName)) {
|
if (!tabs.includes(tabName)) {
|
||||||
|
|
@ -28,7 +27,7 @@ const Dashboard = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseTab = (tabName) => {
|
const handleCloseTab = (tabName) => {
|
||||||
const newTabs = tabs.filter(tab => tab !== tabName);
|
const newTabs = tabs.filter((tab) => tab !== tabName);
|
||||||
setTabs(newTabs);
|
setTabs(newTabs);
|
||||||
if (activeTab === tabName) {
|
if (activeTab === tabName) {
|
||||||
setActiveTab(newTabs.length > 0 ? newTabs[newTabs.length - 1] : "Главная");
|
setActiveTab(newTabs.length > 0 ? newTabs[newTabs.length - 1] : "Главная");
|
||||||
|
|
@ -40,7 +39,6 @@ const Dashboard = () => {
|
||||||
<SidebarMenu onOpenTab={handleOpenTab} />
|
<SidebarMenu onOpenTab={handleOpenTab} />
|
||||||
|
|
||||||
<div className="main-content">
|
<div className="main-content">
|
||||||
{/* Вкладки */}
|
|
||||||
<div className="tabs">
|
<div className="tabs">
|
||||||
<div
|
<div
|
||||||
className={`tab ${activeTab === "Главная" ? "active" : ""}`}
|
className={`tab ${activeTab === "Главная" ? "active" : ""}`}
|
||||||
|
|
@ -48,7 +46,13 @@ const Dashboard = () => {
|
||||||
>
|
>
|
||||||
Главная
|
Главная
|
||||||
</div>
|
</div>
|
||||||
{tabs.map(tab => (
|
<div
|
||||||
|
className={`tab ${activeTab === "Визуализация" ? "active" : ""}`}
|
||||||
|
onClick={() => setActiveTab("Визуализация")}
|
||||||
|
>
|
||||||
|
Визуализация
|
||||||
|
</div>
|
||||||
|
{tabs.map((tab) => (
|
||||||
<div
|
<div
|
||||||
key={tab}
|
key={tab}
|
||||||
className={`tab ${activeTab === tab ? "active" : ""}`}
|
className={`tab ${activeTab === tab ? "active" : ""}`}
|
||||||
|
|
@ -57,7 +61,10 @@ const Dashboard = () => {
|
||||||
{tab}
|
{tab}
|
||||||
<button
|
<button
|
||||||
className="close-tab"
|
className="close-tab"
|
||||||
onClick={(e) => { e.stopPropagation(); handleCloseTab(tab); }}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleCloseTab(tab);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -65,7 +72,6 @@ const Dashboard = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Контент */}
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{activeTab === "Главная" ? (
|
{activeTab === "Главная" ? (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -74,8 +80,10 @@ const Dashboard = () => {
|
||||||
<SystemStatusTable />
|
<SystemStatusTable />
|
||||||
<SystemStatusTableSoftware />
|
<SystemStatusTableSoftware />
|
||||||
</div>
|
</div>
|
||||||
|
) : activeTab === "Визуализация" ? (
|
||||||
|
<TreeChart data={treeData} onNodeClick={(node) => handleOpenTab(node.title)} />
|
||||||
) : (
|
) : (
|
||||||
tabContent[activeTab] || <p>Нет контента</p>
|
<div dangerouslySetInnerHTML={{ __html: tabContent[activeTab] || "<p>Нет данных</p>" }} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,8 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import "../Style/SidebarMenu.css";
|
import "../Style/SidebarMenu.css";
|
||||||
|
import menuData from "./menuData.json";
|
||||||
|
|
||||||
|
|
||||||
const menuItems = [
|
|
||||||
{
|
|
||||||
title: "Выбор сервиса",
|
|
||||||
items: ["Сервис ВКС", "Сервис 2", "Сервис 3"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Функциональные задачи",
|
|
||||||
items: ["Контроль системы", "Система управления", "Проведение ВКС", "Резервное копирование", "Ретрансляция информации"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Программное обеспечение",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "ПО 1",
|
|
||||||
items: ["компонент ПО1", "компонент ПО2"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "ПО 2",
|
|
||||||
items: ["компонент ПО3"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Аппаратное обеспечение",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Оборудование 1",
|
|
||||||
items: ["компонент Оборудование 1"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Оборудование 2",
|
|
||||||
items: ["компонент Оборудование 2"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Рекурсивный компонент для отображения меню
|
// Рекурсивный компонент для отображения меню
|
||||||
const MenuItem = ({ item, onSelectItem }) => {
|
const MenuItem = ({ item, onSelectItem }) => {
|
||||||
|
|
@ -63,10 +29,11 @@ const MenuItem = ({ item, onSelectItem }) => {
|
||||||
{item.items.map((child, index) => (
|
{item.items.map((child, index) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={index}
|
key={index}
|
||||||
item={typeof child === "string" ? { title: child } : child}
|
item={typeof child === "string" ? { title: child, id: child } : child}
|
||||||
onSelectItem={onSelectItem}
|
onSelectItem={onSelectItem}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -76,14 +43,14 @@ const MenuItem = ({ item, onSelectItem }) => {
|
||||||
// Основной компонент SidebarMenu
|
// Основной компонент SidebarMenu
|
||||||
function SidebarMenu({ onOpenTab }) {
|
function SidebarMenu({ onOpenTab }) {
|
||||||
const handleSelectItem = (item) => {
|
const handleSelectItem = (item) => {
|
||||||
onOpenTab(item.title); // Передаем название вкладки в родительский компонент
|
onOpenTab(item.id, item.title); // Передаем и ID, и название
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebar">
|
<div className="sidebar">
|
||||||
<h2 className="sidebar-title">Уровень доверия:</h2>
|
<h2 className="sidebar-title">Уровень доверия:</h2>
|
||||||
<h2 className="sidebar-title">Меню</h2>
|
<h2 className="sidebar-title">Меню</h2>
|
||||||
{menuItems.map((section, index) => (
|
{menuData.map((section, index) => ( // Используем menuData вместо menuItems
|
||||||
<MenuItem key={index} item={section} onSelectItem={handleSelectItem} />
|
<MenuItem key={index} item={section} onSelectItem={handleSelectItem} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import React, { useRef, useEffect } from "react";
|
||||||
|
import * as d3 from "d3";
|
||||||
|
|
||||||
|
const TreeChart = ({ data, onNodeClick }) => {
|
||||||
|
const chartRef = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
// Очищаем старый граф перед отрисовкой
|
||||||
|
d3.select(chartRef.current).selectAll("*").remove();
|
||||||
|
|
||||||
|
const width = 928;
|
||||||
|
const height = 600;
|
||||||
|
|
||||||
|
const root = d3.hierarchy(data, (d) => d.items);
|
||||||
|
const links = root.links();
|
||||||
|
const nodes = root.descendants();
|
||||||
|
|
||||||
|
const simulation = d3
|
||||||
|
.forceSimulation(nodes)
|
||||||
|
.force("link", d3.forceLink(links).id((d) => d.data.title).distance(80).strength(1)) // Увеличил дистанцию
|
||||||
|
.force("charge", d3.forceManyBody().strength(-500)) // Увеличил отталкивание узлов
|
||||||
|
.force("x", d3.forceX())
|
||||||
|
.force("y", d3.forceY());
|
||||||
|
|
||||||
|
const svg = d3
|
||||||
|
.select(chartRef.current)
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
.attr("viewBox", [-width / 2, -height / 2, width, height])
|
||||||
|
.attr("style", "max-width: 100%; height: auto;");
|
||||||
|
|
||||||
|
const link = svg
|
||||||
|
.append("g")
|
||||||
|
.attr("stroke", "#999")
|
||||||
|
.attr("stroke-opacity", 0.6)
|
||||||
|
.selectAll("line")
|
||||||
|
.data(links)
|
||||||
|
.join("line");
|
||||||
|
|
||||||
|
const node = svg
|
||||||
|
.append("g")
|
||||||
|
.attr("stroke", "#000")
|
||||||
|
.attr("stroke-width", 1.5)
|
||||||
|
.selectAll("circle")
|
||||||
|
.data(nodes)
|
||||||
|
.join("circle")
|
||||||
|
.attr("fill", (d) => (d.children ? "#555" : "#000"))
|
||||||
|
.attr("stroke", "#fff")
|
||||||
|
.attr("r", 7) // Немного увеличил размер узлов для удобства клика
|
||||||
|
.call(drag(simulation));
|
||||||
|
|
||||||
|
// Добавляем текстовые подписи
|
||||||
|
const text = svg
|
||||||
|
.append("g")
|
||||||
|
.attr("fill", "#000")
|
||||||
|
.attr("font-family", "Arial")
|
||||||
|
.attr("font-size", 12)
|
||||||
|
.attr("pointer-events", "none") // Отключаем обработку событий текста
|
||||||
|
.selectAll("text")
|
||||||
|
.data(nodes)
|
||||||
|
.join("text")
|
||||||
|
.text((d) => d.data.title)
|
||||||
|
.attr("dx", 12) // Отодвигаем текст дальше от узла
|
||||||
|
.attr("dy", 4) // Немного поднимаем текст
|
||||||
|
|
||||||
|
node.append("title").text((d) => d.data.title);
|
||||||
|
|
||||||
|
node.on("click", (event, d) => {
|
||||||
|
if (onNodeClick) {
|
||||||
|
onNodeClick(d.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
simulation.on("tick", () => {
|
||||||
|
link
|
||||||
|
.attr("x1", (d) => d.source.x)
|
||||||
|
.attr("y1", (d) => d.source.y)
|
||||||
|
.attr("x2", (d) => d.target.x)
|
||||||
|
.attr("y2", (d) => d.target.y);
|
||||||
|
|
||||||
|
node
|
||||||
|
.attr("cx", (d) => d.x)
|
||||||
|
.attr("cy", (d) => d.y);
|
||||||
|
|
||||||
|
text
|
||||||
|
.attr("x", (d) => d.x + 12) // Смещаем текст правее узла
|
||||||
|
.attr("y", (d) => d.y + 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
simulation.stop();
|
||||||
|
};
|
||||||
|
}, [data, onNodeClick]);
|
||||||
|
|
||||||
|
const drag = (simulation) => {
|
||||||
|
function dragstarted(event, d) {
|
||||||
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||||
|
d.fx = d.x;
|
||||||
|
d.fy = d.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragged(event, d) {
|
||||||
|
d.fx = event.x;
|
||||||
|
d.fy = event.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragended(event, d) {
|
||||||
|
if (!event.active) simulation.alphaTarget(0);
|
||||||
|
d.fx = null;
|
||||||
|
d.fy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <svg ref={chartRef}></svg>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TreeChart;
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Выбор сервиса",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "service1",
|
||||||
|
"title": "Сервис ВКС"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service2",
|
||||||
|
"title": "Сервис 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service3",
|
||||||
|
"title": "Сервис 3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Функциональные задачи",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "system_control",
|
||||||
|
"title": "Контроль системы"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "system_management",
|
||||||
|
"title": "Система управления"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "conference",
|
||||||
|
"title": "Проведение ВКС"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "backup",
|
||||||
|
"title": "Резервное копирование"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "relay_info",
|
||||||
|
"title": "Ретрансляция информации"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Аппаратное ПО",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "hardware_software_1",
|
||||||
|
"title": "ПО1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hardware_software_2",
|
||||||
|
"title": "ПО2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hardware_software_3",
|
||||||
|
"title": "ПО3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hardware_software_4",
|
||||||
|
"title": "ПО4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hardware_software_5",
|
||||||
|
"title": "ПО5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const tabContent = {
|
||||||
|
service1: <div><h2>Сервис ВКС</h2></div>,
|
||||||
|
service2: <div><h2>Сервис 2</h2></div>,
|
||||||
|
service3: <div><h2>Сервис 3</h2></div>,
|
||||||
|
system_control: <div><h2>Контроль системы</h2><p>Описание контроля.</p></div>,
|
||||||
|
system_management: <div><h2>Система управления</h2><p>Описание системы управления.</p></div>,
|
||||||
|
conference: <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div>,
|
||||||
|
backup: <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div>,
|
||||||
|
relay_info: <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tabContent;
|
||||||
Loading…
Reference in New Issue