Compare commits

...

6 Commits

Author SHA1 Message Date
龚皓 b80c991378 路由更新,crosrToken接口增加 2024-10-25 17:00:18 +08:00
龚皓 733e548068 登录服务器设置自动获取 2024-10-25 16:57:02 +08:00
龚皓 32feaeb0b5 告警主页左侧搜索功能,右侧颜色设置 2024-10-25 16:55:59 +08:00
龚皓 c086787dfe 数据列表高度设置 2024-10-25 16:54:18 +08:00
龚皓 474d7b533d 告警弹窗设置,子页面生效 2024-10-25 16:52:42 +08:00
龚皓 47dc751cca jsmpeg地址指向,侧边栏指向修改 2024-10-25 16:51:53 +08:00
8 changed files with 521 additions and 182 deletions

View File

@ -5,7 +5,7 @@
<link rel="icon" href="/icon.ico"> <link rel="icon" href="/icon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>告警管理</title> <title>告警管理</title>
<script src="public\static\jsmpeg-master\jsmpeg.min.js"></script> <script src="/static/jsmpeg-master/jsmpeg.min.js"></script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -22,7 +22,11 @@
<el-icon><Avatar /></el-icon> <el-icon><Avatar /></el-icon>
<template #title><span>用户管理</span></template> <template #title><span>用户管理</span></template>
</el-menu-item> </el-menu-item>
<el-sub-menu index="1"> <el-menu-item index="/settings">
<el-icon><Setting /></el-icon>
<template #title><span>告警设置</span></template>
</el-menu-item>
<!-- <el-sub-menu index="1">
<template #title> <template #title>
<el-icon><Location /></el-icon> <el-icon><Location /></el-icon>
<span>面板测试</span> <span>面板测试</span>
@ -43,7 +47,7 @@
<el-icon><Document /></el-icon> <el-icon><Document /></el-icon>
<template #title><span>功能点3测试</span></template> <template #title><span>功能点3测试</span></template>
</el-menu-item> </el-menu-item>
</el-sub-menu> </el-sub-menu> -->
</el-menu> </el-menu>
</el-aside> </el-aside>
@ -64,7 +68,7 @@
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item> <el-dropdown-item @click="goToUserManagement">
<el-icon><User /></el-icon> <el-icon><User /></el-icon>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item @click="onLogout"> <el-dropdown-item @click="onLogout">
@ -92,7 +96,7 @@ import { ref, watch, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { import {
Document, WarningFilled, Location, User, SwitchButton, Document, WarningFilled, Location, User, SwitchButton,
House, Management, TrendCharts, Avatar, Fold, Expand House, Management, TrendCharts, Avatar, Fold, Expand ,Setting
} from '@element-plus/icons-vue'; } from '@element-plus/icons-vue';
import { BoxApi } from '@/utils/boxApi'; import { BoxApi } from '@/utils/boxApi';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
@ -103,6 +107,10 @@ const activeIndex = ref(route.path);
const isCollapse = ref(false); // const isCollapse = ref(false); //
const username = ref(''); // const username = ref(''); //
const goToUserManagement = () => {
router.push('/userList'); //
};
onMounted(() => { onMounted(() => {
const storedUsername = localStorage.getItem('username'); const storedUsername = localStorage.getItem('username');
username.value = storedUsername ? storedUsername : '用户'; username.value = storedUsername ? storedUsername : '用户';
@ -241,14 +249,18 @@ const onLogout = async () => {
} }
.nav-footer { .nav-footer {
background-color: #fff; background-color: #001529;
font-size: 16px; /* background: linear-gradient(to top, rgb(3, 158, 185,0.7), rgb(123, 3, 153,0.7)); */
color: #333; font-size: 12px;
height: 5vh; color: #fff;
font-weight: bold;
height: 2vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
align-items: center;
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 1px solid #001529;
} }
</style> </style>

152
src/components/Settings.vue Normal file
View File

@ -0,0 +1,152 @@
<template>
<div class="settings-container">
<el-row class="popup-row">
<el-checkbox v-model="isPopupEnabled" @change="handleCheckboxChange">开启弹窗</el-checkbox>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
//
const isPopupEnabled = ref(false);
let websocket: WebSocket | null = null;
let heartbeatInterval: number | null = null; //
// localStorage
onMounted(() => {
const storedState = localStorage.getItem('isPopupEnabled');
isPopupEnabled.value = storedState === 'true';
if (isPopupEnabled.value) {
connectWebSocket();
}
});
// WebSocket
onBeforeUnmount(() => {
closeWebSocket();
});
//
const handleCheckboxChange = () => {
if (isPopupEnabled.value) {
//
ElMessageBox.confirm('是否开启弹窗提示?', '提示', {
confirmButtonText: '是',
cancelButtonText: '否',
type: 'warning',
})
.then(() => {
// WebSocket
localStorage.setItem('isPopupEnabled', 'true'); // localStorage
connectWebSocket();
})
.catch(() => {
//
isPopupEnabled.value = false;
localStorage.setItem('isPopupEnabled', 'false'); // localStorage
});
} else {
// WebSocket
closeWebSocket();
}
};
// WebSocket
const connectWebSocket = () => {
websocket = new WebSocket('ws://192.168.28.11:8080/event/ws');
websocket.onopen = () => {
ElMessage.success('弹窗告警开启成功');
startHeartbeat(); //
};
websocket.onmessage = (event) => {
//
showNotification(event.data);
};
websocket.onclose = () => {
ElMessage.warning('WebSocket 已关闭');
isPopupEnabled.value = false; //
localStorage.setItem('isPopupEnabled', 'false'); // localStorage
stopHeartbeat(); //
};
websocket.onerror = () => {
ElMessage.error('WebSocket 连接出错');
isPopupEnabled.value = false;
localStorage.setItem('isPopupEnabled', 'false');
stopHeartbeat(); //
};
};
// WebSocket
const closeWebSocket = () => {
if (websocket) {
websocket.close();
ElMessage.info('WebSocket 连接已关闭');
localStorage.setItem('isPopupEnabled', 'false'); // localStorage
stopHeartbeat(); //
}
};
//
const showNotification = (message: string) => {
if (Notification.permission === 'granted') {
new Notification('新消息', {
body: message,
icon: '/path/to/icon.png', //
});
} else if (Notification.permission !== 'denied') {
//
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
new Notification('新消息', {
body: message,
icon: '/path/to/icon.png',
});
}
});
}
};
// ping WebSocket
const startHeartbeat = () => {
if (heartbeatInterval) return;
heartbeatInterval = window.setInterval(() => {
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.send('ping'); //
}
}, 5000); // 5
};
//
const stopHeartbeat = () => {
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
};
//
onMounted(() => {
if (Notification.permission !== 'granted') {
Notification.requestPermission();
}
});
</script>
<style scoped>
.settings-container {
padding: 20px;
}
.popup-row {
margin-bottom: 20px;
}
</style>

