設備管理點位新增 | 密碼欄位可視 | 能源管理英文版

This commit is contained in:
koko 2024-10-18 09:17:07 +08:00
parent d2a9027869
commit 68f0af6229
22 changed files with 355 additions and 147 deletions

View File

@ -13,3 +13,4 @@ export const GET_ASSET_FLOOR_LIST_API = `/AssetManage/GetFloorList`;
export const POST_ASSET_FLOOR_API = `/AssetManage/SaveFloor`; export const POST_ASSET_FLOOR_API = `/AssetManage/SaveFloor`;
export const GET_ASSET_IOT_LIST_API = `/AssetManage/GetIOTList`; export const GET_ASSET_IOT_LIST_API = `/AssetManage/GetIOTList`;
export const GET_ASSET_SUB_POINT_API = `/AssetManage/GetSubPoint`;

View File

@ -10,6 +10,7 @@ import {
GET_ASSET_IOT_LIST_API, GET_ASSET_IOT_LIST_API,
DELETE_ASSET_ITEM_API, DELETE_ASSET_ITEM_API,
POST_ASSET_SINGLE_API, POST_ASSET_SINGLE_API,
GET_ASSET_SUB_POINT_API,
} from "./api"; } from "./api";
import instance from "@/util/request"; import instance from "@/util/request";
import apihandler from "@/util/apihandler"; import apihandler from "@/util/apihandler";
@ -98,7 +99,8 @@ export const postAssetSingle = async (data) => {
}); });
} else { } else {
value.forEach((element, index) => { value.forEach((element, index) => {
formData.append(`${key}[${index}].device_guid`, element); formData.append(`sub_device[${index}].device_number`, element.device_number);
formData.append(`sub_device[${index}].points`, element.points);
}); });
} }
} else { } else {
@ -141,8 +143,22 @@ export const postAssetFloor = async (formData) => {
}); });
}; };
export const getAssetIOTList = async () => { export const getAssetIOTList = async (sub_system_tag, points) => {
const res = await instance.post(GET_ASSET_IOT_LIST_API); const res = await instance.post(GET_ASSET_IOT_LIST_API, {
sub_system_tag,
points,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const getAssetSubPoint = async (sub_system_tag) => {
const res = await instance.post(GET_ASSET_SUB_POINT_API, {
sub_system_tag,
});
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,

View File

@ -37,7 +37,7 @@
@layer utilities { @layer utilities {
.btn{ .btn{
@apply px-6 py-1; @apply px-4 py-1;
text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.9); text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.9);
box-shadow: 0px 0px 5px rgba(255, 255, 255, 0.8); box-shadow: 0px 0px 5px rgba(255, 255, 255, 0.8);
} }

View File

@ -3,9 +3,9 @@ import { defineProps, computed } from "vue";
import VueDatePicker from "@vuepic/vue-datepicker"; import VueDatePicker from "@vuepic/vue-datepicker";
import "@vuepic/vue-datepicker/dist/main.css"; import "@vuepic/vue-datepicker/dist/main.css";
import { zhTW } from "date-fns/locale"; import { zhTW } from "date-fns/locale";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
items: Array, items: Array,
withLine: Boolean, withLine: Boolean,
@ -62,13 +62,12 @@ const curWidth = computed(() => {
showPreview: false, showPreview: false,
}" }"
v-model="item.value" v-model="item.value"
locale="zh-TW" locale='en-US'
:day-names="['一', '二', '三', '四', '五', '六', '日']"
:format="item.dateFormat" :format="item.dateFormat"
:enable-time-picker="false" :enable-time-picker="false"
:time-picker="Boolean(item.timePicker)" :time-picker="Boolean(item.timePicker)"
:placeholder="item.placeholder" :placeholder="item.placeholder"
selectText="確定" :selectText="t('button.submit')"
:input-class-name=" :input-class-name="
twMerge('dp-custom-input', 'btn border', inputClass) twMerge('dp-custom-input', 'btn border', inputClass)
" "

View File

@ -52,13 +52,19 @@ const changePageData = (currentPage) => {
watch( watch(
() => [props.dataSource, props.sort], () => [props.dataSource, props.sort],
([newVal, newVal2]) => { ([newVal, newVal2]) => {
// console.log(props.dataSource, newVal); const totalPageNumber =
currentPage.value = 1;
totalPage.value =
props.totalPages || Math.ceil(props.dataSource.length / props.pageSize); props.totalPages || Math.ceil(props.dataSource.length / props.pageSize);
if (currentPage.value > totalPageNumber) {
currentPage.value = 1;
} else {
//
currentPage.value = currentPage.value;
}
totalPage.value = totalPageNumber;
props.onPageChange props.onPageChange
? current_table_data.updateDataSource(props.dataSource) ? current_table_data.updateDataSource(props.dataSource)
: changePageData(1); : changePageData(currentPage.value || 1);
}, },
{ {
deep: true, deep: true,
@ -99,7 +105,7 @@ const pageInput = computed(() => {
:key="`page${page}`" :key="`page${page}`"
:class=" :class="
twMerge( twMerge(
'w-10 h-10 mx-1 border-2 border-sub-success rounded-full flex items-center justify-center', 'w-10 h-10 mx-1 border-2 border-sub-success rounded-full flex items-center justify-center cursor-pointer',
currentPage === page ? 'bg-sub-success' : 'bg-transparent' currentPage === page ? 'bg-sub-success' : 'bg-transparent'
) )
" "
@ -145,7 +151,7 @@ const pageInput = computed(() => {
<span <span
class="w-full text-center absolute -bottom-8 left-1/2 -translate-x-1/2 text-base" class="w-full text-center absolute -bottom-8 left-1/2 -translate-x-1/2 text-base"
> >
{{ totalItems || dataSource.length }} {{ $t("table.in_otal") }}</span {{ totalItems || dataSource.length }} {{ $t("table.in_otal") }}</span
> >
</label> </label>
<ul <ul

View File

@ -163,14 +163,33 @@ onMounted(() => {
} }
::v-deep .ant-menu-submenu-title:active { ::v-deep .ant-menu-submenu-title:active {
background-color: #35759d !important; color: #35759d !important;
background-color: transparent !important;
}
::v-deep .ant-menu-item:not(.ant-menu-item-selected) {
&::before {
@apply absolute w-[15px] h-[15px] bottom-3.5 left-7 bg-no-repeat z-10 grayscale;
content: "";
background: url(@ASSET/img/chart-data-background03.svg) center center;
}
&:active {
background-color: transparent !important;
}
} }
::v-deep .ant-menu-item-selected { ::v-deep .ant-menu-item-selected {
background-color: #35759d !important; @apply bg-transparent relative;
&::before {
@apply absolute w-[15px] h-[15px] bottom-3.5 left-7 bg-no-repeat z-10;
content: "";
background: url(@ASSET/img/chart-data-background03.svg) center center;
}
&:active {
background-color: transparent !important;
}
a { a {
color: #fff !important; color: #89d2ff !important;
text-shadow: 0px 0px 1px #fff;
} }
} }
</style> </style>

View File

@ -204,6 +204,7 @@
"operate_text": "显示名称", "operate_text": "显示名称",
"fill_text": "请由系统人员填写", "fill_text": "请由系统人员填写",
"equipment_point": "设备点位", "equipment_point": "设备点位",
"point": "点位",
"add_sensor": "新增感测器", "add_sensor": "新增感测器",
"associated_device": "关联设备", "associated_device": "关联设备",
"choose": "选择", "choose": "选择",

View File

@ -204,6 +204,7 @@
"operate_text": "顯示名稱", "operate_text": "顯示名稱",
"fill_text": "請由系統人員填寫", "fill_text": "請由系統人員填寫",
"equipment_point": "設備點位", "equipment_point": "設備點位",
"point": "點位",
"add_sensor": "新增感測器", "add_sensor": "新增感測器",
"associated_device": "關聯設備", "associated_device": "關聯設備",
"choose": "選擇", "choose": "選擇",

View File

@ -204,6 +204,7 @@
"operate_text": "Display name", "operate_text": "Display name",
"fill_text": "Please fill it in by system personnel", "fill_text": "Please fill it in by system personnel",
"equipment_point": "Equipment point", "equipment_point": "Equipment point",
"point": "Point",
"add_sensor": "Add new sensor", "add_sensor": "Add new sensor",
"associated_device": "Associated devices", "associated_device": "Associated devices",
"choose": "Choose", "choose": "Choose",

View File

@ -53,7 +53,9 @@ import {
faFireExtinguisher, faFireExtinguisher,
faDoorOpen, faDoorOpen,
faCar, faCar,
faWind faWind,
faEye,
faEyeSlash,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
/* add icons to the library */ /* add icons to the library */
@ -108,7 +110,9 @@ library.add(
faFireExtinguisher, faFireExtinguisher,
faDoorOpen, faDoorOpen,
faCar, faCar,
faWind faWind,
faEye,
faEyeSlash
); );
export default library; export default library;

View File

@ -71,7 +71,7 @@ const deleteItem = async (id) => {
> >
<template #buttonContent="{ item }"> <template #buttonContent="{ item }">
<span class="text-base">{{ item.title }}</span> <span class="text-base">{{ item.title }}</span>
<template v-if="!item.is_IOT"> <!-- <template v-if="!item.is_IOT">
<span <span
class="ml-2 text-base text-warning" class="ml-2 text-base text-warning"
@click.stop.prevent="() => edit(item)" @click.stop.prevent="() => edit(item)"
@ -84,7 +84,7 @@ const deleteItem = async (id) => {
> >
<FontAwesomeIcon :icon="['fas', 'trash-alt']" /> <FontAwesomeIcon :icon="['fas', 'trash-alt']" />
</span> </span>
</template> </template> -->
</template> </template>
</ButtonConnectedGroup> </ButtonConnectedGroup>
</div> </div>

View File

@ -140,7 +140,10 @@ const edit = async (id) => {
ext: file.saveName.split(".")[file.saveName.split(".").length - 1], ext: file.saveName.split(".")[file.saveName.split(".").length - 1],
})); }));
res.data.sub_device = res.data.sub_device?.map( res.data.sub_device = res.data.sub_device?.map(
({ device_guid }) => device_guid ({ device_number, points }) => ({
device_number,
points,
})
); );
editRecord.value = res.data; editRecord.value = res.data;
openModal(); openModal();

View File

@ -1,6 +1,10 @@
<script setup> <script setup>
import { getAssetIOTList } from "@/apis/asset"; import { getAssetIOTList } from "@/apis/asset";
import { ref, computed, inject, watch } from "vue"; import { ref, computed, inject, watch, onMounted } from "vue";
import ButtonGroup from "@/components/customUI/ButtonGroup.vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import useBuildingStore from "@/stores/useBuildingStore";
import { getAssetSubPoint } from "@/apis/asset";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const { formState } = inject("asset_table_modal_form"); const { formState } = inject("asset_table_modal_form");
@ -11,65 +15,98 @@ const tableColumns = computed(() => [
}, },
{ {
title: "tag", title: "tag",
key: "tag", key: "device_name",
}, },
{ {
title: t("assetManagement.system_name"), title: t("assetManagement.point"),
key: "full_name", key: "point_name",
}, },
// {
// title: "",
// key: "status",
// },
{ {
title: t("assetManagement.operation"), title: t("assetManagement.operation"),
key: "operation", key: "operation",
width: 100, width: 100,
}, },
]); ]);
//
const store = useBuildingStore();
const {
items: sysTagItems,
changeActiveBtn: changeSysActiveBtn,
setItems: setSysItems,
selectedBtn: selectedSysItems,
} = useActiveBtn();
const modalColumns = computed(() => [ const modalColumns = computed(() => [
{ {
title: t("assetManagement.choose"), title: t("assetManagement.choose"),
key: "check", key: "check",
width: 100,
}, },
{ {
title: "tag", title: "tag",
key: "tag", key: "device_name",
}, },
{ {
title: t("assetManagement.system_name"), title: t("assetManagement.point"),
key: "full_name", key: "point_name",
}, },
]); ]);
//
const {
items: points,
changeActiveBtn: changeActivePoint,
setItems: setPoints,
selectedBtn: selectedPoints,
} = useActiveBtn();
const getPoint = async (sub_system_tag) => {
const res = await getAssetSubPoint(sub_system_tag);
setPoints(
res.data.map((d, index) => ({
...d,
title: d.points,
key: d.points,
active: false,
}))
);
};
// TODO: // TODO:
const iotData = ref([]); const iotData = ref([]);
const iotCheckedList = ref([]); //
const currentCheckedList = ref([]); //
const getIOTData = async () => { const getIOTData = async (sub_system_tag = null, points = null) => {
const res = await getAssetIOTList(); const res = await getAssetIOTList(sub_system_tag, points);
let data = res.data.map((d) => ({ let data = res.data.map((d) => ({
...d, ...d,
title: d.full_name, title: d.full_name,
key: d.device_guid, key: d.device_number + d.points,
tag: d.device_number, checked: formState.value.sub_device?.some(
checked: formState.value.sub_device?.includes(d.device_guid), ({ device_number, points }) =>
device_number + points === `${d.device_number}${d.points}`
),
})); }));
iotData.value = data; iotData.value = data;
iotCheckedList.value = data.filter(({ checked }) => checked); if (!sub_system_tag && !points) {
iotCheckedList.value = data.filter(({ checked }) => checked);
currentCheckedList.value = [...iotCheckedList.value];
}
}; };
const iotCheckedList = ref([]);
const onChange = (value, checked) => { const onChange = (value, checked) => {
const d = iotData.value.find(({ device_guid }) => device_guid === value); const d = iotData.value.find(
({ device_number, points }) => `${device_number}${points}` === value
);
let newList = []; let newList = [];
if (checked) { if (checked) {
newList = [...iotCheckedList.value, d]; newList = [...currentCheckedList.value, d];
} else { } else {
newList = iotCheckedList.value.filter( newList = currentCheckedList.value.filter(
(checked) => checked.device_guid !== d.device_guid (item) => item.device_number + item.points !== value
); );
} }
iotCheckedList.value = newList; currentCheckedList.value = newList;
return newList; return newList;
}; };
@ -78,8 +115,12 @@ const openModal = () => {
}; };
const onOk = () => { const onOk = () => {
formState.value.sub_device = iotCheckedList.value.map( iotCheckedList.value = currentCheckedList.value;
({ device_guid }) => device_guid formState.value.sub_device = currentCheckedList.value.map(
({ device_number, points }) => ({
device_number,
points,
})
); );
onCancel(); onCancel();
}; };
@ -88,23 +129,57 @@ const onCancel = () => {
asset_add_IoT_item.close(); asset_add_IoT_item.close();
}; };
watch(
[selectedSysItems, selectedPoints],
([newSys, newPoint]) => {
if (newSys || newPoint) {
getIOTData(newSys?.key, newPoint?.key);
}
},
{
deep: true,
}
);
watch(selectedSysItems, (newVal) => {
if (newVal) {
getPoint(newVal.key);
}
});
watch( watch(
formState, formState,
() => { () => {
getIOTData(); getIOTData();
setSysItems(
store.subSys.map(({ full_name, sub_system_tag }, index) => ({
title: full_name,
key: sub_system_tag,
active: 0,
}))
);
}, },
{ {
deep: true, deep: true,
} }
); );
const deleteItem = (id) => { const deleteItem = (value) => {
console.log(formState.value.sub_device, id); const itemToDelete = iotCheckedList.value.find(
// formState.value.device_guid = formState.value.device_guid.filter( ({ device_number, points }) => `${device_number}${points}` === value
// (d) => d !== id );
// );
const newList = onChange(id, false); if (itemToDelete) {
formState.value.sub_device = newList.map(({ device_guid }) => device_guid); iotCheckedList.value = iotCheckedList.value.filter(
(item) => item.device_number + item.points !== value
);
currentCheckedList.value = iotCheckedList.value;
formState.value.sub_device = iotCheckedList.value.map(
({ device_number, points }) => ({
device_number,
points,
})
);
}
}; };
</script> </script>
@ -131,15 +206,11 @@ const deleteItem = (id) => {
<template #bodyCell="{ record, column, index }"> <template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template <template v-if="column.key === 'index'">{{ index + 1 }}</template
><template v-if="column.key === 'operation'"> ><template v-if="column.key === 'operation'">
<!-- <span
class="mr-5 text-base text-warning cursor-pointer"
@click.stop.prevent="() => edit(record)"
>
<FontAwesomeIcon :icon="['fas', 'pencil-alt']"></FontAwesomeIcon>
</span> -->
<span <span
class="text-base text-error cursor-pointer" class="text-base text-error cursor-pointer"
@click.stop.prevent="() => deleteItem(record.device_guid)" @click.stop.prevent="
() => deleteItem(record.device_number + record.points)
"
> >
<FontAwesomeIcon <FontAwesomeIcon
:icon="['fas', 'trash-alt']" :icon="['fas', 'trash-alt']"
@ -155,16 +226,39 @@ const deleteItem = (id) => {
width="900" width="900"
> >
<template #modalContent> <template #modalContent>
<ButtonGroup
:items="sysTagItems"
:withLine="true"
:onclick="
(e, item) => {
changeSysActiveBtn(item);
}
"
class="my-3"
/>
<ButtonGroup
v-if="selectedSysItems"
:items="points"
:withLine="true"
:onclick="
(e, item) => {
changeActivePoint(item);
}
"
class="my-3"
/>
<Table :columns="modalColumns" :dataSource="iotData" :withStyle="false"> <Table :columns="modalColumns" :dataSource="iotData" :withStyle="false">
<template #bodyCell="{ record, column, index }"> <template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'check'"> <template v-if="column.key === 'check'">
<Checkbox <Checkbox
name="device_guid" name="device_number"
:value="record.device_guid" :value="record.device_number + record.points"
:onChange="onChange" :onChange="onChange"
:checked=" :checked="
iotCheckedList?.some( currentCheckedList?.some(
({ device_guid }) => device_guid === record.device_guid ({ device_number, points }) =>
`${device_number}${points}` ===
`${record.device_number}${record.points}`
) )
" "
></Checkbox> ></Checkbox>

View File

@ -13,6 +13,10 @@ const props = defineProps({
reset: Function, reset: Function,
getData: Function, getData: Function,
}); });
const showPassword = ref(false);
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value;
};
let userSchema = ref( let userSchema = ref(
yup.object({ yup.object({
@ -143,17 +147,24 @@ const onOk = async () => {
<Input <Input
v-if="!Boolean(formState?.Id)" v-if="!Boolean(formState?.Id)"
:value="formState" :value="formState"
class="my-2" class="relative my-2"
name="Password" name="Password"
type="password" :type="showPassword ? 'text' : 'password'"
> >
<template #topLeft>{{ $t("accountManagement.password") }}</template> <template #topLeft>{{ $t("accountManagement.password") }}</template>
<template #bottomLeft <template #bottomLeft
><span class="text-error text-base"> ><span class="text-error text-base">
{{ formErrorMsg.Password }} {{ formErrorMsg.Password }}
</span></template </span></template
></Input >
> <template #bottomRight>
<FontAwesomeIcon
:icon="['fas', showPassword ? 'eye-slash' : 'eye']"
class="fa-2x absolute top-14 right-0 transform -translate-x-1/2 cursor-pointer"
@click="togglePasswordVisibility"
/>
</template>
</Input>
<Input :value="formState" class="my-2" name="Name"> <Input :value="formState" class="my-2" name="Name">
<template #topLeft>{{ $t("accountManagement.name") }}</template> <template #topLeft>{{ $t("accountManagement.name") }}</template>
<template #bottomLeft <template #bottomLeft

View File

@ -15,6 +15,10 @@ const props = defineProps({
const formState = ref({ const formState = ref({
Password: "", Password: "",
}); });
const showPassword = ref(false);
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value;
};
let userSchema = yup.object({ let userSchema = yup.object({
Password: yup.string().required(t("button.required")), Password: yup.string().required(t("button.required")),
@ -49,7 +53,12 @@ const onOk = async () => {
<template #modalContent> <template #modalContent>
<p class="mt-10 text-3xl">{{ account.Name }}</p> <p class="mt-10 text-3xl">{{ account.Name }}</p>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between"> <form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input :value="formState" class="my-2" name="Password" type="password"> <Input
:value="formState"
class="relative my-2"
name="Password"
:type="showPassword ? 'text' : 'password'"
>
<template #topLeft>{{ <template #topLeft>{{
$t("accountManagement.change_password") $t("accountManagement.change_password")
}}</template> }}</template>
@ -57,8 +66,15 @@ const onOk = async () => {
><span class="text-error text-base"> ><span class="text-error text-base">
{{ formErrorMsg.Password }} {{ formErrorMsg.Password }}
</span></template </span></template
></Input >
> <template #bottomRight>
<FontAwesomeIcon
:icon="['fas', showPassword ? 'eye-slash' : 'eye']"
class="fa-2x absolute top-14 right-0 transform -translate-x-1/2 cursor-pointer"
@click="togglePasswordVisibility"
/>
</template>
</Input>
</form> </form>
</template> </template>
<template #modalAction> <template #modalAction>

View File

@ -20,7 +20,7 @@ const yesterdayTodayData = {
// //
const weekComparisonData = { const weekComparisonData = {
categories: ["週五", "週六", "週日", "週一", "週二", "週三", "週四"], categories: ["Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"],
values: [ values: [
{ {
name: "This week's electricity consumption", name: "This week's electricity consumption",
@ -131,6 +131,31 @@ const generateCylinderChartOption = (categories, values) => {
}, },
z: 12, z: 12,
}, },
//
{
name: null,
type: "pictorialBar",
symbolSize: [barWidth, 5],
symbolOffset: [-9, 4],
symbolPosition: "start",
data: values[0].value,
z: 12,
itemStyle: {
color: "#0ca9d4",
},
},
{
name: null,
type: "pictorialBar",
symbolSize: [barWidth, 5],
symbolOffset: [9, 4],
symbolPosition: "start",
data: values[1].value,
itemStyle: {
color: "#ffe000",
},
z: 12,
},
], ],
grid: { grid: {
left: "3%", left: "3%",
@ -180,14 +205,14 @@ const weekComparisonOption = generateCylinderChartOption(
(kWH) (kWH)
</h2> </h2>
<div class="w-[48%]"> <div class="w-[48%]">
<a href="#" class="text-con"> <div class="text-con">
<img src="@ASSET/img/chart-title01.svg" /> <img src="@ASSET/img/chart-title01.svg" />
<span>{{ $t("dashboard.today_electricity_consumption") }}</span> <span>{{ $t("dashboard.today_electricity_consumption") }}</span>
</a> </div>
<a href="#" class="text-con"> <div class="text-con">
<img src="@ASSET/img/chart-title02.svg" /> <img src="@ASSET/img/chart-title02.svg" />
<span>{{ $t("dashboard.yesterday_electricity_consumption") }}</span> <span>{{ $t("dashboard.yesterday_electricity_consumption") }}</span>
</a> </div>
</div> </div>
</div> </div>
<div class="h-[250px]"> <div class="h-[250px]">
@ -208,14 +233,14 @@ const weekComparisonOption = generateCylinderChartOption(
(kWH) (kWH)
</h2> </h2>
<div class="w-[48%]"> <div class="w-[48%]">
<a href="#" class="text-con"> <div class="text-con">
<img src="@ASSET/img/chart-title01.svg" /> <img src="@ASSET/img/chart-title01.svg" />
<span>{{ $t("dashboard.thisweek_electricity_consumption") }}</span> <span>{{ $t("dashboard.thisweek_electricity_consumption") }}</span>
</a> </div>
<a href="#" class="text-con"> <div class="text-con">
<img src="@ASSET/img/chart-title02.svg" /> <img src="@ASSET/img/chart-title02.svg" />
<span>{{ $t("dashboard.lastweek_electricity_consumption") }}</span> <span>{{ $t("dashboard.lastweek_electricity_consumption") }}</span>
</a> </div>
</div> </div>
</div> </div>
<div class="h-[250px]"> <div class="h-[250px]">

View File

@ -10,10 +10,10 @@ const defaultChartOption = ref({
}, },
}, },
legend: { legend: {
data: ["尖峰", "半尖峰", "離峰度數"], data: ["Peak", "Semi-Peak", "Off-Peak"],
textStyle: { textStyle: {
color: "#ffffff", color: "#ffffff",
fontSize: 16, fontSize: 14,
}, },
orient: "horizontal", orient: "horizontal",
bottom: "0%", bottom: "0%",
@ -28,18 +28,18 @@ const defaultChartOption = ref({
xAxis: { xAxis: {
type: "category", type: "category",
data: [ data: [
"1月", "Jan",
"2月", "Feb",
"3月", "Mar",
"4月", "Apr",
"5月", "May",
"6月", "Jun",
"7月", "Jul",
"8月", "Aug",
"9月", "Sep",
"10月", "Oct",
"11月", "Nov",
"12月", "Dec",
], ],
axisLabel: { axisLabel: {
color: "#ffffff", color: "#ffffff",
@ -53,7 +53,7 @@ const defaultChartOption = ref({
}, },
series: [ series: [
{ {
name: "尖峰", name: "Peak",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [
@ -65,7 +65,7 @@ const defaultChartOption = ref({
}, },
}, },
{ {
name: "半尖峰", name: "Semi-Peak",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [
@ -76,7 +76,7 @@ const defaultChartOption = ref({
}, },
}, },
{ {
name: "離峰度數", name: "Off-Peak",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [

View File

@ -10,10 +10,10 @@ const defaultChartOption = ref({
}, },
}, },
legend: { legend: {
data: ["碳排當量", "減量目標"], data: ["Carbon Equivalent", "Reduction Target"],
textStyle: { textStyle: {
color: "#ffffff", color: "#ffffff",
fontSize: 16, fontSize: 14,
}, },
orient: "horizontal", orient: "horizontal",
bottom: "0%", bottom: "0%",
@ -28,18 +28,18 @@ const defaultChartOption = ref({
xAxis: { xAxis: {
type: "category", type: "category",
data: [ data: [
"1月", "Jan",
"2月", "Feb",
"3月", "Mar",
"4月", "Apr",
"5月", "May",
"6月", "Jun",
"7月", "Jul",
"8月", "Aug",
"9月", "Sep",
"10月", "Oct",
"11月", "Nov",
"12月", "Dec",
], ],
axisLabel: { axisLabel: {
color: "#ffffff", color: "#ffffff",
@ -53,7 +53,7 @@ const defaultChartOption = ref({
}, },
series: [ series: [
{ {
name: "碳排當量", name: "Carbon Equivalent",
type: "bar", type: "bar",
data: [ data: [
5400, 6500, 7200, 7500, 9800, 9500, 11200, 11500, 11800, 7500, 6500, 5400, 6500, 7200, 7500, 9800, 9500, 11200, 11500, 11800, 7500, 6500,
@ -64,7 +64,7 @@ const defaultChartOption = ref({
}, },
}, },
{ {
name: "減量目標", name: "Reduction Target",
type: "bar", type: "bar",
data: [ data: [
5000, 6000, 6000, 7000, 9500, 9000, 10000, 11000, 10000, 7200, 6000, 5000, 6000, 6000, 7000, 9500, 9000, 10000, 11000, 10000, 7200, 6000,

View File

@ -14,7 +14,7 @@ const data = {
], ],
series: [ series: [
{ {
name: "即時趨勢", name: "Real-Time Trend", //
type: "line", type: "line",
data: [320, 310, 300, 305, 310, 300], data: [320, 310, 300, 305, 310, 300],
smooth: true, smooth: true,
@ -26,7 +26,7 @@ const data = {
}, },
}, },
{ {
name: "契約容量", name: "Contract Capacity", //
type: "line", type: "line",
data: [400, 400, 400, 400, 400, 400], data: [400, 400, 400, 400, 400, 400],
smooth: true, smooth: true,
@ -38,7 +38,7 @@ const data = {
}, },
}, },
{ {
name: "警戒容量", name: "Alert Capacity", //
type: "line", type: "line",
data: [350, 350, 350, 350, 350, 350], data: [350, 350, 350, 350, 350, 350],
smooth: true, smooth: true,
@ -50,7 +50,7 @@ const data = {
}, },
}, },
{ {
name: "偵測值", name: "Measured Value", //
type: "line", type: "line",
data: [280, 300, 290, 295, 300, 290], data: [280, 300, 290, 295, 300, 290],
smooth: true, smooth: true,

View File

@ -19,10 +19,10 @@ const defaultChartOption = ref({
}, },
}, },
legend: { legend: {
data: ["尖峰", "半尖峰", "離峰度數"], data: ["Peak", "Semi-Peak", "Off-Peak"],
textStyle: { textStyle: {
color: "#ffffff", color: "#ffffff",
fontSize: 16, fontSize: 14,
}, },
orient: "horizontal", orient: "horizontal",
bottom: "0%", bottom: "0%",
@ -31,7 +31,7 @@ const defaultChartOption = ref({
top: "5%", top: "5%",
left: "0%", left: "0%",
right: "0%", right: "0%",
bottom: "10%", bottom: "15%",
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
@ -49,7 +49,7 @@ const defaultChartOption = ref({
}, },
series: [ series: [
{ {
name: "尖峰", name: "Peak",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [
@ -61,7 +61,7 @@ const defaultChartOption = ref({
}, },
}, },
{ {
name: "半尖峰", name: "Semi-Peak",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [
@ -73,7 +73,7 @@ const defaultChartOption = ref({
}, },
}, },
{ {
name: "離峰度數", name: "Off-Peak",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [

View File

@ -10,10 +10,10 @@ const defaultChartOption = ref({
}, },
}, },
legend: { legend: {
data: [ "流動電費","基本電費", "電費總額"], data: ["Var. Elec. Cost", "Fixed Elec. Cost", "Total Elec. Cost"],
textStyle: { textStyle: {
color: "#ffffff", color: "#ffffff",
fontSize: 16, fontSize: 14,
}, },
orient: "horizontal", orient: "horizontal",
bottom: "0%", bottom: "0%",
@ -28,18 +28,18 @@ const defaultChartOption = ref({
xAxis: { xAxis: {
type: "category", type: "category",
data: [ data: [
"1月", "Jan",
"2月", "Feb",
"3月", "Mar",
"4月", "Apr",
"5月", "May",
"6月", "Jun",
"7月", "Jul",
"8月", "Aug",
"9月", "Sep",
"10月", "Oct",
"11月", "Nov",
"12月", "Dec",
], ],
axisLabel: { axisLabel: {
color: "#ffffff", color: "#ffffff",
@ -53,7 +53,7 @@ const defaultChartOption = ref({
}, },
series: [ series: [
{ {
name: "流動電費", name: "Var. Elec. Cost",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [
@ -65,7 +65,7 @@ const defaultChartOption = ref({
}, },
}, },
{ {
name: "基本電費", name: "Fixed Elec. Cost",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [
@ -78,7 +78,7 @@ const defaultChartOption = ref({
}, },
{ {
name: "電費總額", name: "Total Elec. Cost",
type: "bar", type: "bar",
stack: "total", stack: "total",
data: [ data: [
@ -99,7 +99,9 @@ onMounted(() => {
<template> <template>
<div class="bg-slate-800 p-3"> <div class="bg-slate-800 p-3">
<div class="text-white mb-3 text-base">{{$t("energy.monthly_elec_consumption")}}</div> <div class="text-white mb-3 text-base">
{{ $t("energy.monthly_elec_consumption") }}
</div>
<div class="bar-box"> <div class="bar-box">
<BarChart <BarChart
id="electricity_bill_chart" id="electricity_bill_chart"

View File

@ -22,6 +22,10 @@ const formState = ref({
account: "", account: "",
password: "", password: "",
}); });
const showPassword = ref(false);
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value;
};
const imageSrc = import.meta.env.MODE === "production" ? "./logo.svg" : Image; const imageSrc = import.meta.env.MODE === "production" ? "./logo.svg" : Image;
@ -63,17 +67,22 @@ const doLogin = async () => {
</span> </span>
</div> </div>
<div class="w-full flex flex-col items-end my-2"> <div class="w-full flex flex-col items-end my-2">
<div class="w-full flex justify-between"> <div class="w-full flex justify-between relative">
<label class="mr-2 text-2xl text-black" for="password">{{ <label class="mr-2 text-2xl text-black" for="password">{{
$t("password") $t("password")
}}</label> }}</label>
<input <input
name="password" name="password"
class="w-5/6 border-2 rounded-full bg-white text-black px-4 py-1" class="w-5/6 border-2 rounded-full bg-white text-black px-4 py-1"
type="password" :type="showPassword ? 'text' : 'password'"
v-model="formState.password" v-model="formState.password"
autocomplete="off" autocomplete="off"
/> />
<FontAwesomeIcon
:icon="['fas', showPassword ? 'eye-slash' : 'eye']"
class="text-gray-400 text-2xl absolute top-1/2 right-4 transform -translate-y-1/2 cursor-pointer"
@click="togglePasswordVisibility"
/>
</div> </div>
<span class="text-error text-base"> <span class="text-error text-base">
{{ formErrorMsg.password }} {{ formErrorMsg.password }}