用電即時分佈圖表 | 首頁進度小卡 | 系統小卡樣式修改 | 歷史資料新增大類 | 設備管理修改文字

This commit is contained in:
koko 2024-11-05 15:49:19 +08:00
parent c5345db462
commit a7054c07b5
13 changed files with 223 additions and 206 deletions

View File

@ -1,2 +1,3 @@
export const GET_REALTIME_DIST_API = `/api/Energe/GetRealTimeDistribution`;
export const GET_ELECUSE_DAY_API = `/api/Energe/GetElecUseDay`;
export const GET_TAI_POWER_API = `/api/Energe/GetTaipower`;

View File

@ -1,10 +1,20 @@
import {
GET_REALTIME_DIST_API,
GET_ELECUSE_DAY_API,
GET_TAI_POWER_API,
} from "./api";
import instance from "@/util/request";
import apihandler from "@/util/apihandler";
export const getRealTimeDist = async () => {
const res = await instance.post(GET_REALTIME_DIST_API);
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const getElecUseDay = async () => {
const res = await instance.post(GET_ELECUSE_DAY_API);

View File

@ -27,6 +27,7 @@
"title": "历史资料",
"building_name": "厂区",
"device_name": "设备名称",
"system_category": "系统类别",
"device_category": "设备类别",
"category": "类别",
"value": "数值",
@ -243,7 +244,7 @@
"phone": "手机",
"created_at": "建立时间",
"operation": "功能",
"name_placeholder": "请输入使用者名称",
"name_placeholder": "请输入使用者、帐号名称",
"role_placeholder": "请输入角色名称",
"change_password": "变更密码",
"choose": "选择"

View File

@ -27,6 +27,7 @@
"title": "歷史資料",
"building_name": "廠區",
"device_name": "設備名稱",
"system_category": "系統類別",
"device_category": "設備類別",
"category": "類別",
"value": "數值",
@ -243,7 +244,7 @@
"phone": "手機",
"created_at": "建立時間",
"operation": "功能",
"name_placeholder": "請輸入使用者名稱",
"name_placeholder": "請輸入使用者、帳號名稱",
"role_placeholder": "請輸入角色名稱",
"change_password": "變更密碼",
"choose": "選擇"

View File

@ -18,6 +18,7 @@
"title": "Historical Data",
"building_name": "Building",
"device_name": "Device Name",
"system_category": "System Category",
"device_category": "Device Category",
"category": "Category",
"value": "Value",
@ -155,7 +156,7 @@
"phone": "Phone",
"email": "Email",
"created_at": "Creation Date",
"maintenance": "Maintenance",
"maintenance": "Upkeep",
"repair": "Repair",
"company_info": "Company Info",
"repair_item": "Repair Item",
@ -243,8 +244,8 @@
"phone": "Phone",
"created_at": "Created Time",
"operation": "Function",
"name_placeholder": "Please enter user name",
"role_placeholder": "Please enter the role name",
"name_placeholder": "Please enter the user's name / account",
"role_placeholder": "Please enter the role's name",
"change_password": "Change Password",
"choose": "Choose"
},
@ -261,8 +262,8 @@
"submit": "Submit",
"edit": "Edit",
"delete": "Delete",
"deselect_all": "Deselect all",
"select_all": "Select all",
"deselect_all": "Deselect All",
"select_all": "Select All",
"phone_format": "Please enter the correct phone number format",
"email_format": "Please enter correct email address",
"password_format": "The password must be at least 8 characters long and must contain English and numbers.",

View File

@ -147,7 +147,7 @@ const removeAccount = async (id) => {
:placeholder="t('accountManagement.name_placeholder')"
name="Full_name"
:value="searchData"
class="mr-3"
class="mr-3 w-96"
/>
<Input
:placeholder="t('accountManagement.role_placeholder')"

View File

@ -1,93 +1,87 @@
<script setup>
import { ref, computed } from "vue";
// Mock data based on the image, grouped data
const mockData = ref([
{
title: "Abnormal state",
items: [
[
{ label: "Abnormal", value: 0 },
{ label: "Return", value: 5168 },
],
[
{ label: "Confirmed", value: 182 },
{ label: "Unacknowledged", value: 4986 },
],
],
},
{
title: "Progress",
items: [
[
{ label: "Not dispatched", value: 5126 },
{ label: "Dispatched", value: 42 },
],
[
{ label: "Completed", value: 28 },
{ label: "Unfinished", value: 14 },
],
],
},
]);
const equipmentData = ref({
title: "System Status",
items: [
{ label: "Auxiliary", online: 6, offline: 0, alarm: 0 },
{ label: "Air Detection", online: 31, offline: 0, alarm: 2 },
{ label: "Electricity", online: 12, offline: 0, alarm: 1 },
{ label: "Lighting", online: 20, offline: 3, alarm: 0 },
{ label: "Air Condition", online: 23, offline: 0, alarm: 0 },
],
});
// Compute progress value for each group
const getProgressValue = (group) => {
const total = group.reduce((sum, item) => sum + item.value, 0);
return (group[0].value / total) * 100;
};
const orderData = ref({
title: "Work Order",
items: [
{ label: "Unassigned", value: 2 },
{ label: "Assigned", value: 4 },
{ label: "Completed", value: 1 },
],
});
</script>
<template>
<div class="flex flex-wrap">
<div
v-for="(section, index) in mockData"
:key="index"
class="w-full sm:w-1/2 state-box-col relative px-4"
>
<div class="w-full sm:w-3/5 state-box-col relative ps-2">
<div class="state-box">
<div class="title">
<img class="state-title01" src="@ASSET/img/state-title01.svg" />
<span>{{ section.title }}</span>
<span class="">{{ equipmentData.title }}</span>
<img class="state-title02" src="@ASSET/img/state-title02.svg" />
</div>
<div
v-for="(group, groupIndex) in section.items"
:key="groupIndex"
class="item"
>
<div class="item-title">
<div
v-for="(item, itemIndex) in group"
:key="itemIndex"
class="text"
<table class="table table-sm text-center">
<thead>
<tr class="border-cyan-400 text-cyan-100">
<th></th>
<th>Online</th>
<th>Offline</th>
<th>Alarm</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in equipmentData.items"
:key="index"
class="border-cyan-400"
>
<div class="text-position">
<span>{{ item.label }}</span>
<span>{{ item.value }}</span>
</div>
</div>
</div>
<div class="state-ul">
<img src="@ASSET/img/state-ul.svg" />
<div class="box">
<div class="mark">
<span class="w-10">{{ group[0].value }}</span>
<span
><img class="w-[50px]" src="@ASSET/img/state-ul-text.svg" />
</span>
<span class="w-10">{{ group[1].value }}</span>
</div>
<progress
class="progress [&::-webkit-progress-value]:bg-red-600 [&::-moz-progress-bar]:bg-red-600"
:value="getProgressValue(group)"
max="100"
size=""
></progress>
</div>
</div>
<th class="px-0 text-start">{{ item.label }}</th>
<td>{{ item.online }}</td>
<td>{{ item.offline }}</td>
<td>{{ item.alarm }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="w-full sm:w-2/5 state-box-col relative ps-2">
<div class="state-box">
<div class="title">
<img class="state-title01" src="@ASSET/img/state-title01.svg" />
<span>{{ orderData.title }}</span>
<img class="state-title02" src="@ASSET/img/state-title02.svg" />
</div>
<table class="table table-sm text-center">
<thead>
<tr class="border-cyan-400 text-cyan-100">
<th></th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in orderData.items"
:key="index"
class="border-cyan-400"
>
<th class="px-0 text-start">
<span>{{ item.label }}</span>
</th>
<td>{{ item.value }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
@ -107,7 +101,7 @@ const getProgressValue = (group) => {
}
.state-box {
@apply border-2 border-light-info rounded-sm py-2 px-6 text-white relative;
@apply h-80 border-2 border-light-info rounded-sm py-2 px-6 text-white relative;
}
.state-box:after {
@ -133,58 +127,4 @@ const getProgressValue = (group) => {
.state-box .title .state-title02 {
@apply w-5 ml-1.5;
}
.state-box .item-title {
@apply flex justify-between items-center m-auto mb-1 relative;
}
.state-box .item-title:after {
@apply absolute right-0 -bottom-2.5 w-full h-4 bg-no-repeat bg-center z-10;
content: "";
background-image: url(@ASSET/img/text-position-line.svg);
}
.state-box .item-title .text {
@apply w-1/2 m-0 mb-1.5 flex justify-center items-center relative;
}
.state-box .item-title .text .text-position span {
@apply block text-center text-xs;
}
.state-box .item-title .text .text-position span:nth-child(2) {
text-shadow: 0px 0px 5px rgba(255, 255, 255, 0.8);
}
.mark {
@apply flex justify-between items-center mb-2.5;
}
.state-ul {
@apply relative mb-2.5;
}
.state-ul::after {
@apply absolute -left-3.5 top-0 w-4 h-16 bg-no-repeat bg-center z-10;
content: "";
background-image: url(@ASSET/img/state-ul-background01.svg);
}
.state-ul .box {
@apply absolute top-1/2 w-4/5 left-0 right-0 m-auto text-center -translate-y-1/2;
}
.progress {
@apply w-full h-3 rounded;
appearance: none;
&::-webkit-progress-bar {
border: 1px solid #ffffff;
background-color: #5eabea;
}
&::-webkit-progress-value {
border-radius: 4px;
}
}
</style>

View File

@ -1,35 +1,29 @@
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { ref, onMounted, nextTick, computed } from "vue";
import * as echarts from "echarts";
import { getRealTimeDist } from "@/apis/energy";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const chartDiv = ref(null);
const chartOption = {
tooltip: {
trigger: "item",
formatter: "{b}: {c}kWh",
formatter: (p) => {
return `${p.name}: ${p.value}kWh (${p.data.percentage}%)`;
},
},
series: [
{
type: "sankey",
layout: "none",
nodeWidth: 20,
nodeWidth: 10,
nodeGap: 10,
data: [
{ name: "Total", value: 100 },
{ name: "HVAC System", value: 40 },
{ name: "Lighting System", value: 25 },
{ name: "Elevator System", value: 15 },
{ name: "Outlets", value: 10 },
{ name: "Others", value: 10 },
],
links: [
{ source: "Total", target: "HVAC System", value: 40 },
{ source: "Total", target: "Lighting System", value: 25 },
{ source: "Total", target: "Elevator System", value: 15 },
{ source: "Total", target: "Outlets", value: 10 },
{ source: "Total", target: "Others", value: 10 },
],
right: 180,
data: [],
links: [],
emphasis: {
focus: "adjacency",
},
@ -37,6 +31,9 @@ const chartOption = {
position: "right",
fontSize: 14,
color: "#fff",
formatter: (p) => {
return `${p.name} (${p.data.percentage}%)`;
},
},
itemStyle: {
borderWidth: 0,
@ -50,11 +47,42 @@ const chartOption = {
],
};
onMounted(() => {
nextTick(() => {
const loadData = async () => {
const res = await getRealTimeDist();
if (res.isSuccess) {
const rawData = res.data;
const totalValue = rawData.reduce((acc, item) => acc + item.value, 0);
// data
const data = [
{ name: "Total", value: totalValue, percentage: 100 },
...rawData.map((item) => ({
name: item.key,
value: item.value,
percentage: item.percentage,
})),
];
// links
const links = rawData.map((item) => ({
source: "Total",
target: item.key,
value: item.value,
percentage: item.percentage,
}));
// chartOption
chartOption.series[0].data = data;
chartOption.series[0].links = links;
//
const myChart = echarts.init(chartDiv.value);
myChart.setOption(chartOption);
});
}
};
onMounted(() => {
loadData();
});
</script>
@ -64,19 +92,7 @@ onMounted(() => {
{{ $t("energy.elec_consumption") }}
</h2>
<div class="chart-container">
<div ref="chartDiv" class="w-full min-h-[190px] h-full"></div>
</div>
<div class="text-sm mt-3.5">
<ul class="flex flex-wrap items-center text-white">
<li class="pr-5 relative z-20">
<span class="pr-3.5"> {{ $t("energy.total_elec") }} (kWh)</span>
<span class="pr-3.5">160.05</span>
</li>
<li class="pr-5 relative z-20">
<span class="pr-3.5">{{ $t("energy.green_elec") }} (kWh)</span>
<span class="pr-3.5">39.50</span>
</li>
</ul>
<div ref="chartDiv" class="w-full min-h-[200px] h-full"></div>
</div>
</div>
</template>

View File

@ -25,8 +25,15 @@ import dayjs from "dayjs";
const { searchParams, changeParams } = useSearchParam();
//
const store = useBuildingStore();
//
const {
items: sysMainTagItems,
changeActiveBtn: changeMainSysActiveBtn,
setItems: setMainSysItems,
selectedBtn: selectedMainSysItems,
} = useActiveBtn();
//
const {
items: sysTagItems,
changeActiveBtn: changeSysActiveBtn,
@ -34,6 +41,38 @@ const {
selectedBtn: selectedSysItems,
} = useActiveBtn();
watch(
() => store.mainSys,
() => {
setMainSysItems(
store.mainSubSys.map(({ full_name, main_system_tag }, index) => ({
title: full_name,
key: main_system_tag,
active: searchParams.value.main_system_tag
? searchParams.value.main_system_tag === mian_system_tag
: index === 0,
}))
);
}
);
watch(
() => selectedMainSysItems,
(newVal, oldVal) => {
setSysItems(
store.subSys.filter((s) => s.main_system_tag === newVal.value?.key).map(({ full_name, sub_system_tag }, index) => ({
title: full_name,
key: sub_system_tag,
active: index === 0,
}))
);
},
{
deep: true,
immediate: true,
}
);
watch(
() => selectedSysItems,
(newVal, oldVal) => {
@ -54,21 +93,6 @@ watch(
}
);
watch(
() => store.subSys,
() => {
setSysItems(
store.subSys.map(({ full_name, sub_system_tag }, index) => ({
title: full_name,
key: sub_system_tag,
active: searchParams.value.sub_system_tag
? searchParams.value.sub_system_tag === sub_system_tag
: index === 0,
}))
);
}
);
//
const {
items: points,
@ -82,7 +106,7 @@ const getPoint = async (deviceList) => {
setPoints(
res.data.map((d, index) => ({
...d,
title: d.points,
title: d.item_name,
key: d.points,
active: index === 0,
}))
@ -118,10 +142,10 @@ watch(searchParams, (newVal, oldValue) => {
});
onMounted(() => {
setSysItems(
store.subSys.map(({ full_name, sub_system_tag }, index) => ({
setMainSysItems(
store.mainSubSys.map(({ full_name, main_system_tag }, index) => ({
title: full_name,
key: sub_system_tag,
key: main_system_tag,
active: index === 0,
}))
);
@ -136,7 +160,23 @@ onBeforeMount(() => {
<div class="flex flex-col custom-border p-4 mb-4">
<!-- <HistoryFavoriteOption class="mb-4" />-->
<div class="flex items-center gap-4 mb-4">
<h2 class="text-lg font-bold ps-2">{{ $t("history.device_category") }} :</h2>
<h2 class="text-lg font-bold ps-2">
{{ $t("history.system_category") }} :
</h2>
<ButtonGroup
:items="sysMainTagItems"
:withLine="true"
:onclick="
(e, item) => {
changeMainSysActiveBtn(item);
}
"
/>
</div>
<div class="flex items-center gap-4 mb-4">
<h2 class="text-lg font-bold ps-2">
{{ $t("history.device_category") }} :
</h2>
<ButtonGroup
:items="sysTagItems"
:withLine="true"

View File

@ -65,9 +65,12 @@ watch(searchParams, (newValue, oldValue) => {
</script>
<template>
<div class="flex flex-wrap mt-3">
<div class="flex flex-wrap items-center mt-6">
<h2 class="text-lg font-bold ps-1 pe-4">
{{ $t("history.device_category") }} :
</h2>
<button
class="btn btn-success mr-4"
class="btn btn-sm btn-success"
@click.stop.prevent="changeCheckedItem"
>
{{ checkedItem.length === store.subSys.length ? t("button.deselect_all") : t("button.select_all") }}

View File

@ -216,7 +216,7 @@ provide("system_selectedDevice", { selectedDeviceRealtime, selectedDevice, getCu
<SystemInfoModal :data="selectedDevice" />
<SystemFloorBar />
<div class="grid grid-cols-2 gap-5 mt-8 mb-4">
<div class="col-span-1 h-[80vh] flex flex-col justify-start">
<div class="col-span-1 h-[79vh] flex flex-col justify-start">
<div>
<div class="flex mb-4 items-center">
<span class="flex items-center mr-3" v-if="statusList?.device_normal_text">
@ -237,13 +237,13 @@ provide("system_selectedDevice", { selectedDeviceRealtime, selectedDevice, getCu
</div>
<SystemSubBar class="mt-2 mb-4" />
</div>
<div class="h-full max-h-[75vh] pr-2 overflow-y-auto">
<div class="h-full pr-2 overflow-y-auto">
<RouterView />
</div>
</div>
<div class="col-span-1 h-full flex flex-col justify-between">
<SystemMode />
<div class="min-h-[75vh] relative">
<div class="h-full relative">
<SystemFloor
:class="twMerge('absolute h-full w-full', route.query.mode === '2D' ? 'opacity-100 z-10' : 'opacity-0 z-0')" />
<div

View File

@ -22,14 +22,14 @@ const fitToView = (forge_dbid) => {
<div class="equipment-show" v-for="d in showData" :key="d.full_name">
<template v-if="d.device_list.length > 0">
<p class="title">{{ d.full_name }}</p>
<div class="grid grid-cols-3 gap-5">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
<div class="col-auto relative" v-for="device in d.device_list" :key="device.device_guid">
<div class="item h-full">
<div class="left h-full flex flex-wrap justify-center">
<div class="sec02 w-full">
<img v-if="device.device_image_url" :src="device.device_image_url" alt="" class="w-8 h-8">
<span class="w-8 h-8" v-else></span>
<span class="w-32 break-all">{{ device.full_name }}</span>
<img v-if="device.device_image_url" :src="device.device_image_url" alt="" >
<span v-else></span>
<span>{{ device.full_name }}</span>
</div>
<div class="flex justify-between w-full self-end">
<div class="sec03">
@ -60,7 +60,7 @@ const fitToView = (forge_dbid) => {
}
.item {
@apply flex items-center border border-success py-4 px-5 relative mb-5 after:absolute after:right-0 after:top-3 after:w-6 after:h-10 after:bg-[url(/src/assets/img/equipment/state-background.svg)] after:z-10;
@apply flex items-center border border-success rounded shadow-emerald-600 shadow-inner py-4 px-5 relative mb-5 after:absolute after:right-0 after:top-3 after:w-6 after:h-10 after:bg-[url(/src/assets/img/equipment/state-background.svg)] after:z-10;
;
}
@ -105,7 +105,7 @@ const fitToView = (forge_dbid) => {
background: url(/src/assets/img/equipment/state-title.svg) center center;
position: absolute;
right: 0;
bottom: -10px;
bottom: -18px;
height: 25px;
width: 105px;
background-repeat: no-repeat;
@ -118,10 +118,14 @@ const fitToView = (forge_dbid) => {
display: block;
border-radius: 5px;
margin-right: 10px;
width: 2rem !important;
height: 2rem;
}
.equipment-show .item .sec02 span:nth-child(2) {
font-size: 1.5rem;
font-size: 1.4rem;
width: calc(100% - 2rem);
word-break: break-all;
}
.equipment-show .item .sec03 {

View File

@ -16,7 +16,7 @@ const data = computed(() => {
const columns = [{
title: t("system.attribute"),
key: "points"
key: "full_name"
},
{
title: t("system.value"),