宁德大屏第二版总结
碰到难点
1.wss 心跳机制
实现前端和后端双向绑定 只要后端发送了消息 前端通过全局总线去触发你想要的函数。
全局总线
vue3可以全局总线下一个mitt
新建一个eventBus.js
import mitt from "mitt";
const eventBus = mitt();export default eventBus;
然后wss新建一个useWebSocket.js
import { ref } from "vue";
import eventBus from "../mixins/eventBus";// 连接状态
export const SocketStatus = {Connecting: "正在连接...", // 表示正在连接,这是初始状态。Connected: "连接已建立", // 表示连接已经建立。Disconnecting: "连接正在关闭", // 表示连接正在关闭。Disconnected: "连接已断开", // 表示连接已经关闭。
};const DEFAULT_OPTIONS = {url: "", // WebSocket URLheartBeatData: "", // 心跳数据heartBeatInterval: 60 * 1000, // 心跳间隔,单位 msreconnectInterval: 5 * 1000, // 断线重连间隔,单位 msmaxReconnectAttempts: 10, // 最大重连次数
};const SocketCloseCode = 1000;export default function useWebSocket(options = {}, onMessageCallback) {//onMessageCallback 处理回调函数 确保在收到消息时候调用const state = ref({options: { ...DEFAULT_OPTIONS, ...options },socket: null,heartBeatSendTimer: null, // 心跳发送定时器heartBeatTimeoutTimer: null, // 心跳超时定时器reconnectAttempts: 0,reconnectTimeout: null,});const status = ref(SocketStatus.Disconnected);// 连接 WebSocketconst connect = () => {disconnect(); // 断开之前的连接status.value = SocketStatus.Connecting;state.value.socket = new WebSocket(state.value.options.url);state.value.socket.onopen = (openEvent) => {console.log("socket连接:", openEvent);status.value = SocketStatus.Connected;startHeartBeat(); // 开始心跳};state.value.socket.onmessage = (msgEvent) => {console.log("socket消息:", msgEvent);if (typeof onMessageCallback === "function") {// onMessageCallback(); // 调用传入的回调函数// 广播消息
//在这边可以不用调用传入的函数 可以直接全局调用函数 懒得改了eventBus.emit("socketMessage", msgEvent.data);eventBus.emit("Messageaa");eventBus.emit("Messagebb");} else {console.error("getDate is not a function");}// if (typeof getDate === "function") {// getDate(); // 调用 getDate 函数// } else {// console.error("getDate is not a function");// }startHeartBeat(); // 收到消息时重新开始心跳};state.value.socket.onclose = (closeEvent) => {console.log("socket关闭:", closeEvent);status.value = SocketStatus.Disconnected;// 非正常关闭,尝试重连if (closeEvent.code !== SocketCloseCode) {reconnect();}};state.value.socket.onerror = (errEvent) => {console.log("socket报错:", errEvent);status.value = SocketStatus.Disconnected;reconnect(); // 连接失败,尝试重连};};// 断开 WebSocketconst disconnect = () => {// 如果 WebSocket 实例存在且处于开放或连接中的状态,则关闭连接。if (state.value.socket && (state.value.socket.OPEN || state.value.socket.CONNECTING)) {console.log("socket断开连接");status.value = SocketStatus.Disconnecting;state.value.socket.close(SocketCloseCode, "normal closure");state.value.socket = null;stopHeartBeat(); // 停止心跳stopReconnect(); // 停止重连}};// 开始心跳检测const startHeartBeat = () => {stopHeartBeat(); // 先清除之前的定时器state.value.heartBeatSendTimer = setTimeout(() => {if (status.value === SocketStatus.Connected) {state.value.socket.send(state.value.options.heartBeatData);console.log("socket心跳发送:", state.value.options.heartBeatData);}// 心跳超时state.value.heartBeatTimeoutTimer = setTimeout(() => {console.log("心跳超时,关闭连接");state.value.socket.close(4444, "heart timeout");}, state.value.options.heartBeatInterval);}, state.value.options.heartBeatInterval);};// 停止心跳检测const stopHeartBeat = () => {if (state.value.heartBeatSendTimer) {clearTimeout(state.value.heartBeatSendTimer);state.value.heartBeatSendTimer = null;}if (state.value.heartBeatTimeoutTimer) {clearTimeout(state.value.heartBeatTimeoutTimer);state.value.heartBeatTimeoutTimer = null;}};// 重连机制const reconnect = () => {// reconnect:如果连接状态不是 Connected 或 Connecting,并且重连尝试次数小于最大值,则尝试重连。if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) {return;}stopHeartBeat(); // 停止心跳if (state.value.reconnectAttempts < state.value.options.maxReconnectAttempts) {console.log("socket重连:", state.value.reconnectAttempts);// 重连间隔,5秒起步,下次递增1秒const interval = Math.max(state.value.options.reconnectInterval, state.value.reconnectAttempts * 1000);console.log("间隔时间:", interval);state.value.reconnectTimeout = setTimeout(() => {if (status.value !== SocketStatus.Connected && status.value !== SocketStatus.Connecting) {connect();}}, interval);state.value.reconnectAttempts += 1;} else {status.value = SocketStatus.Disconnected;stopReconnect(); // 停止重连}};// 停止重连const stopReconnect = () => {if (state.value.reconnectTimeout) {clearTimeout(state.value.reconnectTimeout);state.value.reconnectTimeout = null;}};return {connect,disconnect,status,};
}
重点是这步
state.value.socket.onmessage = (msgEvent) => {console.log("socket消息:", msgEvent);if (typeof onMessageCallback === "function") {// onMessageCallback(); // 调用传入的回调函数// 广播消息
//在这边可以不用调用传入的函数 可以直接全局调用函数 懒得改了eventBus.emit("socketMessage", msgEvent.data);eventBus.emit("Messageaa");eventBus.emit("Messagebb");} else {console.error("getDate is not a function");}
然后在自己的组件
import useWebSocket, { SocketStatus } from "@/mixins/useWebSocket";
import eventBus from "@/mixins/eventBus";onMounted(() => {eventBus.on("tenDays", getDate);eventBus.on("socketMessage", getDate); // 监听 WebSocket 消息事件getDate(); // 在组件初始化时调用 getDategetUser(); //为了链接wss});
onUnmounted(() => {eventBus.off("tenDays", getDate);eventBus.off("socketMessage", getDate);disconnect(); // 断开 WebSocket 连接
});
我传函数进去了其实不用的 懒得改了 因为 我要接收到数据 好几个函数一起被触发 所以全局总线比较好
const ID = ref("");
const getUser = () => {userInfo().then((res) => {// console.log("用户res", res);localStorage.setItem("userInof", JSON.stringify(res.data.sysUser));ID.value = res.data.sysUser.id;if (ID.value) {console.log("连接WebSocket");// setLoginCookie(); // 设置登录 Cookie(包括 token)connect(); // 连接 WebSocket} // 组件挂载时连接 WebSocket});
};const { connect, disconnect, status } = useWebSocket({url: computed(() => {// const token = Cookie.get("Authorization") || "";return ID.value? `wss://www.tbaowl.com:9992/ws/mini/websocket/${ID.value}`: "";}), // 替换为实际的 WebSocket URLheartBeatData: "ping", // 心跳数据heartBeatInterval: 30000, // 心跳间隔,30秒reconnectInterval: 5000, // 重连间隔,5秒maxReconnectAttempts: 5, // 最大重连次数},getDate
);
2.关于弹窗红色预警,逻辑。
<template><transition-group name="scroll" tag="div" class="warmTanChuan-container"><divv-if="isRunning && currentItem"class="warmTanChuan"ref="warmTanChuan":key="currentItem.id"><div class="title"><img src="@/assets/images/Frame103(91).png" alt="" /><span v-if="currentItem.deviceType == 1">摄像头警告:</span><span v-if="currentItem.deviceType == 2">灵思传感器警告:</span><span v-if="currentItem.deviceType == 3">大华电气设备警告:</span><span v-if="currentItem.deviceType == 4">消防设备警告:</span><span v-if="currentItem.deviceType == 5">车载设备警告:</span><span v-if="currentItem.deviceType == 6">海康消防设备警告:</span><span v-if="currentItem.deviceType == 7">消防传感器警告:</span><span class="time">{{ time }}s</span></div><div class="content_text"><p>{{ currentItem.incidentDescribe }}</p></div></div></transition-group><div class="test" v-if="isTestVisible && warmList.length > 0"></div>
</template><script setup>
import { ref, onMounted, watch, computed, onUnmounted, nextTick } from "vue";
import { warmEvents } from "@/api/api.js";
import dayjs from "dayjs";
import useWebSocket, { SocketStatus } from "@/mixins/useWebSocket";
import { getSystemData, userInfo } from "@/api/api";
import eventBus from "@/mixins/eventBus";
const warmList = ref([]);
const queue = ref([]); // 用于存储接收到的警告数据的队列const currentIndex = ref(0);
const time = ref(30);
const isRunning = ref(false);
const isTestVisible = ref(true);let audio = new Audio(require("@/assets/warm.mp3"));
audio.hidden = true; // 隐藏音频控件
document.body.appendChild(audio); // 将音频控件添加到页面中let interval;
onMounted(() => {eventBus.on("socketMessage", handleMessage);audio.addEventListener("ended", handleAudioEnded);
});onUnmounted(() => {eventBus.off("socketMessage", handleMessage);clearInterval(interval); // 清除1分钟的定时器clearInterval(countDown); // 清除倒计时定时器audio.pause();document.body.removeChild(audio); // 移除音频控件
});const getDate = () => {const time = new Date();const endTime = dayjs(time).format("YYYY-MM-DD HH:mm:ss");const startTime = dayjs(time).subtract(1, "days").format("YYYY-MM-DD HH:mm:ss");const equipWarnPageDTO = {startTime: startTime,endTime: endTime,};const page = {size: 999,};const obj = Object.assign(equipWarnPageDTO, page);warmEvents(obj).then((res) => {if (res.code == 0) {const records = res.data?.records ?? [];warmList.value = [...warmList.value, ...records]; // 将新数据添加到 warmList 末尾nextTick(() => {if (warmList.value.length > 0) {playAudio();}});}});
};const handleMessage = (msg) => {const data = JSON.parse(msg);queue.value.push(data); // 将新数据添加到队列中if (!isRunning.value && !currentItem.value) {// 如果当前没有正在播放的警告信息,开始播放showNextItem();}
};const ID = ref("");
const getUser = () => {userInfo().then((res) => {localStorage.setItem("userInof", JSON.stringify(res.data.sysUser));ID.value = res.data.sysUser.id;if (ID.value) {connect(); // 连接 WebSocket}});
};const { connect, disconnect, status } = useWebSocket({url: computed(() => {return ID.value? `wss://www.tbaowl.com:9992/ws/mini/websocket/${ID.value}`: "";}),heartBeatData: "ping", // 心跳数据heartBeatInterval: 30000, // 心跳间隔,30秒reconnectInterval: 5000, // 重连间隔,5秒maxReconnectAttempts: 5, // 最大重连次数},handleMessage
);let countDown;
const startCountDown = () => {if (countDown) {clearInterval(countDown); // 清除之前的倒计时定时器}countDown = setInterval(() => {if (isRunning.value) {time.value -= 1;if (time.value <= 0) {clearInterval(countDown);handleAudioEnded();}}}, 1000);
};const showNextItem = () => {if (queue.value.length > 0) {warmList.value.push(queue.value.shift()); // 从队列中取出一条数据并添加到warmListconsole.log("queue.value", queue.value);console.log("warmList.value2222", warmList.value);isRunning.value = true; // 标记为正在播放isTestVisible.value = true;time.value = 30; // 重置时间playAudio();startCountDown(); // 开始倒计时} else {isRunning.value = false; // 如果队列为空,停止播放isTestVisible.value = false;}
};watch(currentIndex, () => {if (isRunning.value) {if (countDown) {clearInterval(countDown);startCountDown();}}
});const currentItem = computed(() => {return warmList.value.length > 0 ? warmList.value[currentIndex.value] : null;
});const playAudio = () => {audio.currentTime = 0;audio.play().catch((error) => {console.error("Error playing audio:", error);});// 设置定时器,10秒后停止音频setTimeout(() => {audio.pause();}, 10000); // 10000毫秒 = 10秒
};
const handleAudioEnded = () => {if (queue.value.length > 0) {currentIndex.value++;showNextItem();} else {isRunning.value = false; // 没有更多数据,停止播放isTestVisible.value = false;audio.pause();document.body.removeChild(audio); // 移除音频控件}
};// const handleAudioEnded = () => {
// if (queue.value.length > 0) {
// // 如果队列中还有未播放的数据,播放下一条
// showNextItem();
// } else {
// // 如果没有更多数据,停止播放
// isRunning.value = false;
// audio.pause();
// document.body.removeChild(audio); // 移除音频控件
// }
// };
</script><style lang="scss" scoped>
.warmTanChuan-container {position: absolute;top: 25%;left: 35%;transform: translate(-50%, -50%);z-index: 999;
}.warmTanChuan {width: 327px;color: #ffffff;.title {height: 40px;line-height: 40px;background: url("@/assets/images/jbbg.png") no-repeat;background-size: 100% 100%;font-weight: bold;padding: 0 10px;font-size: 14px;img {width: 20px;height: 20px;margin: 10px;}.time {float: right;}}.content_text {background-color: #3a0e0b;border: 2px solid #be4b44;padding: 10px;font-size: 14px;position: relative;}
}.scroll-enter-active,
.scroll-leave-active {transition: transform 1s;
}
.scroll-enter {transform: translateY(100%);
}
.scroll-leave-to {transform: translateY(-100%);
}
.test {width: 972px;height: calc(100vh - 440px);background: url("@/assets/images/image2/warm.png") no-repeat;background-size: 100%;position: absolute;top: 96px;left: 50%;transform: translateX(-50%);animation: blink 1s infinite; // 添加闪烁动画
}
// 闪烁动画
@keyframes blink {0%,100% {opacity: 1;}50% {opacity: 0;}
}
</style>
难点:
1.一个是关于弹窗如何控制30s显示然后下一个显示,同时伴有警告声音10s消失?
2.数据如果是一个一个传给你或者是一次性多个传给你,怎么办?
3.闪烁动画怎么做?
先处理第三个问题:首先闪烁动画是一个比较图片的盒子,实现一闪一闪的效果
如下
样式可以这样写
闪烁由数据的长度和isTestVisible共同决定
<div class="test" v-if="isTestVisible && warmList.length > 0"></div>
.test {width: 972px;height: calc(100vh - 440px);background: url("@/assets/images/image2/warm.png") no-repeat;background-size: 100%;position: absolute;top: 96px;left: 50%;transform: translateX(-50%);animation: blink 1s infinite; // 添加闪烁动画
}
// 闪烁动画
@keyframes blink {0%, // 动画开始时100% { // 动画结束时opacity: 1; // 元素完全可见 (不透明)}50% { // 动画进行到一半时opacity: 0; // 元素完全不可见 (透明)}
}
好 现在解决第一个问题。如何让他实现关于弹窗如何控制30s显示然后下一个显示,同时伴有警告声音10s消失。这边就会说明刚才isTestVisible是什么东西了。
首先还是从样式transition-group
来处理这些警告信息的进入和离开动画。
<template><transition-group name="scroll" tag="div" class="warmTanChuan-container"><divv-if="isRunning && currentItem"class="warmTanChuan"ref="warmTanChuan":key="currentItem.id"><div class="title"><img src="@/assets/images/Frame103(91).png" alt="" /><span v-if="currentItem.deviceType == 1">摄像头警告:</span><span v-if="currentItem.deviceType == 2">灵思传感器警告:</span><span v-if="currentItem.deviceType == 3">大华电气设备警告:</span><span v-if="currentItem.deviceType == 4">消防设备警告:</span><span v-if="currentItem.deviceType == 5">车载设备警告:</span><span v-if="currentItem.deviceType == 6">海康消防设备警告:</span><span v-if="currentItem.deviceType == 7">消防传感器警告:</span><span class="time">{{ time }}s</span></div><div class="content_text"><p>{{ currentItem.incidentDescribe }}</p></div></div></transition-group><div class="test" v-if="isTestVisible && warmList.length > 0"></div>
</template>
-
transition-group
:- 名称为
scroll
的transition-group
组件,用于处理列表项的动画。 tag="div"
设置容器元素为div
。- 内部包含一个动态渲染的
div
元素,用于显示当前警告信息。
- 名称为
-
div
:- 根据
isRunning
和currentItem
的值来决定是否显示警告信息。 - 使用
:key
绑定唯一的标识符,以便transition-group
能够正确跟踪元素的变化。
- 根据
-
.warmTanChuan-container
:- 定位样式,使警告信息居中显示。
-
.test
:- 一个用于测试的
div
元素,当isTestVisible
为真时显示,带有闪烁动画。
- 一个用于测试的
第一步肯定是拿到数据 如果没有正在播放的数据才可以播
import { ref, onMounted, watch, computed, onUnmounted, nextTick } from "vue";
import { warmEvents } from "@/api/api.js";
import dayjs from "dayjs";
import useWebSocket, { SocketStatus } from "@/mixins/useWebSocket";
import { getSystemData, userInfo } from "@/api/api";
import eventBus from "@/mixins/eventBus";
const warmList = ref([]);
const queue = ref([]); // 用于存储接收到的警告数据的队列const currentIndex = ref(0);
const time = ref(30);
const isRunning = ref(false);
const isTestVisible = ref(true);let audio = new Audio(require("@/assets/warm.mp3"));
audio.hidden = true; // 隐藏音频控件
document.body.appendChild(audio); // 将音频控件添加到页面中const currentItem = computed(() => {return warmList.value.length > 0 ? warmList.value[currentIndex.value] : null;
});const handleMessage = (msg) => {const data = JSON.parse(msg);queue.value.push(data); // 将新数据添加到队列中if (!isRunning.value && !currentItem.value) {// 如果当前没有正在播放的警告信息,开始播放showNextItem();}
};
下一条给warnList添加数据 同时queue移除
const showNextItem = () => {if (queue.value.length > 0) {warmList.value.push(queue.value.shift()); // 从队列中取出一条数据并添加到warmListconsole.log("queue.value", queue.value);console.log("warmList.value2222", warmList.value);isRunning.value = true; // 标记为正在播放isTestVisible.value = true;time.value = 30; // 重置时间playAudio();//播放声音startCountDown(); // 开始倒计时} else {isRunning.value = false; // 如果队列为空,停止播放isTestVisible.value = false;}
};
播放声音
const playAudio = () => {audio.currentTime = 0;audio.play().catch((error) => {console.error("Error playing audio:", error);});// 设置定时器,10秒后停止音频setTimeout(() => {audio.pause();}, 10000); // 10000毫秒 = 10秒
};
开始倒计时
const startCountDown = () => {if (countDown) {clearInterval(countDown); // 清除之前的倒计时定时器}countDown = setInterval(() => {if (isRunning.value) {time.value -= 1;if (time.value <= 0) {clearInterval(countDown);handleAudioEnded();}}}, 1000);
};
const handleAudioEnded = () => {if (queue.value.length > 0) {currentIndex.value++;showNextItem();} else {isRunning.value = false; // 没有更多数据,停止播放isTestVisible.value = false;audio.pause();document.body.removeChild(audio); // 移除音频控件}
};
watch(currentIndex, () => {if (isRunning.value) {if (countDown) {clearInterval(countDown);startCountDown();}}
});
因为warmList肯定是有数据的所以再加一个条件isTestVisible来控制闪烁动画。
3.地图部分的数据筛选。
效果如图
代码:
<template><div class="DataSelectModal" ref="DataSelectModal" v-if="isModalVisible"><div class="title">预警数据筛选<imgsrc="@/assets/images/Frame103(37).png"alt=""@click="close"class="closeModals"/></div><div class="warmSelectContent"><div class="slect">常用:<!-- <div class="ofenUse" @click="getTime()">总计</div><div class="ofenUse" @click="getTime(1)">过去24小时</div><div class="ofenUse" @click="getTime(7)">过去7天</div><div class="ofenUse" @click="getTime(30)">过去30天</div><div class="ofenUse" @click="getTime(90)">过去90天</div><div class="ofenUse" @click="getTime(180)">过去180天</div><div class="ofenUse" @click="getTime(365)">过去365天</div> --><divv-for="item in buttonData":key="item.id":class="item.id === selectedButton ? 'selected' : 'ofenUse'"@click="handleButtonClick(item)">{{ item.name }}</div></div><div class="slect">条件筛选:<a-select v-model="selectedYear" :key="resetKey" @change="handleChange"><a-select-option v-for="year in yearArr" :key="year" :value="year">{{ year }} 年</a-select-option></a-select><a-selectv-model="selectedSeason"@change="handleChange2":key="resetKey":disabled="!selectedYear"><a-select-optionv-for="season in seasonArr":key="season.value":value="season.value">{{ season.name }}</a-select-option></a-select><a-selectv-model="selectedMonth"@change="handleChange3":key="selectedSeason + selectedYear"><a-select-optionv-for="(month, index) in monthArr":key="month":value="index":disabled="!selectedYear || !selectedSeason">{{ month }}月</a-select-option></a-select></div><div class="slect">时间筛选:<a-range-pickerv-model:value="datea"separator="至"valueFormat="YYYY-MM-DD"@change="dataCheck"placeholder=""><template #suffixIcon><down-outlined /></template></a-range-picker></div></div><div class="immediately" @click="search">立即查询</div></div>
</template><script setup>
import { ref, watch, onMounted, computed, reactive, nextTick } from "vue";
import { DownOutlined } from "@ant-design/icons-vue";
const emit = defineEmits(["update:start-time", "update:end-time"]);
import dayjs from "dayjs";
// const emit = defineEmits(["close"]);
const DataSelectModal = ref(null);
const isModalVisible = ref(false);
const form = reactive({startTime: "",endDate: "",
});
const time = new Date().getFullYear();
const yearArr = ref([]);
const startTime = ref("");
const endTime = ref("");
const selectedMonth = ref("");
const selectedSeason = ref("");
const selectedYear = ref("");import eventBus from "@/mixins/eventBus";
import { message } from "ant-design-vue";const seasonArr = [{value: 1,name: "第一季度",},{value: 2,name: "第二季度",},{value: 3,name: "第三季度",},{value: 4,name: "第四季度",},
];
const monthArr = ref([]);const initializeYears = () => {var i;for (i = 2024; i <= time; i++) {yearArr.value.push(i);}
};
const datea = ref([]);const buttonData = [{ id: 1, name: "总计", time: 0 },{ id: 2, name: "过去24小时", time: 1 },{ id: 3, name: "过去7天", time: 7 },{ id: 4, name: "过去30天", time: 30 },{ id: 5, name: "过去90天", time: 90 },{ id: 6, name: "过去180天", time: 180 },{ id: 7, name: "过去365天", time: 365 },
];
// 当前选中的按钮ID
const selectedButton = ref(0);
const resetKey = ref(0);
const changeButton = ref("");onMounted(() => {nextTick(() => {// selectedButton.value = 1;});initializeYears();// getTime();
});
// 处理按钮点击事件
const handleButtonClick = (date) => {selectedYear.value = null;selectedSeason.value = null;selectedMonth.value = null;datea.value = [];selectedButton.value = date.id;resetKey.value++; // 触发组件重新渲染// const resetKey = selectedYear.value + "_" + selectedSeason.value;nextTick(() => {console.log(" selectedYear.value", selectedYear.value);console.log(" selectedSeason.value", selectedSeason.value);console.log(" selectedMonth.value", selectedMonth.value);});getTime(date.time);changeButton.value = date.name;
};// const date = computed({
// get() {
// // if (!form.startTime || !form.endTime)
// // return [
// // dayjs().subtract(3, "day").format("YYYY-MM-DD HH:mm:ss"),
// // dayjs().format("YYYY-MM-DD HH:mm:ss"),
// // ];
// return [
// dayjs().format("YYYY-MM-DD HH:mm:ss"),
// dayjs().format("YYYY-MM-DD HH:mm:ss"),
// ];
// },
// set(date) {
// console.log("date: ", date);
// selectedButton.value = null;
// startTime.value = date[0];
// endTime.value = date[1];
// },
// });const showModal = () => {isModalVisible.value = true;selectedSeason.value = null;selectedMonth.value = null;datea.value = null;selectedYear.value = null;startTime.value = null;endTime.value = null;// selectedButton.value = 1;changeButton.value = "";
};
const close = () => {isModalVisible.value = false;selectedMonth.value = "";
};const getTime = (data) => {if (data) {startTime.value = dayjs().subtract(data, "day").format("YYYY-MM-DD HH:mm:ss");endTime.value = dayjs().format("YYYY-MM-DD HH:mm:ss");console.log("startDate: ", startTime.value);console.log("endTime: ", endTime.value);} else {startTime.value = null;endTime.value = null;}
};const handleChange = (value) => {console.log("value", value);selectedYear.value = value;selectedSeason.value = null;selectedMonth.value = null;selectedButton.value = null;datea.value = [];updateStartEndTime();changeButton.value = "条件筛选";// console.log("queryParam", queryParam.value.deviceType);
};
const handleChange2 = (value) => {console.log("value", value);selectedSeason.value = value;selectedButton.value = null;selectedMonth.value = null; // 先清空月份monthArr.value = []; // 清空月份数组switch (Number.parseInt(value)) {case 1:monthArr.value = [1, 2, 3];break;case 2:monthArr.value = [4, 5, 6];break;case 3:monthArr.value = [7, 8, 9];break;case 4:monthArr.value = [10, 11, 12];break;}updateStartEndTime();
};const handleChange3 = (value) => {console.log("value", value);selectedMonth.value = value;selectedButton.value = null;updateStartEndTime();// console.log("queryParam", queryParam.value.deviceType);
};const updateStartEndTime = () => {console.log(111);if (selectedYear.value && selectedSeason.value) {// 如果选择了年和季度const seasonStartMonth = monthArr.value[0] - 1;const seasonEndMonth = monthArr.value[2];startTime.value = dayjs(new Date(selectedYear.value, seasonStartMonth, 1)).startOf("month").format("YYYY-MM-DD HH:mm:ss");endTime.value = dayjs(new Date(selectedYear.value, seasonEndMonth, 0)).endOf("month").format("YYYY-MM-DD HH:mm:ss");console.log("startDate: ", startTime.value);console.log("endTime: ", endTime.value);} else if (selectedYear.value &&selectedSeason.value &&selectedMonth.value !== "") {// 如果选择了年、季度和月份// 如果 monthArr.value 是 ['01', '02', '03'] 并且 selectedMonth.value 是 1,那么 month 的值将会是 2。// 如果 monthArr.value 是 [1, 2, 3],并且 selectedMonth.value 是 1,那么 month 的值将会是 2。parseInt(2, 10) 仍然返回 2。const month = parseInt(monthArr.value[selectedMonth.value], 10);startTime.value = dayjs(new Date(selectedYear.value, month - 1, 1)).startOf("month").format("YYYY-MM-DD HH:mm:ss");endTime.value = dayjs(new Date(selectedYear.value, month, 0)).endOf("month").format("YYYY-MM-DD HH:mm:ss");console.log("startDate: ", startTime.value);console.log("endTime: ", endTime.value);} else if (selectedYear.value && selectedSeason.value) {// 如果选择了年和季度const seasonStartMonth = monthArr.value[0] - 1;const seasonEndMonth = monthArr.value[2];startTime.value = dayjs(new Date(selectedYear.value, seasonStartMonth, 1)).startOf("month").format("YYYY-MM-DD HH:mm:ss");endTime.value = dayjs(new Date(selectedYear.value, seasonEndMonth, 0)).endOf("month").format("YYYY-MM-DD HH:mm:ss");console.log("startDate: ", startTime.value);console.log("endTime: ", endTime.value);} else if (selectedYear.value) {// 如果只选择了年startTime.value = dayjs(new Date(selectedYear.value, 0, 1)).startOf("year").format("YYYY-MM-DD HH:mm:ss");endTime.value = dayjs(new Date(selectedYear.value, 11, 31)).endOf("year").format("YYYY-MM-DD HH:mm:ss");console.log("startDate: ", startTime.value);console.log("endTime: ", endTime.value);}
};const search = () => {if (!changeButton.value) {message.error("请选择查询时间范围");returm;} else {emit("update:start-time", startTime.value);emit("update:end-time", endTime.value);emit("changeButton", changeButton.value);console.log("查询时间范围:", startTime.value, endTime.value);close();}
};const dataCheck = (data) => {console.log("Range picker clicked:", data);startTime.value = data[0];endTime.value = data[1];resetKey.value++; // 触发组件重新渲染console.log("startDate: ", startTime.value);console.log("endTime: ", endTime.value);selectedSeason.value = null;selectedMonth.value = null;selectedYear.value = null;selectedButton.value = null;changeButton.value = "时间筛选";// 在这里处理点击事件
};// onChangeTime (data) {
// if (data.length != 0) {
// this.queryParam.startDate = data[0];
// this.queryParam.endDate = data[1];
// } else {
// delete this.queryParam.startDate;
// delete this.queryParam.endDate;
// }
// },defineExpose({// close,showModal,
});
</script><style lang="scss" scoped>
.DataSelectModal {position: relative;font-size: 14px;width: 800px;height: 296px;color: #ffffff;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 999;background: url("@/assets/images/image2/selectbox.png") no-repeat;background-size: 100%;.title {height: 40px;line-height: 40px;padding-left: 20px;font-size: 14px;.closeModals {float: right;width: 24px;height: 24px;margin: 5px;cursor: pointer;}}.warmSelectContent {padding: 20px;.slect {display: flex;align-items: center;margin-bottom: 20px;.ofenUse {font-size: 12px;background: #042931;border-radius: 4px;border: 1px solid #0a8fab;margin-right: 20px;padding: 5px;cursor: pointer;}.selected {font-size: 12px;background: #148aa5;border-radius: 4px;border: 1px solid #0a8fab;margin-right: 20px;padding: 5px;cursor: pointer;}}}.immediately {position: absolute;bottom: 10%;left: 50%;transform: translateX(-50%);width: 104px;height: 44px;line-height: 44px;text-align: center;background: #134451;border-radius: 4px;border: 1px solid #165a6b;cursor: pointer;}:deep(.ant-select-selector) {background: #134451 !important;border: 1px solid #165a6b !important;color: #fff;width: 120px !important;line-height: 35px !important;height: 35px !important;}:deep(.ant-select-selection-item) {line-height: 35px !important;}
}
:deep(.ant-picker-range) {width: 300px !important;
}
</style>
关于下拉框无法置空
这边的难点是关于下拉框是三级联动同时3种筛选选择其中一种的时候,其他两种都必须置为空。难就难在下拉框无法置空踩的坑。后面发现双向绑定还是无法置空 可以重置他们的key
有几个地方需要理解下
关于十进制的用法
// 如果 monthArr.value 是 ['01', '02', '03'] 并且 selectedMonth.value 是 1,那么 month 的值将会是 2。// 如果 monthArr.value 是 [1, 2, 3],并且 selectedMonth.value 是 1,那么 month 的值将会是 2。parseInt(2, 10) 仍然返回 2。const month = parseInt(monthArr.value[selectedMonth.value], 10);
关于一个月的开始和尾巴
// 创建一个日期对象,表示selectedYear.value年seasonStartMonth月的第一天。startTime.value = dayjs(new Date(selectedYear.value, seasonStartMonth, 1)).startOf("month").format("YYYY-MM-DD HH:mm:ss");// 这里0表示该月的最后一天。endTime.value = dayjs(new Date(selectedYear.value, seasonEndMonth, 0)).endOf("month").format("YYYY-MM-DD HH:mm:ss");
相关文章:

宁德大屏第二版总结
碰到难点 1.wss 心跳机制 实现前端和后端双向绑定 只要后端发送了消息 前端通过全局总线去触发你想要的函数。 全局总线 vue3可以全局总线下一个mitt 新建一个eventBus.js import mitt from "mitt"; const eventBus mitt();export default eventBus; 然后wss…...

冥想第一千二百四十七天(1247)
1.今天上午带桐桐去游泳了,买了卡吉诺,吃过最好吃的甜点。推荐。还有鸡排。 2.回来后带着媳妇,先加油。去给丈母娘看腿,等丈母娘等了好久,还帮她推车。 3.回来后,在丈母娘家跑步。很舒服。家长麦田的香味。…...

基于光学动捕定位下的Unity-VR手柄交互
Unity VR 场景手柄交互实现方案 需求 在已创建好的 Unity VR 场景中,接入游戏手柄,通过结合动捕系统与 VRPN,建立刚体,实时系统获取到手柄的定位数据与按键数据,通过编写代码实现手柄的交互逻辑,实现手柄…...

php json_decode 带反斜杠字符串json解析
PHP json_decode 带反斜杠字符串json解析 今天再次遇到了json字符串中包含反斜杠的问题,记录下解决方法 在JSON字符串中,反斜杠\用作转义字符。当JSON_UNESCAPED_SLASHES选项被用于json_encode()函数时,不会在slashes前面添加反斜杠。 但是…...

【NLP】文本张量表示方法【word2vec、词嵌入】
文章目录 1、文本张量表示2、one-hot词向量表示2.1、one-hot编码代码实现:2.2、onehot编码器的使用2.3、one-hot编码的优劣势 3、word2vec模型3.1、模型介绍3.2、CBOW模式3.3、skipgram模式3.4、word2vec的训练和使用3.4.1、获取训练数据3.4.2、训练词向量3.4.3、查…...

疯狂Java讲义_08_泛型
文章目录 泛型的传参若函数里的参数使用基类接受所有的派生类,怎么做? 类型通配符的上限类型通配符的下限 泛型的传参 注意 若类 Base 是类 Derived 的基类(父类),那么数组类型 Base[] 是 Derived[] 的基类࿰…...

HCIA、OSPF笔记
一、OSI参考模型 1、OSI的结构 应用层:把人类语言转化成编码,为各种应用程序提供网络服务。 表示层:定义一些数据的格式,(对数据进行加密、解密、编码、解码、压缩、解压缩,每一层都可以实现,…...

Python删除lru_cache缓存
在 Python 中,lru_cache 是一个装饰器,用于添加缓存功能以提高函数的性能。如果你想清除或者删除 lru_cache 中的缓存,有几种方法可以做到: 手动清除缓存: lru_cache 对象有一个方法叫做 cache_clear(),可以手动清除所有缓存。示例:@lru_cache(maxsize=128) def some_fun…...

Android面试必问题:大白文讲透Android View工作原理
目录 第一章 引言 第二章 Android View 基础概念 2.1 视图(View) 2.2 布局(Layout) 2.3 绘制(Drawing) 第三章 Android View 工作原理详解 3.1 测量过程剖析 3.2 布局流程探究 第四章 Android View 性能优化建议 4.1 视图层级优化 4.2 避免过度的视觉效果 4.…...

WinDbg配置远程调试
WinDbg配置远程调试 1、为什么需要远程调试 某些特殊的场合需要远程调试,如: ①调试特殊的程序,比如在调试全屏程序,内核。 ②需要别人帮助调试或者帮助别人调试。比如由于商业性质不能直接给你pdb和源代码。 ③还有一类就是…...

spl注入实战thinkphp
目录 一、环境的部署 二、本地创建数据库 三、填写数据库连接文件 四、编写控制器 五、访问分析 debug报错会显示物理路径 原因是config.php文件相关配置 六、注入分析 七、进入断点调试 八、通过mysql执行语句查看结果 九、总结: 一、环境的部署 二、本地…...

整理深度学习时最常用的Linux命令(自用)
清华大学镜像源: https://pypi.tuna.tsinghua.edu.cn/simple/tar文件解压 tar -xzvf xxx.tar.gztar xvf xxx.tarzip文件解压 unzip xxx.zip -d path/to/your/fold清理GPU异常内存占用 杀掉 1 号显卡的所有进程 fuser -v /dev/nvidia1 | xargs -t -n 1 kill -9杀掉…...

LVS——>linux 虚拟服务器知识汇总
一、概念: LVS(Linux Virtual Server),是Linux Virtual Server的简写,也就是Linux 虚拟服务器,是一个虚拟的服务器集群系统负载均衡解决方案,它将一个真实服务器集群虚拟成一台服务器来对外提供…...

AI赋能周界安防:智能视频分析技术构建无懈可击的安全防线
周界安全防范是保护机场、电站、油库、监狱、工业园区等关键设施免受非法入侵和破坏的重要措施。传统的周界安防手段主要依靠人员巡查和物理屏障,但这种方式不仅人力成本高,而且效率较低,难以满足日益复杂多变的安全需求。随着AI技术的引入&a…...

FastAPI+Vue3工程项目管理系统项目实战私教课 上课笔记20240808 课程和学习计划制定
学习目标 将Word和Excel做的东西放到数据库里面去工程类公司,甲方,劳务存到数据库存储的信息主要是人员的信息 基本信息: 人员信息,资料库,甲方的人出现在哪些项目上,考勤材料信息,进货记录&…...

Robot Operating System——发布相对湿度数据
大纲 应用场景定义字段解释 案例 sensor_msgs::msg::RelativeHumidity 是 ROS (Robot Operating System) 中的一个消息类型,用于表示相对湿度数据。 应用场景 环境监测 气象站:在气象站中,相对湿度传感器可以用于监测环境湿度,帮…...

一文搞懂后端面试之不停机数据迁移【中间件 | 数据库 | MySQL | 数据一致性】
数据迁移方面的工作: 重构老系统:使用新的表结构来存储数据单库拆分分库分表、分库分表扩容大表修改表结构定义 数据备份工具 MySQL上常用的两款数据备份工具:mysqldump和XtraBackup mysqldump:一个用于备份和恢复数据库的命令…...

【ESP01开发实例】- ISD1820录音控制
ISD1820录音控制 文章目录 ISD1820录音控制1、ISD1820模块介绍2、硬件准备及接线3、代码以实现录音技术已经取得了长足的进步,它已成为从语音助手到安全系统的各种应用不可或缺的一部分。如果您有兴趣构建自己的录音系统,将 ISD1820 模块与 ESP01 微控制器相结合可能是一个不…...

Linux驱动面试高频考点后面继续改整理
Linux驱动开发是将硬件设备与操作系统内核连接起来的重要环节,它涉及到设备模型、中断处理、文件操作等方面,是一项挑战性且充满乐趣的工作。今天给大家分享45道Linux驱动面试高频考点,直接上干货。 1、驱动程序分为几类? 内核驱动…...

【Python】nn.ConvTranspose1、2、3d()函数详解和示例
前言 在深度学习中,特别是在处理图像、音频和三维数据时,转置卷积(Transposed Convolution)或称为反卷积(Deconvolution)是一种非常重要的操作。PyTorch提供了nn.ConvTranspose1d、nn.ConvTranspose2d和nn…...

vtkConnectivityFilter提取连通区域中的问题
直接使用vtkConnectivityFilter提取连通区域,渲染上没问题,但是打印出polydata中的点数,发现跟原始数据是一致的。 for (int i 0; i < numRegions; i){vtkSmartPointer<vtkConnectivityFilter> connectivityFilter vtkSmartPointe…...

购物系统小程序的设计
管理员账户功能包括:系统首页,个人中心,商品分类管理,商品信息管理,特价商品管理,用户管理,留言板管理,订单管理,系统管理 微信端账号功能包括:系统首页&…...

做报表用什么工具?不想再用Excel了!!!
一、什么是中国式报表? 不知道大家现在还是使用Excel来制作报表,然后跟领导汇报工作吗?虽然Excel功能很强大,但是用Excel做过中国式报表的小伙伴一定知道它的制作过程有多复杂。 中国式报表可以用一句话简单概括:格式…...

c++实现学生管理系统(附源码)
目录 一、基本功能: 二、包含的模块: 三、系统介绍 1. 学生管理系统的功能: 2. 具体的需求: 3. 支持的标准: 四、系统结构功能图: 五、系统设计 1. 退出系统: 2. 增加学生:…...

JS防抖是什么?干嘛用的?
你好同学,我是沐爸,欢迎点赞、收藏和关注!个人知乎 防抖在前端开发中可以说经常用到,有诸多使用场景。接下来我们一起看下防抖的定义、防抖函数的实现、应用场景、lodash防抖函数以及防抖在框架中的使用。Let’s go 一、什么是防…...

Linux磁盘管理与文件系统(二):实用工具和命令、fdisk分区示例
文章目录 4、查看或管理磁盘分区-fdisk格式选项示例 4、示例:使用 fdisk 命令创建分区需求操作步骤 5、创建文件系统-mkfs格式常用选项示例创建其他类型的文件系统 6、创建文件系统-mkswap格式常用选项示例拓展:关闭和启用交换分区拓展:swap分…...

使用vtkRenderer创建的显示点云的窗口如何刷新(QT/C++)
一、使用vtkRenderer创建点云显示窗口,参考 在Qt创建的UI中放一个显示点云的窗口(PCLQT5)_pcl点云和qt-CSDN博客 二、刷新vtkRenderer创建的窗口 使用场景:在某些情况下代码中需要对显示窗口的显示物改动,例如通过滑…...

Mysql绕过小技巧
上源码。 <?php $mysqli new mysqli("localhost", "root", "root", "security");/* check connection */ if ($mysqli->connect_errno) {printf("Connect failed: %s\n", $mysqli->connect_error);exit(); }$my…...

气象大数据案例项目(求各气象站的平均气温)
气象大数据案例项目(求各气象站的平均气温) 一、项目需求二、数据格式三、项目开发3.1 在windows 进行开发3.2 运行结果3.3 对项目打包 一、项目需求 现在有一份来自美国国家海洋和大气管理局的数据集,里面包含近30年每个气象站、每小时的天…...

博客摘录「 一个ModBus RTU程序(支持01、02、03、05、06、15、16功能码)」2024年4月19日
发送数据最好改为中断模式,不然通信速度不够。...