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",
|
||||
"private": true,
|
||||
"homepage": "./",
|
||||
"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/react": "^13.4.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-bootstrap": "^2.7.0",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.7.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"requirejs": "^2.3.6",
|
||||
"three": "^0.149.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "craco eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@ -35,8 +52,9 @@
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"proxy": "http://localhost:3604",
|
||||
"devDependencies": {
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"jest-editor-support": "^31.0.1"
|
||||
"@craco/craco": "^7.0.0",
|
||||
"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>
|
||||
<html lang="en">
|
||||
<!-- @noSnoop -->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<meta name="description" content="Web site created using create-react-app" />
|
||||
<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" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Mitsubishi</title>
|
||||
<script src="%PUBLIC_URL%/Require.js"></script>
|
||||
<!-- <script type="text/javascript" src="http://220.132.206.5:8080/requirejs/config.js"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="http://220.132.206.5:8080/module/js/com/tridium/js/ext/require/require.min.js?"
|
||||
></script> -->
|
||||
<script>
|
||||
//重新轉址 for Niagara4
|
||||
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>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="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`.
|
||||
-->
|
||||
<div id="model_root"></div>
|
||||
</body>
|
||||
</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 './App.css';
|
||||
import { useMemo, useEffect, useState } from "react";
|
||||
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() {
|
||||
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 (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
{/* 主選單 */}
|
||||
<Navbar bg="dark" variant="dark">
|
||||
<Container fluid>
|
||||
<Navbar.Brand href="#home">
|
||||
<img alt="" src={Logo} width="150" className="d-inline-block align-top img-fluid" />
|
||||
</Navbar.Brand>
|
||||
<Nav className="w-100 d-flex justify-content-between">
|
||||
<NavDropdown
|
||||
title={selectedBuiFullName ? selectedBuiFullName : "選擇大樓"}
|
||||
className="d-flex align-items-center fs-5"
|
||||
>
|
||||
<NavDropdown.Item disabled href="#">
|
||||
選擇大樓
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
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 ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { createHashRouter, RouterProvider } from "react-router-dom";
|
||||
import store from "./stores/index";
|
||||
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(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
// 添加请求拦截器
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
const myCookie = document.cookie.replace(
|
||||
/(?:(?:^|.*;\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))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
// 添加响应拦截器
|
||||
axios.interceptors.response.use(
|
||||
function (response) {
|
||||
// 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