View File

@ -85,15 +85,17 @@ onMounted(async () => {
/* margin-bottom: 10px; */ /* margin-bottom: 10px; */
/* display: flex; */ /* display: flex; */
/* gap: 5px; */ /* gap: 5px; */
background-color: #fff; /* background-color: #fff; */
/* background-color: #1E2E4A; */
/* overflow: hidden; */ /* overflow: hidden; */
/* height: 55vh; */ /* height: 55vh; */
/* max-height: 450px; */ /* max-height: 450px; */
/* padding-left: 1vh; */ /* padding-left: 1vh; */
/* padding-right:1vh ; */ /* padding-right:1vh ; */
/* overflow: hidden; */ /* overflow: hidden; */
height: 55vh; height: 56vh;
margin-top: 60px; margin-top: 60px;
/* border: 1px solid #1E2E4A; */
} }
.bottom-pan{ .bottom-pan{
margin: 0; margin: 0;

View File

@ -1,8 +1,15 @@
<template> <template>
<div class="camera-container"> <div class="camera-container">
<!-- 左侧摄像头列表 --> <!-- 左侧摄像头列表 -->
<div class="camera-list"> <div class="camera-list">
<el-card v-for="camera in cameras" :key="camera.id" class="camera-item" @click="selectCamera(camera)"> <el-input
v-model="searchQuery"
placeholder="搜索摄像头名称"
prefix-icon="el-icon-search"
clearable
class="search-input"
/>
<el-card v-for="camera in filteredCameras" :key="camera.id" class="camera-item" @click="selectCamera(camera)">
<div class="camera-header"> <div class="camera-header">
<span>ID: {{ camera.id }}</span> <span>ID: {{ camera.id }}</span>
<span class="status" :class="{ 'online': camera.status === 'online', 'offline': camera.status !== 'online' }"> <span class="status" :class="{ 'online': camera.status === 'online', 'offline': camera.status !== 'online' }">
@ -16,208 +23,232 @@
</el-card> </el-card>
</div> </div>
<!-- 右侧摄像头详情和播放控制 --> <!-- 右侧摄像头详情栅格 -->
<div class="camera-details" v-if="selectedCamera"> <div class="camera-grid">
<el-card> <div v-for="camera in selectedCameras" :key="camera.id" class="camera-details">
<div class="stream-control"> <el-card class="camera-card">
<p>{{ selectedCamera.name }}</p> <div class="stream-control">
</div> <p class="camera-name-title">{{ camera.name }}</p>
<el-button @click="closeStream(camera)" class="close-button" circle size="mini">X</el-button>
</div>
<!-- 在未播放时显示播放按钮 --> <!-- 视频播放和快照部分 -->
<div class="play-button-container" @mouseenter="showButton = true" @mouseleave="showButton = false"> <div class="play-button-container" @mouseenter="showButton = true" @mouseleave="showButton = false">
<img v-show="!playing && selectedCamera.snapshot" :src="selectedCamera.snapshot" alt="camera snapshot" <!-- 未播放时显示快照或占位符 -->
class="camera-snapshot" /> <div class="camera-placeholder" v-if="!camera.playing && !camera.snapshot">
<canvas v-show="playing" ref="canvasRef" class="camera-large"></canvas> <el-icon size="48">
<VideoCameraFilled />
</el-icon>
</div>
<!-- 播放按钮 --> <img v-if="!camera.playing && camera.snapshot" :src="camera.snapshot" alt="camera snapshot"
<el-button v-show="!playing || showButton" class="play-button" type="primary" circle size="large" class="camera-snapshot" />
@click="handlePlayPause">
<el-icon> <!-- 播放视频流的 canvas -->
<VideoPlay v-if="!playing" /> <canvas v-show="camera.playing" :ref="el => setCanvasRef(camera.id, el)" class="camera-large"></canvas>
<VideoPause v-if="playing" />
</el-icon> <!-- 播放和暂停按钮 -->
</el-button> <el-button v-show="!camera.playing || showButton" class="play-button" type="primary" circle size="large"
</div> @click="handlePlayPause(camera)">
</el-card> <el-icon>
<VideoPlay v-if="!camera.playing" />
<VideoPause v-if="camera.playing" />
</el-icon>
</el-button>
</div>
</el-card>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'; import { ref, onMounted, onBeforeUnmount, nextTick,computed } from 'vue';
import { BoxApi } from '@/utils/boxApi.ts'; import { BoxApi } from '@/utils/boxApi.ts';
import { VideoPlay, VideoPause } from '@element-plus/icons-vue'; import { VideoPlay, VideoPause, VideoCameraFilled } from '@element-plus/icons-vue';
//
const cameras = ref([]); const cameras = ref([]);
const selectedCamera = ref(null); //
const playing = ref(false); const selectedCameras = ref([]);
const streamPort = ref(null); //
const canvasRef = ref(null); const showButton = ref(false);
const playerRef = ref(null); // API
const showButton = ref(false); //
const apiInstance = new BoxApi(); const apiInstance = new BoxApi();
// canvas
const canvasRefs = ref({});
// //
const searchQuery = ref('');
//
const filteredCameras = computed(() =>
cameras.value.filter(camera =>
camera.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
);
//
const fetchCameras = async () => { const fetchCameras = async () => {
try { try {
const token = localStorage.getItem('alertToken'); const token = localStorage.getItem('alertToken');
const cameraData = await apiInstance.getMinCameras(token); // getMinCameras const cameraData = await apiInstance.getMinCameras(token);
cameras.value = cameraData; // cameras.value = cameraData;
} catch (error) { } catch (error) {
console.error('Error fetching cameras:', error); console.error('获取摄像头列表失败:', error);
} }
}; };
// //
const selectCamera = (camera) => { const selectCamera = (camera) => {
selectedCamera.value = camera; // if (!selectedCameras.value.some(c => c.id === camera.id)) {
playing.value = false; // selectedCameras.value.push({ ...camera, playing: false, streamPort: null });
streamPort.value = null; //
showButton.value = true; //
};
//
const handlePlayPause = async () => {
if (playing.value) {
handleStopStream();
} else {
handleStartStream();
} }
}; };
// // canvas
const handleStartStream = async () => { const setCanvasRef = (cameraId, el) => {
console.log('Button clicked and playing is:', playing.value); if (el) {
if (!selectedCamera.value || !canvasRef.value) { canvasRefs.value[cameraId] = el;
}
};
// /
const handlePlayPause = async (camera) => {
if (camera.playing) {
handleStopStream(camera);
} else {
handleStartStream(camera);
}
};
//
const handleStartStream = async (camera) => {
await nextTick(); // DOM
const canvas = canvasRefs.value[camera.id];
if (!camera || !canvas) {
console.error('未找到对应的 canvas');
return; return;
} }
const token = localStorage.getItem('alertToken'); const token = localStorage.getItem('alertToken');
try { try {
const response = await apiInstance.startCameraStream(token, selectedCamera.value.id); const response = await apiInstance.startCameraStream(token, camera.id);
streamPort.value = response.port; camera.streamPort = response.port;
playing.value = true; camera.playing = true;
// const rememberedAddress = localStorage.getItem('rememberedAddress') || '127.0.0.1';
const url = `ws://192.168.28.33:${streamPort.value}/`; const url = `ws://${rememberedAddress}:${camera.streamPort}/`;
console.log('Playing set to true:', playing.value); // const url = `ws://192.168.28.33:${camera.streamPort}/`;
console.log('播放路径:', url);
if (playerRef.value) {
playerRef.value.destroy(); //
}
if (window.JSMpeg) { if (window.JSMpeg) {
playerRef.value = new window.JSMpeg.Player(url, { const player = new window.JSMpeg.Player(url, {
canvas: canvasRef.value, canvas: canvas,
autoplay: true, autoplay: true,
videoBufferSize: 15 * 1024 * 1024, videoBufferSize: 15 * 1024 * 1024,
audioBufferSize: 5 * 1024 * 1024, audioBufferSize: 5 * 1024 * 1024,
}); });
camera.player = player;
} else { } else {
console.error('JSMpeg is not available on window object.'); console.error('JSMpeg 未加载');
} }
} catch (error) { } catch (error) {
console.error('Error starting stream:', error); console.error('启动视频流失败:', error);
} }
}; };
// //
const handleStopStream = async () => { const handleStopStream = async (camera) => {
if (!selectedCamera.value) {
return;
}
const token = localStorage.getItem('alertToken'); const token = localStorage.getItem('alertToken');
try { try {
await apiInstance.stopCameraStream(token, selectedCamera.value.id); await apiInstance.stopCameraStream(token, camera.id);
playing.value = false; camera.playing = false;
// if (camera.player) {
if (playerRef.value) { camera.player.destroy();
playerRef.value.destroy(); camera.player = null;
playerRef.value = null;
} }
} catch (error) { } catch (error) {
console.error('Error stopping stream:', error); console.error('停止视频流失败:', error);
} }
}; };
// //
onBeforeUnmount(() => { const closeStream = (camera) => {
if (playerRef.value) { handleStopStream(camera);
playerRef.value.destroy(); selectedCameras.value = selectedCameras.value.filter(c => c.id !== camera.id);
playerRef.value = null; };
}
//
onMounted(() => {
fetchCameras();
}); });
onMounted(() => { //
fetchCameras(); // onBeforeUnmount(() => {
selectedCameras.value.forEach(camera => {
if (camera.player) {
camera.player.destroy();
}
});
}); });
</script> </script>
<style scoped> <style scoped>
.camera-container { .camera-container {
display: flex; display: flex;
height: 100vh;
background-color: #1E2E4A;
} }
/* 左侧摄像头列表 */
.camera-list { .camera-list {
width: 20%; width: 20%;
min-width: 215px; min-width: 215px;
max-height: 90vh; max-height: 100vh;
/* 限制高度为一屏 */ /* 限制高度为一屏 */
overflow-y: auto; overflow-y: auto;
/* 超出时滚动 */ /* 超出时滚动 */
box-sizing: border-box; box-sizing: border-box;
border-right: 1px solid #1E2E4A;
padding-right: 10px;
} }
.search-input {
margin-bottom: 10px;
width: 100%;
}
/* 每个摄像头项目的样式 */
.camera-item { .camera-item {
margin-bottom: 5px; margin-bottom: 8px;
cursor: pointer; cursor: pointer;
padding: 10px; padding: 12px;
border: 1px solid #458388;
border-radius: 4px;
transition: background-color 0.3s, box-shadow 0.3s;
} }
.camera-item:hover {
background-color: #f5f7fa;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
}
/* 摄像头项目头部 */
.camera-header { .camera-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-size: 14px; font-size: 14px;
margin-bottom: 5px;
} }
/* 摄像头状态标签 */
.status { .status {
margin-left: 5px; margin-left: 5px;
} font-weight: bold;
.camera-content {
display: flex;
align-items: center;
}
.camera-thumbnail {
width: 60px;
height: 40px;
margin-right: 10px;
}
.camera-name {
flex: 1;
word-break: break-word;
}
.camera-details {
width: 80%;
padding: 10px;
}
.camera-snapshot {
width: 100%;
height: 68vh;
object-fit: cover;
}
.camera-large {
width: 100%;
height: 68vh;
} }
.online { .online {
@ -228,28 +259,141 @@ onMounted(() => {
color: red; color: red;
} }
/* 摄像头内容:缩略图和名称 */
.camera-content {
display: flex;
align-items: center;
}
.camera-thumbnail {
width: 70px;
height: 50px;
margin-right: 10px;
object-fit: cover;
border-radius: 4px;
border: 2px solid #12d1df;
}
.camera-name {
flex: 1;
word-break: break-word;
font-size: 14px;
font-weight: bold;
color: #333;
}
/* 右侧摄像头详情展示 */
.camera-grid {
width: 80%;
height: 95vh; /* 占满页面右侧区域 */
display: grid;
grid-template-columns: repeat(2, 1fr); /* 两列 */
grid-template-rows: repeat(2, 1fr); /* 两行 */
gap: 5px; /* 栅格块之间的间距 */
padding: 10px;
/* background-color: #1E2E4A; */
background: linear-gradient(to top, rgba(0, 3, 3, 0.4), rgba(9, 21, 196, 0.3));
/* background: linear-gradient(to top, rgba(8, 53, 61, 0.4), rgba(9, 21, 196, 0.3)); */
/* border: 2px solid #ece9e9; */
box-sizing: border-box;
overflow-y: auto;
}
.camera-details {
height: 45vh;
/* background-color: #3b2c2c; */
/* background: linear-gradient(to top, rgba(8, 53, 61, 0.4), rgba(9, 21, 196, 0.3)); */
border-radius: 4px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
/* overflow-y: auto; */
}
.camera-card {
width: 100%;
height: 100%;
/* background-color: #1E2E4A; */
background: linear-gradient(to top, rgba(64, 226, 255, 0.7), rgba(211, 64, 248, 0.7));
/* background: linear-gradient(to top, rgba(0, 7, 8, 0.9), rgba(12, 2, 155, 0.7)); */
border: 2px solid #0b4c5f;
border-radius: 8px;
position: relative;
/* padding: 10px; */
box-sizing: border-box;
}
.stream-control { .stream-control {
display: flex; display: flex;
margin-top: 20px; justify-content: space-between;
text-align: center; align-items: center;
margin-bottom: 5px;
}
/* .stream-control p {
color: #f5f7fa;
font-size: 18px;
margin: 0;
line-height: 1;
font-weight: bold;
} */
.camera-name-title {
font-size: 18px;
color: white;
font-weight: bold;
margin: 0;
padding: 0;
line-height: 1;
}
.close-button {
position: absolute;
top: 10px;
right: 20px;
z-index: 10;
}
.camera-placeholder {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: #6b6b6b;
border: 2px dashed rgba(109, 109, 109, 0.7);
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
background-color: rgba(50, 50, 50, 0.5);
}
/* 播放中的 canvas 样式 */
.camera-large {
width: 100%;
height: 100%;
border-radius: 8px;
object-fit: cover;
border: 2px solid rgba(109, 109, 109, 0.3);
}
/* 摄像头大图和快照 */
.camera-snapshot {
width: 100%;
height: 40vh;
object-fit: cover;
border-radius: 4px;
border: 2px solid #dcdcdc;
} }
.stream-control p { /* 播放按钮容器 */
margin-right: 20px;
font-size: 15px;
line-height: 5px;
}
.stream-control el-button {
height: 40px;
line-height: 40px;
}
.play-button-container { .play-button-container {
position: relative; position: relative;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
} }
/* 播放按钮 */
.play-button { .play-button {
position: absolute; position: absolute;
top: 50%; top: 50%;

View File

@ -1,18 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, onMounted } from 'vue'; import { reactive, ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { BoxApi } from '@/utils/boxApi'; import { BoxApi } from '@/utils/boxApi';
import { User, Lock } from '@element-plus/icons-vue'; import { User, Lock } from '@element-plus/icons-vue';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
//
const getHostAddress = () => window.location.hostname;
const getHostPort = () => '8000';
const loginForm = reactive({ const loginForm = reactive({
username: '', username: '',
password: '', password: '',
address: '', address: localStorage.getItem('rememberedAddress') || getHostAddress(),
port: '', port: localStorage.getItem('rememberedPort') || getHostPort(),
remember: false // "" remember: false,
serverSettings: false
}); });
const errorMessage = ref(''); const errorMessage = ref('');
@ -23,50 +29,48 @@ const cookless = ref(false);
// //
let apiInstance: BoxApi | null = null; let apiInstance: BoxApi | null = null;
// //
onMounted(() => { onMounted(() => {
const savedUsername = localStorage.getItem('username'); const savedUsername = localStorage.getItem('username');
const savedPassword = localStorage.getItem('rememberedPassword'); const savedPassword = localStorage.getItem('rememberedPassword');
const savedAddress = localStorage.getItem('rememberedAddress'); // const savedAddress = localStorage.getItem('rememberedAddress');
const savedPort = localStorage.getItem('rememberedPort'); // const savedPort = localStorage.getItem('rememberedPort');
if (savedUsername && savedPassword) {
if (savedUsername && savedPassword && savedAddress && savedPort) {
loginForm.username = savedUsername; loginForm.username = savedUsername;
loginForm.password = savedPassword; loginForm.password = savedPassword;
loginForm.address = savedAddress;
loginForm.port = savedPort;
loginForm.remember = true; // loginForm.remember = true; //
} }
}); });
const onSubmit = async () => { const onSubmit = async () => {
try { try {
// // if (!loginForm.address || !loginForm.port) {
if (!loginForm.address || !loginForm.port) { // errorMessage.value = '';
errorMessage.value = '请输入服务器地址和端口'; // return;
return; // }
} const address = loginForm.serverSettings ? loginForm.address : getHostAddress();
const port = loginForm.serverSettings ? loginForm.port : getHostPort();
// SuperboxApi // SuperboxApi
apiInstance = new BoxApi(loginForm.address, parseInt(loginForm.port)); apiInstance = new BoxApi(address, parseInt(port));
// SuperboxApi login cookless false // SuperboxApi login cookless false
const apiToken = await apiInstance.login(loginForm.username, loginForm.password,false); const apiToken = await apiInstance.login(loginForm.username, loginForm.password, false);
// token // token
localStorage.setItem('alertToken', apiToken); localStorage.setItem('alertToken', apiToken);
localStorage.setItem('username', loginForm.username);
localStorage.setItem('rememberedAddress', address);
localStorage.setItem('rememberedPort', port);
// localStorage // localStorage
if (loginForm.remember) { if (loginForm.remember) {
localStorage.setItem('username', loginForm.username);
localStorage.setItem('rememberedPassword', loginForm.password); localStorage.setItem('rememberedPassword', loginForm.password);
localStorage.setItem('rememberedAddress', loginForm.address);
localStorage.setItem('rememberedPort', loginForm.port);
} else { } else {
// //
localStorage.removeItem('rememberedPassword'); localStorage.removeItem('rememberedPassword');
localStorage.removeItem('rememberedAddress');
localStorage.removeItem('rememberedPort');
} }
// //
@ -84,9 +88,12 @@ const onSubmit = async () => {
const onReset = () => { const onReset = () => {
loginForm.username = ''; loginForm.username = '';
loginForm.password = ''; loginForm.password = '';
loginForm.address = ''; // loginForm.address = '';
loginForm.port = ''; // loginForm.port = '';
loginForm.address = getHostAddress(); //
loginForm.port = getHostPort(); //
loginForm.remember = false; // loginForm.remember = false; //
loginForm.serverSettings = false;
errorMessage.value = ''; errorMessage.value = '';
}; };
</script> </script>
@ -102,10 +109,10 @@ const onReset = () => {
</div> </div>
<div class="login-body"> <div class="login-body">
<el-form :model="loginForm" style="max-width: 600px;"> <el-form :model="loginForm" style="max-width: 600px;">
<el-form-item> <el-form-item v-show="loginForm.serverSettings">
<el-input v-model="loginForm.address" placeholder="服务器地址" /> <el-input v-model="loginForm.address" placeholder="服务器地址" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item v-show="loginForm.serverSettings">
<el-input v-model="loginForm.port" placeholder="端口" type="number" /> <el-input v-model="loginForm.port" placeholder="端口" type="number" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -126,10 +133,14 @@ const onReset = () => {
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<!-- 添加记住密码复选框 --> <el-row class="setting-row">
<el-form-item> <el-form-item>
<el-checkbox v-model="loginForm.remember">记住密码</el-checkbox> <el-checkbox v-model="loginForm.remember">记住密码</el-checkbox>
</el-form-item> </el-form-item>
<el-form-item>
<el-checkbox v-model="loginForm.serverSettings">服务器设置</el-checkbox>
</el-form-item>
</el-row>
<el-row justify="end"> <el-row justify="end">
<el-form-item size="small" :gutter="30"> <el-form-item size="small" :gutter="30">
<el-button type="primary" @click="onReset" plain>重置</el-button> <el-button type="primary" @click="onReset" plain>重置</el-button>
@ -201,4 +212,8 @@ const onReset = () => {
.error-message { .error-message {
color: red; color: red;
} }
.setting-row {
gap: 20px;
}
</style> </style>

View File

@ -10,6 +10,7 @@ import UserList from '@/html/UserList.vue';
import Home from '@/html/Home.vue'; import Home from '@/html/Home.vue';
import DataStatistics from '@/html/DataStatistics.vue'; import DataStatistics from '@/html/DataStatistics.vue';
import Cameras from '@/components/Cameras.vue'; import Cameras from '@/components/Cameras.vue';
import Settings from '@/components/Settings.vue';
const routes = [ const routes = [
{ {
@ -65,6 +66,12 @@ const routes = [
name: 'Cameras', name: 'Cameras',
component: Cameras, component: Cameras,
meta: { requiresAuth: true } meta: { requiresAuth: true }
},
{
path:'/settings',
name: 'Settings',
component: Settings,
meta: { requiresAuth: true }
} }
] ]
}, },

View File

@ -382,17 +382,24 @@ class BoxApi {
} }
private _authHeader(token: string | null = null): object { private _authHeader(token: string | null = null): object {
// const access_token = token === "" ? this.token : token; const accessToken = token || this.token || localStorage.getItem('alertToken') || "";
const alertToken = localStorage.getItem(`alertToken`) || token || this.token || "" || "";
return { return {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
'Authorization': `Bearer ${alertToken}`, 'Authorization': `Bearer ${accessToken}`,
// 'Cookie': `jwt=${access_token}` // 'X-CSRFToken': this.getCsrfToken()
} }
} };
} }
private getCsrfToken(): string {
const cookie = document.cookie.split(';').find(cookie => cookie.trim().startsWith('csrftoken='));
if (cookie) {
return cookie.split('=')[1];
}
return '';
}
private _boxAddr() { private _boxAddr() {
return window.location.host.replace(/:\d+/, ""); return window.location.host.replace(/:\d+/, "");