設備管理:部門與樓層管理移至設定、小類新稱icon | 能源管理:能匯出excel、報表日期設置 | 運維管理: 廠商管理移至設定

This commit is contained in:
koko 2025-02-17 09:59:36 +08:00
parent a91c4397a4
commit 80fcfda16c
30 changed files with 1317 additions and 503 deletions

View File

@ -26,3 +26,5 @@ export const POST_ASSET_DEPARTMENT_API = `/AssetManage/SaveDepartment`;
export const DELETE_ASSET_DEPARTMENT_API = `/AssetManage/DeleteDepartment`;
export const GET_ASSET_ELECTYPE_API = `/AssetManage/GetElecType`;
export const POST_ASSET_ELECTYPE_API = `/AssetManage/SaveElecType`;
export const DELETE_ASSET_ELECTYPE_API = `/AssetManage/DeleteElecType`;

View File

@ -19,6 +19,8 @@ import {
POST_ASSET_DEPARTMENT_API,
DELETE_ASSET_DEPARTMENT_API,
GET_ASSET_ELECTYPE_API,
POST_ASSET_ELECTYPE_API,
DELETE_ASSET_ELECTYPE_API
} from "./api";
import instance from "@/util/request";
import apihandler from "@/util/apihandler";
@ -251,3 +253,24 @@ export const getElecTypeList = async () => {
code: res.code,
});
};
export const postElecTypeList = async ({ name, id }) => {
const res = await instance.post(POST_ASSET_ELECTYPE_API, {
name,
id,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const deleteElecTypeItem = async (id) => {
const res = await instance.post(DELETE_ASSET_ELECTYPE_API, { id });
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};

View File

@ -2,8 +2,9 @@ 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`;
export const GET_SIDEBAR_API = `/api/Energe/GetEnergySideBar`;
export const GET_SIDEBAR_API = `/api/GetSideBar`;
export const GET_SEARCH_API = `/api/Energe/GetFilter`;
export const GET_REPORT_API = `/api/Energe/GetReport`;
export const GET_Excel_API = `/api/Energe/GetReportExcel`;

View File

@ -5,9 +5,11 @@ import {
GET_SIDEBAR_API,
GET_SEARCH_API,
GET_REPORT_API,
GET_Excel_API,
} from "./api";
import instance from "@/util/request";
import instance, { fileInstance } from "@/util/request";
import apihandler from "@/util/apihandler";
import downloadExcel from "@/util/downloadExcel";
export const getRealTimeDist = async () => {
const res = await instance.post(GET_REALTIME_DIST_API);
@ -36,8 +38,8 @@ export const getTaipower = async () => {
});
};
export const getEnergySideBar = async () => {
const res = await instance.post(GET_SIDEBAR_API);
export const getSideBar = async (system_type) => {
const res = await instance.post(GET_SIDEBAR_API, { system_type });
return apihandler(res.code, res.data, {
msg: res.msg,
@ -76,3 +78,35 @@ export const getReport = async ({
code: res.code,
});
};
export const getExcel = async ({
department,
elecType,
floor,
start_time,
end_time,
type,
}) => {
const res = await fileInstance.post(
GET_Excel_API,
{
department,
elecType,
floor,
start_time,
end_time,
type,
},
{ responseType: "blob" }
);
return apihandler(
res.code,
res,
{
msg: res.msg,
code: res.code,
},
downloadExcel
);
};

View File

@ -2,7 +2,7 @@
import { onMounted, ref, watch, computed } from "vue";
import { AUTHPAGES } from "@/constant";
import { getAuth, getAllSysSidebar } from "@/apis/building";
import { getEnergySideBar } from "@/apis/energy";
import { getSideBar } from "@/apis/energy";
import useBuildingStore from "@/stores/useBuildingStore";
import useUserInfoStore from "@/stores/useUserInfoStore";
import { useI18n } from "vue-i18n";
@ -42,15 +42,15 @@ const getSubMonitorPage = async (building) => {
buildingStore.mainSubSys = res.data.history_Main_Systems;
menu_array.value = res.data.history_Main_Systems;
};
const getSubEnergyPage = async () => {
const res = await getEnergySideBar();
const getSubPage = async (system_type) => {
const res = await getSideBar(system_type);
menu_array.value = res.data;
};
const showDrawer = (authCode) => {
if (authCode === "PF1") {
getSubMonitorPage();
} else if (authCode === "PF2") {
getSubEnergyPage();
} else if (authCode === "PF2" || authCode === "PF11") {
getSubPage(authCode === "PF2" ? "Energy" : "Setting");
}
currentAuthCode.value = authCode;
open.value = true;
@ -105,7 +105,11 @@ onMounted(() => {
:key="page.authCode"
>
<a
v-if="page.authCode === 'PF1' || page.authCode === 'PF2'"
v-if="
page.authCode === 'PF1' ||
page.authCode === 'PF2' ||
page.authCode === 'PF11'
"
@click="showDrawer(page.authCode)"
:class="
twMerge(
@ -115,6 +119,9 @@ onMounted(() => {
: '',
page.authCode === 'PF2' &&
route.fullPath.includes('/energyManagement')
? 'router-link-active router-link-exact-active'
: '',
page.authCode === 'PF11' && route.fullPath.includes('/setting')
? 'router-link-active router-link-exact-active'
: ''
)
@ -168,18 +175,26 @@ onMounted(() => {
<a-menu-item
v-for="sub in currentAuthCode === 'PF1'
? main.history_Sub_systems
: currentAuthCode === 'PF2'
? main.sub
: main.sub"
:key="sub.sub_system_tag+`_`+sub.type"
:key="sub.sub_system_tag + `_` + sub.type"
@click="() => onClose()"
>
<router-link
:to="{
name:
currentAuthCode === 'PF2' ? 'energyManagement' : 'sub_system',
currentAuthCode === 'PF2'
? 'energyManagement'
: currentAuthCode === 'PF11'
? 'setting'
: 'sub_system',
params: {
main_system_id: main.main_system_tag,
sub_system_id: sub.sub_system_tag,
...(currentAuthCode === 'PF2' ? { type: sub.type } : { floor_id: 'main' }),
...(currentAuthCode === 'PF2' || currentAuthCode === 'PF11'
? { type: sub.type }
: { floor_id: 'main' }),
},
}"
>

View File

@ -61,4 +61,10 @@ export const AUTHPAGES = [
pageName: "ProductSetting",
navigate: "/productSetting",
},
{
authCode: "PF11",
icon: "cog",
pageName: "Setting",
navigate: "/Setting",
},
];

View File

@ -8,6 +8,7 @@ import AssetManagement from "@/views/AssetManagement/AssetManagement.vue";
import AlertManagement from "@/views/alert/AlertManagement.vue";
import ProductSetting from "@/views/productSetting/ProductSetting.vue";
import EnergyManagement from "@/views/energyManagement/EnergyManagement.vue";
import SettingManagement from "@/views/setting/SettingManagement.vue";
import Login from "@/views/login/Login.vue";
import useUserInfoStore from "@/stores/useUserInfoStore";
import useGetCookie from "@/hooks/useGetCookie";
@ -84,6 +85,11 @@ const router = createRouter({
name: "energyManagement",
component: EnergyManagement,
},
{
path: "/setting/:main_system_id/:sub_system_id/:type",
name: "setting",
component: SettingManagement,
},
{
path: "/mytestfile/mjm",
name: "mytestfile",

View File

@ -4,12 +4,14 @@ import AssetMainList from "./components/AssetMainList.vue";
import AssetSubList from "./components/AssetSubList.vue";
import AssetTable from "./components/AssetTable.vue";
import { getOperationCompanyList } from "@/apis/operation";
import { getIOTSchema, getElecTypeList } from "@/apis/asset";
import { getDepartmentList, getIOTSchema, getElecTypeList, getAssetFloorList } from "@/apis/asset";
import useSearchParam from "@/hooks/useSearchParam";
const { searchParams, changeParams } = useSearchParam();
const companyOptions = ref([]);
const iotSchemaOptions = ref([]);
const elecTypeOptions = ref([]);
const departmentList = ref([]);
const floors = ref([]);
const getCompany = async () => {
const res = await getOperationCompanyList();
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
@ -22,10 +24,20 @@ const getElecType = async () => {
const res = await getElecTypeList();
elecTypeOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
};
const getDepartment = async () => {
const res = await getDepartmentList();
departmentList.value = res.data.map((d) => ({ ...d, key: d.id }));
};
const getFloors = async () => {
const res = await getAssetFloorList();
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
};
onMounted(() => {
getCompany();
getElecType();
getDepartment();
getFloors();
});
watch(
@ -44,6 +56,8 @@ provide("asset_modal_options", {
companyOptions,
iotSchemaOptions,
elecTypeOptions,
departmentList,
floors,
});
</script>

View File

@ -15,6 +15,9 @@ const props = defineProps({
});
const mainSystem = ref([]);
const updateFileList = (files) => {
formState.value.icon = files;
};
const getMainSystems = async () => {
const res = await getAssetMainList();
mainSystem.value = res.data.map((d) => ({ ...d, key: d.id }));
@ -33,6 +36,7 @@ const formState = ref({
system_key: "",
system_value: "",
system_parent_id: 0,
icon: [],
});
const resetForm = () => {
@ -40,6 +44,7 @@ const resetForm = () => {
system_key: "",
system_value: "",
system_parent_id: 0,
icon: [],
};
};
@ -120,6 +125,15 @@ const onOk = async () => {
</span></template
>
</Select>
<Upload
name="icon"
:fileList="formState.icon"
:getFileList="updateFileList"
:multiple="false"
:baseUrl="`${BASEURL}/upload/graph_manage`"
>
<template #topLeft>{{ $t("operation.upload_file") }}</template>
</Upload>
</form>
</template>
<template #modalAction>

View File

@ -4,7 +4,6 @@ import * as yup from "yup";
import "yup-phone-lite";
import useSearchParam from "@/hooks/useSearchParam";
import AssetTableModalLeftInfoIoT from "./AssetTableModalLeftInfoIoT.vue";
import AssetTableModalLeftInfoDept from "./AssetTableModalLeftInfoDept.vue";
import AssetTableModalLeftInfoGraph from "./AssetTableModalLeftInfoGraph.vue";
import AssetTableModalLeftInfoMQTT from "./AssetTableModalLeftInfoMQTT.vue";
import useUserInfoStore from "@/stores/useUserInfoStore";
@ -16,7 +15,7 @@ const { searchParams, changeParams } = useSearchParam();
const { updateLeftFields, formErrorMsg, formState } = inject(
"asset_table_modal_form"
);
const { companyOptions, iotSchemaOptions, elecTypeOptions } = inject("asset_modal_options");
const { companyOptions, iotSchemaOptions, elecTypeOptions, departmentList } = inject("asset_modal_options");
const store = useUserInfoStore();
let schema = {
full_name: yup.string().nullable(true),
@ -53,7 +52,7 @@ onBeforeMount(() => {
operation_id: 0,
response_schema_id: 0,
department_id: 0,
elec_id: null,
elec_type_id: null,
asset_number: "",
topic: "",
sub_device: [],
@ -115,7 +114,17 @@ watch(
</span></template
></Input
>
<AssetTableModalLeftInfoDept />
<div class="flex items-center w-72">
<Select
:value="formState"
selectClass="border-info focus-within:border-info"
name="department_id"
Attribute="name"
:options="departmentList"
>
<template #topLeft>{{ $t("assetManagement.department") }}</template>
</Select>
</div>
<Input
:value="formState"
width="290"
@ -147,7 +156,7 @@ watch(
<Select
:value="formState"
selectClass="border-info focus-within:border-info"
name="elec_id"
name="elec_type_id"
Attribute="name"
:options="elecTypeOptions"
:required="true"

View File

@ -1,21 +1,15 @@
<script setup>
import { onMounted, ref, inject, onBeforeMount, watch, computed } from "vue";
import EffectScatter from "@/components/chart/EffectScatter.vue";
import {
getAssetFloorList,
postAssetFloor,
deleteAssetFloor,
} from "@/apis/asset";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import useBuildingStore from "@/stores/useBuildingStore";
import * as yup from "yup";
import { twMerge } from "tailwind-merge";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { totalCoordinates } = inject("asset_table_data");
const { floors } = inject("asset_modal_options");
const { updateRightFields, formErrorMsg, formState } = inject(
"asset_table_modal_form"
);
@ -34,7 +28,6 @@ onBeforeMount(() => {
const asset_floor_chart = ref(null);
const currentFloor = ref(null);
const selectedOption = ref("add");
const parsedCoordinates = ref(null);
const defaultOption = (map, data = []) => {
@ -45,7 +38,10 @@ const defaultOption = (map, data = []) => {
name: coordString,
value: coordinate,
itemStyle: {
color: coordString === formState.value.device_coordinate ? "#0000FF" : "#b02a02",
color:
coordString === formState.value.device_coordinate
? "#0000FF"
: "#b02a02",
},
};
});
@ -75,13 +71,14 @@ const defaultOption = (map, data = []) => {
watch(currentFloor, (newValue) => {
if (newValue?.floor_map_name) {
const coordinates =
(totalCoordinates.value?.[newValue.floor_guid]?.filter(
totalCoordinates.value?.[newValue.floor_guid]?.filter(
(coord) => coord !== ""
) || []);
) || [];
parsedCoordinates.value = coordinates.length > 0
? coordinates.map((coord) => JSON.parse(coord))
: [];
parsedCoordinates.value =
coordinates.length > 0
? coordinates.map((coord) => JSON.parse(coord))
: [];
asset_floor_chart.value.updateSvg(
{
@ -92,12 +89,6 @@ watch(currentFloor, (newValue) => {
);
}
});
const floors = ref([]);
const getFloors = async () => {
const res = await getAssetFloorList();
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
};
watch(
formState,
@ -120,136 +111,31 @@ const getCoordinate = (position) => {
formState.value.device_coordinate = JSON.stringify(position);
};
onMounted(() => {
getFloors();
});
// modal
const openModal = () => {
if (selectedOption.value === "add") {
FloorFormState.value = {
full_name: "",
floorFile: [],
};
} else if (selectedOption.value === "edit") {
const floor = floors.value.find(
(f) => f.floor_guid === formState.value.floor_guid
);
if (floor) {
console.log("floor", floor);
FloorFormState.value = {
full_name: floor.full_name,
floorFile: [],
};
}
}
asset_add_floor.showModal();
};
const form = ref(null);
const FloorFormState = ref({
full_name: "",
floorFile: [],
});
const floorScheme = yup.object({
full_name: yup.string().required(t("button.required")),
floorFile: yup.array(),
});
const updateFileList = (files) => {
console.log("file", files);
FloorFormState.value.floorFile = files;
};
const {
formErrorMsg: floorFormErrorMsg,
handleSubmit,
handleErrorReset,
updateScheme,
} = useFormErrorMessage(floorScheme);
const onOk = async () => {
const value = handleSubmit(floorScheme, FloorFormState.value);
const formData = new FormData(form.value);
formData.append("floor_guid", selectedOption.value === "add" ? null :currentFloor.value.floor_guid);
formData.append("building_tag", store.selectedBuilding.building_tag);
formData.append("initMapName", FloorFormState.value.floorFile[0]?.name);
formData.append("mapFile", FloorFormState.value.floorFile[0]);
formData.delete("floorFile");
for (let [key, value] of formData) {
console.log(key, value);
}
const res = await postAssetFloor(formData);
if (res.isSuccess) {
getFloors();
onCancel();
}
};
const onDelete = async () => {
openToast("warning", t("msg.sure_to_delete"), "#asset_add_table_item", async () => {
await cancelToastOpen();
const res = await deleteAssetFloor({
floor_guid: formState.value.floor_guid,
});
if (res.isSuccess) {
getFloors();
openToast("success", t("msg.delete_success"), "#asset_add_table_item");
} else {
openToast("error", res.msg, "#asset_add_table_item");
}
});
};
const onCancel = () => {
FloorFormState.value = {
full_name: "",
floorFile: [],
};
asset_add_floor.close();
};
</script>
<template>
<!-- 平面圖 -->
<div class="flex gap-4 mb-5">
<div className="join w-80 mb-4">
<Select
:value="formState"
selectClass="border-info focus-within:border-info rounded-r-none"
name="floor_guid"
Attribute="full_name"
:options="floors"
:isBottomLabelExist="false"
>
<template #topLeft>{{ $t("assetManagement.floor") }}</template>
</Select>
<select
v-model="selectedOption"
className="select border-info focus-within:border-info join-item mt-11"
>
<option value="add" selected>{{ $t("button.add") }}</option>
<option value="edit">{{ $t("button.edit") }}</option>
<option value="delete">{{ $t("button.delete") }}</option>
</select>
<button
type="button"
class="btn btn-success join-item mt-11"
@click="selectedOption === 'delete' ? onDelete() : openModal()"
:aria-label="$t('button.submit')"
>
{{ $t("button.submit") }}
</button>
</div>
<Select
:value="formState"
selectClass="border-info focus-within:border-info"
name="floor_guid"
Attribute="full_name"
:options="floors"
:isBottomLabelExist="false"
>
<template #topLeft>{{ $t("assetManagement.floor") }}</template>
</Select>
<Input
:value="formState"
width="270"
name="device_coordinate"
:disabled="true"
>
<template #topLeft>{{ $t("assetManagement.device_coordinate") }}</template>
<template #topLeft>{{
$t("assetManagement.device_coordinate")
}}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.device_coordinate }}
@ -276,47 +162,6 @@ const onCancel = () => {
<p class="text-2xl">{{ $t("assetManagement.add_floor_text") }}</p>
</div>
</div>
<Modal
id="asset_add_floor"
:title="t('assetManagement.floor_plan')"
:onCancel="onCancel"
width="400"
>
<template #modalContent>
<form ref="form">
<Input :value="FloorFormState" width="270" name="full_name">
<template #topLeft>{{ $t("assetManagement.system_name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ floorFormErrorMsg.full_name }}
</span></template
></Input
>
<Upload
name="floorFile"
:fileList="FloorFormState.floorFile"
:getFileList="updateFileList"
:multiple="false"
class="col-span-2"
formats="svg"
>
<template #topLeft>{{ $t("assetManagement.oriFile") }}</template>
</Upload>
</form>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button type="submit" class="btn btn-outline-success" @click="onOk">
{{ $t("button.submit") }}
</button>
</template></Modal
>
</template>
<style lang="scss" scoped></style>

View File

@ -8,7 +8,7 @@ import {
getAssetFloorList,
getElecTypeList,
} from "@/apis/asset";
import { getReport } from "@/apis/energy";
import { getReport, getExcel } from "@/apis/energy";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const route = useRoute();
@ -83,6 +83,10 @@ const onSearch = async () => {
loading.value = false;
};
const onExcel = async () => {
const res = await getExcel(formState.value);
};
//
watch(
selectedDeptItems,
@ -176,7 +180,7 @@ onMounted(() => {
<font-awesome-icon :icon="['fas', 'search']" class="text-lg" />
{{ $t("button.search") }}
</button>
<button class="btn btn-export" @click.stop.prevent="search">
<button class="btn btn-export" @click.stop.prevent="onExcel">
<font-awesome-icon :icon="['fas', 'download']" class="text-lg" />
{{ $t("button.export") }}
</button>

View File

@ -1,11 +1,12 @@
<script setup>
import Table from "@/components/customUI/Table.vue";
import { inject, computed, ref } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import dayjs from "dayjs";
const { t } = useI18n();
const { tableData, loading } = inject("energy_table_data");
const route = useRoute();
//
const defaultColumns = ref([
{
@ -29,59 +30,64 @@ const defaultColumns = ref([
//
const columns = computed(() => {
if (!tableData.value || tableData.value.length === 0) {
return [...defaultColumns.value, {
title: t("energy.subtotal"),
key: "subtotal",
}];
}
//
const dynamicColumns = [...defaultColumns.value];
// data
const firstDataItem = tableData.value[0];
if (firstDataItem && firstDataItem.data) {
// data
firstDataItem.data.forEach((item, index) => {
// 使 dayjs format
const formattedTime = dayjs(item.time).format("MM-DD");
dynamicColumns.push({
title: formattedTime,
key: `data[${index}].value`,
if (tableData.value && tableData.value.length > 0) {
const firstDataItem = tableData.value[0];
if (firstDataItem && firstDataItem.data) {
firstDataItem.data.forEach((item, index) => {
let formatString = "MM/DD"; //
switch (route.params.type) {
case "3":
formatString = "YYYY/MM";
break;
case "4":
formatString = "YYYY";
break;
default:
formatString = "MM/DD"; // case 1 case 2 "MM-DD"
break;
}
const formattedTime = dayjs(item.time).format(formatString);
// data key "data_0", "data_1"
dynamicColumns.push({
title: formattedTime,
key: `data_${index}`,
});
});
});
}
}
dynamicColumns.push({
title: t("energy.subtotal"),
key: "subtotal",
});
dynamicColumns.push({
title: t("energy.subtotal"),
key: "subtotal",
});
return dynamicColumns;
});
// 調 subtotal
// 調 subtotal data
const dataSource = computed(() => {
if (!tableData.value || tableData.value.length === 0) return [];
if(!tableData.value || tableData.value.length === 0) return [];
return tableData.value.map(item => {
let subtotalValue = 0;
if(item.data && item.data.length > 0) {
item.data.forEach((dataItem) => {
subtotalValue += parseFloat(dataItem.value || 0);
});
}
return {
...item,
subtotal: subtotalValue.toFixed(2)
}
})
})
return tableData.value.map((item) => {
let subtotalValue = 0;
const newData = {}; // data 便 Table
if (item.data && item.data.length > 0) {
item.data.forEach((dataItem, index) => {
const value = parseFloat(dataItem.value || 0);
subtotalValue += value;
// newData key columns key
newData[`data_${index}`] = dataItem.value;
});
}
return {
...item,
...newData, // data item
subtotal: subtotalValue.toFixed(2),
};
});
});
</script>
<template>

View File

@ -26,9 +26,7 @@ const updateDataSource = (data) => {
finish_time: d?.finish_time
? dayjs(d?.finish_time).format("YYYY-MM-DD")
: "",
updated_at: d?.updated_at
? dayjs(d?.updated_at).format("YYYY-MM-DD")
: "",
updated_at: d?.updated_at ? dayjs(d?.updated_at).format("YYYY-MM-DD") : "",
}));
};
@ -105,7 +103,7 @@ const getAllOptions = async (id = "") => {
const openModal = async (id = "") => {
console.log("open modal", id);
if(searchParams.value?.work_type < 3){
if (searchParams.value?.work_type < 3) {
getAllOptions(id);
}
operation_action_item.showModal();
@ -115,11 +113,7 @@ const tableLoading = ref(false);
const search = async () => {
tableLoading.value = true;
let res;
if (searchParams.value?.work_type < 3) {
res = await getOperationRecord(searchParams.value);
} else {
res = await getOperationCompanyList();
}
res = await getOperationRecord(searchParams.value);
updateDataSource(res.data);
tableLoading.value = false;
};

View File

@ -61,10 +61,7 @@ watch(
([newValue, newSelected]) => {
const keys = Object.keys(newValue).filter((v) => v !== "search_type");
if (newValue.work_type === "3") {
isSearchDisabled.value = false;
initSubmit(true);
} else if (newSelected) {
if (newSelected) {
isSearchDisabled.value = !searchParams.value.sub_system_tag;
initSubmit(searchParams.value.sub_system_tag);
}
@ -81,7 +78,6 @@ watch(
:items="submitBtns"
:withLine="false"
:withBtnClass="true"
v-if="selectedWorkType?.work_type !== 3"
/>
<button class="btn btn-add" @click.stop.prevent="() => openModal()">

View File

@ -42,17 +42,7 @@ const setButtonItems = () => {
active: searchParams.value.work_type === "1",
work_type: 1,
params: ["work_type", "sub_system_tag"],
},
{
title: t("operation.company_info"),
key: "company_info",
active: false,
active:
searchParams.value.work_type === null ||
searchParams.value.work_type === "3",
work_type: 3,
params: [],
},
}
]);
setSearchTypesItems([
@ -85,21 +75,17 @@ watch(
[selectedWorkType, selectedSearchType],
([newWorkType, newSearchType]) => {
if (newWorkType && newSearchType) {
if (newWorkType.work_type === 3) {
changeParams({ work_type: newWorkType?.work_type });
} else {
const oldSearchParams = Object.fromEntries(
[...newWorkType?.params, ...newSearchType?.params].map((key) => [
key,
searchParams.value[key] ?? null,
])
);
changeParams({
...oldSearchParams,
work_type: newWorkType?.work_type,
search_type: newSearchType?.key,
});
}
const oldSearchParams = Object.fromEntries(
[...newWorkType?.params, ...newSearchType?.params].map((key) => [
key,
searchParams.value[key] ?? null,
])
);
changeParams({
...oldSearchParams,
work_type: newWorkType?.work_type,
search_type: newSearchType?.key,
});
}
}
);
@ -119,12 +105,9 @@ watch(
}
"
/>
<OperationSearchSubSys
:class="twMerge(selectedWorkType?.work_type === 3 ? 'hidden' : 'block')"
/>
<OperationSearchSubSys />
<div class="w-full flex flex-wrap items-center justify-start">
<ButtonGroup
v-if="selectedWorkType?.work_type !== 3"
:class="twMerge('mr-3')"
:items="searchTypesItems"
:withLine="true"
@ -135,8 +118,7 @@ watch(
"
/>
<OperationSearchType :selected="selectedSearchType"
v-if="selectedWorkType?.work_type !== 3" />
<OperationSearchType :selected="selectedSearchType" />
<OperationActionButton :selectedWorkType="selectedWorkType" />
</div>
</form>

View File

@ -128,7 +128,7 @@ watch(
<template>
<ButtonGroup
v-if="selected?.key === 'date' && searchParams?.work_type !== '3'"
v-if="selected?.key === 'date' "
:items="DateItems"
:withLine="true"
:onclick="
@ -140,7 +140,7 @@ watch(
<DateGroup
:isTopLabelExist="false"
:isBottomLabelExist="false"
v-if="selected?.key === 'date' && searchParams?.work_type !== '3'"
v-if="selected?.key === 'date' "
:items="searchDateItems"
:withLine="true"
/>

View File

@ -1,9 +1,8 @@
<script setup>
import { getFIX_COL, getCOM_COL } from "../constant/OperationTableColumns";
import { getFIX_COL } from "../constant/OperationTableColumns";
import { defineProps, computed, inject, watch } from "vue";
import {
deleteOperationRecord,
deleteOperationCompany,
deleteOperationRecord
} from "@/apis/operation";
import useSearchParam from "@/hooks/useSearchParam";
import { useI18n } from "vue-i18n";
@ -16,7 +15,7 @@ const { dataSource, openModal, updateEditRecord, search, tableLoading } =
inject("operation_table");
const columns = computed(() =>
searchParams.value?.work_type < 3 ? getFIX_COL(t) : getCOM_COL(t)
getFIX_COL(t)
);
const changeData = (record) => {
@ -29,11 +28,7 @@ const deleteItem = async (id) => {
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
await cancelToastOpen();
let res;
if (searchParams.value?.work_type < 3) {
res = await deleteOperationRecord(id);
} else {
res = await deleteOperationCompany(id);
}
res = await deleteOperationRecord(id);
if (res.isSuccess) {
search();
openToast("success", t("msg.delete_success"));

View File

@ -4,8 +4,6 @@ import useSearchParam from "@/hooks/useSearchParam";
import dayjs from "dayjs";
import {
postOperationRecord,
postOperationCompany,
updateOperationCompany,
} from "@/apis/operation";
import * as yup from "yup";
import "yup-phone-lite";
@ -71,17 +69,6 @@ const formState = ref([
},
]);
let companySchema = yup.object({
name: yup.string().required(t("button.required")),
contact_person: yup.string().nullable(true),
email: yup.string().email().nullable(true),
phone: yup.string().nullable(true),
city: yup.string().nullable(true),
address: yup.string().nullable(true),
tax_id_number: yup.string().nullable(true),
remark: yup.string().nullable(true),
});
let maintainSchema = yup.object({
formId: yup.string().required(t("button.required")),
work_type: yup.string().required(t("button.required")),
@ -168,36 +155,9 @@ const saveMaintain = async () => {
}
};
const {
formErrorMsg: companyFormErrorMsg,
handleSubmit: handleCompanySubmit,
handleErrorReset: handleCompanyErrorReset,
} = useFormErrorMessage(companySchema);
const saveCompany = async () => {
const value = await handleCompanySubmit(
companySchema,
formState.value[searchParams.value?.work_type - 1]
);
let res;
if (props.editRecord) {
res = await updateOperationCompany(value);
} else {
res = await postOperationCompany(value);
}
if (res.isSuccess) {
search();
onCancel();
} else {
openToast("error", res.msg, "#operation_action_item");
}
};
const onOk = async () => {
if (searchParams.value.work_type < 3) {
saveMaintain();
} else {
saveCompany();
}
};
const reset = () => {
@ -221,7 +181,6 @@ const onCancel = () => {
updateEditRecord?.(null);
reset();
handleMaintainErrorReset();
handleCompanyErrorReset();
operation_action_item.close();
};
@ -469,139 +428,6 @@ watch(
<template #topLeft>{{ $t("operation.upload_file") }}</template>
</Upload>
</template>
<template v-else>
<Input
v-if="
searchParams?.work_type &&
Object.hasOwn(formState[searchParams?.work_type - 1], 'name')
"
:value="formState[searchParams?.work_type - 1]"
class="my-2"
name="name"
>
<template #topLeft>{{ $t("operation.name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ companyFormErrorMsg.name }}
</span></template
>
</Input>
<Input
v-if="
searchParams?.work_type &&
Object.hasOwn(
formState[searchParams?.work_type - 1],
'contact_person'
)
"
:value="formState[searchParams?.work_type - 1]"
class="my-2"
name="contact_person"
>
<template #topLeft>{{ $t("operation.contact_person") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ companyFormErrorMsg.contact_person }}
</span></template
></Input
>
<Input
v-if="
searchParams?.work_type &&
Object.hasOwn(formState[searchParams?.work_type - 1], 'phone')
"
:value="formState[searchParams?.work_type - 1]"
class="my-2"
name="phone"
>
<template #topLeft>{{ $t("operation.phone") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ companyFormErrorMsg.phone }}
</span></template
></Input
>
<Input
v-if="
searchParams?.work_type &&
Object.hasOwn(formState[searchParams?.work_type - 1], 'email')
"
:value="formState[searchParams?.work_type - 1]"
class="my-2"
name="email"
>
<template #topLeft>{{ $t("operation.email") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ companyFormErrorMsg.email }}
</span></template
>
</Input>
<Input
v-if="
searchParams?.work_type &&
Object.hasOwn(formState[searchParams?.work_type - 1], 'city')
"
:value="formState[searchParams?.work_type - 1]"
class="my-2 w-[250px]"
name="city"
>
<template #topLeft>{{ $t("operation.city") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ companyFormErrorMsg.city }}
</span></template
></Input
>
<Input
v-if="
searchParams?.work_type &&
Object.hasOwn(formState[searchParams?.work_type - 1], 'address')
"
:value="formState[searchParams?.work_type - 1]"
class="my-2 w-4/5"
name="address"
>
<template #topLeft>{{ $t("operation.address") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ companyFormErrorMsg.address }}
</span></template
></Input
>
<Input
v-if="
searchParams?.work_type &&
Object.hasOwn(
formState[searchParams?.work_type - 1],
'tax_id_number'
)
"
:value="formState[searchParams?.work_type - 1]"
class="my-2"
name="tax_id_number"
>
<template #topLeft> {{ $t("operation.tax_id_number") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ companyFormErrorMsg.tax_id_number }}
</span></template
></Input
>
<Input
v-if="
searchParams?.work_type &&
Object.hasOwn(formState[searchParams?.work_type - 1], 'remark')
"
:value="formState[searchParams?.work_type - 1]"
class="my-2"
name="remark"
>
<template #topLeft>
{{ $t("operation.remark") }}
</template></Input
>
</template>
</form>
</template>
<template #modalAction>

View File

@ -55,31 +55,3 @@ export const getFIX_COL = (t) => [
width: 250,
},
];
export const getCOM_COL = (t) => [
{
title: t("operation.vendor"),
key: "name",
},
{
title: t("operation.contact_person"),
key: "contact_person",
},
{
title: t("operation.phone"),
key: "phone",
},
{
title: t("operation.email"),
key: "email",
},
{
title: t("operation.created_at"),
key: "created_at",
},
{
title: t("operation.operation"),
key: "operation",
width: 250,
},
];

View File

@ -0,0 +1,47 @@
<script setup>
import { ref, onMounted, watch } from "vue";
import { useRoute } from "vue-router";
import Dept from "./components/Dept.vue";
import ElecType from "./components/ElecType.vue";
import Vendor from "./components/Vendor.vue";
import Floors from "./components/Floors.vue";
import MITTList from "./components/MITTList.vue";
const route = useRoute();
const currentComponent = ref(null);
const updateComponent = () => {
const { main_system_id, sub_system_id } = route.params;
if (main_system_id === "Setting") {
if (sub_system_id === "Department") {
currentComponent.value = Dept;
} else if(sub_system_id === "ElecType") {
currentComponent.value = ElecType;
} else if(sub_system_id === "Vendor") {
currentComponent.value = Vendor;
} else if(sub_system_id === "Floor") {
currentComponent.value = Floors;
}else{
currentComponent.value = MITTList;
}
} else if (main_system_id === "MQTT") {
currentComponent.value = MITTList;
} else {
currentComponent.value = null;
}
};
onMounted(updateComponent);
watch(
() => route.params,
() => {
updateComponent();
}
);
</script>
<template>
<component :is="currentComponent" />
</template>

View File

@ -0,0 +1,104 @@
<script setup>
import Table from "@/components/customUI/Table.vue";
import DeptModal from "./DeptModal.vue";
import { getDepartmentList, deleteDepartmentItem } from "@/apis/asset";
import { onMounted, ref, inject, computed } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const columns = computed(() => [
{
title: t("accountManagement.index"),
key: "index",
},
{
title: t("assetManagement.department_name"),
key: "name",
filter: true,
},
{
title: t("accountManagement.operation"),
key: "operation",
},
]);
const dataSource = ref([]);
const loading = ref(false);
const getDataSource = async () => {
loading.value = true;
const res = await getDepartmentList();
dataSource.value = res.data.map((d) => ({ ...d, key: d.id }));
loading.value = false;
};
onMounted(() => {
getDataSource();
});
const formState = ref({
id: 0,
name: "",
});
const openModal = (record) => {
if (record.id) {
formState.value = { ...record };
} else {
formState.value = {
id: 0,
name: "",
};
}
dept_modal.showModal();
};
const remove = async (id) => {
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
await cancelToastOpen();
const res = await deleteDepartmentItem(id);
if (res.isSuccess) {
getDataSource();
openToast("success", t("msg.delete_success"));
} else {
openToast("error", res.msg);
}
});
};
</script>
<template>
<div class="flex justify-start items-center mt-10 mb-5">
<h3 class="text-xl mr-5">{{ $t("assetManagement.department") }}</h3>
<DeptModal
:formState="formState"
:getData="getDataSource"
:openModal="openModal"
/>
</div>
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'operation'">
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.id)"
>
{{ $t("button.delete") }}
</button>
</template>
<template v-else>
{{ record[column.key] }}
</template>
</template>
</Table>
</template>
<style lang="css" scoped></style>

View File

@ -0,0 +1,84 @@
<script setup>
import { ref, onMounted, defineProps, inject, watch } from "vue";
import * as yup from "yup";
import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { postDepartmentList } from "@/apis/asset";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const props = defineProps({
formState: Object,
getData: Function,
openModal: Function
});
const deptScheme = yup.object({
name: yup.string().required(t("button.required")),
});
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
useFormErrorMessage(deptScheme);
const onCancel = () => {
handleErrorReset();
dept_modal.close();
};
const onOk = async () => {
const value = await handleSubmit(deptScheme, props.formState);
const res = await postDepartmentList(value);
if (res.isSuccess) {
props.getData();
onCancel();
} else {
openToast("error", res.msg, "#dept_modal");
}
};
</script>
<template>
<button class="btn btn-sm btn-add " @click.stop.prevent="props.openModal">
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal
id="dept_modal"
:title="props.formState?.id ? t('button.edit') : t('button.add')"
:onCancel="onCancel"
width="400"
>
<template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input :value="formState" class="my-2" name="name">
<template #topLeft>{{
$t("assetManagement.department_name")
}}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.name }}
</span></template
></Input
>
</form>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template>
</Modal>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,104 @@
<script setup>
import Table from "@/components/customUI/Table.vue";
import ElecTypeModal from "./ElecTypeModal.vue";
import { getElecTypeList, deleteElecTypeItem } from "@/apis/asset";
import { onMounted, ref, inject, computed } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const columns = computed(() => [
{
title: t("accountManagement.index"),
key: "index",
},
{
title: t("energy.electricity_classification"),
key: "name",
filter: true,
},
{
title: t("accountManagement.operation"),
key: "operation",
},
]);
const dataSource = ref([]);
const loading = ref(false);
const getDataSource = async () => {
loading.value = true;
const res = await getElecTypeList();
dataSource.value = res.data.map((d) => ({ ...d, key: d.id }));
loading.value = false;
};
onMounted(() => {
getDataSource();
});
const formState = ref({
id: 0,
name: "",
});
const openModal = (record) => {
if (record.id) {
formState.value = { ...record };
} else {
formState.value = {
id: 0,
name: "",
};
}
elec_modal.showModal();
};
const remove = async (id) => {
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
await cancelToastOpen();
const res = await deleteElecTypeItem(id);
if (res.isSuccess) {
getDataSource();
openToast("success", t("msg.delete_success"));
} else {
openToast("error", res.msg);
}
});
};
</script>
<template>
<div class="flex justify-start items-center mt-10 mb-5">
<h3 class="text-xl mr-5">{{ $t("energy.electricity_classification") }}</h3>
<ElecTypeModal
:formState="formState"
:getData="getDataSource"
:openModal="openModal"
/>
</div>
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'operation'">
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.id)"
>
{{ $t("button.delete") }}
</button>
</template>
<template v-else>
{{ record[column.key] }}
</template>
</template>
</Table>
</template>
<style lang="css" scoped></style>

View File

@ -0,0 +1,84 @@
<script setup>
import { ref, onMounted, defineProps, inject, watch } from "vue";
import * as yup from "yup";
import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { postElecTypeList } from "@/apis/asset";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const props = defineProps({
formState: Object,
getData: Function,
openModal: Function
});
const deptScheme = yup.object({
name: yup.string().required(t("button.required")),
});
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
useFormErrorMessage(deptScheme);
const onCancel = () => {
handleErrorReset();
elec_modal.close();
};
const onOk = async () => {
const value = await handleSubmit(deptScheme, props.formState);
const res = await postElecTypeList(value);
if (res.isSuccess) {
props.getData();
onCancel();
} else {
openToast("error", res.msg, "#elec_modal");
}
};
</script>
<template>
<button class="btn btn-sm btn-add " @click.stop.prevent="props.openModal">
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal
id="elec_modal"
:title="props.formState?.id ? t('button.edit') : t('button.add')"
:onCancel="onCancel"
width="400"
>
<template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input :value="formState" class="my-2" name="name">
<template #topLeft>{{
$t("energy.electricity_classification")
}}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.name }}
</span></template
></Input
>
</form>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template>
</Modal>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,114 @@
<script setup>
import Table from "@/components/customUI/Table.vue";
import FloorsModal from "./FloorsModal.vue";
import { getAssetFloorList, deleteAssetFloor } from "@/apis/asset";
import { onMounted, ref, inject, computed } from "vue";
import { useI18n } from "vue-i18n";
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const columns = computed(() => [
{
title: t("accountManagement.index"),
key: "index",
},
{
title: t("energy.floor"),
key: "full_name",
filter: true,
},
{
title: t("assetManagement.floor_plan"),
key: "floor_plan",
},
{
title: t("accountManagement.operation"),
key: "operation",
},
]);
const dataSource = ref([]);
const loading = ref(false);
const getDataSource = async () => {
loading.value = true;
const res = await getAssetFloorList();
dataSource.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
loading.value = false;
};
onMounted(() => {
getDataSource();
});
const formState = ref({
full_name: "",
floorFile: [],
});
const openModal = (record) => {
if (record.floor_guid) {
formState.value = { ...record };
} else {
formState.value = {
full_name: "",
floorFile: [],
};
}
floor_modal.showModal();
};
const remove = async () => {
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
await cancelToastOpen();
const res = await deleteAssetFloor({
floor_guid: formState.value.floor_guid,
});
if (res.isSuccess) {
getDataSource();
openToast("success", t("msg.delete_success"));
} else {
openToast("error", res.msg);
}
});
};
</script>
<template>
<div class="flex justify-start items-center mt-10 mb-5">
<h3 class="text-xl mr-5">{{ $t("energy.floor") }}</h3>
<FloorsModal
:formState="formState"
:getData="getDataSource"
:openModal="openModal"
/>
</div>
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'operation'">
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove()"
>
{{ $t("button.delete") }}
</button>
</template>
<template v-else-if="column.key === 'floor_plan'">
<img :src="`${FILE_BASEURL}/${record.floor_map_url}.svg`" class="w-[200px] bg-white mx-auto" />
</template>
<template v-else>
{{ record[column.key] }}
</template>
</template>
</Table>
</template>
<style lang="css" scoped></style>

View File

@ -0,0 +1,108 @@
<script setup>
import { ref, onMounted, defineProps, inject, watch } from "vue";
import * as yup from "yup";
import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { postAssetFloor } from "@/apis/asset";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const props = defineProps({
formState: Object,
getData: Function,
openModal: Function,
});
const form = ref(null);
const floorScheme = yup.object({
full_name: yup.string().required(t("button.required")),
floorFile: yup.array(),
});
const updateFileList = (files) => {
console.log("file", files);
FloorFormState.value.floorFile = files;
};
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
useFormErrorMessage(floorScheme);
const onCancel = () => {
handleErrorReset();
floor_modal.close();
};
const onOk = async () => {
const value = await handleSubmit(floorScheme, props.formState);
const formData = new FormData(form.value);
formData.append(
"floor_guid",
selectedOption.value === "add" ? null : currentFloor.value.floor_guid
);
formData.append("building_tag", store.selectedBuilding.building_tag);
formData.append("initMapName", FloorFormState.value.floorFile[0]?.name);
formData.append("mapFile", FloorFormState.value.floorFile[0]);
formData.delete("floorFile");
const res = await postAssetFloor(formData);
if (res.isSuccess) {
props.getData();
onCancel();
} else {
openToast("error", res.msg, "#floor_modal");
}
};
</script>
<template>
<button class="btn btn-sm btn-add" @click.stop.prevent="props.openModal">
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal
id="floor_modal"
:title="props.formState?.floor_guid ? t('button.edit') : t('button.add')"
:onCancel="onCancel"
width="400"
>
<template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input :value="formState" class="my-2" name="full_name">
<template #topLeft>{{ $t("assetManagement.system_name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.name }}
</span></template
></Input
>
<Upload
name="floorFile"
:fileList="formState.floorFile"
:getFileList="updateFileList"
:multiple="false"
class="col-span-2"
formats="svg"
>
<template #topLeft>{{ $t("assetManagement.oriFile") }}</template>
</Upload>
</form>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template>
</Modal>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,144 @@
<script setup>
import { ref, onMounted, watch, computed } from "vue";
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
import useActiveBtn from "@/hooks/useActiveBtn";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const formState = ref({});
const dataSource = ref([]);
const loading = ref(false);
//
const {
items: mainSysItems,
changeActiveBtn: changeMainSysActiveBtn,
setItems: setMainSysItems,
selectedBtn: selectedMainSysItems,
} = useActiveBtn();
//
const {
items: subSysItems,
changeActiveBtn: changeSubSysActiveBtn,
setItems: setSubSysItems,
selectedBtn: selectedSubSysItems,
} = useActiveBtn();
const getMainSystems = async () => {
const res = await getAssetMainList();
const cate = res.data.map((d, index) => ({
...d,
title: d.system_key,
key: d.id,
active: index === 0,
}));
setMainSysItems(cate);
};
const getSubSystems = async (id) => {
const res = await getAssetSubList(id);
const sub = res.data.map((d, index) => ({
...d,
title: d.system_key,
key: d.id,
active: index === 0,
}));
setSubSysItems(sub);
};
const columns = computed(() => [
{
title: "schema",
key: "schema_name",
},
{
title: "point",
key: "pointOrg",
filter: true,
},
{
title: "Description",
key: "description",
},
]);
const getDataSource = async (id) => {
loading.value = true;
const res = await getIOTSchema(id);
dataSource.value = res.data.flatMap((d) =>
d.details.map((detail) => ({
key: `${d.id}-${detail.pointOrg}`,
schema_name: d.name,
pointOrg: detail.pointOrg,
description: detail.describe,
}))
);
loading.value = false;
};
watch(
selectedMainSysItems,
(newValue) => {
if (newValue && newValue.key) {
getSubSystems(parseInt(newValue.key));
}
},
{
deep: true,
}
);
watch(
selectedSubSysItems,
(newValue) => {
if (newValue && newValue.key) {
getDataSource(parseInt(newValue.key));
}
},
{
deep: true,
}
);
onMounted(() => {
getMainSystems();
});
</script>
<template>
<div class="">
<div class="flex items-center gap-4 mb-4">
<h2 class="text-lg font-bold whitespace-nowrap">
{{ $t("history.system_category") }} :
</h2>
<ButtonGroup
:items="mainSysItems"
:withLine="true"
:onclick="
(e, item) => {
changeMainSysActiveBtn(item);
}
"
/>
</div>
<div class="flex items-center gap-4 mb-4">
<h2 class="text-lg font-bold whitespace-nowrap">
{{ $t("history.device_category") }} :
</h2>
<ButtonGroup
:items="subSysItems"
:withLine="true"
:onclick="
(e, item) => {
changeSubSysActiveBtn(item);
}
"
/>
</div>
<Table
:columns="columns"
:dataSource="dataSource"
:loading="loading"
></Table>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,128 @@
<script setup>
import Table from "@/components/customUI/Table.vue";
import VendorModal from "./VendorModal.vue";
import { getOperationCompanyList,deleteOperationCompany } from "@/apis/operation";
import { onMounted, ref, inject, computed } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const columns = computed(() => [
{
title: t("operation.vendor"),
key: "name",
},
{
title: t("operation.contact_person"),
key: "contact_person",
},
{
title: t("operation.phone"),
key: "phone",
},
{
title: t("operation.email"),
key: "email",
},
{
title: t("operation.created_at"),
key: "created_at",
},
{
title: t("operation.operation"),
key: "operation",
width: 250,
},
]);
const dataSource = ref([]);
const loading = ref(false);
const getDataSource = async () => {
loading.value = true;
const res = await getOperationCompanyList();
dataSource.value = res.data;
loading.value = false;
};
onMounted(() => {
getDataSource();
});
const formState = ref({
contact_person: "",
email: "",
name: "",
phone: "",
remark: "",
tax_id_number: "",
city: "",
address: "",
});
const openModal = (record) => {
if (record.id) {
formState.value = { ...record };
} else {
formState.value = {
contact_person: "",
email: "",
name: "",
phone: "",
remark: "",
tax_id_number: "",
city: "",
address: "",
};
}
company_modal.showModal();
};
const remove = async (id) => {
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
await cancelToastOpen();
const res = await deleteOperationCompany(id);
if (res.isSuccess) {
getDataSource();
openToast("success", t("msg.delete_success"));
} else {
openToast("error", res.msg);
}
});
};
</script>
<template>
<div class="flex justify-start items-center mt-10 mb-5">
<h3 class="text-xl mr-5">{{ $t("assetManagement.company") }}</h3>
<VendorModal
:formState="formState"
:getData="getDataSource"
:openModal="openModal"
/>
</div>
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'operation'">
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.id)"
>
{{ $t("button.delete") }}
</button>
</template>
<template v-else>
{{ record[column.key] }}
</template>
</template>
</Table>
</template>
<style lang="css" scoped></style>

View File

@ -0,0 +1,153 @@
<script setup>
import { ref, onMounted, defineProps, inject, watch } from "vue";
import * as yup from "yup";
import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import {
postOperationCompany,
updateOperationCompany,
} from "@/apis/operation";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const props = defineProps({
formState: Object,
getData: Function,
openModal: Function
});
const deptScheme = yup.object({
name: yup.string().required(t("button.required")),
contact_person: yup.string().nullable(true),
email: yup.string().email().nullable(true),
phone: yup.string().nullable(true),
city: yup.string().nullable(true),
address: yup.string().nullable(true),
tax_id_number: yup.string().nullable(true),
remark: yup.string().nullable(true),
});
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
useFormErrorMessage(deptScheme);
const onCancel = () => {
handleErrorReset();
company_modal.close();
};
const onOk = async () => {
const value = await handleSubmit(deptScheme, props.formState);
if (props.formState?.id) {
res = await updateOperationCompany(value);
} else {
res = await postOperationCompany(value);
}
if (res.isSuccess) {
props.getData();
onCancel();
} else {
openToast("error", res.msg, "#company_modal");
}
};
</script>
<template>
<button class="btn btn-sm btn-add " @click.stop.prevent="props.openModal">
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal
id="company_modal"
:title="props.formState?.id ? t('button.edit') : t('button.add')"
:onCancel="onCancel"
width="710"
>
<template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input :value="formState" class="my-2" name="name">
<template #topLeft>{{ $t("operation.name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.name }}
</span></template
></Input
>
<Input :value="formState" class="my-2" name="contact_person">
<template #topLeft>{{ $t("operation.contact_person") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.contact_person }}
</span></template
></Input
>
<Input :value="formState" class="my-2" name="phone">
<template #topLeft>{{ $t("operation.phone") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.phone }}
</span></template
></Input
>
<Input :value="formState" class="my-2" name="email">
<template #topLeft>{{ $t("operation.email") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.email }}
</span></template
></Input
>
<Input :value="formState" class="my-2" name="city">
<template #topLeft>{{ $t("operation.city") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.city }}
</span></template
></Input
>
<Input :value="formState" class="my-2" name="address">
<template #topLeft>{{ $t("operation.address") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.address }}
</span></template
></Input
>
<Input :value="formState" class="my-2" name="tax_id_number">
<template #topLeft>{{ $t("operation.tax_id_number") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.tax_id_number }}
</span></template
></Input
>
<Input :value="formState" class="my-2" name="remark">
<template #topLeft>{{ $t("operation.remark") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.remark }}
</span></template
></Input
>
</form>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template>
</Modal>
</template>
<style lang="scss" scoped></style>