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

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 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,
DELETE_ASSET_ITEM_API,
POST_ASSET_SINGLE_API,
GET_ASSET_SUB_POINT_API,
} from "./api";
import instance from "@/util/request";
import apihandler from "@/util/apihandler";
@ -98,7 +99,8 @@ export const postAssetSingle = async (data) => {
});
} else {
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 {
@ -141,8 +143,22 @@ export const postAssetFloor = async (formData) => {
});
};
export const getAssetIOTList = async () => {
const res = await instance.post(GET_ASSET_IOT_LIST_API);
export const getAssetIOTList = async (sub_system_tag, points) => {
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, {
msg: res.msg,

View File

@ -37,7 +37,7 @@
@layer utilities {
.btn{
@apply px-6 py-1;
@apply px-4 py-1;
text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.9);
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 "@vuepic/vue-datepicker/dist/main.css";
import { zhTW } from "date-fns/locale";
import { twMerge } from "tailwind-merge";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({
items: Array,
withLine: Boolean,
@ -62,13 +62,12 @@ const curWidth = computed(() => {
showPreview: false,
}"
v-model="item.value"
locale="zh-TW"
:day-names="['一', '二', '三', '四', '五', '六', '日']"
locale='en-US'
:format="item.dateFormat"
:enable-time-picker="false"
:time-picker="Boolean(item.timePicker)"
:placeholder="item.placeholder"
selectText="確定"
:selectText="t('button.submit')"
:input-class-name="
twMerge('dp-custom-input', 'btn border', inputClass)
"

View File

@ -52,13 +52,19 @@ const changePageData = (currentPage) => {
watch(
() => [props.dataSource, props.sort],
([newVal, newVal2]) => {
// console.log(props.dataSource, newVal);
currentPage.value = 1;
totalPage.value =
const totalPageNumber =
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
? current_table_data.updateDataSource(props.dataSource)
: changePageData(1);
: changePageData(currentPage.value || 1);
},
{
deep: true,
@ -99,7 +105,7 @@ const pageInput = computed(() => {
:key="`page${page}`"
:class="
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'
)
"
@ -145,7 +151,7 @@ const pageInput = computed(() => {
<span
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>
<ul

View File

@ -163,14 +163,33 @@ onMounted(() => {
}
::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 {
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 {
color: #fff !important;
color: #89d2ff !important;
text-shadow: 0px 0px 1px #fff;
}
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,10 @@
<script setup>
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";
const { t } = useI18n();
const { formState } = inject("asset_table_modal_form");
@ -11,65 +15,98 @@ const tableColumns = computed(() => [
},
{
title: "tag",
key: "tag",
key: "device_name",
},
{
title: t("assetManagement.system_name"),
key: "full_name",
title: t("assetManagement.point"),
key: "point_name",
},
// {
// title: "",
// key: "status",
// },
{
title: t("assetManagement.operation"),
key: "operation",
width: 100,
},
]);
//
const store = useBuildingStore();
const {
items: sysTagItems,
changeActiveBtn: changeSysActiveBtn,
setItems: setSysItems,
selectedBtn: selectedSysItems,
} = useActiveBtn();
const modalColumns = computed(() => [
{
title: t("assetManagement.choose"),
key: "check",
width: 100,
},
{
title: "tag",
key: "tag",
key: "device_name",
},
{
title: t("assetManagement.system_name"),
key: "full_name",
title: t("assetManagement.point"),
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:
const iotData = ref([]);
const iotCheckedList = ref([]); //
const currentCheckedList = ref([]); //
const getIOTData = async () => {
const res = await getAssetIOTList();
const getIOTData = async (sub_system_tag = null, points = null) => {
const res = await getAssetIOTList(sub_system_tag, points);
let data = res.data.map((d) => ({
...d,
title: d.full_name,
key: d.device_guid,
tag: d.device_number,
checked: formState.value.sub_device?.includes(d.device_guid),
key: d.device_number + d.points,
checked: formState.value.sub_device?.some(
({ device_number, points }) =>
device_number + points === `${d.device_number}${d.points}`
),
}));
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 d = iotData.value.find(({ device_guid }) => device_guid === value);
const d = iotData.value.find(
({ device_number, points }) => `${device_number}${points}` === value
);
let newList = [];
if (checked) {
newList = [...iotCheckedList.value, d];
newList = [...currentCheckedList.value, d];
} else {
newList = iotCheckedList.value.filter(
(checked) => checked.device_guid !== d.device_guid
newList = currentCheckedList.value.filter(
(item) => item.device_number + item.points !== value
);
}
iotCheckedList.value = newList;
currentCheckedList.value = newList;
return newList;
};
@ -78,8 +115,12 @@ const openModal = () => {
};
const onOk = () => {
formState.value.sub_device = iotCheckedList.value.map(
({ device_guid }) => device_guid
iotCheckedList.value = currentCheckedList.value;
formState.value.sub_device = currentCheckedList.value.map(
({ device_number, points }) => ({
device_number,
points,
})
);
onCancel();
};
@ -88,23 +129,57 @@ const onCancel = () => {
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(
formState,
() => {
getIOTData();
setSysItems(
store.subSys.map(({ full_name, sub_system_tag }, index) => ({
title: full_name,
key: sub_system_tag,
active: 0,
}))
);
},
{
deep: true,
}
);
const deleteItem = (id) => {
console.log(formState.value.sub_device, id);
// formState.value.device_guid = formState.value.device_guid.filter(
// (d) => d !== id
// );
const newList = onChange(id, false);
formState.value.sub_device = newList.map(({ device_guid }) => device_guid);
const deleteItem = (value) => {
const itemToDelete = iotCheckedList.value.find(
({ device_number, points }) => `${device_number}${points}` === value
);
if (itemToDelete) {
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>
@ -131,15 +206,11 @@ const deleteItem = (id) => {
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template
><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
class="text-base text-error cursor-pointer"
@click.stop.prevent="() => deleteItem(record.device_guid)"
@click.stop.prevent="
() => deleteItem(record.device_number + record.points)
"
>
<FontAwesomeIcon
:icon="['fas', 'trash-alt']"
@ -155,16 +226,39 @@ const deleteItem = (id) => {
width="900"
>
<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">
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'check'">
<Checkbox
name="device_guid"
:value="record.device_guid"
name="device_number"
:value="record.device_number + record.points"
:onChange="onChange"
:checked="
iotCheckedList?.some(
({ device_guid }) => device_guid === record.device_guid
currentCheckedList?.some(
({ device_number, points }) =>
`${device_number}${points}` ===
`${record.device_number}${record.points}`
)
"
></Checkbox>

View File

@ -13,6 +13,10 @@ const props = defineProps({
reset: Function,
getData: Function,
});
const showPassword = ref(false);
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value;
};
let userSchema = ref(
yup.object({
@ -143,17 +147,24 @@ const onOk = async () => {
<Input
v-if="!Boolean(formState?.Id)"
:value="formState"
class="my-2"
class="relative my-2"
name="Password"
type="password"
:type="showPassword ? 'text' : 'password'"
>
<template #topLeft>{{ $t("accountManagement.password") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.Password }}
</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">
<template #topLeft>{{ $t("accountManagement.name") }}</template>
<template #bottomLeft

View File

@ -15,6 +15,10 @@ const props = defineProps({
const formState = ref({
Password: "",
});
const showPassword = ref(false);
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value;
};
let userSchema = yup.object({
Password: yup.string().required(t("button.required")),
@ -49,7 +53,12 @@ const onOk = async () => {
<template #modalContent>
<p class="mt-10 text-3xl">{{ account.Name }}</p>
<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>{{
$t("accountManagement.change_password")
}}</template>
@ -57,8 +66,15 @@ const onOk = async () => {
><span class="text-error text-base">
{{ formErrorMsg.Password }}
</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>
</template>
<template #modalAction>

View File

@ -20,7 +20,7 @@ const yesterdayTodayData = {
//
const weekComparisonData = {
categories: ["週五", "週六", "週日", "週一", "週二", "週三", "週四"],
categories: ["Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"],
values: [
{
name: "This week's electricity consumption",
@ -131,6 +131,31 @@ const generateCylinderChartOption = (categories, values) => {
},
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: {
left: "3%",
@ -180,14 +205,14 @@ const weekComparisonOption = generateCylinderChartOption(
(kWH)
</h2>
<div class="w-[48%]">
<a href="#" class="text-con">
<div class="text-con">
<img src="@ASSET/img/chart-title01.svg" />
<span>{{ $t("dashboard.today_electricity_consumption") }}</span>
</a>
<a href="#" class="text-con">
</div>
<div class="text-con">
<img src="@ASSET/img/chart-title02.svg" />
<span>{{ $t("dashboard.yesterday_electricity_consumption") }}</span>
</a>
</div>
</div>
</div>
<div class="h-[250px]">
@ -208,14 +233,14 @@ const weekComparisonOption = generateCylinderChartOption(
(kWH)
</h2>
<div class="w-[48%]">
<a href="#" class="text-con">
<div class="text-con">
<img src="@ASSET/img/chart-title01.svg" />
<span>{{ $t("dashboard.thisweek_electricity_consumption") }}</span>
</a>
<a href="#" class="text-con">
</div>
<div class="text-con">
<img src="@ASSET/img/chart-title02.svg" />
<span>{{ $t("dashboard.lastweek_electricity_consumption") }}</span>
</a>
</div>
</div>
</div>
<div class="h-[250px]">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,10 @@ const formState = ref({
account: "",
password: "",
});
const showPassword = ref(false);
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value;
};
const imageSrc = import.meta.env.MODE === "production" ? "./logo.svg" : Image;
@ -63,17 +67,22 @@ const doLogin = async () => {
</span>
</div>
<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">{{
$t("password")
}}</label>
<input
name="password"
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"
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>
<span class="text-error text-base">
{{ formErrorMsg.password }}