Merge branch 'feature/solarEnergy' into MCUT

This commit is contained in:
koko 2024-09-05 09:59:02 +08:00
commit c66ac43c14
39 changed files with 8429 additions and 4 deletions

1
Frontend/ibms_solar/.env Normal file
View File

@ -0,0 +1 @@
VITE_API_BASEURL = "http://192.168.0.206:8040"

24
Frontend/ibms_solar/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,8 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

View File

@ -0,0 +1,38 @@
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<title>Marketing Dashboard - Application Intel - SmartAdmin v4.5.1</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

5571
Frontend/ibms_solar/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
{
"name": "ibms_solar",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"apexcharts": "^3.53.0",
"axios": "^1.7.7",
"date-fns": "^3.6.0",
"react": "^18.3.1",
"react-apexcharts": "^1.4.1",
"react-datepicker": "^7.3.0",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.1"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"postcss": "^8.4.44",
"tailwindcss": "^3.4.10",
"vite": "^5.4.1"
}
}

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,23 @@
import React from "react";
import { HashRouter, Link, Route, Routes } from "react-router-dom";
import { SiteProvider } from "@/context";
import { Home, Energy } from "@/page";
import { Navbar } from "@/components";
const App = () => (
<HashRouter>
<SiteProvider>
<header>
<Navbar />
</header>
<main className="py-8 w-full bg-zinc-700 min-h-[100vh]">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/energy" element={<Energy />} />
</Routes>
</main>
</SiteProvider>
</HashRouter>
);
export default App;

View File

