From 229894d52a0f4cdca90bebfde323104d1810ef4f Mon Sep 17 00:00:00 2001
From: ZhangXianQiang <1135831638@qq.com>
Date: 星期三, 03 七月 2024 17:18:16 +0800
Subject: [PATCH] feat:添加webscoket
---
.env.development | 3
src/hooks/useWebScoket.js | 188 +++++++++++++++++++++++++++++++++++++++++++++++
src/views/exam/index.vue | 35 +++++++-
3 files changed, 220 insertions(+), 6 deletions(-)
diff --git a/.env.development b/.env.development
index f6b70c2..4a153b1 100644
--- a/.env.development
+++ b/.env.development
@@ -1 +1,2 @@
-VITE_BASE_API_URL =
\ No newline at end of file
+VITE_BASE_API_URL =
+VITE_SCOKET_URL = ws://192.168.3.64:8000/websocket/
\ No newline at end of file
diff --git a/src/hooks/useWebScoket.js b/src/hooks/useWebScoket.js
new file mode 100644
index 0000000..5f3da78
--- /dev/null
+++ b/src/hooks/useWebScoket.js
@@ -0,0 +1,188 @@
+import { ref, onBeforeUnmount, onMounted } from 'vue';
+
+const DEFAULT_OPTIONS = {
+ url: '', // websocket url
+ heartBeatData: '', // 浣犵殑蹇冭烦鏁版嵁
+ heartBeatInterval: 60 * 1000, // 蹇冭烦闂撮殧锛屽崟浣峬s
+ reconnectInterval: 5000, // 鏂嚎閲嶈繛闂撮殧锛屽崟浣峬s
+ maxReconnectAttempts: 10 // 鏈�澶ч噸杩炴鏁�
+};
+
+export const SocketStatus = {
+ Connecting: '姝e湪杩炴帴...', //琛ㄧず姝e湪杩炴帴锛岃繖鏄垵濮嬬姸鎬併��
+ Connected: '杩炴帴宸插缓绔�', //琛ㄧず杩炴帴宸茬粡寤虹珛銆�
+ Disconnecting: '杩炴帴姝e湪鍏抽棴', //琛ㄧず杩炴帴姝e湪鍏抽棴銆�
+ Disconnected: '杩炴帴宸叉柇寮�' //琛ㄧず杩炴帴宸茬粡鍏抽棴
+};
+
+const SocketCloseCode = 1000;
+
+export default function useWebSocket(options = {}) {
+ const state = {
+ options: { ...DEFAULT_OPTIONS, ...options },
+
+ socket: null,
+ reconnectAttempts: 0,
+ reconnectTimeout: null,
+
+ heartBetaSendTimer: null, // 蹇冭烦鍙戦�佸畾鏃跺櫒
+ heartBetaTimeoutTimer: null // 蹇冭烦瓒呮椂瀹氭椂鍣�
+ };
+
+ // 杩炴帴鐘舵��
+ const status = ref(SocketStatus.Disconnected);
+ const message = ref(null);
+ const error = ref(null);
+
+ // 杩炴帴
+ const connect = (connectCallBack) => {
+ disconnect();
+
+ status.value = SocketStatus.Connecting;
+ if (!window.navigator.onLine) {
+ setTimeout(() => {
+ status.value = SocketStatus.Disconnected;
+ }, 500);
+ return;
+ }
+
+ state.socket = new WebSocket(state.options.url);
+
+ state.socket.onopen = openEvent => {
+ console.log('socket杩炴帴:', openEvent);
+ state.reconnectAttempts = 0;
+ status.value = SocketStatus.Connected;
+ error.value = null;
+ startHeartBeat();
+ if(connectCallBack) {
+ connectCallBack();
+ }
+ };
+
+ state.socket.onmessage = msgEvent => {
+ console.log('socket娑堟伅:', msgEvent);
+
+ // 鏀跺埌浠讳綍鏁版嵁锛岄噸鏂板紑濮嬪績璺�
+ startHeartBeat();
+
+ const { data } = msgEvent;
+ const msg = JSON.parse(data);
+
+ //蹇冭烦鏁版嵁, 鍙嚜琛屼慨鏀�
+ if (+msg.msg_id === 0) {
+ return;
+ }
+ message.value = msg;
+ };
+
+ state.socket.onclose = closeEvent => {
+ console.log('socket鍏抽棴:', closeEvent);
+ status.value = SocketStatus.Disconnected;
+ // 闈炴甯稿叧闂�,灏濊瘯閲嶈繛
+ if (closeEvent.code !== SocketCloseCode) {
+ reconnect();
+ }
+ };
+
+ state.socket.onerror = errEvent => {
+ console.log('socket鎶ラ敊:', errEvent);
+ status.value = SocketStatus.Disconnected;
+ error.value = errEvent;
+ // 杩炴帴澶辫触锛屽皾璇曢噸杩�
+ reconnect();
+ };
+ };
+
+ const disconnect = () => {
+ if (state.socket && (state.socket.OPEN || state.socket.CONNECTING)) {
+ console.log('socket鏂紑杩炴帴');
+ status.value = SocketStatus.Disconnecting;
+ state.socket.onmessage = null;
+ state.socket.onerror = null;
+ state.socket.onclose = null;
+ // 鍙戦�佸叧闂抚缁欐湇鍔$
+ state.socket.close(SocketCloseCode, 'normal closure');
+ status.value = SocketStatus.Disconnected;
+ state.socket = null;
+ }
+ stopHeartBeat();
+ stopReconnect();
+ };
+
+ // 鍙戦�佹秷鎭�
+ const sendMessage = (data) => {
+ if (state.socket && status.value === SocketStatus.Connected) {
+ state.socket.send(JSON.stringify(data));
+ console.log('socket鍙戦��:', JSON.stringify(data));
+ }
+ };
+
+ const startHeartBeat = () => {
+ stopHeartBeat();
+ onHeartBeat(() => {
+ if (status.value === SocketStatus.Connected) {
+ state.socket.send(state.options.heartBeatData);
+ console.log('socket蹇冭烦鍙戦��:', state.options.heartBeatData);
+ }
+ });
+ };
+
+ const onHeartBeat = callback => {
+ state.heartBetaSendTimer = setTimeout(() => {
+ callback && callback();
+ state.heartBetaTimeoutTimer = setTimeout(() => {
+ // 蹇冭烦瓒呮椂,鐩存帴鍏抽棴socket,鎶涘嚭鑷畾涔塩ode=4444, onclose閲岃繘琛岄噸杩�
+ state.socket.close(4444, 'heart timeout');
+ }, state.options.heartBeatInterval);
+ }, state.options.heartBeatInterval);
+ };
+
+ const stopHeartBeat = () => {
+ state.heartBetaSendTimer && clearTimeout(state.heartBetaSendTimer);
+ state.heartBetaTimeoutTimer && clearTimeout(state.heartBetaTimeoutTimer);
+ };
+
+ // 閲嶈繛
+ const reconnect = () => {
+ if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) {
+ return;
+ }
+ stopHeartBeat();
+ if (state.reconnectAttempts < state.options.maxReconnectAttempts) {
+ console.log('socket閲嶈繛:', state.reconnectAttempts);
+
+ // 閲嶈繛闂撮殧锛�5绉掕捣姝ワ紝涓嬫閫掑1绉�
+ const interval = Math.max(state.options.reconnectInterval, state.reconnectAttempts * 1000);
+ console.log('闂撮殧鏃堕棿锛�', interval);
+ state.reconnectTimeout = setTimeout(() => {
+ if (status.value !== SocketStatus.Connected && status.value !== SocketStatus.Connecting) {
+ connect();
+ }
+ }, interval);
+ state.reconnectAttempts += 1;
+ } else {
+ status.value = SocketStatus.Disconnected;
+ stopReconnect();
+ }
+ };
+
+ // 鍋滄閲嶈繛
+ const stopReconnect = () => {
+ state.reconnectTimeout && clearTimeout(state.reconnectTimeout);
+ };
+
+ onBeforeUnmount(() => {
+ if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) {
+ disconnect();
+ }
+ });
+
+ return {
+ status,
+ message,
+ error,
+ connect,
+ disconnect,
+ sendMessage
+ };
+}
\ No newline at end of file
diff --git a/src/views/exam/index.vue b/src/views/exam/index.vue
index 0918145..591e735 100644
--- a/src/views/exam/index.vue
+++ b/src/views/exam/index.vue
@@ -130,7 +130,7 @@
</template>
<script setup>
-import { ref, watchEffect } from 'vue';
+import { ref, watchEffect, watch, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import { Close, Timer } from '@element-plus/icons-vue';
import AnswerTag from './components/answer-tag/index.vue';
@@ -146,15 +146,19 @@
import AnswerShort from './components/answer-main/answer-short/index.vue';
import AnswerCount from './components/answer-main/answer-count/index.vue';
-import { useExamStore } from '@/store/index.js';
+import { useExamStore, useUserStore } from '@/store/index.js';
import { useRouter } from 'vue-router';
import { submitExam } from '@/api/modules/exam.js';
+import useWebScoket from '@/hooks/useWebScoket.js';
const router = useRouter();
const examStore = useExamStore();
+const userStore = useUserStore();
+
+const { userInfo } = storeToRefs(userStore);
const { currentType, currentIndex, examDetail, examType, examInfo } = storeToRefs(examStore);
const typeComponent = {
@@ -172,17 +176,25 @@
const submitDialog = ref(false);
const timeDialog = ref(false);
+const { status, message, error, connect, disconnect, sendMessage } = useWebScoket({
+ url: 'ws://192.168.3.64:8000/websocket/' + userInfo.value.id,
+ heartBeatData: 'ping'
+});
+
+// 涓婁竴棰�
const prevQuestion = () => {
currentIndex.value--;
checkList();
};
+// 涓嬩竴棰�
const nextQuestion = () => {
currentIndex.value++;
checkList();
};
+// 鏌ヨ棰樼洰
const checkList = () => {
let tempIndex = 0;
const typeQuestion = examDetail.value.find((typeItem, index) => {
@@ -218,7 +230,6 @@
};
-
// 閫�鍑鸿�冭瘯
const closeClick = () => {
quitDialog.value = true;
@@ -244,12 +255,15 @@
// 鏃堕棿缁撴潫
const timeOut = () => {
- resetAllDialog();
- timeDialog.value = true;
const temp = {
...examInfo.value,
titleList: examDetail.value
};
+ timeDialog.value = true;
+ resetAllDialog();
+
+ disconnect();
+
submitExam(temp).then(res => {
returnBack();
}).catch(() => {
@@ -274,6 +288,17 @@
});
examStore.setProgress(progress);
});
+
+watch(() => message.value, (msg) => {
+ console.log(msg);
+});
+
+
+// -----------------------------------鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ // 杩炴帴webscoket
+ connect();
+});
</script>
<style lang="scss" scoped>
--
Gitblit v1.8.0