更新環境變數設定,新增 MQTT 相關 API,並新增總部帳戶管理初版
This commit is contained in:
parent
2dfc2e5297
commit
64f35db51b
@ -1,4 +1,4 @@
|
|||||||
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw"
|
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw"
|
||||||
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
|
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
|
||||||
VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
# VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
||||||
VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"
|
VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"
|
@ -1,4 +1,4 @@
|
|||||||
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw"
|
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw"
|
||||||
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
|
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
|
||||||
VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
# VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
||||||
# VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"
|
# VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"
|
@ -1,3 +1,3 @@
|
|||||||
VITE_API_BASEURL = "https://ibms-cvilux-demo-api.production.mjmtech.com.tw"
|
VITE_API_BASEURL = "https://ibms-cvilux-demo-api.production.mjmtech.com.tw"
|
||||||
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
|
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
|
||||||
VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
# VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
@ -36,4 +36,6 @@ export const DELETE_ASSET_ELECTYPE_API = `/AssetManage/DeleteElecType`;
|
|||||||
|
|
||||||
export const POST_ASSET_ELEC_SETTING_API = `/AssetManage/SaveAssetSetting`;
|
export const POST_ASSET_ELEC_SETTING_API = `/AssetManage/SaveAssetSetting`;
|
||||||
|
|
||||||
export const POST_ASSET_MQTT_PUBLISH_API = `/api/mqtt/publish`;
|
export const POST_ASSET_MQTT_PUBLISH_API = `/api/mqtt/publish`;
|
||||||
|
export const POST_MQTT_TOPIC_API = `api/Device/MQTTTopicTest`;
|
||||||
|
export const POST_MQTT_TOPIC_STOP_API = `api/Device/MQTTTopicTestStop`;
|
@ -27,6 +27,8 @@ import {
|
|||||||
DELETE_ASSET_ELECTYPE_API,
|
DELETE_ASSET_ELECTYPE_API,
|
||||||
POST_ASSET_ELEC_SETTING_API,
|
POST_ASSET_ELEC_SETTING_API,
|
||||||
POST_ASSET_MQTT_PUBLISH_API,
|
POST_ASSET_MQTT_PUBLISH_API,
|
||||||
|
POST_MQTT_TOPIC_API,
|
||||||
|
POST_MQTT_TOPIC_STOP_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";
|
||||||
@ -359,3 +361,21 @@ export const postMQTTpublish = async ({ Topic, Payload }) => {
|
|||||||
code: res.code,
|
code: res.code,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const postMqttTopic = async ({ iotTag, Topic }) => {
|
||||||
|
const res = await instance.post(POST_MQTT_TOPIC_API, { iotTag, Topic });
|
||||||
|
|
||||||
|
return apihandler(res.code, res.data, {
|
||||||
|
msg: res.msg,
|
||||||
|
code: res.code,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const postMqttTopicStop = async ({ iotTag, Topic }) => {
|
||||||
|
const res = await instance.post(POST_MQTT_TOPIC_STOP_API, { iotTag, Topic });
|
||||||
|
|
||||||
|
return apihandler(res.code, res.data, {
|
||||||
|
msg: res.msg,
|
||||||
|
code: res.code,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -3,4 +3,6 @@ export const GET_SITES_SYSTEM_ENERGY_COST_RANK_API = `/api/energy-manager/all-si
|
|||||||
export const GET_SITES_SYSTEM_ENERGY_COST_TREND_API = `/api/energy-manager/all-site/energy-cost-trend`;
|
export const GET_SITES_SYSTEM_ENERGY_COST_TREND_API = `/api/energy-manager/all-site/energy-cost-trend`;
|
||||||
export const GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API = `/api/energy-manager/all-site/energy-cost-growth-rate`;
|
export const GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API = `/api/energy-manager/all-site/energy-cost-growth-rate`;
|
||||||
|
|
||||||
|
export const GET_USER_API = `/api/user/user-list`;
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
GET_SITES_SYSTEM_ENERGY_COST_RANK_API,
|
GET_SITES_SYSTEM_ENERGY_COST_RANK_API,
|
||||||
GET_SITES_SYSTEM_ENERGY_COST_TREND_API,
|
GET_SITES_SYSTEM_ENERGY_COST_TREND_API,
|
||||||
GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API,
|
GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API,
|
||||||
|
GET_USER_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";
|
||||||
@ -37,6 +38,25 @@ export const getSystemEnergyCostTrend = async (building_ids) => {
|
|||||||
export const getSystemEnergyCostGrowth = async (building_ids) => {
|
export const getSystemEnergyCostGrowth = async (building_ids) => {
|
||||||
const res = await instance.get(GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API, building_ids);
|
const res = await instance.get(GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API, building_ids);
|
||||||
|
|
||||||
|
return apihandler(res.code, res.data, {
|
||||||
|
msg: res.msg,
|
||||||
|
code: res.code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getUserList = async (params = {}) => {
|
||||||
|
const {
|
||||||
|
page = 1,
|
||||||
|
pageSize = 9999999
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
const requestData = {
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await instance.post(GET_USER_API, requestData);
|
||||||
|
|
||||||
return apihandler(res.code, res.data, {
|
return apihandler(res.code, res.data, {
|
||||||
msg: res.msg,
|
msg: res.msg,
|
||||||
code: res.code,
|
code: res.code,
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"graphManagement": "图资管理",
|
"graphManagement": "图资管理",
|
||||||
"AssetManagement": "资产管理",
|
"AssetManagement": "资产管理",
|
||||||
"accountManagement": "帐号管理",
|
"accountManagement": "帐号管理",
|
||||||
|
"UserManagement": "帐号管理",
|
||||||
"Setting": "系统设定",
|
"Setting": "系统设定",
|
||||||
"energy_analysis": "能源分析",
|
"energy_analysis": "能源分析",
|
||||||
"consumption_report": "用电报表",
|
"consumption_report": "用电报表",
|
||||||
@ -430,6 +431,7 @@
|
|||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"sure_to_delete": "是否确认删除该项目?",
|
"sure_to_delete": "是否确认删除该项目?",
|
||||||
|
"is_headquarters": "该帐号为总部帐号,是否仍要删除该项目?",
|
||||||
"sure_to_delete_permanent": "是否确认永久删除该项目?",
|
"sure_to_delete_permanent": "是否确认永久删除该项目?",
|
||||||
"delete_success": "删除成功",
|
"delete_success": "删除成功",
|
||||||
"delete_failed": "删除失败",
|
"delete_failed": "删除失败",
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"graphManagement": "圖資管理",
|
"graphManagement": "圖資管理",
|
||||||
"AssetManagement": "資產管理",
|
"AssetManagement": "資產管理",
|
||||||
"accountManagement": "帳號管理",
|
"accountManagement": "帳號管理",
|
||||||
|
"UserManagement": "帳號管理",
|
||||||
"Setting": "系統設定",
|
"Setting": "系統設定",
|
||||||
"energy_analysis": "能耗分析",
|
"energy_analysis": "能耗分析",
|
||||||
"consumption_report": "用電報表",
|
"consumption_report": "用電報表",
|
||||||
@ -430,6 +431,7 @@
|
|||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"sure_to_delete": "是否確認刪除該項目?",
|
"sure_to_delete": "是否確認刪除該項目?",
|
||||||
|
"is_headquarters": "該帳號為總部帳號,是否仍要刪除該項目?",
|
||||||
"sure_to_delete_permanent": "是否確認永久刪除該項目?",
|
"sure_to_delete_permanent": "是否確認永久刪除該項目?",
|
||||||
"delete_success": "刪除成功",
|
"delete_success": "刪除成功",
|
||||||
"delete_failed": "刪除失敗",
|
"delete_failed": "刪除失敗",
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
"graphManagement": "Graph",
|
"graphManagement": "Graph",
|
||||||
"AssetManagement": "Devices",
|
"AssetManagement": "Devices",
|
||||||
"accountManagement": "Account",
|
"accountManagement": "Account",
|
||||||
|
"UserManagement": "Account",
|
||||||
"Setting": "Setting",
|
"Setting": "Setting",
|
||||||
"energy_analysis": "Energy Analysis",
|
"energy_analysis": "Energy Analysis",
|
||||||
"consumption_report": "Consumption Report",
|
"consumption_report": "Consumption Report",
|
||||||
"chart_analysis": "Chart Analysis",
|
"chart_analysis": "Chart Analysis",
|
||||||
"historical_curve": "Historical Curve",
|
"historical_curve": "Historical Curve",
|
||||||
@ -430,6 +431,7 @@
|
|||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"sure_to_delete": "Are you sure to delete this item?",
|
"sure_to_delete": "Are you sure to delete this item?",
|
||||||
|
"is_headquarters": "This account is a headquarters account. Are you sure you want to delete this item?",
|
||||||
"sure_to_delete_permanent": "Are you sure you want to permanently delete this item?",
|
"sure_to_delete_permanent": "Are you sure you want to permanently delete this item?",
|
||||||
"delete_success": "Delete successfully",
|
"delete_success": "Delete successfully",
|
||||||
"delete_failed": "Delete failed",
|
"delete_failed": "Delete failed",
|
||||||
|
@ -18,6 +18,7 @@ export default function useForgeHeatmap() {
|
|||||||
|
|
||||||
//create the heatmap
|
//create the heatmap
|
||||||
function getSensorValue(device, sensorType, pointData) {
|
function getSensorValue(device, sensorType, pointData) {
|
||||||
|
console.log("heatmap", device, realtimeData.value);
|
||||||
const dev = realtimeData.value.find(
|
const dev = realtimeData.value.find(
|
||||||
({ device_number }) => device_number === device.id
|
({ device_number }) => device_number === device.id
|
||||||
);
|
);
|
||||||
|
@ -10,6 +10,7 @@ import ProductSetting from "@/views/productSetting/ProductSetting.vue";
|
|||||||
import EnergyManagement from "@/views/energyManagement/EnergyManagement.vue";
|
import EnergyManagement from "@/views/energyManagement/EnergyManagement.vue";
|
||||||
import SettingManagement from "@/views/setting/SettingManagement.vue";
|
import SettingManagement from "@/views/setting/SettingManagement.vue";
|
||||||
import HeadquartersManagement from "@/views/headquarters/HeadquartersManagement.vue";
|
import HeadquartersManagement from "@/views/headquarters/HeadquartersManagement.vue";
|
||||||
|
import UserManagement from "@/views/headquarters/HeadquartersAccountManagement.vue";
|
||||||
import Login from "@/views/login/Login.vue";
|
import Login from "@/views/login/Login.vue";
|
||||||
import useUserInfoStore from "@/stores/useUserInfoStore";
|
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||||
import useBuildingStore from "@/stores/useBuildingStore";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
@ -97,6 +98,11 @@ const router = createRouter({
|
|||||||
name: "headquarters",
|
name: "headquarters",
|
||||||
component: HeadquartersManagement,
|
component: HeadquartersManagement,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/UserManagement",
|
||||||
|
name: "UserManagement",
|
||||||
|
component: UserManagement,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/mytestfile/mjm",
|
path: "/mytestfile/mjm",
|
||||||
name: "mytestfile",
|
name: "mytestfile",
|
||||||
|
@ -12,13 +12,23 @@ const { searchParams, changeParams } = useSearchParam();
|
|||||||
const companyOptions = ref([]);
|
const companyOptions = ref([]);
|
||||||
const iotSchemaOptions = ref([]);
|
const iotSchemaOptions = ref([]);
|
||||||
const elecTypeOptions = ref([]);
|
const elecTypeOptions = ref([]);
|
||||||
|
const iotSchemaTag = ref(""); // 儲存 IOT Schema 的 tagIoT
|
||||||
const getCompany = async () => {
|
const getCompany = async () => {
|
||||||
const res = await getOperationCompanyList();
|
const res = await getOperationCompanyList();
|
||||||
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||||
};
|
};
|
||||||
const getIOTSchemaOptions = async (id) => {
|
const getIOTSchemaOptions = async (id) => {
|
||||||
const res = await getIOTSchema(Number(id));
|
const res = await getIOTSchema(Number(id));
|
||||||
iotSchemaOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
const data = res.data || [];
|
||||||
|
|
||||||
|
iotSchemaOptions.value = data.map((d) => ({ ...d, key: d.id }));
|
||||||
|
|
||||||
|
// 取出第一筆的 tagIoT,提供給最深層元件使用
|
||||||
|
if (data.length > 0 && data[0].tagIoT) {
|
||||||
|
iotSchemaTag.value = data[0].tagIoT;
|
||||||
|
} else {
|
||||||
|
iotSchemaTag.value = "";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const getElecType = async () => {
|
const getElecType = async () => {
|
||||||
const res = await getElecTypeList();
|
const res = await getElecTypeList();
|
||||||
@ -51,6 +61,7 @@ provide("asset_modal_options", {
|
|||||||
elecTypeOptions,
|
elecTypeOptions,
|
||||||
departmentList,
|
departmentList,
|
||||||
floors,
|
floors,
|
||||||
|
iotSchemaTag
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,98 +1,122 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref, inject, watch, computed } from "vue";
|
import { onMounted, ref, inject, watch, computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { postMQTTpublish } from "@/apis/asset";
|
import { postMQTTpublish, postMqttTopic, postMqttTopicStop } from "@/apis/asset";
|
||||||
import mqtt from "mqtt";
|
import mqtt from "mqtt";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
const { formState } = inject("asset_table_modal_form");
|
const { formState } = inject("asset_table_modal_form");
|
||||||
const BASEURL = import.meta.env.VITE_MQTT_BASEURL;
|
const { iotSchemaTag } = inject("asset_modal_options");
|
||||||
|
|
||||||
// MQTT相關
|
// MQTT相關
|
||||||
const mqttClient = ref(null); // MQTT客戶端
|
|
||||||
const receivedMessages = ref([]); // 儲存接收到的訊息
|
const receivedMessages = ref([]); // 儲存接收到的訊息
|
||||||
const countdown = ref(60); // 倒計時初始為 60 秒
|
const countdown = ref(60); // 倒計時初始為 60 秒
|
||||||
|
const hasStartedCountdown = ref(false); // 是否已開始倒數
|
||||||
let timer = null; // 記錄計時器
|
let timer = null; // 記錄計時器
|
||||||
|
let mqttInterval = null;
|
||||||
|
const mqttCardDataList = ref([]); // 顯示在畫面上的卡片資料
|
||||||
|
|
||||||
const openModal = () => {
|
const openModal = async () => {
|
||||||
if (!mqttClient.value) {
|
|
||||||
connectMqtt();
|
|
||||||
}
|
|
||||||
mqtt_test.showModal();
|
mqtt_test.showModal();
|
||||||
startCountdown(); // 開始倒計時
|
|
||||||
};
|
|
||||||
|
|
||||||
const connectMqtt = () => {
|
// 先立即呼叫一次
|
||||||
const topic = formState.value.topic || ""; // 取得主題
|
try {
|
||||||
const mqttHost = `${BASEURL}`;
|
await postMqttTopic({
|
||||||
const protocol = "wss"; // 根據伺服器配置,需要設置為 "ws" 或 "wss"
|
iotTag: iotSchemaTag?.value,
|
||||||
mqttClient.value = mqtt.connect(mqttHost, {
|
Topic: formState.value.topic,
|
||||||
protocol,
|
|
||||||
reconnectPeriod: 1000, // 每秒嘗試重新連線
|
|
||||||
username: "admin", // MQTT 帳號
|
|
||||||
password: "mjmadmin@99", // MQTT 密碼
|
|
||||||
port: 443,
|
|
||||||
});
|
|
||||||
|
|
||||||
mqttClient.value.on("connect", () => {
|
|
||||||
console.log("MQTT 已連接");
|
|
||||||
if (topic) {
|
|
||||||
mqttClient.value.subscribe(topic, (err) => {
|
|
||||||
if (!err) {
|
|
||||||
console.log(`已訂閱主題: ${topic}`);
|
|
||||||
} else {
|
|
||||||
console.error("訂閱失敗: ", err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mqttClient.value.on("message", (topic, message) => {
|
|
||||||
// 儲存接收到的訊息
|
|
||||||
const now = dayjs(); // 使用 dayjs() 取得當前時間
|
|
||||||
const timestamp = now.format("YYYY-MM-DD HH:mm:ss");
|
|
||||||
|
|
||||||
receivedMessages.value.push({
|
|
||||||
topic,
|
|
||||||
message: message.toString(),
|
|
||||||
timestamp: timestamp,
|
|
||||||
});
|
});
|
||||||
clearInterval(timer); // 收到訊息後清除倒計時
|
// console.log("首次 postMqttTopic API 已呼叫");
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error("首次 postMqttTopic 發送失敗", error);
|
||||||
|
}
|
||||||
|
|
||||||
mqttClient.value.on("error", (err) => {
|
// 啟動每 5 秒重複呼叫
|
||||||
console.error("MQTT 連線錯誤: ", err);
|
mqttInterval = setInterval(async () => {
|
||||||
});
|
try {
|
||||||
|
const res = await postMqttTopic({
|
||||||
|
iotTag: iotSchemaTag?.value,
|
||||||
|
Topic: formState.value.topic,
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("postMqttTopic 回傳:", res?.data);
|
||||||
|
|
||||||
|
const payload = res?.data;
|
||||||
|
|
||||||
|
// 確保 payload 結構正確
|
||||||
|
if (payload && payload.data && payload.time) {
|
||||||
|
const timeAlreadyExists = mqttCardDataList.value.some(
|
||||||
|
(item) => item.time === payload.time
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!timeAlreadyExists) {
|
||||||
|
mqttCardDataList.value.unshift({
|
||||||
|
...payload.data,
|
||||||
|
time: payload.time,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ⬇第一次收到資料才開始倒數
|
||||||
|
if (!hasStartedCountdown.value) {
|
||||||
|
hasStartedCountdown.value = true;
|
||||||
|
startCountdown();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// console.log("已存在相同時間略過:", payload.time);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// console.warn("回傳資料結構錯誤或缺少 time");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("postMqttTopic 呼叫失敗:", error);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const startCountdown = () => {
|
const startCountdown = () => {
|
||||||
if (timer) return; // 防止重複啟動計時器
|
countdown.value = 60;
|
||||||
|
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
if (countdown.value > 0) {
|
if (countdown.value > 1) {
|
||||||
countdown.value--;
|
countdown.value--;
|
||||||
} else {
|
} else {
|
||||||
onCancel(); // 1分鐘後如果沒有收到訊息則觸發 onCancel
|
clearInterval(timer);
|
||||||
|
timer = null;
|
||||||
|
onCancel(); // 60秒結束自動關閉
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = async () => {
|
||||||
|
// 清空資料與狀態
|
||||||
receivedMessages.value = [];
|
receivedMessages.value = [];
|
||||||
|
mqttCardDataList.value = [];
|
||||||
|
countdown.value = 60;
|
||||||
|
hasStartedCountdown.value = false;
|
||||||
|
|
||||||
mqtt_test.close();
|
mqtt_test.close();
|
||||||
|
|
||||||
// 斷開 MQTT 連線
|
// 停止 API 呼叫
|
||||||
if (mqttClient.value) {
|
try {
|
||||||
mqttClient.value.end();
|
await postMqttTopicStop({
|
||||||
mqttClient.value = null;
|
iotTag: iotSchemaTag?.value,
|
||||||
|
Topic: formState.value.topic,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("postMqttTopicStop 發送失敗", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置倒計時
|
// 清除 API Interval
|
||||||
|
if (mqttInterval) {
|
||||||
|
clearInterval(mqttInterval);
|
||||||
|
mqttInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除倒數 Timer
|
||||||
if (timer) {
|
if (timer) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
timer = null; // 清除計時器引用
|
timer = null;
|
||||||
}
|
}
|
||||||
countdown.value = 60;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
@ -142,42 +166,69 @@ const onSubmit = async () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal id="mqtt_test" title="MQTT Topic" :onCancel="onCancel" :width="400">
|
<Modal id="mqtt_test" title="MQTT Topic" :onCancel="onCancel" width="400">
|
||||||
<template #modalContent>
|
<template #modalContent>
|
||||||
<!-- 顯示接收到的訊息 -->
|
<!-- 顯示接收到的訊息 -->
|
||||||
<div v-if="receivedMessages.length > 0" class="overflow-y-auto h-96">
|
<div v-if="mqttCardDataList.length > 0" class="overflow-y-auto h-96 mt-4">
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
v-for="(message, index) in receivedMessages"
|
v-for="(item, index) in mqttCardDataList"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="bg-base-200 rounded-md text-wrap shadow shadow-slate-400 p-4 my-2 me-2"
|
class="bg-base-200 rounded-md text-wrap shadow shadow-slate-400 p-4 my-2 me-2"
|
||||||
>
|
>
|
||||||
<strong class="text-base block text-info mb-2"
|
<strong
|
||||||
>{{ message.topic }} :</strong
|
class="text-base block text-info mb-2 flex justify-between items-center"
|
||||||
>
|
>
|
||||||
<p class="text-sm break-words">{{ message.message }}</p>
|
<span>
|
||||||
<p class="text-xs text-slate-200 pt-2">
|
<FontAwesomeIcon :icon="['fas', 'clock']" class="me-1" />
|
||||||
<FontAwesomeIcon :icon="['fas', 'clock']" class="me-1" />
|
{{ dayjs(item.time).format("YYYY-MM-DD HH:mm:ss") }}
|
||||||
{{ message.timestamp }}
|
</span>
|
||||||
</p>
|
|
||||||
|
<!-- 只在第一筆資料加上「最新」標籤 -->
|
||||||
|
<span
|
||||||
|
v-if="index === 0"
|
||||||
|
class="text-xs text-white bg-green-600 px-2 py-1 rounded"
|
||||||
|
>
|
||||||
|
New
|
||||||
|
</span>
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<!-- 動態顯示除了 time 以外的所有欄位 -->
|
||||||
|
<template v-for="[key, value] in Object.entries(item)" :key="key">
|
||||||
|
<p v-if="key !== 'time'" class="text-sm break-words">
|
||||||
|
<strong>{{ key }}:</strong>{{ value }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- 顯示 loading 和倒計時 -->
|
|
||||||
<p v-else class="text-center mt-20">
|
<!-- 顯示 loading 和倒計時(只有沒資料才顯示) -->
|
||||||
|
<p v-if="mqttCardDataList.length === 0" class="text-center mt-20">
|
||||||
<Loading />
|
<Loading />
|
||||||
<br />
|
<br />
|
||||||
<span class="text-base">{{ countdown }} seconds</span>
|
<span class="text-base">Loading...</span>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<template #modalAction>
|
<template #modalAction>
|
||||||
<button
|
<div class="relative w-full flex justify-end items-center gap-12">
|
||||||
type="reset"
|
<!-- 資料出現後才顯示倒數計時,置中顯示 -->
|
||||||
class="btn btn-outline-success mr-2"
|
<div
|
||||||
@click.prevent="onCancel"
|
v-if="mqttCardDataList.length > 0"
|
||||||
>
|
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center gap-2 text-sm"
|
||||||
{{ t("button.cancel") }}
|
>
|
||||||
</button>
|
<span>Auto close in</span>
|
||||||
|
<span>{{ countdown }}s</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="reset"
|
||||||
|
class="btn btn-outline-success"
|
||||||
|
@click.prevent="onCancel"
|
||||||
|
>
|
||||||
|
{{ t("button.cancel") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
@ -127,8 +127,9 @@ const resetModalForm = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeAccount = async (id) => {
|
const removeAccount = async (id, notHeadquarters) => {
|
||||||
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
|
const message = notHeadquarters ? t("msg.sure_to_delete") : t("msg.is_headquarters");
|
||||||
|
openToast("warning", message, "body", async () => {
|
||||||
await cancelToastOpen();
|
await cancelToastOpen();
|
||||||
const res = await delAccount(id);
|
const res = await delAccount(id);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
@ -202,7 +203,7 @@ const removeAccount = async (id) => {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-error text-white"
|
class="btn btn-sm btn-error text-white"
|
||||||
@click.stop.prevent="() => removeAccount(record.userinfo_guid)"
|
@click.stop.prevent="() => removeAccount(record.userinfo_guid,record.canDeleted)"
|
||||||
>
|
>
|
||||||
{{ $t("button.delete") }}
|
{{ $t("button.delete") }}
|
||||||
</button>
|
</button>
|
||||||
|
123
src/views/headquarters/HeadquartersAccountManagement.vue
Normal file
123
src/views/headquarters/HeadquartersAccountManagement.vue
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<script setup>
|
||||||
|
import Table from "@/components/customUI/Table.vue";
|
||||||
|
// import AccountModal from "./AccountModal.vue";
|
||||||
|
import {
|
||||||
|
getUserList
|
||||||
|
} from "@/apis/headquarters";
|
||||||
|
import { onMounted, ref, inject, computed } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
|
|
||||||
|
const dataSource = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const searchData = ref({
|
||||||
|
Full_name: "",
|
||||||
|
Role_full_name: "",
|
||||||
|
});
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
title: t("accountManagement.index"),
|
||||||
|
key: "index",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("accountManagement.name"),
|
||||||
|
key: "full_name",
|
||||||
|
filter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("accountManagement.account"),
|
||||||
|
key: "account",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("accountManagement.role"),
|
||||||
|
key: "role_full_name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("accountManagement.email"),
|
||||||
|
key: "email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("accountManagement.phone"),
|
||||||
|
key: "phone",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("accountManagement.created_at"),
|
||||||
|
key: "created_at",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("accountManagement.operation"),
|
||||||
|
key: "operation",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getDataSource = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getUserList();
|
||||||
|
dataSource.value = res.data?.users || [];
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearch = () => {
|
||||||
|
getDataSource();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
getDataSource();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUser = async (id) => {
|
||||||
|
// const res = await getAccountOneUser(id);
|
||||||
|
openModal(res.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getDataSource();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1 class="text-2xl font-extrabold mb-2">
|
||||||
|
{{ $t("accountManagement.account_title") }}
|
||||||
|
</h1>
|
||||||
|
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
|
||||||
|
<template #beforeTable>
|
||||||
|
<div class="flex items-center mb-8">
|
||||||
|
<Input
|
||||||
|
:placeholder="t('accountManagement.name_placeholder')"
|
||||||
|
name="Full_name"
|
||||||
|
:value="searchData"
|
||||||
|
class="mr-3 w-96"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
:placeholder="t('accountManagement.role_placeholder')"
|
||||||
|
name="Role_full_name"
|
||||||
|
:value="searchData"
|
||||||
|
/>
|
||||||
|
<button class="btn btn-search ml-5" @click.stop.prevent="onSearch">
|
||||||
|
<font-awesome-icon :icon="['fas', 'search']" />
|
||||||
|
{{ $t("button.search") }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-neutral mx-4" @click.stop.prevent="onReset">
|
||||||
|
{{ $t("button.reset") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<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="() => getUser(record.userinfo_guid)"
|
||||||
|
>
|
||||||
|
{{ $t("button.edit") }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ record[column.key] }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
@ -191,7 +191,7 @@ const getAllDeviceRealtime = async () => {
|
|||||||
const res = await getSystemRealTime(
|
const res = await getSystemRealTime(
|
||||||
subscribeData.value.map((d) => d.device_number)
|
subscribeData.value.map((d) => d.device_number)
|
||||||
);
|
);
|
||||||
console.log(res.data);
|
console.log("realtimeData",res.data);
|
||||||
realtimeData.value = res.data;
|
realtimeData.value = res.data;
|
||||||
};
|
};
|
||||||
await fetchData(); // 立即執行一次
|
await fetchData(); // 立即執行一次
|
||||||
|
Loading…
Reference in New Issue
Block a user