当前位置: 首页 > news >正文

宁德大屏第二版总结

碰到难点 

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>
  1. transition-group:

    • 名称为 scroll 的 transition-group 组件,用于处理列表项的动画。
    • tag="div" 设置容器元素为 div
    • 内部包含一个动态渲染的 div 元素,用于显示当前警告信息。
  2. div:

    • 根据 isRunning 和 currentItem 的值来决定是否显示警告信息。
    • 使用 :key 绑定唯一的标识符,以便 transition-group 能够正确跟踪元素的变化。
  3. .warmTanChuan-container:

    • 定位样式,使警告信息居中显示。
  4. .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.今天上午带桐桐去游泳了&#xff0c;买了卡吉诺&#xff0c;吃过最好吃的甜点。推荐。还有鸡排。 2.回来后带着媳妇&#xff0c;先加油。去给丈母娘看腿&#xff0c;等丈母娘等了好久&#xff0c;还帮她推车。 3.回来后&#xff0c;在丈母娘家跑步。很舒服。家长麦田的香味。…...

基于光学动捕定位下的Unity-VR手柄交互

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

php json_decode 带反斜杠字符串json解析

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

【NLP】文本张量表示方法【word2vec、词嵌入】

文章目录 1、文本张量表示2、one-hot词向量表示2.1、one-hot编码代码实现&#xff1a;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_泛型

文章目录 泛型的传参若函数里的参数使用基类接受所有的派生类&#xff0c;怎么做&#xff1f; 类型通配符的上限类型通配符的下限 泛型的传参 注意 若类 Base 是类 Derived 的基类&#xff08;父类&#xff09;&#xff0c;那么数组类型 Base[] 是 Derived[] 的基类&#xff0…...

HCIA、OSPF笔记

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

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、为什么需要远程调试 某些特殊的场合需要远程调试&#xff0c;如&#xff1a; ①调试特殊的程序&#xff0c;比如在调试全屏程序&#xff0c;内核。 ②需要别人帮助调试或者帮助别人调试。比如由于商业性质不能直接给你pdb和源代码。 ③还有一类就是…...

spl注入实战thinkphp

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

整理深度学习时最常用的Linux命令(自用)

清华大学镜像源&#xff1a; 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 虚拟服务器知识汇总

一、概念&#xff1a; LVS&#xff08;Linux Virtual Server&#xff09;&#xff0c;是Linux Virtual Server的简写&#xff0c;也就是Linux 虚拟服务器&#xff0c;是一个虚拟的服务器集群系统负载均衡解决方案&#xff0c;它将一个真实服务器集群虚拟成一台服务器来对外提供…...

AI赋能周界安防:智能视频分析技术构建无懈可击的安全防线

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

FastAPI+Vue3工程项目管理系统项目实战私教课 上课笔记20240808 课程和学习计划制定

学习目标 将Word和Excel做的东西放到数据库里面去工程类公司&#xff0c;甲方&#xff0c;劳务存到数据库存储的信息主要是人员的信息 基本信息&#xff1a; 人员信息&#xff0c;资料库&#xff0c;甲方的人出现在哪些项目上&#xff0c;考勤材料信息&#xff0c;进货记录&…...

Robot Operating System——发布相对湿度数据

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

一文搞懂后端面试之不停机数据迁移【中间件 | 数据库 | MySQL | 数据一致性】

数据迁移方面的工作&#xff1a; 重构老系统&#xff1a;使用新的表结构来存储数据单库拆分分库分表、分库分表扩容大表修改表结构定义 数据备份工具 MySQL上常用的两款数据备份工具&#xff1a;mysqldump和XtraBackup mysqldump&#xff1a;一个用于备份和恢复数据库的命令…...

【ESP01开发实例】- ISD1820录音控制

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

Linux驱动面试高频考点后面继续改整理

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

【Python】nn.ConvTranspose1、2、3d()函数详解和示例

前言 在深度学习中&#xff0c;特别是在处理图像、音频和三维数据时&#xff0c;转置卷积&#xff08;Transposed Convolution&#xff09;或称为反卷积&#xff08;Deconvolution&#xff09;是一种非常重要的操作。PyTorch提供了nn.ConvTranspose1d、nn.ConvTranspose2d和nn…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

学习一下用鸿蒙​​DevEco Studio HarmonyOS5实现百度地图

在鸿蒙&#xff08;HarmonyOS5&#xff09;中集成百度地图&#xff0c;可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API&#xff0c;可以构建跨设备的定位、导航和地图展示功能。 ​​1. 鸿蒙环境准备​​ ​​开发工具​​&#xff1a;下载安装 ​​De…...