react 版初始化
This commit is contained in:
parent
f53ba1276d
commit
6c215f987b
2
ibms_react/.env
Normal file
2
ibms_react/.env
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
REACT_APP_FORGE_TOKEN= "eyJhbGciOiJSUzI1NiIsImtpZCI6IlU3c0dGRldUTzlBekNhSzBqZURRM2dQZXBURVdWN2VhIn0.eyJzY29wZSI6WyJ2aWV3YWJsZXM6cmVhZCJdLCJjbGllbnRfaWQiOiJUQTNocXNGZnpRYk5PVVhLcGxkS1VLU2V3NFNKMjF3NSIsImF1ZCI6Imh0dHBzOi8vYXV0b2Rlc2suY29tL2F1ZC9hand0ZXhwNjAiLCJqdGkiOiJxd3huTzBMQ05OYllFYjBQbWY1MTltNXZoTTBVUW5uc3NBZ1JsaG1MTk16aEw4ZjhPeDBVZVlpVHZaVnVyTEc2IiwiZXhwIjoxNjc0MDUwMjUzfQ.Od82JlAwal50fTw3vD-l7kaKYGKiYjUuDRYg4VvPVVgNQ3CNklPSHee8JnAYSCeNV_XWxkYjv1uh8M14gEB5YCmT8R6KMixXWF_dP_EkOTP1-E9PvCKAZIGx1MFE8XG0I9FbPX6Kd3LHdtKViBTS1V88VZckagRhdWJK4kVGtkXGWJRujIvQLq7VrHeQhOIB8UiEqVA91sTtW-g7-5Xz2U11_UZ6WMZMuENNOJHRN7mIyv2WWj_axHQhlTYtBZXuM_3O4bzNb30i47QPDkuotEEG-J-4OF53rzDYxOIysnutkGgnnz4AwR18xoP0xH8dIFAM1XyuAAdHI-NQh2DTDg"
|
||||||
|
REACT_APP_FORGE_URN="dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6Zm9yZ2Vfc2FtcGxlX3RhM2hxc2ZmenFibm91eGtwbGRrdWtzZXc0c2oyMXc1LyVFMyU4MCU5MCVFNSU4RiVCMCVFNSU4QyU5NyVFNCVCOCVBRCVFOCU4RiVCMSVFNSVBNCVBNyVFNiVBOCU5MyVFMyU4MCU5MUFSQyVFOSU5QiU5OSVFNiVBOCVBMSVFNSVCQyU4RitNRVAlRTYlOEIlODYlRTclQjMlQkIlRTclQjUlQjEubndk"
|
13
ibms_react/craco.config.js
Normal file
13
ibms_react/craco.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const path = require("path");
|
||||||
|
module.exports = {
|
||||||
|
webpack: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "src/"),
|
||||||
|
"@COM": "@/components",
|
||||||
|
"@ASSET": "@/assets",
|
||||||
|
"@UTIL": "@/utils",
|
||||||
|
"@STORE": "@/stores",
|
||||||
|
"@CON": "@/constants",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
13410
ibms_react/package-lock.json
generated
13410
ibms_react/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,21 +1,38 @@
|
|||||||
{
|
{
|
||||||
"name": "ibms_react",
|
"name": "react_bims_forge",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"homepage": "./",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
|
"@popperjs/core": "^2.11.6",
|
||||||
|
"@reduxjs/toolkit": "^1.9.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"axios": "^1.2.3",
|
||||||
|
"bootstrap": "^5.2.3",
|
||||||
|
"chart.js": "^4.2.0",
|
||||||
|
"http-proxy-middleware": "^2.0.6",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-bootstrap": "^2.7.0",
|
||||||
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-redux": "^8.0.5",
|
||||||
|
"react-router-dom": "^6.7.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
|
"requirejs": "^2.3.6",
|
||||||
|
"three": "^0.149.0",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "craco start",
|
||||||
"build": "react-scripts build",
|
"build": "craco build",
|
||||||
"test": "react-scripts test",
|
"test": "craco test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "craco eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@ -35,8 +52,9 @@
|
|||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"proxy": "http://localhost:3604",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"@craco/craco": "^7.0.0",
|
||||||
"jest-editor-support": "^31.0.1"
|
"sass": "^1.57.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2145
ibms_react/public/Require.js
Normal file
2145
ibms_react/public/Require.js
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 15 KiB |
@ -1,43 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<!-- @noSnoop -->
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta name="description" content="Web site created using create-react-app" />
|
||||||
name="description"
|
|
||||||
content="Web site created using create-react-app"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<!--
|
<title>Mitsubishi</title>
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
<script src="%PUBLIC_URL%/Require.js"></script>
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
<!-- <script type="text/javascript" src="http://220.132.206.5:8080/requirejs/config.js"></script>
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
<script
|
||||||
|
type="text/javascript"
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
src="http://220.132.206.5:8080/module/js/com/tridium/js/ext/require/require.min.js?"
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
></script> -->
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
<script>
|
||||||
-->
|
//重新轉址 for Niagara4
|
||||||
<title>React App</title>
|
var temp_cuurent_Url_pathname = window.location.pathname.split("/").slice(0, 3);
|
||||||
|
var redirectionUrl =
|
||||||
|
window.location.origin +
|
||||||
|
"/" +
|
||||||
|
temp_cuurent_Url_pathname[temp_cuurent_Url_pathname.length - 1].replace(":%5E", "/") +
|
||||||
|
"/" +
|
||||||
|
window.location.pathname.split("/").slice(3).join("/");
|
||||||
|
//判斷url是否包含"ord",如果有重新轉址
|
||||||
|
if (temp_cuurent_Url_pathname.findIndex((x) => x == "ord") > -1) {
|
||||||
|
window.location.replace(redirectionUrl.substr(0, redirectionUrl.length - 1));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
<div id="model_root"></div>
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
18
ibms_react/public/require.config.js
Normal file
18
ibms_react/public/require.config.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
require.config({
|
||||||
|
baseUrl: 'js',
|
||||||
|
|
||||||
|
"paths": {
|
||||||
|
"lib": "../lib",
|
||||||
|
"jquery-ui": "//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui",
|
||||||
|
"jquery-ui-min": "//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min",
|
||||||
|
"d3": "https://d3js.org/d3.v4.min",
|
||||||
|
"datatables.net": "https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min",
|
||||||
|
"datatables.net.b4": "https://cdn.datatables.net/1.10.21/js/dataTables.bootstrap4.min"
|
||||||
|
},
|
||||||
|
urlArgs: "ts=" + new Date().getTime(),
|
||||||
|
shim: {
|
||||||
|
"d3": {
|
||||||
|
exports: "d3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
10
ibms_react/setupProxy.js
Normal file
10
ibms_react/setupProxy.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||||
|
// module.exports = function (app) {
|
||||||
|
// app.use(
|
||||||
|
// "^/api",
|
||||||
|
// createProxyMiddleware({
|
||||||
|
// target: "http://localhost:3604",
|
||||||
|
// changeOrigin: true,
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
// };
|
@ -1,23 +1,116 @@
|
|||||||
import logo from './logo.svg';
|
import { useMemo, useEffect, useState } from "react";
|
||||||
import './App.css';
|
import { Outlet, NavLink } from "react-router-dom";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { Container, Nav, Navbar, NavDropdown } from "react-bootstrap";
|
||||||
|
import { fetchUserInfo, fetchUserAuthPages, changeBuilding, fetchSysMainSub } from "@STORE";
|
||||||
|
import Logo from "@ASSET/img/logo.png";
|
||||||
|
import { userAllAuthPages } from "@CON";
|
||||||
|
import AlarmOffcanvas from "@COM/app/AlarmOffcanvas";
|
||||||
|
import SystemOffcanvas from "@COM/app/SystemOffcanvas";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
// user
|
||||||
|
const { userAuthPages, userInfo } = useSelector((state) => state.user);
|
||||||
|
const TopMenu = useMemo(() => {
|
||||||
|
let menu = [
|
||||||
|
userAllAuthPages.find((auth) => auth.name === "首頁"),
|
||||||
|
...userAuthPages?.map((page) => {
|
||||||
|
if (userAllAuthPages.some((auth) => auth.name === page.subName)) {
|
||||||
|
return { ...page, ...userAllAuthPages.find((auth) => auth.name === page.subName) };
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
return menu;
|
||||||
|
}, [userAuthPages]);
|
||||||
|
|
||||||
|
// building
|
||||||
|
const { buildingList, selectedBuiFullName, selectedBuiTag } = useSelector(
|
||||||
|
(state) => state.buildingInfo,
|
||||||
|
);
|
||||||
|
const updateBuilding = (building_tag, urn_3D) => {
|
||||||
|
dispatch(changeBuilding({ building_tag, urn_3D }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// system
|
||||||
|
const { mainSub } = useSelector((state) => state.system);
|
||||||
|
const [systemMenuShow, setSystemMenuShow] = useState(false);
|
||||||
|
const systemOffCanvasHandler = (e, name) => {
|
||||||
|
if (name === "系統監控") {
|
||||||
|
e.preventDefault();
|
||||||
|
setSystemMenuShow(!systemMenuShow);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
// 取得使用者資料跟建築物資料(地區及棟別)
|
||||||
|
dispatch(fetchUserAuthPages());
|
||||||
|
if (selectedBuiTag) {
|
||||||
|
dispatch(fetchSysMainSub(selectedBuiTag));
|
||||||
|
}
|
||||||
|
}, [selectedBuiTag]);
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
{/* 主選單 */}
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<Navbar bg="dark" variant="dark">
|
||||||
<p>
|
<Container fluid>
|
||||||
Edit <code>src/App.js</code> and save to reload.
|
<Navbar.Brand href="#home">
|
||||||
</p>
|
<img alt="" src={Logo} width="150" className="d-inline-block align-top img-fluid" />
|
||||||
<a
|
</Navbar.Brand>
|
||||||
className="App-link"
|
<Nav className="w-100 d-flex justify-content-between">
|
||||||
href="https://reactjs.org"
|
<NavDropdown
|
||||||
target="_blank"
|
title={selectedBuiFullName ? selectedBuiFullName : "選擇大樓"}
|
||||||
rel="noopener noreferrer"
|
className="d-flex align-items-center fs-5"
|
||||||
>
|
>
|
||||||
Learn React
|
<NavDropdown.Item disabled href="#">
|
||||||
</a>
|
選擇大樓
|
||||||
</header>
|
</NavDropdown.Item>
|
||||||
|
{buildingList.map(({ full_name, building_tag, urn_3D }) => (
|
||||||
|
<NavDropdown.Item
|
||||||
|
data-urn={urn_3D}
|
||||||
|
key={building_tag}
|
||||||
|
onClick={() => {
|
||||||
|
updateBuilding(building_tag, urn_3D);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{full_name}
|
||||||
|
</NavDropdown.Item>
|
||||||
|
))}
|
||||||
|
</NavDropdown>
|
||||||
|
<div className="d-flex">
|
||||||
|
{TopMenu.map(({ name, path, icon }) => (
|
||||||
|
<NavLink
|
||||||
|
key={name}
|
||||||
|
to={path ?? "none"}
|
||||||
|
className="nav-link mx-2 d-flex flex-column align-items-center justify-content-between"
|
||||||
|
onClick={(e) => {
|
||||||
|
systemOffCanvasHandler(e, name);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
<span className="mt-1">{name}</span>
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<AlarmOffcanvas />
|
||||||
|
<div className="mx-3 text-center">
|
||||||
|
<label className="mb-0 fs-6 position-relative">
|
||||||
|
Diamond Controls<span className="position-absolute">®</span>
|
||||||
|
</label>
|
||||||
|
<br />
|
||||||
|
<label className="mb-0 fs-6">智慧大樓管理平台</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Nav>
|
||||||
|
</Container>
|
||||||
|
</Navbar>
|
||||||
|
<SystemOffcanvas
|
||||||
|
list={mainSub}
|
||||||
|
systemMenuShow={systemMenuShow}
|
||||||
|
setSystemMenuShow={setSystemMenuShow}
|
||||||
|
/>
|
||||||
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
BIN
ibms_react/src/assets/img/clouds.png
Normal file
BIN
ibms_react/src/assets/img/clouds.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 KiB |
BIN
ibms_react/src/assets/img/defdev.png
Normal file
BIN
ibms_react/src/assets/img/defdev.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
17
ibms_react/src/assets/img/hotspot.svg
Normal file
17
ibms_react/src/assets/img/hotspot.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<svg width="164" height="164" viewBox="0 0 164 164" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d)">
|
||||||
|
<circle cx="81.7363" cy="81.8212" r="70.3389" fill="#535353"/>
|
||||||
|
<circle cx="81.7363" cy="81.8212" r="62.3112" stroke="white" stroke-width="16.0554"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d" x="0.174763" y="0.259602" width="163.123" height="163.123" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||||
|
<feOffset/>
|
||||||
|
<feGaussianBlur stdDeviation="5.61135"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 916 B |
BIN
ibms_react/src/assets/img/logo.png
Normal file
BIN
ibms_react/src/assets/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
ibms_react/src/assets/video/cc.mp4
Normal file
BIN
ibms_react/src/assets/video/cc.mp4
Normal file
Binary file not shown.
BIN
ibms_react/src/assets/video/cc.webm
Normal file
BIN
ibms_react/src/assets/video/cc.webm
Normal file
Binary file not shown.
7
ibms_react/src/components/Loading.js
Normal file
7
ibms_react/src/components/Loading.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function Loading() {
|
||||||
|
return <div>Loading</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Loading;
|
46
ibms_react/src/components/app/AlarmOffcanvas.js
Normal file
46
ibms_react/src/components/app/AlarmOffcanvas.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { Button, Offcanvas } from "react-bootstrap";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faCommentDots, faCommentSlash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { fetchBuiAlarmStateByBaja } from "@STORE";
|
||||||
|
function AlarmOffcanvas() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { selectedBuiArea, selectedBuiTag } = useSelector((state) => state.buildingInfo);
|
||||||
|
// alarm
|
||||||
|
const [showAlarm, setShowAlarm] = useState(false);
|
||||||
|
const getAlarmList = () => {
|
||||||
|
dispatch(fetchBuiAlarmStateByBaja(selectedBuiArea, selectedBuiTag));
|
||||||
|
setShowAlarm(!showAlarm);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
className="d-flex flex-column align-items-center justify-content-center mx-3 text-decoration-none text-white"
|
||||||
|
onClick={getAlarmList}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={showAlarm ? faCommentSlash : faCommentDots} size="2x" />
|
||||||
|
<span className="mt-1">{showAlarm ? "隱藏警告" : "顯示警告"}</span>
|
||||||
|
</Button>
|
||||||
|
<Offcanvas
|
||||||
|
className="opacity-75"
|
||||||
|
show={showAlarm}
|
||||||
|
onHide={() => {
|
||||||
|
setShowAlarm(false);
|
||||||
|
}}
|
||||||
|
placement="end"
|
||||||
|
>
|
||||||
|
<Offcanvas.Header closeButton>
|
||||||
|
<Offcanvas.Title>Offcanvas</Offcanvas.Title>
|
||||||
|
</Offcanvas.Header>
|
||||||
|
<Offcanvas.Body>
|
||||||
|
Some text as placeholder. In real life you can have the elements you have chosen. Like,
|
||||||
|
text, images, lists, etc.
|
||||||
|
</Offcanvas.Body>
|
||||||
|
</Offcanvas>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlarmOffcanvas;
|
38
ibms_react/src/components/app/SystemOffcanvas.js
Normal file
38
ibms_react/src/components/app/SystemOffcanvas.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
import { Offcanvas, ListGroup } from "react-bootstrap";
|
||||||
|
function SystemOffcanvas({ list, systemMenuShow, setSystemMenuShow }) {
|
||||||
|
return (
|
||||||
|
<Offcanvas
|
||||||
|
show={systemMenuShow}
|
||||||
|
onHide={() => {
|
||||||
|
setSystemMenuShow(false);
|
||||||
|
}}
|
||||||
|
placement="start"
|
||||||
|
>
|
||||||
|
<Offcanvas.Header
|
||||||
|
closeButton
|
||||||
|
closeVariant="white"
|
||||||
|
className="align-self-end"
|
||||||
|
></Offcanvas.Header>
|
||||||
|
<Offcanvas.Body className="px-0">
|
||||||
|
<ListGroup>
|
||||||
|
{list.map(({ full_name, main_system_tag, sub_system_tag }) => (
|
||||||
|
<NavLink
|
||||||
|
className="text-decoration-none py-2 list-group-item list-group-item-action"
|
||||||
|
to={`/monitor/${main_system_tag}_${sub_system_tag}`}
|
||||||
|
key={`${main_system_tag}_${sub_system_tag}`}
|
||||||
|
onClick={(e) => setSystemMenuShow(false)}
|
||||||
|
>
|
||||||
|
<ListGroup.Item action className="border-0 bg-dark text-center">
|
||||||
|
{full_name}
|
||||||
|
</ListGroup.Item>
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</ListGroup>
|
||||||
|
</Offcanvas.Body>
|
||||||
|
</Offcanvas>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemOffcanvas;
|
91
ibms_react/src/components/forges/ForgeModel.js
Normal file
91
ibms_react/src/components/forges/ForgeModel.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { fetchForge, shutdownForge } from "@STORE/forgeSlice";
|
||||||
|
import hotspot from "@ASSET/img/hotspot.svg";
|
||||||
|
|
||||||
|
function ForgeModel({ forgeHeight, sprites = [] }) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const viewerEl = useRef(null);
|
||||||
|
const {
|
||||||
|
buildingInfo: { urn_3D },
|
||||||
|
forgeViewer: { viewer, dataVizExtn, DataVizCore },
|
||||||
|
} = useSelector((store) => store);
|
||||||
|
|
||||||
|
// 載入 Forge Viewer
|
||||||
|
useEffect(() => {
|
||||||
|
if (urn_3D) {
|
||||||
|
dispatch(
|
||||||
|
fetchForge({
|
||||||
|
viewerEl: viewerEl.current,
|
||||||
|
urn: urn_3D,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
dispatch(shutdownForge());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [urn_3D]);
|
||||||
|
|
||||||
|
// 熱點
|
||||||
|
const spriteUpdate = (e, targetDbIds= [], style) => {
|
||||||
|
e.hasStopped = true;
|
||||||
|
dataVizExtn.invalidateViewables(targetDbIds, (viewable) => {
|
||||||
|
console.log(style)
|
||||||
|
return style;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 熱點點擊事件
|
||||||
|
const onSpriteClicked = (event) => {
|
||||||
|
const targetDbIds = [event.dbId];
|
||||||
|
spriteUpdate(event, targetDbIds, { scale: 1.3, color: { r: 1, g: 0, b: 0 } });
|
||||||
|
console.log(`Sprite clicked:${targetDbIds}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 熱點取消點擊
|
||||||
|
const onSpriteClickedOut = (event) => {
|
||||||
|
const targetDbIds = [event.dbId];
|
||||||
|
spriteUpdate(event, targetDbIds, { scale: 1, color: { r: 1.0, g: 1.0, b: 1.0 } });
|
||||||
|
console.log(`Sprite clickedout:${targetDbIds}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 進入系統監控頁面,出現熱點
|
||||||
|
useEffect(() => {
|
||||||
|
if (sprites.length !== 0 && viewer && dataVizExtn) {
|
||||||
|
// const filterSprites = sprites.map;
|
||||||
|
dataVizExtn.removeAllViewables();
|
||||||
|
sprites.forEach(async ({ device_coordinate_3d, forge_dbid }, index) => {
|
||||||
|
const viewableType = DataVizCore.ViewableType.SPRITE;
|
||||||
|
const spriteColor = new THREE.Color(0xffffff);
|
||||||
|
const spriteIconUrl = hotspot;
|
||||||
|
const style = new DataVizCore.ViewableStyle(viewableType, spriteColor, spriteIconUrl);
|
||||||
|
const viewableData = new DataVizCore.ViewableData();
|
||||||
|
viewableData.spriteSize = 24;
|
||||||
|
if (forge_dbid && device_coordinate_3d) {
|
||||||
|
const dbId = 10 + index;
|
||||||
|
const position = JSON.parse(device_coordinate_3d);
|
||||||
|
const viewable = new DataVizCore.SpriteViewable(position, style, dbId);
|
||||||
|
viewable.myContextData = {
|
||||||
|
manufacturer: "MJM Co. Ltd.",
|
||||||
|
};
|
||||||
|
viewableData.addViewable(viewable);
|
||||||
|
};
|
||||||
|
await viewableData.finish();
|
||||||
|
dataVizExtn.addViewables(viewableData);
|
||||||
|
});
|
||||||
|
viewer.addEventListener(DataVizCore.MOUSE_CLICK, onSpriteClicked);
|
||||||
|
viewer.addEventListener(DataVizCore.MOUSE_CLICK_OUT, onSpriteClickedOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (sprites.length !== 0 && viewer && dataVizExtn) {
|
||||||
|
viewer.removeEventListener(DataVizCore.MOUSE_CLICK, onSpriteClicked);
|
||||||
|
viewer.removeEventListener(DataVizCore.MOUSE_CLICK_OUT, onSpriteClickedOut);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [sprites, dataVizExtn, viewer]);
|
||||||
|
|
||||||
|
return <div ref={viewerEl} id="forgeViewer" style={{ height: forgeHeight }}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgeModel;
|
63
ibms_react/src/components/general/BarChart.js
Normal file
63
ibms_react/src/components/general/BarChart.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
BarElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
} from "chart.js";
|
||||||
|
import { Bar } from "react-chartjs-2";
|
||||||
|
|
||||||
|
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||||
|
|
||||||
|
// 傳入的 barData: {current: ["今日/本週"], last: ["昨日/上週"]}
|
||||||
|
function BarChart({ title, period, barData }) {
|
||||||
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: "top",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
align: "start",
|
||||||
|
position: "top",
|
||||||
|
text: title,
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
suggestedMin: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const hourLabels = Array.from({ length: 24 }, (v, i) => i);
|
||||||
|
const dayLabels = ["週一", "週二", "週三", "週四", "週五", "週六", "週日"];
|
||||||
|
const labels = period === "days" ? dayLabels : hourLabels;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: period === "days" ? "本週用電量" : "今日用電量",
|
||||||
|
data: barData.current,
|
||||||
|
backgroundColor: "#10b7b9",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: period === "days" ? "上週用電量" : "昨日用電量",
|
||||||
|
data: barData.last,
|
||||||
|
backgroundColor: "#7dbffa",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Bar options={options} data={data} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BarChart;
|
34
ibms_react/src/components/general/PieChart.js
Normal file
34
ibms_react/src/components/general/PieChart.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
|
||||||
|
import { Pie } from "react-chartjs-2";
|
||||||
|
|
||||||
|
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||||
|
function PieChart({ labels }) {
|
||||||
|
const options = {
|
||||||
|
responsive: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: "top",
|
||||||
|
align: "center",
|
||||||
|
labels: {
|
||||||
|
boxWidth: 30,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "數量",
|
||||||
|
data: [12, 19],
|
||||||
|
backgroundColor: ["#fd3995", "#51adf6"],
|
||||||
|
borderColor: ["#fff", "#fff"],
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return <Pie data={data} options={options} className="mb-4" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PieChart;
|
22
ibms_react/src/components/home/ChartModal.js
Normal file
22
ibms_react/src/components/home/ChartModal.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Modal from "react-bootstrap/Modal";
|
||||||
|
|
||||||
|
function ChartModal({ modalShow, setModalShow, chart }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
size="xl"
|
||||||
|
show={modalShow}
|
||||||
|
onHide={() => {
|
||||||
|
setModalShow(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title className="text-dark">{chart.title}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body style={{ height: "50vh" }}>{chart.component}</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChartModal;
|
59
ibms_react/src/constants/baseSetting.js
Normal file
59
ibms_react/src/constants/baseSetting.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
const n4Sup = "Mitsubishi_JACE8000";
|
||||||
|
// dev
|
||||||
|
const baseApiUrl = "http://localhost:3604";
|
||||||
|
const baseImgUrl = "https://localhost:44376/upload/device_icon";
|
||||||
|
//production
|
||||||
|
// const baseApiUrl = "http://220.132.206.5";
|
||||||
|
// const baseImgUrl = "http://220.132.206.5:8848/upload/device_icon/";
|
||||||
|
// 登入
|
||||||
|
const loginBaseUrl = `${baseApiUrl}/api/Login/`;
|
||||||
|
const userAuthBaseUrl = `${baseApiUrl}/api/GetUsrFroList`;
|
||||||
|
const userInfoBaseUrl = `${baseApiUrl}/api/getUserFull`;
|
||||||
|
|
||||||
|
const forgeTokenBaseUrl = `${baseApiUrl}/api/forge/oauth/token`;
|
||||||
|
// post
|
||||||
|
const mainSubBaseUrl = `${baseApiUrl}/api/Device/GetMainSub`;
|
||||||
|
// 設備
|
||||||
|
const deviceBuiListBaseUrl = `${baseApiUrl}/api/Device/GetBuild`;
|
||||||
|
const deviceBuiMenuBaseUrl = `${baseApiUrl}/api/Device/GetBuildMenu`;
|
||||||
|
const deviceForgeInvBaseUrl = `${baseApiUrl}/api/Device/GetForgeInvType`;
|
||||||
|
const deviceFloorBaseUrl = `${baseApiUrl}/api/Device/GetFloor`;
|
||||||
|
const deviceNextFloorBaseUrl = `${baseApiUrl}/api/Device/GetNextFloor`;
|
||||||
|
const deviceListBaseUrl = `${baseApiUrl}/api/Device/GetDeviceList`;
|
||||||
|
// const deviceMainSubListBaseUrl = `${baseApiUrl}/api/Device/GetMainSub`; // 同 mainSubBaseUrl
|
||||||
|
const deviceForgeNodeIdBaseUrl = `${baseApiUrl}/api/Device/GetForgeNodeIdFromVar`;
|
||||||
|
const deviceHotPointBaseUrl = `${baseApiUrl}/api/GetDevForCor`;
|
||||||
|
|
||||||
|
// 電錶
|
||||||
|
const energyBaseUrl = `${baseApiUrl}/api/Energe/GetElecBySubSysTag`;
|
||||||
|
|
||||||
|
// 歷史
|
||||||
|
// post
|
||||||
|
const historyBaseUrl = `${baseApiUrl}/api/History/GetMainSub`;
|
||||||
|
const historyDeviceBaseUrl = `${baseApiUrl}/api/History/GetDevPoi`;
|
||||||
|
|
||||||
|
// 上傳檔案
|
||||||
|
const uploadFileBaseUrl = `${baseApiUrl}/api/Upload`;
|
||||||
|
|
||||||
|
export {
|
||||||
|
n4Sup,
|
||||||
|
baseApiUrl,
|
||||||
|
baseImgUrl,
|
||||||
|
loginBaseUrl,
|
||||||
|
userAuthBaseUrl,
|
||||||
|
userInfoBaseUrl,
|
||||||
|
forgeTokenBaseUrl,
|
||||||
|
mainSubBaseUrl,
|
||||||
|
deviceBuiListBaseUrl,
|
||||||
|
deviceBuiMenuBaseUrl,
|
||||||
|
deviceForgeInvBaseUrl,
|
||||||
|
deviceFloorBaseUrl,
|
||||||
|
deviceNextFloorBaseUrl,
|
||||||
|
deviceListBaseUrl,
|
||||||
|
deviceForgeNodeIdBaseUrl,
|
||||||
|
deviceHotPointBaseUrl,
|
||||||
|
energyBaseUrl,
|
||||||
|
historyBaseUrl,
|
||||||
|
historyDeviceBaseUrl,
|
||||||
|
uploadFileBaseUrl,
|
||||||
|
};
|
52
ibms_react/src/constants/homeMonitorElectricity.js
Normal file
52
ibms_react/src/constants/homeMonitorElectricity.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faUser, faGem, faLightbulb, faGlobe } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
export const homeMonitorElectricity = [
|
||||||
|
{
|
||||||
|
title: "今日用電量 kWH",
|
||||||
|
icon: (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
size="10x"
|
||||||
|
className="opacity-75 position-absolute"
|
||||||
|
style={{ right: "-25px", top: "-5px" }}
|
||||||
|
icon={faUser}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
backgroundColor: "bg-primary-300",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "昨日用電量 kWH",
|
||||||
|
icon: (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
size="10x"
|
||||||
|
className="opacity-75 position-absolute"
|
||||||
|
style={{ right: "-25px", top: "-5px" }}
|
||||||
|
icon={faGem}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
backgroundColor: "bg-warning-400",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "即時功率 kW",
|
||||||
|
icon: (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
size="10x"
|
||||||
|
className="opacity-75 position-absolute"
|
||||||
|
style={{ right: "-25px", top: "-5px" }}
|
||||||
|
icon={faLightbulb}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
backgroundColor: "bg-success-200",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "即時契約容量占比 %",
|
||||||
|
icon: (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
size="10x"
|
||||||
|
className="opacity-75 position-absolute"
|
||||||
|
style={{ right: "-25px", top: "-5px" }}
|
||||||
|
icon={faGlobe}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
backgroundColor: "bg-info-200",
|
||||||
|
},
|
||||||
|
];
|
118
ibms_react/src/constants/homeMonitorSystem.js
Normal file
118
ibms_react/src/constants/homeMonitorSystem.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import {
|
||||||
|
faGripVertical,
|
||||||
|
faGripHorizontal,
|
||||||
|
faBolt,
|
||||||
|
faCarBattery,
|
||||||
|
faLightbulb,
|
||||||
|
faSun,
|
||||||
|
faIcicles,
|
||||||
|
faBong,
|
||||||
|
faSnowflake,
|
||||||
|
faDoorOpen,
|
||||||
|
faFireExtinguisher,
|
||||||
|
faSmog,
|
||||||
|
faStopwatch,
|
||||||
|
faTint,
|
||||||
|
faUserShield,
|
||||||
|
faWind,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
export const homeMonitorSystem = [
|
||||||
|
{
|
||||||
|
text: "高壓配電盤",
|
||||||
|
mainSys: "EE",
|
||||||
|
subSys: "E1",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faGripVertical} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "低壓配電盤",
|
||||||
|
mainSys: "EE",
|
||||||
|
subSys: "E2",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faGripHorizontal} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "緊急發電機",
|
||||||
|
mainSys: "EE",
|
||||||
|
subSys: "E3",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faBolt} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "電錶系統",
|
||||||
|
mainSys: "EE",
|
||||||
|
subSys: "E4",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faCarBattery} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "二線式照明系統",
|
||||||
|
mainSys: "LT",
|
||||||
|
subSys: "L1",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faLightbulb} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "景觀照明系統",
|
||||||
|
mainSys: "LT",
|
||||||
|
subSys: "L2",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faSun} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "儲冰系統",
|
||||||
|
mainSys: "ME",
|
||||||
|
subSys: "M1",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faIcicles} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "小型送風機",
|
||||||
|
mainSys: "ME",
|
||||||
|
subSys: "M10",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faWind} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "排油煙設備",
|
||||||
|
mainSys: "ME",
|
||||||
|
subSys: "M8",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faBong} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "環境感測設備",
|
||||||
|
mainSys: "ME",
|
||||||
|
subSys: "M12",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faSnowflake} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "電梯設備",
|
||||||
|
mainSys: "ELEV",
|
||||||
|
subSys: "EL",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faDoorOpen} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "消防設備",
|
||||||
|
mainSys: "FE",
|
||||||
|
subSys: "F1",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faFireExtinguisher} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "排煙系統",
|
||||||
|
mainSys: "FE",
|
||||||
|
subSys: "F2",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faSmog} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "電子水錶",
|
||||||
|
mainSys: "WP",
|
||||||
|
subSys: "W1",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faStopwatch} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "熱水系統",
|
||||||
|
mainSys: "W3",
|
||||||
|
subSys: "W1",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faTint} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "門禁安全系統",
|
||||||
|
mainSys: "S",
|
||||||
|
subSys: "R",
|
||||||
|
icon: <FontAwesomeIcon size="xl" icon={faUserShield} />,
|
||||||
|
},
|
||||||
|
];
|
51
ibms_react/src/constants/index.js
Normal file
51
ibms_react/src/constants/index.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import {
|
||||||
|
n4Sup,
|
||||||
|
baseApiUrl,
|
||||||
|
baseImgUrl,
|
||||||
|
loginBaseUrl,
|
||||||
|
userAuthBaseUrl,
|
||||||
|
userInfoBaseUrl,
|
||||||
|
forgeTokenBaseUrl,
|
||||||
|
mainSubBaseUrl,
|
||||||
|
deviceBuiListBaseUrl,
|
||||||
|
deviceBuiMenuBaseUrl,
|
||||||
|
deviceForgeInvBaseUrl,
|
||||||
|
deviceFloorBaseUrl,
|
||||||
|
deviceNextFloorBaseUrl,
|
||||||
|
deviceListBaseUrl,
|
||||||
|
deviceForgeNodeIdBaseUrl,
|
||||||
|
deviceHotPointBaseUrl,
|
||||||
|
energyBaseUrl,
|
||||||
|
historyBaseUrl,
|
||||||
|
historyDeviceBaseUrl,
|
||||||
|
uploadFileBaseUrl,
|
||||||
|
} from "./baseSetting";
|
||||||
|
|
||||||
|
import { homeMonitorSystem } from "./homeMonitorSystem";
|
||||||
|
import { homeMonitorElectricity } from "./homeMonitorElectricity";
|
||||||
|
import { userAllAuthPages } from "./user";
|
||||||
|
export {
|
||||||
|
n4Sup,
|
||||||
|
baseApiUrl,
|
||||||
|
baseImgUrl,
|
||||||
|
loginBaseUrl,
|
||||||
|
userAuthBaseUrl,
|
||||||
|
userInfoBaseUrl,
|
||||||
|
forgeTokenBaseUrl,
|
||||||
|
mainSubBaseUrl,
|
||||||
|
deviceBuiListBaseUrl,
|
||||||
|
deviceBuiMenuBaseUrl,
|
||||||
|
deviceForgeInvBaseUrl,
|
||||||
|
deviceFloorBaseUrl,
|
||||||
|
deviceNextFloorBaseUrl,
|
||||||
|
deviceListBaseUrl,
|
||||||
|
deviceForgeNodeIdBaseUrl,
|
||||||
|
deviceHotPointBaseUrl,
|
||||||
|
energyBaseUrl,
|
||||||
|
historyBaseUrl,
|
||||||
|
historyDeviceBaseUrl,
|
||||||
|
uploadFileBaseUrl,
|
||||||
|
homeMonitorSystem,
|
||||||
|
homeMonitorElectricity,
|
||||||
|
userAllAuthPages,
|
||||||
|
};
|
35
ibms_react/src/constants/user.js
Normal file
35
ibms_react/src/constants/user.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import {
|
||||||
|
faHouse,
|
||||||
|
faDisplay,
|
||||||
|
faChartPie,
|
||||||
|
faChartArea,
|
||||||
|
faChartLine,
|
||||||
|
faBell,
|
||||||
|
faServer,
|
||||||
|
faImage,
|
||||||
|
faUser,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
export const userAllAuthPages = [
|
||||||
|
{ name: "首頁", path: "/", icon: <FontAwesomeIcon size="2x" icon={faHouse} /> },
|
||||||
|
{ name: "系統監控", path: "/monitor", icon: <FontAwesomeIcon size="2x" icon={faDisplay} /> },
|
||||||
|
{ name: "能源管理", path: "/energy", icon: <FontAwesomeIcon size="2x" icon={faChartPie} /> },
|
||||||
|
{ name: "歷史資料", path: "/history", icon: <FontAwesomeIcon size="2x" icon={faChartArea} /> },
|
||||||
|
{ name: "報表管理", path: "/form", icon: <FontAwesomeIcon size="2x" icon={faChartLine} /> },
|
||||||
|
{ name: "即時告警", path: "/alert", icon: <FontAwesomeIcon size="2x" icon={faBell} /> },
|
||||||
|
{
|
||||||
|
name: "運維管理",
|
||||||
|
path: "/operation",
|
||||||
|
icon: <FontAwesomeIcon size="2x" icon={faServer} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "圖資管理",
|
||||||
|
path: "/graphManagement",
|
||||||
|
icon: <FontAwesomeIcon size="2x" icon={faImage} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "帳號管理",
|
||||||
|
path: "/accountManagement",
|
||||||
|
icon: <FontAwesomeIcon size="2x" icon={faUser} />,
|
||||||
|
},
|
||||||
|
];
|
@ -1,17 +1,48 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from "react-dom/client";
|
||||||
import './index.css';
|
import { createHashRouter, RouterProvider } from "react-router-dom";
|
||||||
import App from './App';
|
import store from "./stores/index";
|
||||||
import reportWebVitals from './reportWebVitals';
|
import { Provider } from "react-redux";
|
||||||
|
import axios from "axios";
|
||||||
|
import "./scss/index.scss";
|
||||||
|
import routes from "./routes/routes";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
// 添加请求拦截器
|
||||||
root.render(
|
axios.interceptors.request.use(
|
||||||
<React.StrictMode>
|
(config) => {
|
||||||
<App />
|
const myCookie = document.cookie.replace(
|
||||||
</React.StrictMode>
|
/(?:(?:^|.*;\s*)JWT-Authorization\s*\=\s*([^;]*).*$)|^.*$/,
|
||||||
|
"$1",
|
||||||
|
);
|
||||||
|
if (myCookie) {
|
||||||
|
config.headers["Authorization"] = `Bearer ${myCookie}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// 添加响应拦截器
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
axios.interceptors.response.use(
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
function (response) {
|
||||||
reportWebVitals();
|
// 2xx 范围内的状态码都会触发该函数。
|
||||||
|
// 对响应数据做点什么
|
||||||
|
return { ...response.data };
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
// 超出 2xx 范围的状态码都会触发该函数。
|
||||||
|
// 对响应错误做点什么
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const router = createHashRouter(routes);
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<Provider store={store}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</Provider>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
7
ibms_react/src/pages/AccountManagement.js
Normal file
7
ibms_react/src/pages/AccountManagement.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function AccountManagement() {
|
||||||
|
return <div>AccountManagement</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountManagement;
|
7
ibms_react/src/pages/Alert.js
Normal file
7
ibms_react/src/pages/Alert.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function Alert() {
|
||||||
|
return <div>Alert</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Alert;
|
7
ibms_react/src/pages/Energy.js
Normal file
7
ibms_react/src/pages/Energy.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function Energy() {
|
||||||
|
return <div>Energy</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Energy;
|
7
ibms_react/src/pages/GraphManagement.js
Normal file
7
ibms_react/src/pages/GraphManagement.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function GraphManagement() {
|
||||||
|
return <div>GraphManagement</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphManagement;
|
7
ibms_react/src/pages/History.js
Normal file
7
ibms_react/src/pages/History.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function history() {
|
||||||
|
return <div>history</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default history;
|
179
ibms_react/src/pages/Home.js
Normal file
179
ibms_react/src/pages/Home.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Container, Row, Col, Card, ButtonGroup, Button } from "react-bootstrap";
|
||||||
|
import { homeMonitorSystem, homeMonitorElectricity } from "@CON";
|
||||||
|
import ForgeModel from "@COM/forges/ForgeModel";
|
||||||
|
import BarChart from "@COM/general/BarChart";
|
||||||
|
import PieChart from "@COM/general/PieChart";
|
||||||
|
import ChartModal from "@COM/home/ChartModal";
|
||||||
|
import { fetchBuiAlarmStateByBaja, fetchInitElecMeterByBaja } from "@STORE";
|
||||||
|
function Home() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
// building
|
||||||
|
const {
|
||||||
|
buildingInfo: { selectedBuiArea, selectedBuiTag },
|
||||||
|
electricity: { electricMeter, hourElectricMeter, weekElectricMeter, curElectricity },
|
||||||
|
alarm: { sidebarAlarm },
|
||||||
|
} = useSelector((state) => state);
|
||||||
|
// electricity
|
||||||
|
const topElec = useMemo(() => {
|
||||||
|
const newCard = homeMonitorElectricity.map((elec) => {
|
||||||
|
if (electricMeter.some((em) => elec.title.includes(em.text))) {
|
||||||
|
return { ...elec, ...electricMeter.find((em) => elec.title.includes(em.text)) };
|
||||||
|
} else if (curElectricity.some((cur) => elec.title.includes(cur.text))) {
|
||||||
|
return { ...elec, ...curElectricity.find((cur) => elec.title.includes(cur.text)) };
|
||||||
|
}
|
||||||
|
return elec;
|
||||||
|
});
|
||||||
|
return newCard;
|
||||||
|
}, [electricMeter, curElectricity]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedBuiArea && selectedBuiTag) {
|
||||||
|
// 為了取得首頁小卡片的報警資料
|
||||||
|
dispatch(fetchBuiAlarmStateByBaja(selectedBuiArea, selectedBuiTag));
|
||||||
|
dispatch(fetchInitElecMeterByBaja());
|
||||||
|
}
|
||||||
|
}, [selectedBuiArea, selectedBuiTag]);
|
||||||
|
|
||||||
|
// 圖表放大展示
|
||||||
|
const [modalShow, setModalShow] = useState(false);
|
||||||
|
const [chart, setChart] = useState({ title: "", component: <></> });
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container fluid className="mt-3">
|
||||||
|
<Row className="g-0" md={3}>
|
||||||
|
<Col>
|
||||||
|
<ForgeModel forgeHeight="100%" />
|
||||||
|
</Col>
|
||||||
|
<Col xs={1} lg={8} className="h-100">
|
||||||
|
<Container fluid>
|
||||||
|
<Row lg={4} className="g-4">
|
||||||
|
{topElec.map(({ title, data, icon, backgroundColor }) => (
|
||||||
|
<Col key={title}>
|
||||||
|
<Card className={`${backgroundColor} overflow-hidden py-1`}>
|
||||||
|
<div className="w-100 h-100 position-relative">{icon}</div>
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title as="h1" className="fw-bold">
|
||||||
|
{data ?? 0}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Text>{title}</Card.Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
<Row md={2} className="g-2 mt-2">
|
||||||
|
<Col>
|
||||||
|
<Card
|
||||||
|
bg="dark"
|
||||||
|
className="p-2 h-100"
|
||||||
|
onClick={() => {
|
||||||
|
setModalShow(true);
|
||||||
|
setChart({
|
||||||
|
title: "今日/昨日用電量比較",
|
||||||
|
component: (
|
||||||
|
<BarChart
|
||||||
|
title="今日/昨日用電量比較"
|
||||||
|
period="hours"
|
||||||
|
barData={hourElectricMeter}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* barData 透過 baja 取資料 */}
|
||||||
|
<BarChart
|
||||||
|
title="今日/昨日用電量比較"
|
||||||
|
period="hours"
|
||||||
|
barData={hourElectricMeter}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Card
|
||||||
|
bg="dark"
|
||||||
|
className="p-2 h-100"
|
||||||
|
onClick={() => {
|
||||||
|
setModalShow(true);
|
||||||
|
setChart({
|
||||||
|
title: "本週/上週用電量比較",
|
||||||
|
component: (
|
||||||
|
<BarChart
|
||||||
|
title="本週/上週用電量比較"
|
||||||
|
period="days"
|
||||||
|
barData={weekElectricMeter}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BarChart
|
||||||
|
title="本週/上週用電量比較"
|
||||||
|
period="days"
|
||||||
|
barData={weekElectricMeter}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row className="my-1 g-2" md={3}>
|
||||||
|
<Col xs={1} lg={8} className="d-flex justify-content-between flex-wrap">
|
||||||
|
{homeMonitorSystem.map(({ text, mainSys, subSys, icon }) => (
|
||||||
|
<ButtonGroup
|
||||||
|
size="lg"
|
||||||
|
key={`${selectedBuiArea}/${selectedBuiTag}/${mainSys}/${subSys}`}
|
||||||
|
className="my-3"
|
||||||
|
style={{ width: "24%" }}
|
||||||
|
data-id={`${selectedBuiArea}/${selectedBuiTag}/${mainSys}/${subSys}`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
className={`border-0 border-end border-2 p-2 ${
|
||||||
|
sidebarAlarm.some((alarm) =>
|
||||||
|
alarm.sourceName.includes(
|
||||||
|
`${selectedBuiArea}/${selectedBuiTag}/${mainSys}/${subSys}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
? "animated"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
style={{ maxWidth: "30%" }}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" className="p-2 ">
|
||||||
|
{text}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
<Col xs={1} lg={2} className="pt-2 my-3">
|
||||||
|
<Card bg="dark" className="h-100">
|
||||||
|
<Card.Header>異常狀態</Card.Header>
|
||||||
|
<Card.Body className="w-100 d-flex flex-column align-items-center justify-content-between">
|
||||||
|
<PieChart labels={["異常數量", "復歸數量"]} className="mb-4" />
|
||||||
|
<PieChart labels={["已確認異常", "未確認異常"]} />
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col xs={1} lg={2} className="pt-2 my-3">
|
||||||
|
<Card bg="dark" className="h-100">
|
||||||
|
<Card.Header>工單進度</Card.Header>
|
||||||
|
<Card.Body className="w-100 d-flex flex-column align-items-center justify-content-between py-0">
|
||||||
|
<PieChart labels={["異常未派工", "異常已派工"]} />
|
||||||
|
<PieChart labels={["工單已完成", "工單未完成"]} />
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
<ChartModal modalShow={modalShow} setModalShow={setModalShow} chart={chart} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
72
ibms_react/src/pages/Login.js
Normal file
72
ibms_react/src/pages/Login.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Form, Button, Container, Card } from "react-bootstrap";
|
||||||
|
import { userLogin } from "@UTIL/index";
|
||||||
|
import clouds from "@ASSET/img/clouds.png";
|
||||||
|
import video1 from "@ASSET/video/cc.mp4";
|
||||||
|
import video2 from "@ASSET/video/cc.webm";
|
||||||
|
function Login() {
|
||||||
|
const [user, setUser] = useState({ account: "webUser", password: "rJ2T5Kkj" });
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<div className="vh-100 d-flex align-items-center justify-content-center position-relative overflow-hidden">
|
||||||
|
<video poster={clouds} playsInline autoPlay muted className="position-absolute">
|
||||||
|
<source src={video2} type="video/webm" />
|
||||||
|
<source src={video1} type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
<Card className="w-25 position-absolute" bg="dark">
|
||||||
|
<Card.Body>
|
||||||
|
<Form>
|
||||||
|
<Form.Group className="mb-3" controlId="account">
|
||||||
|
<Form.Label>帳號</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
value="webUser"
|
||||||
|
type="text"
|
||||||
|
name="account"
|
||||||
|
placeholder="請輸入帳號"
|
||||||
|
onChange={(e) => {
|
||||||
|
setUser({ ...user, account: e.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Text className="text-muted">您的帳號</Form.Text>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group className="mb-3" controlId="password">
|
||||||
|
<Form.Label>Password</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="請輸入密碼"
|
||||||
|
autoComplete="off"
|
||||||
|
value="rJ2T5Kkj"
|
||||||
|
onChange={(e) => {
|
||||||
|
setUser({ ...user, password: e.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Text className="text-muted">您的密碼</Form.Text>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3" controlId="formBasicCheckbox">
|
||||||
|
<Form.Check type="checkbox" label="記住我" />
|
||||||
|
</Form.Group>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
type="submit"
|
||||||
|
onClick={(e) => {
|
||||||
|
userLogin(e, user, navigate);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
登入
|
||||||
|
</Button>
|
||||||
|
<div className="blankpage-footer text-center">
|
||||||
|
<Button variant="link" asp-controller="Login" asp-action="ForgotPassword">
|
||||||
|
<strong>忘記密碼</strong>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
102
ibms_react/src/pages/Monitor.js
Normal file
102
ibms_react/src/pages/Monitor.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { Container, Row, Col, Card, ButtonGroup, Button } from "react-bootstrap";
|
||||||
|
import ForgeModel from "@COM/forges/ForgeModel";
|
||||||
|
import { fetchSelectedDeviceMenu, fetchSelectedDeviceList, fetchSelectedDevFlTags } from "@STORE";
|
||||||
|
import defdev from "@ASSET/img/defdev.png";
|
||||||
|
function Monitor() {
|
||||||
|
const params = useParams().systemId; // main_sub
|
||||||
|
const [main_system_tag, sub_system_tag] = params.split("_");
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
buildingInfo: { selectedBuiTag },
|
||||||
|
device: { selectedDeviceMenu, selectedDeviceList, selectedDeviceFloorTags },
|
||||||
|
system: { mainSub },
|
||||||
|
} = useSelector((state) => state);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedBuiTag && main_system_tag && sub_system_tag) {
|
||||||
|
dispatch(fetchSelectedDeviceMenu({ main_system_tag, sub_system_tag }));
|
||||||
|
dispatch(fetchSelectedDeviceList({ sub_system_tag }));
|
||||||
|
dispatch(fetchSelectedDevFlTags({ sub_system_tag }));
|
||||||
|
}
|
||||||
|
// 載入資料
|
||||||
|
}, [main_system_tag, sub_system_tag, selectedBuiTag]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container
|
||||||
|
fluid
|
||||||
|
className="mb-3 py-1 d-flex align-items-center"
|
||||||
|
style={{ background: "#505050" }}
|
||||||
|
>
|
||||||
|
<span className="fs-4 text-white-50 mx-4">
|
||||||
|
{mainSub.length !== 0 &&
|
||||||
|
mainSub.find((s) => params.includes(s.sub_system_tag))?.full_name}
|
||||||
|
</span>
|
||||||
|
<ButtonGroup size="sm">
|
||||||
|
<Button variant="secondary">總覽</Button>
|
||||||
|
{selectedDeviceFloorTags.length !== 0 &&
|
||||||
|
selectedDeviceFloorTags.map(({ floor_tag, floor_guid }) => (
|
||||||
|
<Button variant="secondary" key={floor_guid}>
|
||||||
|
{floor_tag}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</ButtonGroup>
|
||||||
|
</Container>
|
||||||
|
<Container fluid className="pe-2 ps-5">
|
||||||
|
<Row>
|
||||||
|
<Col md={6}>
|
||||||
|
{selectedDeviceMenu?.left_system_url &&
|
||||||
|
window.location.href.includes("http://220.132.206.5") ? (
|
||||||
|
<iframe
|
||||||
|
src={selectedDeviceMenu.left_system_url}
|
||||||
|
className="w-100 h-100"
|
||||||
|
title="設計圖稿"
|
||||||
|
></iframe>
|
||||||
|
) : (
|
||||||
|
selectedDeviceFloorTags.map(({ floor_tag, floor_guid }) => (
|
||||||
|
<Row className="g-3" key={floor_guid}>
|
||||||
|
<Col md={1}>
|
||||||
|
<Button className="px-4 py-2 text-white">{floor_tag}</Button>
|
||||||
|
</Col>
|
||||||
|
<Col className="d-flex justify-content-evenly align-items-center flex-wrap">
|
||||||
|
{selectedDeviceList.map(({ device_guid, full_name, forge_dbid }) => (
|
||||||
|
<Card
|
||||||
|
bg="dark"
|
||||||
|
key={device_guid}
|
||||||
|
className="mb-3"
|
||||||
|
style={{ maxWidth: "45%" }}
|
||||||
|
>
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title>
|
||||||
|
<img src={defdev} className="w-25 h-25 me-3" />
|
||||||
|
{full_name}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Text>
|
||||||
|
<span className="text-secondary me-4">功率 kW</span>
|
||||||
|
<Card.Link
|
||||||
|
className="text-white text-decoration-none"
|
||||||
|
data-forgeId={forge_dbid}
|
||||||
|
>
|
||||||
|
詳細內容
|
||||||
|
</Card.Link>
|
||||||
|
</Card.Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
<Col md={6}>
|
||||||
|
<ForgeModel forgeHeight="90vh" sprites={selectedDeviceList} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Monitor;
|
7
ibms_react/src/pages/Operation.js
Normal file
7
ibms_react/src/pages/Operation.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function Operation() {
|
||||||
|
return <div>Operation</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Operation;
|
82
ibms_react/src/routes/routes.js
Normal file
82
ibms_react/src/routes/routes.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import Login from "@/pages/Login";
|
||||||
|
import App from "@/App";
|
||||||
|
import Home from "@/pages/Home";
|
||||||
|
import Monitor from "@/pages/Monitor";
|
||||||
|
import Energy from "@/pages/Energy";
|
||||||
|
import History from "@/pages/History";
|
||||||
|
import Alert from "@/pages/Alert";
|
||||||
|
import Operation from "@/pages/Operation";
|
||||||
|
import GraphManagement from "@/pages/GraphManagement";
|
||||||
|
import AccountManagement from "@/pages/AccountManagement";
|
||||||
|
import { redirect } from "react-router-dom";
|
||||||
|
import { setUserAccountByBaja, userLogin } from "@UTIL/index";
|
||||||
|
const monitor = [{}, {}];
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <App />,
|
||||||
|
loader: async () => {
|
||||||
|
let JWT_Authorization = document.cookie.replace(
|
||||||
|
/(?:(?:^|.*;\s*)JWT-Authorization\s*\=\s*([^;]*).*$)|^.*$/,
|
||||||
|
"$1",
|
||||||
|
);
|
||||||
|
if (window.location.href.includes("localhost:3000") && !JWT_Authorization) {
|
||||||
|
console.log(JWT_Authorization);
|
||||||
|
return redirect("/login");
|
||||||
|
} else if (window.location.href.includes("http://220.132.206.5") && !JWT_Authorization) {
|
||||||
|
const account = await setUserAccountByBaja();
|
||||||
|
console.log("account", account);
|
||||||
|
await userLogin(
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
account,
|
||||||
|
password: "rJ2T5Kkj",
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
element: <Home />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "monitor/:systemId",
|
||||||
|
element: <Monitor />,
|
||||||
|
children: monitor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "energy",
|
||||||
|
element: <Energy />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "history",
|
||||||
|
element: <History />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "alert",
|
||||||
|
element: <Alert />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "operation",
|
||||||
|
element: <Operation />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "graphManagement",
|
||||||
|
element: <GraphManagement />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "accountManagement",
|
||||||
|
element: <AccountManagement />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
element: <Login />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default routes;
|
79
ibms_react/src/scss/index.scss
Normal file
79
ibms_react/src/scss/index.scss
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Include any default variable overrides here (though functions won't be available)
|
||||||
|
$font-size-base: 0.8125rem;
|
||||||
|
$font-weight-bold: 900;
|
||||||
|
$body-bg: #37393e;
|
||||||
|
$body-color: #fff;
|
||||||
|
$primary: #886ab5;
|
||||||
|
$primary-hover: #ccbfdf;
|
||||||
|
$success: #10b7b9;
|
||||||
|
// $danger:
|
||||||
|
$info:#0c7cd5;
|
||||||
|
$dark: #212225;
|
||||||
|
$navbar-dark-color: white;
|
||||||
|
$navbar-dark-hover-color: $primary-hover;
|
||||||
|
$navbar-dark-active-color: $primary;
|
||||||
|
$offcanvas-horizontal-width: 250px;
|
||||||
|
$offcanvas-bg-color: rgba(33, 34, 37,0.95);
|
||||||
|
$list-group-color: $body-color;
|
||||||
|
$list-group-bg: $dark;
|
||||||
|
$list-group-action-color: $primary;
|
||||||
|
$list-group-hover-bg: white;
|
||||||
|
$list-group-action-active-bg: white;
|
||||||
|
$list-group-border-radius: 0;
|
||||||
|
$list-group-border-width: 0;
|
||||||
|
@import "~/node_modules/bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
// Then add additional custom code here
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
color: white
|
||||||
|
}
|
||||||
|
#forgeViewer {
|
||||||
|
width: 95%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item-action:hover, .list-group-item-action:hover>.list-group-item-action{
|
||||||
|
background-color: $list-group-hover-bg !important;
|
||||||
|
color: $list-group-action-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item-action.active,.list-group-item-action.active> .list-group-item-action{
|
||||||
|
background-color: $list-group-action-active-bg !important;
|
||||||
|
color: $list-group-action-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary-300 {
|
||||||
|
background-color: rgba(163, 140, 198,0.4);}
|
||||||
|
.bg-success-200 {
|
||||||
|
background-color: rgba(29,201,183,0.3); }
|
||||||
|
.bg-info-200 {
|
||||||
|
background-color: rgba(106, 184, 247,.3);}
|
||||||
|
.bg-warning-400 {
|
||||||
|
background-color: rgba(255, 202, 91,.5);}
|
||||||
|
|
||||||
|
.animated{
|
||||||
|
animation-name: text_twinkle;
|
||||||
|
animation-duration: .5s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-fill-mode: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes text_twinkle {
|
||||||
|
0% {
|
||||||
|
color : $danger
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
color : $danger
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
color : $white
|
||||||
|
}
|
||||||
|
}
|
37
ibms_react/src/stores/alarmSlice.js
Normal file
37
ibms_react/src/stores/alarmSlice.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { getBuiAlarmStateByBaja } from "@UTIL";
|
||||||
|
|
||||||
|
export const fetchBuiAlarmStateByBaja = createAsyncThunk(
|
||||||
|
"alarm/fetchBuiAlarmStateByBaja",
|
||||||
|
async ({ area_tag, building_tag }, { dispatch }) => {
|
||||||
|
const res = await getBuiAlarmStateByBaja(area_tag, building_tag);
|
||||||
|
console.log(res);
|
||||||
|
dispatch(getSidebarAlarm(res));
|
||||||
|
const timer = window.setInterval(async () => {
|
||||||
|
const res = await getBuiAlarmStateByBaja(area_tag, building_tag);
|
||||||
|
dispatch(getSidebarAlarm(res));
|
||||||
|
}, 300000);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const alarmSlice = createSlice({
|
||||||
|
name: "alarm",
|
||||||
|
initialState: {
|
||||||
|
sidebarAlarm: [],
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
getSidebarAlarm: (state, { payload }) => {
|
||||||
|
console.log(payload);
|
||||||
|
state.sidebarAlarm = payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(fetchBuiAlarmStateByBaja.fulfilled, (state) => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { reducer, actions } = alarmSlice;
|
||||||
|
export const { getSidebarAlarm } = actions;
|
||||||
|
export default reducer;
|
47
ibms_react/src/stores/buildingSlice.js
Normal file
47
ibms_react/src/stores/buildingSlice.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import axios from "axios";
|
||||||
|
import { deviceBuiListBaseUrl } from "@CON";
|
||||||
|
import { ajaxRes } from "@UTIL";
|
||||||
|
// 異步取得區域及棟別
|
||||||
|
export const fetchBuiList = createAsyncThunk("building/fetchBuiList", async (token, thunkAPI) => {
|
||||||
|
const res = await axios.post(deviceBuiListBaseUrl);
|
||||||
|
const { building_tag, urn_3D, full_name } = res.data[0];
|
||||||
|
thunkAPI.dispatch(changeBuilding({ building_tag, urn_3D, full_name }));
|
||||||
|
return ajaxRes(res, thunkAPI);
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildingSlice = createSlice({
|
||||||
|
name: "building",
|
||||||
|
initialState: {
|
||||||
|
buildingList: ["中凌大樓"],
|
||||||
|
urn_3D: "",
|
||||||
|
selectedBuiArea: "TPE",
|
||||||
|
selectedBuiTag: "",
|
||||||
|
selectedBuiFullName: "",
|
||||||
|
errMsg: "",
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
// 改變區域
|
||||||
|
changeArea: () => {},
|
||||||
|
// 改變棟別
|
||||||
|
changeBuilding: (state, { payload: { building_tag, urn_3D, full_name } }) => {
|
||||||
|
state.urn_3D = urn_3D;
|
||||||
|
state.selectedBuiTag = building_tag;
|
||||||
|
state.selectedBuiFullName = full_name;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
// pending, fulfilled, rejected
|
||||||
|
builder
|
||||||
|
.addCase(fetchBuiList.fulfilled, (state, { payload }) => {
|
||||||
|
state.buildingList = payload;
|
||||||
|
})
|
||||||
|
.addCase(fetchBuiList.rejected, (state, { payload }) => {
|
||||||
|
state.errMsg = payload;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { actions, reducer } = buildingSlice;
|
||||||
|
export const { changeBuilding } = actions;
|
||||||
|
export default reducer;
|
92
ibms_react/src/stores/deviceListSlice.js
Normal file
92
ibms_react/src/stores/deviceListSlice.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import axios from "axios";
|
||||||
|
import { deviceBuiMenuBaseUrl, deviceListBaseUrl, deviceFloorBaseUrl } from "@CON";
|
||||||
|
import { ajaxRes } from "@UTIL";
|
||||||
|
|
||||||
|
export const fetchSelectedDeviceMenu = createAsyncThunk(
|
||||||
|
"deviceList/fetchSelectedDeviceMenu",
|
||||||
|
async ({ main_system_tag, sub_system_tag }, thunkAPI) => {
|
||||||
|
const { selectedBuiTag } = thunkAPI.getState().buildingInfo;
|
||||||
|
const res = await axios.post(deviceBuiMenuBaseUrl, {
|
||||||
|
building_tag: selectedBuiTag,
|
||||||
|
main_system_tag,
|
||||||
|
sub_system_tag,
|
||||||
|
});
|
||||||
|
return ajaxRes(res, thunkAPI);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchSelectedDeviceList = createAsyncThunk(
|
||||||
|
"deviceList/fetchSelectedDeviceList",
|
||||||
|
async ({ sub_system_tag }, { getState, fulfillWithValue, rejectWithValue }) => {
|
||||||
|
const { selectedBuiTag } = getState().buildingInfo;
|
||||||
|
const res = await axios.post(deviceListBaseUrl, {
|
||||||
|
building_tag: selectedBuiTag,
|
||||||
|
sub_system_tag,
|
||||||
|
});
|
||||||
|
const { code = 9999, msg, data = null } = res;
|
||||||
|
|
||||||
|
if (code !== "0000" || !data) {
|
||||||
|
return rejectWithValue(msg);
|
||||||
|
} else {
|
||||||
|
console.log(data)
|
||||||
|
let results=[];
|
||||||
|
data.forEach((p) => {
|
||||||
|
p.device_list.forEach((d) => {
|
||||||
|
results.push({ ...d, device_floor: p.full_name });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log("v", [...results]);
|
||||||
|
return fulfillWithValue([...results]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchSelectedDevFlTags = createAsyncThunk(
|
||||||
|
"deviceList/fetchSelectedDevFlTags",
|
||||||
|
async ({ sub_system_tag }, thunkAPI) => {
|
||||||
|
const { selectedBuiTag } = thunkAPI.getState().buildingInfo;
|
||||||
|
const res = await axios.post(deviceFloorBaseUrl, {
|
||||||
|
building_tag: selectedBuiTag,
|
||||||
|
sub_system_tag,
|
||||||
|
});
|
||||||
|
return ajaxRes(res, thunkAPI);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const deviceListSlice = createSlice({
|
||||||
|
name: "deviceList",
|
||||||
|
initialState: {
|
||||||
|
allDeviceList: [],
|
||||||
|
selectedDeviceMenu: {}, //總覽
|
||||||
|
selectedDeviceList: [],
|
||||||
|
selectedDeviceSysTag: "", // ELEV
|
||||||
|
selectedDeviceNameTag: "", // EL
|
||||||
|
selectedDeviceFloorTags: [], // R2F
|
||||||
|
selectedDeviceMaster: "", // BANK1
|
||||||
|
selectedDeviceLastName: "", // ELEV
|
||||||
|
selectedDeviceSerialTag: "", // N1
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
getAllDevice: (state, action) => {},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(fetchSelectedDeviceMenu.fulfilled, (state, { payload }) => {
|
||||||
|
state.selectedDeviceMenu = payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchSelectedDeviceList.fulfilled, (state, { payload }) => {
|
||||||
|
console.log("de", payload);
|
||||||
|
state.selectedDeviceList = payload;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.addCase(fetchSelectedDevFlTags.fulfilled, (state, { payload }) => {
|
||||||
|
state.selectedDeviceFloorTags = payload.map((p) => {
|
||||||
|
return { floor_tag: p.floor_tag, floor_guid: p.floor_guid };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { reducer, actions } = deviceListSlice;
|
||||||
|
export const { getAllDevice } = actions;
|
||||||
|
export default reducer;
|
73
ibms_react/src/stores/electricitySlice.js
Normal file
73
ibms_react/src/stores/electricitySlice.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import axios from "axios";
|
||||||
|
import { energyBaseUrl } from "@CON";
|
||||||
|
import { getTotalElectricByBaja, getCurPower } from "@UTIL";
|
||||||
|
|
||||||
|
const getElecMeter = (ordPath, dispatch) => {
|
||||||
|
const date = new Date();
|
||||||
|
// 取得昨日及今日電錶總量
|
||||||
|
getTotalElectricByBaja(ordPath, date, "daily", dispatch);
|
||||||
|
// 取得昨日及今日電錶總量比較(長條圖用)
|
||||||
|
getTotalElectricByBaja(ordPath, date, "hourly", dispatch);
|
||||||
|
// 取得本週/上週用電量比較(長條圖用)
|
||||||
|
getTotalElectricByBaja(ordPath, date, "weekly", dispatch);
|
||||||
|
};
|
||||||
|
export const fetchInitElecMeterByBaja = createAsyncThunk(
|
||||||
|
"electricity/fetchInitElecMeterByBaja",
|
||||||
|
async (token = "", { fulfillWithValue, rejectWithValue, dispatch }) => {
|
||||||
|
const res = await axios.post(energyBaseUrl);
|
||||||
|
const { code = 9999, msg, data = null } = res;
|
||||||
|
if (code !== "0000" || !data) {
|
||||||
|
return rejectWithValue(msg);
|
||||||
|
} else {
|
||||||
|
// 取得總電錶
|
||||||
|
const total = res.data.find((d) => d.mainSubTag === "total");
|
||||||
|
// 訂閱 "TPE_B1_EE_E4_R2F_NA_WHT_N1" ==> 總電錶
|
||||||
|
getElecMeter(`${total.system_device_tag}_KWH`, dispatch); //"TPE_B1_EE_E4_R2F_NA_WHT_N1_KWH"
|
||||||
|
const ordPath = total?.system_device_tag.replaceAll("_", "/");
|
||||||
|
getCurPower(ordPath, dispatch); // "TPE/B1/EE/E4/R2F/NA/WHT/N1"
|
||||||
|
const timer = window.setInterval(async () => {
|
||||||
|
await getElecMeter(ordPath, dispatch);
|
||||||
|
}, 3600000);
|
||||||
|
return fulfillWithValue(timer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const electricitySlice = createSlice({
|
||||||
|
name: "electricity",
|
||||||
|
initialState: {
|
||||||
|
timer: [],
|
||||||
|
electricMeter: [{ text: "", data: 0 }],
|
||||||
|
hourElectricMeter: { current: [], last: [] },
|
||||||
|
weekElectricMeter: { current: [], last: [] },
|
||||||
|
curElectricity: [
|
||||||
|
{ data: "", text: "即時功率 " },
|
||||||
|
{ data: "", text: "即時契約容量占比 " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
getElectricMeter: (state, { payload }) => {
|
||||||
|
state.electricMeter = payload;
|
||||||
|
},
|
||||||
|
getHourElectricMeter: (state, { payload }) => {
|
||||||
|
state.hourElectricMeter = payload;
|
||||||
|
},
|
||||||
|
getWeekElectricMeter: (state, { payload }) => {
|
||||||
|
state.weekElectricMeter = payload;
|
||||||
|
},
|
||||||
|
getCurElectricity: (state, { payload }) => {
|
||||||
|
state.curElectricity = payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(fetchInitElecMeterByBaja.fulfilled, (state, { payload }) => {
|
||||||
|
state.timer = [...state.timer, payload];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { reducer, actions } = electricitySlice;
|
||||||
|
export const { getElectricMeter, getHourElectricMeter, getWeekElectricMeter, getCurElectricity } =
|
||||||
|
actions;
|
||||||
|
export default reducer;
|
50
ibms_react/src/stores/forgeSlice.js
Normal file
50
ibms_react/src/stores/forgeSlice.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { forgeInit, modelLoad } from "@UTIL";
|
||||||
|
|
||||||
|
export const fetchForge = createAsyncThunk(
|
||||||
|
"forge/fetchForge",
|
||||||
|
async ({ viewerEl, urn }, { dispatch, fulfillWithValue, rejectWithValue }) => {
|
||||||
|
const res = await forgeInit(viewerEl);
|
||||||
|
if (res.ok) {
|
||||||
|
modelLoad(res.viewer, urn, dispatch);
|
||||||
|
return fulfillWithValue(res.viewer);
|
||||||
|
} else {
|
||||||
|
return rejectWithValue(res.errCode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const forgeSlice = createSlice({
|
||||||
|
name: "forge",
|
||||||
|
initialState: {
|
||||||
|
viewer: null,
|
||||||
|
dataVizExtn: null,
|
||||||
|
DataVizCore: null,
|
||||||
|
errCode: 0,
|
||||||
|
urn_3D: "",
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
loadDataVizExtn: (state, { payload }) => {
|
||||||
|
state.dataVizExtn = payload;
|
||||||
|
state.DataVizCore = window.Autodesk.DataVisualization.Core;
|
||||||
|
},
|
||||||
|
shutdownForge: (state, action) => {
|
||||||
|
state.viewer?.finish();
|
||||||
|
state.viewer = null;
|
||||||
|
window.Autodesk?.Viewing.shutdown();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder
|
||||||
|
.addCase(fetchForge.fulfilled, (state, action) => {
|
||||||
|
state.viewer = action.payload;
|
||||||
|
})
|
||||||
|
.addCase(fetchForge.rejected, (state, action) => {
|
||||||
|
state.errCode = action.payload;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { reducer, actions } = forgeSlice;
|
||||||
|
export const { shutdownForge, loadDataVizExtn } = actions;
|
||||||
|
export default reducer;
|
13
ibms_react/src/stores/generalSlice.js
Normal file
13
ibms_react/src/stores/generalSlice.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
const generalSlice = createSlice({
|
||||||
|
name: "general",
|
||||||
|
initialState: {
|
||||||
|
timer: [],
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
addTimer: (state, { payload }) => {
|
||||||
|
state.timer = [...state.timer, payload];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
58
ibms_react/src/stores/index.js
Normal file
58
ibms_react/src/stores/index.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
import buildingInfo, { fetchBuiList, changeBuilding } from "./buildingSlice";
|
||||||
|
import device, {
|
||||||
|
fetchSelectedDeviceList,
|
||||||
|
fetchSelectedDeviceMenu,
|
||||||
|
fetchSelectedDevFlTags,
|
||||||
|
} from "./deviceListSlice";
|
||||||
|
import forgeViewer, { fetchForge, loadDataVizExtn, shutdownForge } from "./forgeSlice";
|
||||||
|
import system, { fetchSysMainSub } from "./systemSlice";
|
||||||
|
import user, { fetchUserInfo, fetchUserAuthPages } from "./userSlice";
|
||||||
|
import alarm, { fetchBuiAlarmStateByBaja } from "./alarmSlice";
|
||||||
|
import electricity, {
|
||||||
|
fetchInitElecMeterByBaja,
|
||||||
|
getCurElectricity,
|
||||||
|
getElectricMeter,
|
||||||
|
getHourElectricMeter,
|
||||||
|
getWeekElectricMeter,
|
||||||
|
} from "./electricitySlice";
|
||||||
|
const reducers = {
|
||||||
|
buildingInfo,
|
||||||
|
device,
|
||||||
|
forgeViewer,
|
||||||
|
system,
|
||||||
|
user,
|
||||||
|
alarm,
|
||||||
|
electricity,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 向外暴露管理對象 store
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: reducers,
|
||||||
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
getDefaultMiddleware({
|
||||||
|
serializableCheck: false,
|
||||||
|
}),
|
||||||
|
devTools: process.env.NODE_ENV !== "production",
|
||||||
|
});
|
||||||
|
export default store;
|
||||||
|
export {
|
||||||
|
fetchBuiList,
|
||||||
|
changeBuilding,
|
||||||
|
fetchSelectedDeviceList,
|
||||||
|
fetchSelectedDeviceMenu,
|
||||||
|
fetchSelectedDevFlTags,
|
||||||
|
fetchForge,
|
||||||
|
loadDataVizExtn,
|
||||||
|
shutdownForge,
|
||||||
|
fetchSysMainSub,
|
||||||
|
fetchUserInfo,
|
||||||
|
fetchUserAuthPages,
|
||||||
|
fetchBuiAlarmStateByBaja,
|
||||||
|
fetchInitElecMeterByBaja,
|
||||||
|
getElectricMeter,
|
||||||
|
getHourElectricMeter,
|
||||||
|
getWeekElectricMeter,
|
||||||
|
getCurElectricity,
|
||||||
|
};
|
37
ibms_react/src/stores/systemSlice.js
Normal file
37
ibms_react/src/stores/systemSlice.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { mainSubBaseUrl } from "@CON";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export const fetchSysMainSub = createAsyncThunk(
|
||||||
|
"systemList/fetchSysMainSub",
|
||||||
|
async (building_tag, { fulfillWithValue }) => {
|
||||||
|
const res = await axios({
|
||||||
|
method: "post",
|
||||||
|
url: mainSubBaseUrl,
|
||||||
|
data: { building_tag },
|
||||||
|
});
|
||||||
|
let result = [];
|
||||||
|
res.data.history_Main_Systems.forEach((mainSys) => {
|
||||||
|
mainSys.history_Sub_systems.forEach((subSys) => {
|
||||||
|
result.push({ ...subSys, main_system_tag: mainSys.main_system_tag });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return fulfillWithValue(result);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const systemSlice = createSlice({
|
||||||
|
name: "systemList",
|
||||||
|
initialState: {
|
||||||
|
mainSub: [],
|
||||||
|
},
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(fetchSysMainSub.fulfilled, (state, { payload }) => {
|
||||||
|
state.mainSub = payload;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { reducer, actions } = systemSlice;
|
||||||
|
export default reducer;
|
40
ibms_react/src/stores/userSlice.js
Normal file
40
ibms_react/src/stores/userSlice.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
|
import axios from "axios";
|
||||||
|
import { userAuthBaseUrl, userInfoBaseUrl } from "@CON";
|
||||||
|
import { ajaxRes, deviceBuiListBaseUrl } from "@UTIL";
|
||||||
|
import { fetchBuiList } from "./buildingSlice";
|
||||||
|
|
||||||
|
export const fetchUserInfo = createAsyncThunk("user/fetchUserInfo", async (token, thunkAPI) => {
|
||||||
|
const res = await axios.post(userInfoBaseUrl);
|
||||||
|
console.log("user/fetchUserInfo", res);
|
||||||
|
return ajaxRes(res, thunkAPI);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchUserAuthPages = createAsyncThunk(
|
||||||
|
"user/fetchUserAuthPages",
|
||||||
|
async (token, thunkAPI) => {
|
||||||
|
const res = await axios.post(userAuthBaseUrl);
|
||||||
|
thunkAPI.dispatch(fetchBuiList());
|
||||||
|
return ajaxRes(res, thunkAPI);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const userSlice = createSlice({
|
||||||
|
name: "user",
|
||||||
|
initialState: {
|
||||||
|
userAuthPages: [],
|
||||||
|
userInfo: {},
|
||||||
|
},
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(fetchUserInfo.fulfilled, (state, { payload }) => {
|
||||||
|
state.userInfo = payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchUserAuthPages.fulfilled, (state, { payload }) => {
|
||||||
|
state.userAuthPages = payload;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { reducer } = userSlice;
|
||||||
|
export default reducer;
|
9
ibms_react/src/utils/ajaxRes.js
Normal file
9
ibms_react/src/utils/ajaxRes.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function ajaxRes(res, thunkAPI) {
|
||||||
|
const { code = 9999, msg, data = null } = res;
|
||||||
|
const { fulfillWithValue, rejectWithValue } = thunkAPI;
|
||||||
|
if (code !== "0000" || !data) {
|
||||||
|
return rejectWithValue(msg);
|
||||||
|
} else {
|
||||||
|
return fulfillWithValue(data);
|
||||||
|
}
|
||||||
|
}
|
49
ibms_react/src/utils/baja/alarmBaja.js
Normal file
49
ibms_react/src/utils/baja/alarmBaja.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 首頁指定區域報警訊息(Model)
|
||||||
|
export async function getBuiAlarmStateByBaja(area_tag, building_tag) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let result = [];
|
||||||
|
window.require(["baja!"], function (baja) {
|
||||||
|
baja.Ord.make(
|
||||||
|
`local:|foxs:|alarm:|bql:select alarmData, alarmData.sourceName, sourceState, uuid, ackState where alarmData.sourceName like '%${area_tag}_${building_tag}%' order by timestamp desc`,
|
||||||
|
).get({
|
||||||
|
cursor: {
|
||||||
|
before: function () {
|
||||||
|
console.log("alarm before");
|
||||||
|
},
|
||||||
|
each: function () {
|
||||||
|
const { msgText, sourceName } = this.get("alarmData").$map.$map;
|
||||||
|
const { $enc: ackState } = this.get("ackState").getType();
|
||||||
|
const { $enc: sourceState } = this.get("sourceState").getType();
|
||||||
|
if (ackState !== "acked" && sourceState !== "normal") {
|
||||||
|
result.push({
|
||||||
|
msgText,
|
||||||
|
sourceName,
|
||||||
|
ackState,
|
||||||
|
sourceState,
|
||||||
|
uuid: this.get("uuid").$val,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
after: function () {
|
||||||
|
console.log("alarm after");
|
||||||
|
resolve(result);
|
||||||
|
},
|
||||||
|
limit: -1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 選定期間報警
|
||||||
|
export function getDeviceAlarmByBaja(
|
||||||
|
ord,
|
||||||
|
{ alarmClass, startDate_millisecond, endDate_millisecond, recoverState, ackState },
|
||||||
|
) {
|
||||||
|
var _recoverState = recoverState ? "!= null" : "= null";
|
||||||
|
var _ackState = ackState ? "= 'acked'" : "= 'unacked'";
|
||||||
|
window.requirejs(["baja!"], (baja) => {
|
||||||
|
baja.Ord.make(
|
||||||
|
`local:|foxs:|alarm:|bql:select timestamp, ackState, alarmClass, alarmClassDisplayName, alarmValue, alarmData, alarmData.sourceName, uuid, alarmData.msgText, alarmData.numericValue, alarmData.presentValue, alarmData.status, alarmData.toState, normalTime from openAlarms where alarmClass='${alarmClass}_AlarmClass' and timestamp.millis > '${startDate_millisecond}' and timestamp.millis < '${endDate_millisecond}' and normalTime ${_recoverState} and ackState ${_ackState} order by timestamp asc`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
26
ibms_react/src/utils/baja/deviceBaja.js
Normal file
26
ibms_react/src/utils/baja/deviceBaja.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 訂閱設備
|
||||||
|
// 針對數值的點位
|
||||||
|
export function SubscribeDeviceByBaja(ord) {
|
||||||
|
window.require &&
|
||||||
|
window.requirejs(["baja!"], (baja) => {
|
||||||
|
console.log("進入 bajaSubscriber 準備執行BQL訂閱");
|
||||||
|
const sub = new baja.subscriber();
|
||||||
|
sub.attach("changed", (prop, cx) => {
|
||||||
|
console.log(prop, cx);
|
||||||
|
});
|
||||||
|
// ord 為要訂閱的點位
|
||||||
|
baja.Ord.make(ord)
|
||||||
|
.get({
|
||||||
|
subscriber: sub,
|
||||||
|
// cursor 對返回的數據做處理
|
||||||
|
cursor: {
|
||||||
|
before: function () {},
|
||||||
|
each: function () {},
|
||||||
|
after: function () {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((point) => {
|
||||||
|
console.log(point);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
118
ibms_react/src/utils/baja/electricityBaja.js
Normal file
118
ibms_react/src/utils/baja/electricityBaja.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { n4Sup } from "@CON";
|
||||||
|
import {
|
||||||
|
getCurElectricity,
|
||||||
|
getElectricMeter,
|
||||||
|
getHourElectricMeter,
|
||||||
|
getWeekElectricMeter,
|
||||||
|
} from "@STORE";
|
||||||
|
import { roundDecimal } from "@UTIL";
|
||||||
|
// 首頁指定區域總電錶歷史信息
|
||||||
|
/*
|
||||||
|
@param {Date} start yyyy-mm-dd
|
||||||
|
@param {Date} end yyyy-mm-dd
|
||||||
|
@param {string} range hourly|daily
|
||||||
|
*/
|
||||||
|
export async function getTotalElectricByBaja(ordPath, date, range, dispatch) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth() + 1 >= 10 ? date.getMonth() + 1 : `0${date.getMonth() + 1}`;
|
||||||
|
const curDate = date.getDate();
|
||||||
|
let startDate;
|
||||||
|
const endDate = `${year}-${month}-${curDate}`;
|
||||||
|
const rangePara = range === "weekly" ? "daily" : range;
|
||||||
|
const weekday = date.getDay();
|
||||||
|
if (range === "weekly") {
|
||||||
|
startDate = `${year}-${month}-${curDate - weekday - 7}`;
|
||||||
|
} else {
|
||||||
|
startDate = `${year}-${month}-${curDate - 1}`;
|
||||||
|
}
|
||||||
|
let result = [];
|
||||||
|
window.require &&
|
||||||
|
window.require(["baja!"], function (baja) {
|
||||||
|
baja.Ord.make(
|
||||||
|
`local:|foxs:|history:/${n4Sup}/${ordPath}?peroid=timerange;start=${startDate}T00:00:00.000+08:00;end=${endDate}T24:00:00.000+08:00;delta=true|bql:history:HistoryRollup.rollup(history:RollupInterval '${rangePara}')`,
|
||||||
|
).get({
|
||||||
|
cursor: {
|
||||||
|
before: function () {},
|
||||||
|
each: function () {
|
||||||
|
result.push({
|
||||||
|
date: this.get("timestamp").$date,
|
||||||
|
// time: this.get("timestamp").$time,
|
||||||
|
timestamp: this.get("timestamp").$cEncStr,
|
||||||
|
min: this.get("min"),
|
||||||
|
max: this.get("max"),
|
||||||
|
sum: this.get("sum"),
|
||||||
|
avg: this.get("avg"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
after: function () {
|
||||||
|
const yesterday = result.filter((r) => r.date.$day === date.getDate() - 1);
|
||||||
|
const today = result.filter((r) => r.date.$day === date.getDate());
|
||||||
|
if (range === "daily") {
|
||||||
|
// 日
|
||||||
|
dispatch(
|
||||||
|
getElectricMeter([
|
||||||
|
{ text: "今日用電量", data: roundDecimal(today[0].sum) },
|
||||||
|
{ text: "昨日用電量", data: roundDecimal(yesterday[0].sum) },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
} else if (range === "hourly") {
|
||||||
|
dispatch(
|
||||||
|
getHourElectricMeter({
|
||||||
|
current: today.map((t) => t.sum),
|
||||||
|
last: yesterday.map((y) => y.sum),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (range === "weekly") {
|
||||||
|
// 周
|
||||||
|
const weekParam = new Date(
|
||||||
|
`${year}-${month}-${curDate - weekday}T00:00:00`,
|
||||||
|
).getTime();
|
||||||
|
const currentData = result.filter(
|
||||||
|
(r) => new Date(`${r.timestamp}`).getTime() >= weekParam,
|
||||||
|
);
|
||||||
|
const lastData = result.filter(
|
||||||
|
(r) => new Date(`${r.timestamp}`).getTime() < weekParam,
|
||||||
|
);
|
||||||
|
getWeekElectricMeter({
|
||||||
|
current: currentData.map((c) => c.sum),
|
||||||
|
last: lastData.map((l) => l.sum),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
limit: -1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurPower(ordPath, dispatch) {
|
||||||
|
window.require &&
|
||||||
|
window.require(["baja!"], function (baja) {
|
||||||
|
const sub = new baja.Subscriber();
|
||||||
|
sub.attach("changed", function (prop) {
|
||||||
|
const value = roundDecimal(this.get("out").getValue());
|
||||||
|
dispatch(
|
||||||
|
getCurElectricity([
|
||||||
|
{ data: value, text: "即時功率 " },
|
||||||
|
{ data: roundDecimal(value / 4), text: "即時契約容量占比 " },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
baja.Ord.make(`local:|foxs:|station:|slot:/${ordPath}`)
|
||||||
|
.get()
|
||||||
|
.then((folder) => {
|
||||||
|
folder
|
||||||
|
.getSlots()
|
||||||
|
.is("control:NumericWritable")
|
||||||
|
.eachValue((point) => {
|
||||||
|
if (point.getDisplayName() === "P") {
|
||||||
|
baja.Ord.make(
|
||||||
|
`local:|foxs:|station:|slot:/TPE/B1/EE/E4/R2F/NA/WHT/N1/${point.getDisplayName()}`,
|
||||||
|
).get({
|
||||||
|
subscriber: sub,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
34
ibms_react/src/utils/baja/index.js
Normal file
34
ibms_react/src/utils/baja/index.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { getBuiAlarmStateByBaja, getDeviceAlarmByBaja } from "./alarmBaja";
|
||||||
|
import { SubscribeDeviceByBaja } from "./deviceBaja";
|
||||||
|
import { getTotalElectricByBaja, getCurPower } from "./electricityBaja";
|
||||||
|
// 下載 N4 內置requireJS
|
||||||
|
function loadRequireJS() {
|
||||||
|
const config = document.createElement("script");
|
||||||
|
config.type = "text/javascript";
|
||||||
|
config.src = "/requirejs/config.js";
|
||||||
|
document.head.appendChild(config);
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.type = "text/javascript";
|
||||||
|
script.src = "/module/js/com/tridium/js/ext/require/require.min.js?";
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUserAccountByBaja(callBackFunc = null) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
window.require &&
|
||||||
|
window.require(["baja!"], function (baja) {
|
||||||
|
const user_name = baja.getUserName();
|
||||||
|
user_name ? resolve(user_name) : window.location.replace("http://220.132.206.5:8080/login");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
loadRequireJS,
|
||||||
|
setUserAccountByBaja,
|
||||||
|
getBuiAlarmStateByBaja,
|
||||||
|
getDeviceAlarmByBaja,
|
||||||
|
SubscribeDeviceByBaja,
|
||||||
|
getTotalElectricByBaja,
|
||||||
|
getCurPower,
|
||||||
|
};
|
101
ibms_react/src/utils/forge.js
Normal file
101
ibms_react/src/utils/forge.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { forgeTokenBaseUrl } from "@CON";
|
||||||
|
import { loadDataVizExtn } from "@STORE";
|
||||||
|
// 載入 forgeCss 樣式
|
||||||
|
function loadForgeCss(src) {
|
||||||
|
const link = document.createElement("link");
|
||||||
|
link.rel = "stylesheet";
|
||||||
|
link.href = src;
|
||||||
|
link.type = "text/css";
|
||||||
|
document.head.appendChild(link);
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
// 載入 forgeScript 樣式,並讓 Autodesk 變成全域
|
||||||
|
function loadForgeScript(src) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.type = "text/javascript";
|
||||||
|
script.src = src;
|
||||||
|
script.async = true;
|
||||||
|
script.defer = true;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
function getForgeToken(onTokenReady) {
|
||||||
|
fetch(forgeTokenBaseUrl)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
// ACCESS_TOKEN;
|
||||||
|
const { access_token, expires_in } = res.dictionary;
|
||||||
|
onTokenReady(access_token, expires_in);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
env: "AutodeskProduction2",
|
||||||
|
api: "streamingV2",
|
||||||
|
getAccessToken: getForgeToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
function initializeForge(container, resolve, reject) {
|
||||||
|
window.Autodesk?.Viewing.Initializer(options, function () {
|
||||||
|
// Create Viewer instance
|
||||||
|
let viewer = new window.Autodesk.Viewing.GuiViewer3D(container);
|
||||||
|
var startedCode = viewer.start();
|
||||||
|
if (startedCode > 0) {
|
||||||
|
console.error("錯誤: 無法載入 WebGL not supported");
|
||||||
|
reject({ ok: false, errCode: startedCode });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ ok: true, viewer });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// forge init
|
||||||
|
export function forgeInit(container) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
if (!window.Autodesk) {
|
||||||
|
loadForgeCss(
|
||||||
|
"https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css",
|
||||||
|
);
|
||||||
|
|
||||||
|
loadForgeScript(
|
||||||
|
"https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.min.js",
|
||||||
|
).onload = () => {
|
||||||
|
initializeForge(container, resolve, reject);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
initializeForge(container, resolve, reject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modelLoad(viewer, urn, dispatch) {
|
||||||
|
const documentId = `urn:${urn}`;
|
||||||
|
window.Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
|
||||||
|
|
||||||
|
function onDocumentLoadSuccess(viewerDocument) {
|
||||||
|
var defaultModel = viewerDocument.getRoot().getDefaultGeometry();
|
||||||
|
viewer.loadDocumentNode(viewerDocument, defaultModel);
|
||||||
|
|
||||||
|
viewer.addEventListener(window.Autodesk.Viewing.GEOMETRY_LOADED_EVENT, async function () {
|
||||||
|
// 取得所有 forge dbid
|
||||||
|
const allDbIds = getAllDbIds(this);
|
||||||
|
const dataVizExtn = await viewer.loadExtension("Autodesk.DataVisualization");
|
||||||
|
dispatch(loadDataVizExtn(dataVizExtn));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDocumentLoadFailure() {
|
||||||
|
console.error("錯誤: Model 導入失敗");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取得 forge dbid
|
||||||
|
function getAllDbIds(viewer) {
|
||||||
|
var instanceTree = viewer.model?.getData().instanceTree;
|
||||||
|
|
||||||
|
var allDbIdsStr = Object.keys(instanceTree.nodeAccess.dbIdToIndex);
|
||||||
|
|
||||||
|
return allDbIdsStr.map(function (id) {
|
||||||
|
return parseInt(id);
|
||||||
|
});
|
||||||
|
}
|
28
ibms_react/src/utils/index.js
Normal file
28
ibms_react/src/utils/index.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {
|
||||||
|
loadRequireJS,
|
||||||
|
setUserAccountByBaja,
|
||||||
|
getBuiAlarmStateByBaja,
|
||||||
|
getDeviceAlarmByBaja,
|
||||||
|
SubscribeDeviceByBaja,
|
||||||
|
getTotalElectricByBaja,
|
||||||
|
getCurPower,
|
||||||
|
} from "./baja/index";
|
||||||
|
|
||||||
|
import { forgeInit, modelLoad } from "./forge";
|
||||||
|
import { ajaxRes } from "./ajaxRes";
|
||||||
|
import { userLogin } from "./login";
|
||||||
|
import { roundDecimal } from "./math";
|
||||||
|
export {
|
||||||
|
loadRequireJS,
|
||||||
|
setUserAccountByBaja,
|
||||||
|
getBuiAlarmStateByBaja,
|
||||||
|
getDeviceAlarmByBaja,
|
||||||
|
SubscribeDeviceByBaja,
|
||||||
|
getTotalElectricByBaja,
|
||||||
|
getCurPower,
|
||||||
|
forgeInit,
|
||||||
|
modelLoad,
|
||||||
|
ajaxRes,
|
||||||
|
userLogin,
|
||||||
|
roundDecimal,
|
||||||
|
};
|
17
ibms_react/src/utils/login.js
Normal file
17
ibms_react/src/utils/login.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { loginBaseUrl } from "@CON/index";
|
||||||
|
export const userLogin = async (e, user, callBackFunc = null) => {
|
||||||
|
e?.preventDefault();
|
||||||
|
const {
|
||||||
|
code,
|
||||||
|
msg,
|
||||||
|
data: { token, expires },
|
||||||
|
} = await axios.post(loginBaseUrl, user);
|
||||||
|
console.log(code, msg);
|
||||||
|
if (code !== "0000") {
|
||||||
|
console.log(msg || "系統內部發生錯誤,請聯絡系統管理員");
|
||||||
|
} else {
|
||||||
|
document.cookie = `JWT-Authorization=${token}; max-age=${expires}`;
|
||||||
|
callBackFunc && callBackFunc("/");
|
||||||
|
}
|
||||||
|
};
|
3
ibms_react/src/utils/math.js
Normal file
3
ibms_react/src/utils/math.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const roundDecimal = (value) => {
|
||||||
|
return parseFloat(value).toFixed(2);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user