@ -0,0 +1,5 @@
import logo from './logo.png';
export {
logo,
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,261 @@
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { logo } from "@ASSET";
import { getCookie } from "@/utils";
import { GET_AUTHPAGE_API, AUTHPAGES, GET_SUBAUTHPAGE_API } from "@/constant";
import { AiOutlineClose, AiOutlineMenu } from "react-icons/ai";
import {
FaHome,
FaSolarPanel,
FaUserCircle,
FaCommentDots,
FaCommentSlash,
} from "react-icons/fa";
import axios from "axios";
const Navbar = () => {
const [nav, setNav] = useState(false);
const [showErr, setShowErr] = useState(false);
const [authPage, setAuthPage] = useState([]);
const [subAuthPage, setSubAuthPage] = useState([]);
const [user, setUser] = useState(null);
const [openDropdowns, setOpenDropdowns] = useState({
home: false,
pf1: false,
user: false,
});
const handleNav = () => {
setNav(!nav);
};
const toggleDropdown = (key) => {
setOpenDropdowns((prev) => ({
...prev,
[key]: !prev[key],
}));
};
const toggleErrIcon = () => {
setShowErr(!showErr);
};
const iniFroList = async () => {
const res = await axios.post(GET_AUTHPAGE_API);
setAuthPage(
res.data.data.map((d) => ({
...d,
icon: AUTHPAGES(d.authCode),
}))
);
};
const getSubList = async () => {
const res = await axios.post(GET_SUBAUTHPAGE_API);
console.log("res", res);
setSubAuthPage(
res.data.data.map((d) => ({
...d,
}))
);
};
const navigate = (pageName) => {
sessionStorage.setItem("lastPage", pageName);
window.location.href = "/file/index.html";
};
const navigateToSub = (sub) => {
let pageAct = JSON.parse(sessionStorage.getItem("pageAct"));
pageAct = {
...pageAct,
buildList: sub,
buiTag: sub.building_tag,
buiName: sub.full_name,
};
sessionStorage.setItem("lastPage", "dashboard");
sessionStorage.setItem("pageAct", JSON.stringify(pageAct));
window.location.href = "/file/index.html";
};
//
useEffect(() => {
iniFroList();
getSubList();
setUser(getCookie("niagara_userid"));
}, []);
return (
<nav className="bg-zinc-800 flex justify-between items-center h-16 max-w-full mx-auto px-8 text-white">
<Link to="/" className="flex items-center space-x-3 rtl:space-x-reverse">
<img src={logo} alt="logo" className="w-[200px] md:w-[272px] object-contain" />
</Link>
<ul
className={
nav
? "fixed left-0 top-0 md:flex w-[60%] h-full border-r border-r-gray-900 bg-[#000300] ease-in-out duration-500"
: "ease-in-out w-[60%] duration-500 fixed top-0 bottom-0 left-[-100%] md:flex md:left-0 md:relative md:w-auto"
}
>
<li className=" hover:text-[#ccbfdf] cursor-pointer">
<button
onClick={() => toggleDropdown("home")}
className="flex md:flex-col justify-start md:justify-center items-center text-2xl md:text-3xl w-full px-4 border-b border-slate-500 md:border-none py-5 md:py-0"
>
<FaHome />
<span className="text-sm md:text-sm ps-2 md:ps-0">首頁</span>
</button>
<div
className={`md:absolute ${
openDropdowns.home ? "block" : "hidden"
} bg-zinc-600 min-w-[200px] rounded shadow-md space-y-2`}
>
<ul className="">
<li
key="1"
onClick={() => navigate("schoolView")}
className="text-md ps-4 py-3 hover:bg-zinc-500"
>
校園總覽
</li>
<li
key="2"
onClick={() => navigate("elecSingleLine")}
className="text-md ps-4 py-3 hover:bg-zinc-500"
>
電表單線路
</li>
</ul>
</div>
</li>
{authPage.map((page) => (
<li
key={page.authCode}
className="hover:text-[#ccbfdf] cursor-pointer"
>
{page.authCode === "PF1" ? (
<>
<button
onClick={() => toggleDropdown("pf1")}
className="flex md:flex-col justify-start md:justify-center items-center text-2xl md:text-3xl w-full px-4 border-b border-slate-500 md:border-none py-5 md:py-0"
>
{page.icon}
<span className="text-sm md:text-sm ps-2 md:ps-0">
{page.subName}
</span>
</button>
<div
className={`md:absolute ${
openDropdowns.pf1 ? "block" : "hidden"
} bg-zinc-600 min-w-[200px] rounded shadow-md space-y-2`}
>
<ul className="">
{subAuthPage.map((sub) => (
<li
key={sub.building_tag}
onClick={() => navigateToSub(sub)}
className="text-md text-white ps-4 py-3 hover:bg-zinc-500"
>
{sub.full_name}
</li>
))}
</ul>
</div>
</>
) : (
<button
onClick={() => navigate(page.showView)}
className="flex md:flex-col justify-start md:justify-center items-center text-2xl md:text-3xl w-full px-4 border-b border-slate-500 md:border-none py-5 md:py-0"
>
{page.icon}
<span className="text-sm md:text-sm ps-2 md:ps-0">
{page.subName}
</span>
</button>
)}
</li>
))}
<li className=" hover:text-[#ccbfdf] cursor-pointer">
<Link to="/"
className="flex md:flex-col justify-start md:justify-center items-center text-2xl md:text-3xl w-full px-4 border-b border-slate-500 md:border-none py-5 md:py-0"
>
<FaSolarPanel />
<span className="text-sm md:text-sm ps-2 md:ps-0">太陽能管理</span>
</Link>
</li>
<li className="md:hidden hover:text-[#ccbfdf] cursor-pointer">
<button
onClick={toggleErrIcon}
className="flex md:flex-col justify-start md:justify-center items-center text-2xl md:text-3xl w-full px-4 border-b border-slate-500 md:border-none py-5 md:py-0"
>
{showErr ? <FaCommentSlash /> : <FaCommentDots />}
<span className="text-sm md:text-sm ps-2 md:ps-0">{showErr ? "隱藏警告" : "顯示警告"}</span>
</button>
</li>
<li className="md:hidden hover:text-[#ccbfdf] cursor-pointer">
<button
onClick={() => toggleDropdown("user")}
className="flex md:flex-col justify-start md:justify-center items-center text-2xl md:text-3xl w-full px-4 border-b border-slate-500 md:border-none py-5 md:py-0"
>
<FaUserCircle />
<span className="text-sm md:text-sm ps-2 md:ps-0">{ user ? user : "webUser" }</span>
</button>
<div
className={`md:absolute ${
openDropdowns.user ? "block" : "hidden"
} bg-zinc-600 min-w-[200px] rounded shadow-md space-y-2`}
>
<ul className="">
<li
className="text-md ps-4 py-3 hover:bg-zinc-500"
>
<a href="/logout">登出</a>
</li>
</ul>
</div>
</li>
</ul>
{/* Mobile Navigation Icon */}
<div onClick={handleNav} className="block md:hidden">
{nav ? <AiOutlineClose size={20} /> : <AiOutlineMenu size={20} />}
</div>
{/* user */}
<ul className="hidden md:flex">
<li className=" hover:text-[#ccbfdf] cursor-pointer">
<button
onClick={toggleErrIcon}
className="flex md:flex-col justify-start md:justify-center items-center text-2xl md:text-3xl w-full px-4 border-b border-slate-500 md:border-none py-5 md:py-0"
>
{showErr ? <FaCommentSlash /> : <FaCommentDots />}
<span className="text-sm md:text-sm ps-2 md:ps-0">
{showErr ? "隱藏警告" : "顯示警告"}
</span>
</button>
</li>
<li className=" hover:text-[#ccbfdf] cursor-pointer">
<button
onClick={() => toggleDropdown("user")}
className="flex md:flex-col justify-start md:justify-center items-center text-2xl md:text-3xl w-full px-4 border-b border-slate-500 md:border-none py-5 md:py-0"
>
<FaUserCircle />
<span className="text-sm md:text-sm ps-2 md:ps-0">{ user ? user : "webUser" }</span>
</button>
<div
className={`md:absolute right-0 ${
openDropdowns.user ? "block" : "hidden"
} bg-zinc-600 min-w-[200px] rounded shadow-md space-y-2`}
>
<ul className="">
<li className="text-md ps-4 py-3 hover:bg-zinc-500">
<a href="/logout">登出</a>
</li>
</ul>
</div>
</li>
</ul>
</nav>
);
};
export default Navbar;

View File

@ -0,0 +1,393 @@
import React, { useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { zhTW } from "date-fns/locale";
import Chart from "react-apexcharts";
import historyData from "@/mock/historyData";
const HistoryData = () => {
const [selectedFilter, setSelectedFilter] = useState("day");
const [subFilter, setSubFilter] = useState("today");
const [selectedDate, setSelectedDate] = useState(new Date());
const handleFilterChange = (filter, subFilter = null) => {
setSelectedFilter(filter);
//
if (subFilter) {
setSubFilter(subFilter);
}
switch (subFilter || filter) {
case "today":
setSelectedDate(new Date());
break;
case "yesterday":
setSelectedDate(new Date(Date.now() - 86400000));
break;
case "currentMonth":
const now = new Date();
setSelectedDate(new Date(now.getFullYear(), now.getMonth(), 1));
break;
case "lastMonth":
const lastMonth = new Date();
setSelectedDate(
new Date(lastMonth.getFullYear(), lastMonth.getMonth() - 1, 1)
);
break;
case "currentYear":
setSelectedDate(new Date(new Date().getFullYear(), 0, 1));
break;
case "lastYear":
setSelectedDate(new Date(new Date().getFullYear() - 1, 0, 1));
break;
default:
break;
}
};
const renderButtons = () => {
const buttonConfigs = {
day: ["today", "yesterday"],
month: ["currentMonth", "lastMonth"],
year: ["currentYear", "lastYear"],
};
// selectedFilter
return buttonConfigs[selectedFilter]?.map((filter) => (
<button
key={filter}
className={`text-white px-5 py-2 hover:bg-gray-500 rounded me-2 ${
subFilter === filter ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleFilterChange(selectedFilter, filter)}
>
{filter === "today" && "今日"}
{filter === "yesterday" && "昨日"}
{filter === "currentMonth" && "本月"}
{filter === "lastMonth" && "上個月"}
{filter === "currentYear" && "今年"}
{filter === "lastYear" && "去年"}
</button>
));
};
//
const timestamps = historyData.map((item) => item.timestamp); //
const tempData = historyData.map((item) => item.temp); //
const irrDayHourData = historyData.map((item) => item.irrDayHour); //
const prData = historyData.map((item) => item.pr); //pr
const kwhkwpData = historyData.map((item) => item.kwhkwp); //
const kwhData = historyData.map((item) => item.kwh); //
const options = {
chart: {
type: "line",
height: 500,
stacked: false,
},
stroke: {
width: [0, 0, 0, 4, 4], // bar 0 line 4
},
plotOptions: {
bar: {
columnWidth: "50%",
},
},
colors: ["#008FFB", "#FEB019", "#FF4560", "#00E396", "#775DD0"],
labels: timestamps,
xaxis: {
type: "category",
},
yaxis: [
{
seriesName: "發電量(kWh)",
axisTicks: {
show: true,
},
axisBorder: {
show: true,
color: "#008FFB",
},
labels: {
style: {
colors: "#008FFB",
},
formatter: function (value) {
return value.toFixed(2); // Y0
},
},
title: {
text: "kWh",
style: {
color: "#008FFB",
},
},
},
{
seriesName: "日均發電度數",
opposite: true,
axisTicks: {
show: true,
},
axisBorder: {
show: true,
color: "#FEB019",
},
labels: {
style: {
colors: "#FEB019",
},
formatter: function (value) {
return value.toFixed(2); // Y0
},
},
title: {
text: "kWh/kWp",
style: {
color: "#FEB019",
},
},
},
{
seriesName: "PR(%)",
opposite: true,
axisTicks: {
show: true,
},
axisBorder: {
show: true,
color: "#FF4560",
},
labels: {
style: {
colors: "#FF4560",
},
formatter: function (value) {
return value.toFixed(2); // Y0
},
},
title: {
text: "PR(%)",
style: {
color: "#FF4560",
},
},
},
{
seriesName: "累積日照量(Wh/m2)",
opposite: true,
axisTicks: {
show: true,
},
axisBorder: {
show: true,
color: "#00E396",
},
labels: {
style: {
colors: "#00E396",
},
formatter: function (value) {
return value.toFixed(2); // Y0
},
},
title: {
text: "Wh/m2",
style: {
color: "#00E396",
},
},
},
{
seriesName: "溫度(℃)",
opposite: true,
axisTicks: {
show: true,
},
axisBorder: {
show: true,
color: "#775DD0",
},
labels: {
style: {
colors: "#775DD0",
},
formatter: function (value) {
return value.toFixed(2); // Y0
},
},
title: {
text: "℃",
style: {
color: "#775DD0",
},
},
},
],
tooltip: {
shared: true,
intersect: false,
},
title: {
text: "發電量及累積日照量",
},
theme: {
mode: "dark",
},
};
const series = [
{
name: "發電量(kWh)",
type: "column",
data: kwhData,
},
{
name: "日均發電度數",
type: "column",
data: kwhkwpData,
},
{
name: "PR(%)",
type: "column",
data: prData,
},
{
name: "累積日照量(Wh/m2)",
type: "line",
data: irrDayHourData,
},
{
name: "溫度(℃)",
type: "line",
data: tempData,
},
];
return (
<section className="p-8">
<div className="flex items-center mb-8">
<div className="inline-flex shadow-sm me-8" role="group">
<button
className={`text-white px-5 py-2 hover:bg-gray-500 rounded-l ${
selectedFilter === "day" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleFilterChange("day", "today")}
>
</button>
<button
className={`text-white px-5 py-2 hover:bg-gray-500 ${
selectedFilter === "month" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleFilterChange("month", "currentMonth")}
>
</button>
<button
className={`text-white px-5 py-2 hover:bg-gray-500 ${
selectedFilter === "year" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleFilterChange("year", "currentYear")}
>
</button>
<button
className={`text-white px-5 py-2 hover:bg-gray-500 rounded-r ${
selectedFilter === "historical" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => setSelectedFilter("historical")}
>
歷年
</button>
</div>
{selectedFilter !== "historical" && (
<>
<div className="inline-flex shadow-sm me-8" role="group">
{renderButtons()}
</div>
<DatePicker
selected={selectedDate}
onChange={(date) => setSelectedDate(date)}
dateFormat={
selectedFilter === "month"
? "yyyy/MM"
: selectedFilter === "year"
? "yyyy"
: "yyyy/MM/dd"
}
showMonthYearPicker={selectedFilter === "month"}
showYearPicker={selectedFilter === "year"}
locale={zhTW}
className="border border-gray-300 rounded px-4 py-2 me-4"
/>
</>
)}
<button className="text-white bg-sky-500 hover:bg-sky-600 rounded px-5 py-2">
查詢
</button>
</div>
<div className="bg-gray-600 shadow-sm shadow-gray-500 p-3 mb-8">
<table className="min-w-full ">
<caption className="text-start text-xl p-4 text-white">總結</caption>
<thead>
<tr className="border-t border-b border-gray-400 text-white ">
<th className="px-4 py-3 text-start">時間</th>
<th className="px-4 py-3 text-start">發電量(kWh)</th>
<th className="px-4 py-3 text-start">日均發電度數</th>
<th className="px-4 py-3 text-start">累積日照量(Wh/m2)</th>
<th className="px-4 py-3 text-start">PR(%)</th>
<th className="px-4 py-3 text-start">溫度()</th>
</tr>
</thead>
<tbody>
<tr className="text-white">
<td className="px-4 py-3">2024-09-04</td>
<td className="px-4 py-3">205.00</td>
<td className="px-4 py-3">4.38</td>
<td className="px-4 py-3">5,691.39</td>
<td className="px-4 py-3">76.95</td>
<td className="px-4 py-3">0.00</td>
</tr>
</tbody>
</table>
</div>
<Chart options={options} series={series} type="line" height={500} />
<div className="bg-gray-600 shadow-sm shadow-gray-500 p-3 mt-6">
<table className="min-w-full ">
<caption className="text-start text-xl p-4 text-white">
詳細資訊
</caption>
<thead>
<tr className="border-t border-b border-gray-400 text-white ">
<th className="px-4 py-3 text-start">時間</th>
<th className="px-4 py-3 text-start">發電量(kWh)</th>
<th className="px-4 py-3 text-start">日均發電度數</th>
<th className="px-4 py-3 text-start">累積日照量(Wh/m2)</th>
<th className="px-4 py-3 text-start">PR(%)</th>
<th className="px-4 py-3 text-start">溫度()</th>
</tr>
</thead>
<tbody>
{historyData.map((item, index) => (
<tr key={index} className="border-t border-gray-400 text-white">
<td className="px-4 py-3">{item.timestamp}</td>
<td className="px-4 py-3">{item.kwh}</td>
<td className="px-4 py-3">{item.solarhour}</td>
<td className="px-4 py-3">{item.irradiance}</td>
<td className="px-4 py-3">{item.pr}</td>
<td className="px-4 py-3">{item.temp}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
);
};
export default HistoryData;

View File

@ -0,0 +1,17 @@
import React, { forwardRef } from "react";
const IframeComponent = forwardRef(({ src, onLoad }, ref) => {
return (
<iframe
ref={ref}
src={src}
width="100%"
height="800"
className="border-0"
title="Solar Management"
onLoad={onLoad}
></iframe>
);
});
export default IframeComponent;

View File

@ -0,0 +1,234 @@
import React, { useState } from "react";
import Chart from "react-apexcharts";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { zhTW } from "date-fns/locale";
import inverterData from "@/mock/inverterData.json";
import yearData from "@/mock/yearData.json";
import quarterData from "@/mock/quarterData";
import monthData from "@/mock/monthData";
import todayData from "@/mock/todayData";
const InverterAnalysis = () => {
const [date, setDate] = useState(new Date());
const [activeButton, setActiveButton] = useState(null);
const [quarter, setQuarter] = useState("1"); //
const [barData, setBarData] = useState(todayData);
const handleDateChange = (date) => {
setDate(date);
setActiveButton(null);
};
const handleButtonClick = (buttonType) => {
if (buttonType === "today") {
setDate(new Date());
setBarData(todayData);
} else if (buttonType === "yesterday") {
setDate(new Date(Date.now() - 86400000));
} else if (buttonType === "month") {
setDate(new Date(Date.now() - 86400000));
setBarData(monthData);
} else if (buttonType === "quarter") {
setDate(new Date(Date.now() - 86400000));
setBarData(quarterData);
} else if (buttonType === "year") {
setDate(new Date(Date.now() - 86400000));
setBarData(yearData);
}
setActiveButton(buttonType); //
};
// x
const xCategories = inverterData.xAxisOnTime;
//
const series = Object.keys(inverterData.series).map((inverterId) => {
return {
name: inverterId,
data: inverterData.series[inverterId].map((point) => point.value),
};
});
const options = {
chart: {
type: "heatmap",
height: 500,
background: "#3f3f46",
},
plotOptions: {
heatmap: {
useFillColorAsStroke: true,
colorScale: {
ranges: [
{ from: 0, to: 0.124999, color: "#0000ff", name: "Low" }, // Blue
{ from: 0.125, to: 0.249999, color: "#5541b5", name: "Moderate" }, // Purple
{ from: 0.25, to: 0.374999, color: "#ab836b", name: "High" }, // Brown
{ from: 0.375, to: 0.499999, color: "#dba841", name: "Very High" }, // Yellow
{ from: 0.5, to: 0.624999, color: "#eda42f", name: "Extreme" }, // Orange
{ from: 0.625, to: 0.749999, color: "#f4621c", name: "Severe" }, // Dark Orange
{ from: 0.75, to: 0.874999, color: "#f65017", name: "Critical" }, // Red-Orange
{ from: 0.875, to: 1, color: "#fb2109", name: "Danger" }, // Red
],
},
},
},
dataLabels: {
enabled: false,
},
xaxis: {
type: "category",
categories: xCategories,
},
title: {
text: "各逆變器當日均發電度數",
},
theme: {
mode: "dark",
},
};
const barseries = barData
? Object.keys(barData.datasets).map((Id) => {
return {
name: Id,
data: barData.datasets[Id],
};
})
: [];
const baroptions = {
chart: {
type: "bar",
height: 500,
background: "#3f3f46",
},
xaxis: {
type: "category",
categories: barData ? barData.labels : [],
},
title: {
text: "發電量",
},
theme: {
mode: "dark",
},
};
return (
<section className="p-8">
<div className="flex items-center mb-8">
<div class="inline-flex shadow-sm me-8" role="group">
<button
type="button"
className={`text-white px-5 py-2 hover:bg-gray-500 rounded-l ${
activeButton === "today" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleButtonClick("today")}
>
今天
</button>
<button
type="button"
className={`text-white px-5 py-2 hover:bg-gray-500 rounded-r ${
activeButton === "yesterday" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleButtonClick("yesterday")}
>
昨天
</button>
</div>
<DatePicker
selected={date}
onChange={handleDateChange}
dateFormat="yyyy/MM/dd"
locale={zhTW}
className="border border-gray-300 rounded px-4 py-2 me-4"
/>
<button className="text-white bg-sky-500 hover:bg-sky-600 rounded px-5 py-2">
查詢
</button>
</div>
<div id="chart">
<Chart options={options} series={series} type="heatmap" height={500} />
</div>
<div className="flex items-center mb-8 mt-16">
<div class="inline-flex shadow-sm me-8" role="group">
<button
type="button"
className={`text-white px-5 py-2 hover:bg-gray-500 rounded-l ${
activeButton === "today" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleButtonClick("today")}
>
</button>
<button
type="button"
className={`text-white px-5 py-2 hover:bg-gray-500 ${
activeButton === "month" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleButtonClick("month")}
>
</button>
<button
type="button"
className={`text-white px-5 py-2 hover:bg-gray-500 ${
activeButton === "quarter" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleButtonClick("quarter")}
>
</button>
<button
type="button"
className={`text-white px-5 py-2 hover:bg-gray-500 rounded-r ${
activeButton === "year" ? "bg-sky-500" : "bg-gray-400"
}`}
onClick={() => handleButtonClick("year")}
>
</button>
</div>
<DatePicker
selected={date}
onChange={handleDateChange}
dateFormat={
activeButton === "month"
? "yyyy/MM"
: activeButton === "year"
? "yyyy"
: activeButton === "quarter"
? "yyyy"
: "yyyy/MM/dd"
}
showMonthYearPicker={activeButton === "month"}
showYearPicker={activeButton === "year" || activeButton === "quarter"}
className="border border-gray-300 rounded px-4 py-2 me-4"
locale={zhTW}
/>
{activeButton === "quarter" && (
<select
className="border border-gray-300 rounded px-4 py-2 me-4"
value={quarter}
onChange={(e) => {
setQuarter(e.target.value);
}}
>
<option value="1">1-3</option>
<option value="2">4-6</option>
<option value="3">7-9</option>
<option value="4">10-12</option>
</select>
)}
<button className="text-white bg-sky-500 hover:bg-sky-600 rounded px-5 py-2">
查詢
</button>
</div>
<div id="chart">
<Chart options={baroptions} series={barseries} type="bar" height={500} />
</div>
</section>
);
};
export default InverterAnalysis;

View File

@ -0,0 +1,7 @@
import Navbar from "./Navbar";
import IframeComponent from "./energy/IframeComponent";
import HistoryData from "./energy/HistoryData";
import InverterAnalysis from "./energy/InverterAnalysis";
export { Navbar, IframeComponent, HistoryData, InverterAnalysis };

View File

@ -0,0 +1,4 @@
const BASEURL = import.meta.env.VITE_API_BASEURL;
export const POST_LOGIN = `${BASEURL}/api/Login/`;
export const GET_AUTHPAGE_API = `${BASEURL}/api/GetUsrFroList`;
export const GET_SUBAUTHPAGE_API = `${BASEURL}/api/Device/GetBuild`;

View File

@ -0,0 +1,30 @@
import { FaTv, FaChartPie, FaChartArea, FaChartLine, FaBell, FaServer, FaImage, FaUser } from "react-icons/fa";
const AUTHPAGES = (authCode) => {
switch (authCode) {
case "PF1":
return <FaTv />;
case "PF2":
return <FaChartPie />;
case "PF3":
return <FaChartArea />;
case "PF4":
return <FaChartLine />;
case "PF5":
return <FaBell />;
case "PF6":
return <FaServer />;
case "PF7":
return <FaImage />;
case "PF8":
return <FaUser />;
case "PF9":
return <FaUser />;
case "PF10":
return <FaChartArea />;
default:
return null;
}
};
export default AUTHPAGES;

View File

@ -0,0 +1,5 @@
import { GET_AUTHPAGE_API, GET_SUBAUTHPAGE_API } from "./api_app";
import AUTHPAGES from "./authPage";
import EnergyTABS from "./tabList";
export { GET_AUTHPAGE_API, GET_SUBAUTHPAGE_API, AUTHPAGES, EnergyTABS };

View File

@ -0,0 +1,54 @@
import {
FaFileMedicalAlt,
FaInfoCircle,
FaRegListAlt,
FaChartLine,
FaHistory,
FaExclamationTriangle,
} from "react-icons/fa";
import {
IframeComponent,
InverterAnalysis,
HistoryData,
} from "../components";
const EnergyTABS = (siteId) => [
{
id: "realtime",
icon: <FaFileMedicalAlt />,
title: "即時資訊",
content: <IframeComponent src={`/ord?file:^Solar/${siteId}_Realtime.px|view:?fullScreen=true`} />,
},
{
id: "basicInfo",
icon: <FaInfoCircle />,
title: "基本資料",
content: <IframeComponent src={`/ord?file:^Solar/${siteId}_Info.px|view:?fullScreen=true`} />,
},
{
id: "monitor",
icon: <FaRegListAlt />,
title: "逆變器監控",
content: <IframeComponent src={`/ord?file:^Solar/${siteId}_Inverter.px|view:?fullScreen=true`} />,
},
{
id: "analysis",
icon: <FaChartLine />,
title: "逆變器分析",
content: <InverterAnalysis />,
},
{
id: "history",
icon: <FaHistory />,
title: "歷史資料",
content: <HistoryData />,
},
{
id: "abnormal",
icon: <FaExclamationTriangle />,
title: "異常紀錄",
content: <div className="text-white p-5 text-md">異常紀錄</div>,
},
];
export default EnergyTABS;

View File

@ -0,0 +1,17 @@
import React, { createContext, useState, useContext } from 'react';
const SiteContext = createContext();
export const SiteProvider = ({ children }) => {
const [siteId, setSiteId] = useState(null);
return (
<SiteContext.Provider value={{ siteId, setSiteId }}>
{children}
</SiteContext.Provider>
);
};
export const useSite = () => {
return useContext(SiteContext);
};

View File

@ -0,0 +1,3 @@
import { SiteProvider, useSite } from './SiteContext.jsx';
export { SiteProvider, useSite } ;

View File

@ -0,0 +1,8 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
font-family: 'Inter', sans-serif;
}

View File

@ -0,0 +1,45 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "@/App.jsx";
import axios from "axios";
import "@/index.css";
import { getCookie } from "@/utils";
//
axios.interceptors.request.use(
function (config) {
//
const token = getCookie("JWT-Authorization");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
function (error) {
//
return Promise.reject(error);
}
);
//
axios.interceptors.response.use(
function (response) {
// 2xx
const { status, data } = response;
return {
status,
data,
};
},
function (error) {
// 2xx
return Promise.reject(error);
}
);
// DOM
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);

View File

@ -0,0 +1,242 @@
[
{
"timestamp": "02 AM",
"kwh": 0,
"solarhour": 0,
"irradiance": 0,
"pr": 0,
"avgPR": 0,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 0,
"irrDay": 0,
"irrDayHour": 0,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "03 AM",
"kwh": 0,
"solarhour": 0,
"irradiance": 0,
"pr": 0,
"avgPR": 0,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 0,
"irrDay": 0,
"irrDayHour": 0,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "04 AM",
"kwh": 0,
"solarhour": 0,
"irradiance": 0,
"pr": 0,
"avgPR": 0,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 0,
"irrDay": 0,
"irrDayHour": 0,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "05 AM",
"kwh": 0,
"solarhour": 0,
"irradiance": 27.86,
"pr": 0,
"avgPR": 0,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 0,
"irrDay": 0,
"irrDayHour": 0,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "06 AM",
"kwh": 3,
"solarhour": 1.9166666269302368,
"irradiance": 91.07,
"pr": 54.90191650390625,
"avgPR": 54.90191650390625,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 0.06410256773233414,
"irrDay": 116.26,
"irrDayHour": 116.26,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "07 AM",
"kwh": 10,
"solarhour": 2.9166667461395264,
"irradiance": 268.35,
"pr": 72.0918197631836,
"avgPR": 72.0918197631836,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 0.2777777910232544,
"irrDay": 384.3,
"irrDayHour": 268.04,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "08 AM",
"kwh": 18,
"solarhour": 3.9166667461395264,
"irradiance": 469.84,
"pr": 77.43690490722656,
"avgPR": 77.43690490722656,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 0.6623931527137756,
"irrDay": 853.82,
"irrDayHour": 469.52,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "09 AM",
"kwh": 26,
"solarhour": 4.916666507720947,
"irradiance": 645.26,
"pr": 81.175537109375,
"avgPR": 81.175537109375,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 1.2179486751556396,
"irrDay": 1499.2,
"irrDayHour": 645.38,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "10 AM",
"kwh": 27,
"solarhour": 5.916666507720947,
"irradiance": 710.99,
"pr": 81.14289093017578,
"avgPR": 81.14289093017578,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 1.7948718070983887,
"irrDay": 2210.18,
"irrDayHour": 710.98,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "11 AM",
"kwh": 21,
"solarhour": 6.916666507720947,
"irradiance": 593.44,
"pr": 79.9531478881836,
"avgPR": 79.9531478881836,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 2.2435896396636963,
"irrDay": 2806.13,
"irrDayHour": 595.95,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "12 PM",
"kwh": 30,
"solarhour": 7.916666507720947,
"irradiance": 866.35,
"pr": 78.54755401611328,
"avgPR": 78.54755401611328,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 2.884615421295166,
"irrDay": 3670.85,
"irrDayHour": 864.72,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "13 PM",
"kwh": 26,
"solarhour": 8.916666984558105,
"irradiance": 738.43,
"pr": 77.9903793334961,
"avgPR": 77.9903793334961,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 3.440171003341675,
"irrDay": 4408.71,
"irrDayHour": 737.86,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "14 PM",
"kwh": 24,
"solarhour": 9.916666984558105,
"irradiance": 736.83,
"pr": 76.79364776611328,
"avgPR": 76.79364776611328,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 3.952991485595703,
"irrDay": 5145.73,
"irrDayHour": 737.02,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "15 PM",
"kwh": 20,
"solarhour": 10.916666984558105,
"irradiance": 545.29,
"pr": 76.94789123535156,
"avgPR": 76.94789123535156,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 4.38034200668335,
"irrDay": 5691.39,
"irrDayHour": 545.66,
"generatingCapacity": 0,
"days": 0
},
{
"timestamp": "16 PM",
"kwh": 10,
"solarhour": 11.916666984558105,
"irradiance": 0,
"pr": 76.3684310913086,
"avgPR": 76.3684310913086,
"temp": 0,
"diffSOLARHOUR": 0,
"totaltime": "2024-09-04 ",
"kwhkwp": 4.594017028808594,
"irrDay": 0,
"irrDayHour": 0,
"generatingCapacity": 0,
"days": 0
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
{
"labels": ["2024-09-01", "2024-09-02", "2024-09-03"],
"datasets": {
"031010004010001": [179, 307, 154],
"031010004010002": [95, 152, 78]
}
}

View File

@ -0,0 +1,7 @@
{
"labels": ["2024-01", "2024-02", "2024-03"],
"datasets": {
"031010004010001": [2461, 2404, 2955],
"031010004010002": [1248, 1227, 1504]
}
}

View File

@ -0,0 +1,11 @@
{
"labels": [
"02 AM", "03 AM", "04 AM", "05 AM", "06 AM",
"07 AM", "08 AM", "09 AM", "10 AM", "11 AM"
],
"datasets": {
"031010004010001": [0, 0, 0, 0, 2, 7, 12, 17, 18, 14],
"031010004010002": [0, 0, 0, 0, 1, 3, 6, 9, 9, 7]
}
}

View File

@ -0,0 +1,11 @@
{
"labels": [
"2024-01", "2024-02", "2024-03", "2024-04",
"2024-05", "2024-06", "2024-07", "2024-08", "2024-09"
],
"datasets": {
"031010004010001": [2461, 2404, 2955, 2925, 3733, 3887, 5109, 5402, 640],
"031010004010002": [1248, 1227, 1504, 1487, 1896, 1983, 2595, 2742, 325]
}
}

View File

@ -0,0 +1,68 @@
import React, { useEffect, useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { useSite } from "@/context";
import { EnergyTABS } from "@/constant";
import { FaCheckCircle, FaCloudSunRain } from "react-icons/fa";
const Energy = () => {
const { siteId, setSiteId } = useSite();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState("realtime");
const tabs = EnergyTABS(siteId);
const goback = () => {
setSiteId(null);
navigate("/");
};
return (
<section className="">
<div className="flex items-center justify-between px-10 mb-4">
<div className="flex items-center">
<button
className="text-white bg-emerald-500 shadow-md shadow-emerald-500/50 rounded px-5 py-2 me-2"
onClick={goback}
>
上一頁
</button>
<FaCheckCircle className="text-teal-400 text-xl mx-2" />
<span className="text-white text-xl ">{siteId}</span>
</div>
<div className="flex items-center">
<FaCloudSunRain className="text-white text-4xl me-2"/>
<span className="text-white text-md">
0°C
<br />
降雨機率: 30%
</span>
</div>
</div>
<div className="px-6 mt-6">
<div className="border-b border-[#ffffff26] ">
<ul className="flex flex-wrap text-sm font-medium text-center text-gray-500 dark:text-gray-400">
{tabs.map((tab) => (
<li className="me-2" key={tab.id}>
<button
onClick={() => setActiveTab(tab.id)}
className={`inline-flex items-center justify-center px-4 py-2 ${
activeTab === tab.id
? "text-gray-200 border-[#ffffff26] border border-b-0"
: "border-transparent hover:text-gray-200 hover:border-[#ffffff26]"
} rounded-t-sm`}
>
{tab.icon}
<span className="ms-2">{tab.title}</span>
</button>
</li>
))}
</ul>
</div>
<div className="border border-[#ffffff26] min-h-[100vh]">
{tabs.find((tab) => tab.id === activeTab)?.content}
</div>
</div>
</section>
);
};
export default Energy;

View File

@ -0,0 +1,46 @@
import React, { useRef } from "react";
import { useNavigate } from "react-router-dom";
import { useSite } from "@/context";
import { IframeComponent } from "@/components";
const Home = () => {
const iframeRef = useRef(null);
const { setSiteId } = useSite();
const navigate = useNavigate();
//
const handleIframeLoad = () => {
const iframe = iframeRef.current;
const currentUrl = iframe.contentWindow.location.href;
if (currentUrl.includes("solarEnergyItem")) {
const urlParams = new URLSearchParams(currentUrl.split("?")[1]);
const id = urlParams.get("siteId");
if (id) {
setSiteId(id);
navigate("/energy");
} else {
console.log("找不到 siteId 参数");
}
} else {
console.log("沒有solarEnergyItem");
}
};
return (
<section>
<div>
<h1 className="font-light text-[#a5abb1] text-[26px] ms-12 mb-4">
太陽能管理
</h1>
</div>
<IframeComponent
ref={iframeRef}
src="/ord?file:^Solar/Site.px|view:?fullScreen=true"
onLoad={handleIframeLoad}
/>
</section>
);
};
export default Home;

View File

@ -0,0 +1,7 @@
import Home from './Home';
import Energy from './Energy';
export {
Home,
Energy,
};

View File

@ -0,0 +1,8 @@
export function getCookie(cookieName) {
let cookie = {};
document.cookie.split(";").forEach(function (el) {
let [key, value] = el.split("=");
cookie[key.trim()] = value;
});
return cookie[cookieName];
}

View File

@ -0,0 +1,3 @@
import { getCookie } from "./cookieHelpers";
export { getCookie };

View File

@ -0,0 +1,20 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{js,jsx}'],
theme: {
extend: {
screens: {
xs: '480px',
},
fontFamily: {
inter: ['Inter var', 'sans-serif'],
},
boxShadow: {
card: '0 0 1px 0 rgba(189,192,207,0.06),0 10px 16px -1px rgba(189,192,207,0.2)',
cardhover: '0 0 1px 0 rgba(189,192,207,0.06),0 10px 16px -1px rgba(189,192,207,0.4)',
},
},
},
plugins: [],
}

View File

@ -0,0 +1,19 @@
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
base: process.env.NODE_ENV === "production" ? "./" : "/",
build: {
outDir: "../solar_dist",
emptyOutDir: true,
},
plugins: [react()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
"@ASSET": fileURLToPath(new URL("./src/assets", import.meta.url)),
},
},
})

View File

@ -2058,7 +2058,7 @@ License: You must have a valid license purchased only from wrapbootstrap.com (li
function (res) { function (res) {
if (!res || res.code != "0000" || !res.data) { if (!res || res.code != "0000" || !res.data) {
} else { } else {
let strHtml = `<div class="btn-group mx-4"> let strHtml = `<div class="btn-group ml-5">
<a href="javascript:;" id="homeBtn" data-toggle="dropdown" data-page="dashboard" data-tabname="topFunBtn" class="text-center"> <a href="javascript:;" id="homeBtn" data-toggle="dropdown" data-page="dashboard" data-tabname="topFunBtn" class="text-center">
<i class="fal fa-home fa-2x"></i><br>首頁 <i class="fal fa-home fa-2x"></i><br>首頁
</a> </a>
@ -2071,7 +2071,7 @@ License: You must have a valid license purchased only from wrapbootstrap.com (li
$.each(res.data, function (i, v) { $.each(res.data, function (i, v) {
if (v.authCode == "PF1") { if (v.authCode == "PF1") {
strHtml += `<div class="btn-group mx-4" > strHtml += `<div class="btn-group ml-5" >
<a href="javascript:;" id="sysMonTopBtn" class="text-center dropdown-toggle" data-toggle="dropdown" data-tabname="topFunBtn" class="text-center"> <a href="javascript:;" id="sysMonTopBtn" class="text-center dropdown-toggle" data-toggle="dropdown" data-tabname="topFunBtn" class="text-center">
<i class="fal fa-tv fa-2x"></i><br>${v.subName} <i class="fal fa-tv fa-2x"></i><br>${v.subName}
</a> </a>
@ -2100,7 +2100,7 @@ License: You must have a valid license purchased only from wrapbootstrap.com (li
: v.authCode == "PF8" : v.authCode == "PF8"
? "fa-user" ? "fa-user"
: ""; : "";
strHtml += `<div class="btn-group mx-4"> strHtml += `<div class="btn-group ml-5">
<a href="javascript:;" name="topFunBtn" data-tabname="topFunBtn" class="dropdown-toggle no-arrow text-center" <a href="javascript:;" name="topFunBtn" data-tabname="topFunBtn" class="dropdown-toggle no-arrow text-center"
data-page="${v.showView}"> data-page="${v.showView}">
<i class="fal ${icon} fa-2x"></i><br>${v.subName} <i class="fal ${icon} fa-2x"></i><br>${v.subName}
@ -2108,7 +2108,11 @@ License: You must have a valid license purchased only from wrapbootstrap.com (li
</div>`; </div>`;
} }
}); });
strHtml += `<div class="btn-group ml-5">
<a href="./solar_dist/index.html" name="topFunBtn" data-tabname="topFunBtn" class="dropdown-toggle no-arrow text-center" >
<i class="fal fa-solar-panel fa-2x"></i><br>太陽能管理
</a>
</div>`
$("#froLisPage").html(strHtml); $("#froLisPage").html(strHtml);
// $("#homeBtn").YTNavbar("init"); // $("#homeBtn").YTNavbar("init");