723第一次提交

This commit is contained in:
龚皓 2024-07-23 09:31:03 +08:00
commit 283eedbcaa
41 changed files with 5844 additions and 0 deletions

14
.eslintrc.cjs Normal file
View File

@ -0,0 +1,14 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

6
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint"
]
}

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# vue_one
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```

8
env.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
// vue3 报错提示 找不到模块“./XXX.vue”或其相应的类型声明
// 报错原因typescript 只能理解 .ts 文件,无法理解 .vue文件
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

3379
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "vue_one",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"axios": "^1.7.2",
"echarts": "^5.5.1",
"element-plus": "^2.7.6",
"http-proxy-middleware": "^3.0.0",
"vue": "^3.4.29",
"vue-router": "^4.4.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.14.10",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"npm-run-all2": "^6.2.0",
"typescript": "~5.4.0",
"vite": "^5.3.1",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-tsc": "^2.0.21"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

105
src/App.vue Normal file
View File

@ -0,0 +1,105 @@
<template>
<div id="app">
<el-header>
<div class="logo">
<span>管理平台</span>
</div>
<div class="nav-menu">
<el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
router
>
<el-menu-item index="/">首页</el-menu-item>
<el-menu-item index="/alert-management">告警管理</el-menu-item>
<el-menu-item index="/real-time-monitoring">实时监测</el-menu-item>
<el-menu-item index="/statistics-analysis">统计分析</el-menu-item>
<!-- <el-menu-item index="/system-settings">系统设置</el-menu-item> -->
<el-menu-item index="/test">系统设置</el-menu-item>
</el-menu>
</div>
<div class="user-info">
<span>线上轮巡</span>
<span>|</span>
<span>English</span>
<span>|</span>
<span>多云 35°C</span>
<span>|</span>
<el-dropdown>
<span class="el-dropdown-link">
test@qqqq.cn<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu>
<el-dropdown-item>Profile</el-dropdown-item>
<el-dropdown-item>Logout</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-header>
<router-view />
</div>
</template>
<script>
export default {
data() {
return {
activeIndex: '/'
}
},
watch: {
$route(to) {
this.activeIndex = to.path
}
}
}
</script>
<style>
.el-header {
background-color: #1f2d3d;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.logo {
display: flex;
align-items: center;
margin-right: 10px; /* 添加适当的右边距 */
}
.logo img {
height: 40px;
margin-right: 10px;
}
.nav-menu {
flex-grow: 1;
display: flex;
justify-content: center;
margin-left: 110px; /* 添加适当的左边距 */
margin-right: 100px; /* 添加适当的右边距 */
}
.el-menu-demo {
background-color: transparent;
color: #fff;
display: flex;
justify-content: space-between; /* 菜单项之间均匀分布 */
flex-grow: 1;
}
.el-menu-demo .el-menu-item {
padding: 0 20px; /* 减少 padding 以增加菜单项之间的间隔 */
text-align: center;
font-size: 16px;
}
.user-info {
display: flex;
align-items: center;
gap: 10px;
margin-left: 10px; /* 添加适当的左边距 */
}
.user-info span {
color: #fff;
}
</style>

20
src/components/About.vue Normal file
View File

@ -0,0 +1,20 @@
<template>
<div class="about">
<h1>关于</h1>
</div>
</template>
<scrip setup lang="ts" name="about">
</scrip>
<style scoped>
.about{
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<div>
<el-card class="alert-card">
<div class="alert-header">告警趋势</div>
<el-tabs v-model="activeName" @tab-click="handleClick" class="alert-tabs">
<el-tab-pane label="24小时" name="first">24小时数据展示</el-tab-pane>
<el-tab-pane label="30天" name="second">30天数据展示</el-tab-pane>
<el-tab-pane label="12月" name="third">12月数据展示</el-tab-pane>
</el-tabs>
<div id="alert-chart" class="chart-container"></div>
</el-card>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'AlertChart',
data() {
return {
activeName: 'first'
};
},
mounted() {
this.initChart();
},
methods: {
handleClick(tab, event) {
console.log(tab, event);
//
this.initChart();
},
initChart() {
var chartDom = document.getElementById('alert-chart');
var myChart = echarts.init(chartDom);
var option;
option = {
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2024-07-08 00:00', '2024-07-08 08:00', '2024-07-08 16:00', '2024-07-09 00:00', '2024-07-09 08:00', '2024-07-09 16:00']
},
yAxis: {
type: 'value'
},
series: [
{
data: [10, 22, 28, 43, 49, 62],
type: 'line',
areaStyle: {}
}
]
};
option && myChart.setOption(option);
}
}
};
</script>
<style scoped>
.alert-card {
background-color: #2a3f54;
color: #fff;
padding: 20px;
border-radius: 8px;
}
.alert-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
border-bottom: 1px solid #3a4b5c;
padding-bottom: 10px;
}
.alert-tabs {
margin-bottom: 20px;
}
.chart-container {
width: 100%;
height: 400px;
}
.el-tabs__item {
color: #fff;
}
.el-tabs__active-bar {
background-color: #409EFF;
}
</style>

View File

@ -0,0 +1,252 @@
<template>
<div class="alert-management">
<div class="breadcrumb">
<el-breadcrumb separator=">">
<el-breadcrumb-item>告警管理</el-breadcrumb-item>
<el-breadcrumb-item>实时告警</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="alert-summary">
<el-row :gutter="20">
<el-col :span="8">
<div class="alert-card alert-pending">
<div class="alert-card-header">告警数待处理</div>
<div class="alert-card-content">{{ alerts.pending }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="alert-card alert-processing">
<div class="alert-card-header">告警数处理中</div>
<div class="alert-card-content">{{ alerts.processing }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="alert-card alert-completed">
<div class="alert-card-header">告警数已完成</div>
<div class="alert-card-content">{{ alerts.completed }}</div>
</div>
</el-col>
</el-row>
</div>
<div class="region-overview">
<el-card>
<div slot="header" class="card-header">
<span>区域概况</span>
</div>
<el-table
:data="tableData.slice((currentPage - 1) * pageSize, currentPage * pageSize)"
style="width: 100%;"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="id" label="序号" width="50"></el-table-column>
<el-table-column prop="region" label="区域名称" width="200"></el-table-column>
<el-table-column prop="siteCount" label="站点数量" width="100"></el-table-column>
<el-table-column prop="siteName" label="站点名称">
<template v-slot="scope">
<span v-if="scope.row.children"></span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template v-slot="scope">
<el-tag v-if="scope.row.status === '在线'" type="success">在线</el-tag>
<el-tag v-else type="info">离线</el-tag>
</template>
</el-table-column>
<el-table-column prop="cameraCount" label="摄像头数量" width="120"></el-table-column>
<el-table-column prop="alertCount" label="告警数量" width="120"></el-table-column>
<el-table-column label="操作" width="100">
<template v-slot="scope">
<el-button v-if="!scope.row.children" type="text" size="small">查看告警</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 20, 30]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
</el-card>
</div>
</div>
</template>
<script>
export default {
name: 'AlertManagement',
data() {
return {
alerts: {
pending: 1460,
processing: 1,
completed: 123830
},
tableData: [
{
id: 1,
region: '安徽区域',
siteCount: 2,
children: [
{
id: 11,
siteName: '天长吾悦广场',
status: '在线',
cameraCount: 20,
alertCount: 6059
},
{
id: 12,
siteName: '颖上吾悦广场',
status: '在线',
cameraCount: 20,
alertCount: 617
}
]
},
{
id: 2,
region: '东北区域',
siteCount: 2,
children: [
{
id: 21,
siteName: '东北站点1',
status: '在线',
cameraCount: 15,
alertCount: 200
},
{
id: 22,
siteName: '东北站点2',
status: '离线',
cameraCount: 18,
alertCount: 150
}
]
},
{
id: 3,
region: '沪苏区域',
siteCount: 1,
children: [
{
id: 31,
siteName: '沪苏站点',
status: '在线',
cameraCount: 10,
alertCount: 300
}
]
},
{
id: 4,
region: '华北区域',
siteCount: 5
},
{
id: 5,
region: '华南区域',
siteCount: 2
},
{
id: 6,
region: '华中区域',
siteCount: 10
},
{
id: 7,
region: '闽浙区域',
siteCount: 2
},
{
id: 8,
region: '南京区域',
siteCount: 1
},
{
id: 9,
region: '山东区域',
siteCount: 7
},
{
id: 10,
region: '苏北区域',
siteCount: 3
}
],
currentPage: 1,
pageSize: 5,
total: 10
};
},
methods: {
handleSizeChange(val) {
this.pageSize = val;
},
handleCurrentChange(val) {
this.currentPage = val;
}
}
};
</script>
<style scoped>
.alert-management {
padding: 40px;
background-color: #f5f7fa;
}
.breadcrumb {
margin-bottom: 20px;
}
.alert-summary {
background-color: #fff;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
.alert-card {
background-color: #fff;
border-radius: 10px;
text-align: center;
padding: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.alert-card-header {
font-size: 16px;
margin-bottom: 10px;
color: #333;
}
.alert-card-content {
font-size: 28px;
font-weight: bold;
color: #333;
}
.alert-pending {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
}
.alert-processing {
background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
}
.alert-completed {
background: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%);
}
.region-overview {
background-color: #fff;
padding: 20px;
border-radius: 10px;
}
.card-header {
font-size: 18px;
font-weight: bold;
}
.pagination {
margin-top: 20px;
text-align: right;
}
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>摄像头管理</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "CameraManagement",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>设备管理</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "DeviceManagement",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

288
src/components/Home.vue Normal file
View File

@ -0,0 +1,288 @@
<template>
<div class="home">
<div class="breadcrumb">
<el-breadcrumb separator=">">
<el-breadcrumb-item>主页</el-breadcrumb-item>
</el-breadcrumb>
</div>
<el-row>
<el-col :span="16">
<div class="table-container">
<el-table :data="paginatedData">
<el-table-column prop="id" label="告警编号" width="140"></el-table-column>
<el-table-column label="告警类型" width="140">
<template v-slot="scope">
<!-- {{ typeMapping[scope.row.types] || codenameTranslate(scope.row.types) }} -->
{{ typeMapping[scope.row.types] }}
</template>
</el-table-column>
<el-table-column prop="camera.name" label="告警位置" width="180"></el-table-column>
<el-table-column label="告警时间" width="180">
<template v-slot="scope">
{{ formatDateTime(scope.row.started_at) }}
</template>
</el-table-column>
<el-table-column prop="status" label="告警状态" width="140">
<template v-slot="scope">
<el-tag :type="scope.row.status === '已处理' ? 'success' : 'warning'">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="140">
<template v-slot="scope">
<el-button type="text" @click="handleView(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-container">
<el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="tableData.length"
:page-size="pageSize" :current-page.sync="currentPage" @current-change="handlePageChange"
@size-change="handleSizeChange">
</el-pagination>
</div>
</el-col>
</el-row>
<el-dialog v-model="dialogVisible" title="告警详情" width="80%">
<div>
<el-row>
<el-col :span="6">
<p>告警编号: {{ selectedRow.id }}</p>
</el-col>
<el-col :span="6">
<!-- <p>告警类型: {{ typeMapping[selectedRow.types] || codenameTranslate(selectedRow.types) }}</p> -->
<p>告警类型: {{ typeMapping[selectedRow.types] }}</p>
</el-col>
<el-col :span="6">
<p>告警位置: {{ selectedRow.camera.name }}</p>
</el-col>
<el-col :span="6">
<p>告警时间: {{ selectedRow.formatted_started_at }}</p>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
<p>告警状态: <el-tag :type="selectedRow.status === '已处理' ? 'success' : 'warning'">{{ selectedRow.status }}</el-tag></p>
</el-col>
<el-col :span="6">
<p>摄像头编号: {{ selectedRow.camera_id }}</p>
</el-col>
<el-col :span="6">
<p>持续时间: {{ duration }}</p>
</el-col>
<el-col :span="6">
<p>备注: {{ selectedRow.note }}</p>
</el-col>
</el-row>
<div class="event-media">
<div v-for="medium in selectedRow.mediums" :key="medium.id" class="media-container">
<div v-if="medium.name === 'video'" class="media-item video-item">
<p>告警关联视频</p>
<video :src="medium.file" controls></video>
</div>
<div v-if="medium.name === 'snapshot'" class="media-item image-item">
<p>告警关联图片</p>
<el-image :src="medium.file" fit="contain"></el-image>
</div>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<div class=""></div>
</span>
</el-dialog>
</div>
</template>
<script>
import * as echarts from 'echarts';
import Statistics from '../components/Statistics.vue';
import AlertChart from '../components/AlertChart.vue';
import { login, getEvents, initCodeNameMap, codenameTranslate } from '../../superbox.js';
export default {
name: 'Home',
components: {
Statistics,
AlertChart,
},
data() {
return {
tableData: [],
dialogVisible: false,
selectedRow: {},
mediums: {},
duration: '',
typeMapping: {
'abnormal:525': '黑屏检测',
'car_blocking:512': '车辆拥塞',
'car_blocking:514': '违规停车',
'escalator_status:518': '扶梯运行状态',
'gathering:520': '人员密集',
'gathering:521': '保洁点名',
'intrude:513': '入侵检测',
'long_term_door_status:526': '长期门状态检测',
'lying_down:527': '人员倒地',
'minizawu:531': '杂物堆积',
'minizawu:532': '杂物堆积',
'personnel_stay:535': '人员逗留',
'vacant:524': '人员离岗',
'zawu:516': '饮料垃圾检测',
'zawu:517': '垃圾桶满溢',
'zawu:519': '违规放置',
'zawu:523': '绿化带垃圾检测',
},
statusMapping: {
'': '待处理',
'': '处理中',
'': '已处理'
},
currentPage: 1,
pageSize: 20,
}
},
async created() {
try {
const token = await login('turingvideo', '1234qwer!');
// await initCodeNameMap(token);
this.tableData = await getEvents(token);
console.log(this.tableData);
} catch (error) {
console.error("Error fetching events:", error);
}
},
computed: {
paginatedData() {
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.tableData.slice(start, end);
}
},
methods: {
handleView(row) {
this.selectedRow = row;
this.duration = this.calculateDuration(row.started_at, row.ended_at);
row.formatted_started_at = this.formatDateTime(row.started_at);
// console.log("duration: ", row.started_at);
// console.log("duration: ", row.ended_at);
// console.log("duration: ", this.duration);
this.dialogVisible = true;
this.mediums = row.mediums || {};
if (Array.isArray(row.mediums)) {
row.mediums.forEach(medium => {
// console.log("medium: ", medium.file);
// console.log("medium: ", medium.id);
});
} else {
// console.log("No mediums available");
}
},
handlePageChange(page) {
this.currentPage = page;
},
handleSizeChange(size) {
this.pageSize = size;
},
calculateDuration(started_at, ended_at) {
const start = new Date(started_at);
const end = new Date(ended_at);
const duration = end - start;
const minutes = Math.floor(duration / 60000);
const seconds = ((duration % 60000) / 1000).toFixed(0);
return `${minutes}${(seconds < 10 ? '0' : '')}${seconds}`;
},
formatDateTime(datetime) {
const date = new Date(datetime);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
}
};
</script>
<style scoped>
.home {
padding: 40px;
background-color: #f5f7fa;
}
.breadcrumb {
margin-bottom: 30px;
}
.top-scroll {
width: 100%;
overflow-x: auto;
}
.top-scroll-content {
/* 高度滚动条显示出来默认值 */
height: 1px;
}
.table-container {
width: 80%;
max-height: 1000px;
overflow-x: auto;
padding-left: 7%;
}
/* 弹窗> 显示左右等分*/
.event-media {
/* display: flex;
flex-direction: column;
gap: 10px;
margin-top: 20px; */
display: flex;
justify-content: space-between;
/* 添加这个属性来左右等分 */
flex-wrap: wrap;
}
.media-container {
flex: 0 0 48%;
/* 确保每块区域占据一行的48% */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* 使内容在区域内居中 */
margin-bottom: 20px;
/* 添加底部间距,使多个元素之间有间隔 */
box-sizing: border-box;
/* 确保padding和border不会影响大小 */
padding: 10px;
/* 添加内边距,使内容不靠近边框 */
}
.video-item video,
.image-item img {
width: 100%;
/* 保证视频和图片在容器内等宽 */
height: auto;
/* 保证视频和图片比例正常 */
border-radius: 8px;
/* 圆润效果 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
/* 阴影效果 */
}
.video-item p,
.image-item p {
margin-bottom: 8px;
text-align: center;
font-weight: bold;
}
.pagination-container {
display: flex;
justify-content: flex-end;
margin-top: 20px;
margin-right: 150px;
}
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>层级列表</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "LevelList",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>层级树</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "LevelTree",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

View File

@ -0,0 +1,18 @@
<template>
<div>
<el-card>
<h2>当前位置中国</h2>
<!-- Add map implementation here -->
</el-card>
</div>
</template>
<script>
export default {
name: 'LocationMap'
}
</script>
<style scoped>
/* Add your styles here */
</style>

83
src/components/News.vue Normal file
View File

@ -0,0 +1,83 @@
<!-- 测试 -->
<template>
<div>
<button @click="loginAndGetToken">Login and Get Token</button>
<button @click="withTokenGetCameras">Get Cameras with Token</button>
<pre>Token: {{ token }}</pre>
<pre>Cameras: {{ cameras }}</pre>
</div>
</template>
<script>
import axios from 'axios';
import { ref } from 'vue';
export default {
name: 'App',
setup() {
const token = ref('');
const cameras = ref([]);
const api_base = "/api/v1";
const api_login = api_base + "/auth/login";
const api_cameras = api_base + "/camera/cameras";
const username = "turingvideo";
const password = "1234qwer!";
const loginAndGetToken = async () => {
try {
const res = await axios.post(api_login,
{
'username': username,
'password': password,
'cookieless': 'True'
},
{
headers: {
'Content-type': 'application/json'
}
}
);
console.log(res.data)
token.value = res.data.ret.token;
console.log('Token:', token.value);
} catch (e) {
console.log(e);
}
};
const withTokenGetCameras = async () => {
try {
if (!token.value) {
console.error('No token available. Please login first.');
return;
}
const res = await axios.get(api_cameras, {
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token.value}`,
'Cookie': `jwt=${token.value}; sessionid=27iy45x18cq2uio2k2gnsl4a8s4si935`
}
});
cameras.value = res.data;
console.log('Cameras:', cameras.value);
} catch (e) {
console.log(e);
}
};
return {
loginAndGetToken,
withTokenGetCameras,
token,
cameras
};
}
};
</script>
<style>
/* Add your styles here */
</style>

View File

@ -0,0 +1,313 @@
<template>
<div class="real-time-monitoring">
<div class="breadcrumb">
<el-breadcrumb separator=">">
<el-breadcrumb-item>实时监测</el-breadcrumb-item>
<el-breadcrumb-item>实时监测数据</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="alert-summary">
<el-row :gutter="20">
<el-col :span="12">
<div class="chart-container">
<div class="chart-header">告警情况汇总</div>
<div ref="cameraChart" class="chart"></div>
<div class="chart-info">
<div>摄像头数量</div>
<div>
<span class="status online"> 在线</span> 1885
<span class="status offline"> 离线</span> 115
</div>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="chart-container">
<div class="chart-header">站点数量</div>
<div ref="siteChart" class="chart"></div>
<div class="chart-info">
<div>编制总数</div>
<div>
<span class="status online"> 在线</span> 47
<span class="status offline"> 离线</span> 7
</div>
</div>
</div>
</el-col>
</el-row>
</div>
<div class="device-status">
<el-row :gutter="20">
<el-col :span="8">
<div class="status-card pending">
<div class="status-header">待处理告警摄像头</div>
<div class="status-content">{{ deviceStatus.pending }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="status-card total">
<div class="status-header">累计产生告警摄像头</div>
<div class="status-content">{{ deviceStatus.total }}</div>
<div class="status-footer">
<div>近7天产生告警摄像头 {{ deviceStatus.last7Days }}</div>
<div>近30天产生告警摄像头 {{ deviceStatus.last30Days }}</div>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="status-card inactive">
<div class="status-header">非活跃摄像头累计90天未产生告警</div>
<div class="status-content">{{ deviceStatus.inactive }}</div>
</div>
</el-col>
</el-row>
</div>
<div class="region-overview">
<el-card>
<div slot="header" class="card-header">
<span>区域概况</span>
</div>
<el-table
:data="pagedData"
style="width: 100%;"
row-key="id"
height="400"
>
<el-table-column prop="id" label="序号" width="50"></el-table-column>
<el-table-column prop="region" label="区域名称" width="200"></el-table-column>
<el-table-column prop="siteCount" label="站点数量" width="100"></el-table-column>
<el-table-column prop="cameraCount" label="摄像头数量" width="120"></el-table-column>
<el-table-column label="操作" width="100">
<template slot-scope="scope">
<el-button type="text" size="small">查看站点</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 20, 30]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.length"
></el-pagination>
</div>
</el-card>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'RealTimeMonitoring',
data() {
return {
deviceStatus: {
pending: 100,
total: 1104,
last7Days: 125,
last30Days: 185,
inactive: 1738
},
tableData: [
{ id: 1, region: '安徽区域', siteCount: 2, cameraCount: 40 },
{ id: 2, region: '东北区域', siteCount: 2, cameraCount: 40 },
{ id: 3, region: '沪苏区域', siteCount: 1, cameraCount: 311 },
{ id: 4, region: '华北区域', siteCount: 5, cameraCount: 120 },
{ id: 5, region: '华南区域', siteCount: 2, cameraCount: 60 },
{ id: 6, region: '华中区域', siteCount: 10, cameraCount: 279 },
{ id: 7, region: '闽浙区域', siteCount: 2, cameraCount: 20 },
{ id: 8, region: '南京区域', siteCount: 1, cameraCount: 377 },
{ id: 9, region: '山东区域', siteCount: 7, cameraCount: 193 },
{ id: 10, region: '苏北区域', siteCount: 3, cameraCount: 100 }
],
currentPage: 1,
pageSize: 5
};
},
computed: {
pagedData() {
const start = (this.currentPage - 1) * this.pageSize;
const end = this.currentPage * this.pageSize;
return this.tableData.slice(start, end);
}
},
mounted() {
this.initCameraChart();
this.initSiteChart();
},
methods: {
initCameraChart() {
const cameraChart = echarts.init(this.$refs.cameraChart);
const cameraOptions = {
series: [
{
name: '摄像头数量',
type: 'pie',
radius: ['50%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '30',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 1885, name: '在线' },
{ value: 115, name: '离线' }
]
}
]
};
cameraChart.setOption(cameraOptions);
},
initSiteChart() {
const siteChart = echarts.init(this.$refs.siteChart);
const siteOptions = {
series: [
{
name: '站点数量',
type: 'pie',
radius: ['50%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '30',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 47, name: '在线' },
{ value: 7, name: '离线' }
]
}
]
};
siteChart.setOption(siteOptions);
},
handleSizeChange(val) {
this.pageSize = val;
},
handleCurrentChange(val) {
this.currentPage = val;
}
}
};
</script>
<style scoped>
.real-time-monitoring {
padding: 20px;
background-color: #f5f7fa;
}
.breadcrumb {
margin-bottom: 20px;
}
.alert-summary {
background-color: #fff;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
.chart-container {
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
text-align: center;
}
.chart-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.chart {
width: 100%;
height: 300px;
}
.chart-info {
margin-top: 10px;
}
.status {
margin-right: 10px;
}
.status.online {
color: #67c23a;
}
.status.offline {
color: #f56c6c;
}
.device-status {
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.status-card {
padding: 20px;
border-radius: 10px;
text-align: center;
color: #fff;
height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
}
.status-card.pending {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
}
.status-card.total {
background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
}
.status-card.inactive {
background: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%);
}
.status-header {
font-size: 16px;
margin-bottom: 10px;
}
.status-content {
font-size: 28px;
font-weight: bold;
}
.status-footer {
margin-top: 10px;
font-size: 14px;
}
.region-overview {
background-color: #fff;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
.card-header {
font-size: 18px;
font-weight: bold;
}
.pagination {
margin-top: 20px;
text-align: right;
}
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>区域1</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "Region1",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>区域2</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "Region2",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>服务器管理</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "ServerManagement",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>站点管理</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "SiteManagement",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

View File

@ -0,0 +1,101 @@
<template>
<div>
<el-card class="stats-card">
<div class="stats-header">布控情况</div>
<el-row :gutter="20" class="stats-row">
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">布控区域数</div>
<div class="stats-value">15</div>
</div>
</el-col>
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">布控站点数</div>
<div class="stats-value">54</div>
<div class="stats-subvalue">7 异常站点</div>
</div>
</el-col>
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">布控摄像头数</div>
<div class="stats-value">2000</div>
</div>
</el-col>
</el-row>
<div class="stats-divider"></div>
<div class="stats-header">告警情况</div>
<el-row :gutter="20" class="stats-row">
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">告警站点</div>
<div class="stats-value">25</div>
</div>
</el-col>
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">未完成告警</div>
<div class="stats-value">1511</div>
</div>
</el-col>
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">已完成告警</div>
<div class="stats-value">123916</div>
</div>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script>
export default {
name: 'Statistics'
}
</script>
<style scoped>
.stats-card {
background-color: #2a3f54;
color: #fff;
padding: 20px;
border-radius: 8px;
}
.stats-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
border-bottom: 1px solid #3a4b5c;
padding-bottom: 10px;
}
.stats-row {
margin-bottom: 20px;
}
.stats-item {
text-align: center;
}
.stats-title {
font-size: 16px;
margin-bottom: 10px;
}
.stats-value {
font-size: 24px;
font-weight: bold;
}
.stats-subvalue {
font-size: 14px;
color: #f39c12;
}
.stats-divider {
border-top: 1px solid #3a4b5c;
margin: 20px 0;
}
</style>

View File

@ -0,0 +1,376 @@
<template>
<div class="statistics-analysis">
<div class="breadcrumb">
<el-breadcrumb separator=">">
<el-breadcrumb-item>统计分析</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="filter-form">
<el-card>
<div slot="header" class="card-header">
<span>筛选条件</span>
</div>
<el-form :inline="true" :model="filterForm" class="demo-form-inline">
<el-form-item label="所属站点:">
<el-select v-model="filterForm.site" placeholder="全部">
<el-option label="全部" value="all"></el-option>
<el-option label="站点一" value="site1"></el-option>
<el-option label="站点二" value="site2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="开始时间:">
<el-date-picker v-model="filterForm.startDate" type="date" placeholder="选择日期"></el-date-picker>
</el-form-item>
<el-form-item label="结束时间:">
<el-date-picker v-model="filterForm.endDate" type="date" placeholder="选择日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch">查询</el-button>
<el-button @click="onReset">清空</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<div class="device-management">
<el-card>
<div slot="header" class="card-header">
<span>设备管理</span>
</div>
<el-row :gutter="10">
<el-col :span="8">
<div class="status-info">
<div class="status-title">摄像头总数</div>
<div class="status-value">{{ deviceData.totalCameras }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="status-info">
<div class="status-title">在线</div>
<div class="status-value">{{ deviceData.online }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="status-info">
<div class="status-title">异常</div>
<div class="status-value">{{ deviceData.abnormal }}</div>
</div>
</el-col>
</el-row>
<el-row :gutter="10" class="chart-row">
<el-col :span="12">
<div ref="statusPieChart" class="chart"></div>
</el-col>
<el-col :span="12">
<div class="status-percentage">
<div><span class="dot online"></span> 在线 {{ deviceData.onlinePercentage }}%</div>
<div><span class="dot abnormal"></span> 异常 {{ deviceData.abnormalPercentage }}%</div>
</div>
</el-col>
</el-row>
<div class="chart-container">
<div ref="distributionBarChart" class="chart"></div>
</div>
</el-card>
</div>
<div class="alert-management">
<el-card>
<div slot="header" class="card-header">
<span>告警管理</span>
</div>
<el-row :gutter="10">
<el-col :span="8">
<div class="alert-info">
<div class="alert-title">总告警数</div>
<div class="alert-value">{{ alertData.totalAlerts }}</div>
</div>
</el-col>
<el-col :span="4">
<div class="alert-info pending">
<div class="alert-title">待处理</div>
<div class="alert-value">{{ alertData.pending }}</div>
</div>
</el-col>
<el-col :span="4">
<div class="alert-info processing">
<div class="alert-title">处理中</div>
<div class="alert-value">{{ alertData.processing }}</div>
</div>
</el-col>
<el-col :span="4">
<div class="alert-info completed">
<div class="alert-title">已完成</div>
<div class="alert-value">{{ alertData.completed }}</div>
</div>
</el-col>
</el-row>
<el-row :gutter="10" class="chart-row">
<el-col :span="12">
<div ref="alertPieChart" class="chart"></div>
</el-col>
<el-col :span="12">
<div ref="alertTrendChart" class="chart"></div>
</el-col>
</el-row>
</el-card>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'StatisticsAnalysis',
data() {
return {
filterForm: {
site: 'all',
startDate: '',
endDate: ''
},
deviceData: {
totalCameras: 2000,
online: 1885,
abnormal: 115,
onlinePercentage: 94,
abnormalPercentage: 6,
distribution: [
{ name: '杂物堆积', value: 600 },
{ name: '车辆拥堵', value: 500 },
{ name: '违规停车', value: 400 },
{ name: '饮料垃圾检测', value: 300 },
{ name: '垃圾桶满溢', value: 200 },
{ name: '入侵检测', value: 150 },
{ name: '扶梯运行状态', value: 100 },
{ name: '违规放置', value: 80 },
{ name: '长期门状态检测', value: 70 },
{ name: '人员密集', value: 60 },
{ name: '保洁点名', value: 50 }
]
},
alertData: {
totalAlerts: 0,
pending: 0,
processing: 0,
completed: 0
}
};
},
mounted() {
this.initStatusPieChart();
this.initDistributionBarChart();
this.initAlertPieChart();
this.initAlertTrendChart();
},
methods: {
onSearch() {
//
console.log('查询参数:', this.filterForm);
},
onReset() {
this.filterForm = {
site: 'all',
startDate: '',
endDate: ''
};
},
initStatusPieChart() {
const statusPieChart = echarts.init(this.$refs.statusPieChart);
const options = {
series: [
{
name: '设备状态分布',
type: 'pie',
radius: '50%',
data: [
{ value: this.deviceData.online, name: '在线' },
{ value: this.deviceData.abnormal, name: '异常' }
],
label: {
formatter: '{b}: {d}%'
}
}
]
};
statusPieChart.setOption(options);
},
initDistributionBarChart() {
const distributionBarChart = echarts.init(this.$refs.distributionBarChart);
const options = {
xAxis: {
type: 'category',
data: this.deviceData.distribution.map(item => item.name)
},
yAxis: {
type: 'value'
},
series: [
{
data: this.deviceData.distribution.map(item => item.value),
type: 'bar'
}
]
};
distributionBarChart.setOption(options);
},
initAlertPieChart() {
const alertPieChart = echarts.init(this.$refs.alertPieChart);
const options = {
series: [
{
name: '告警事件分布',
type: 'pie',
radius: '50%',
data: [
{ value: this.alertData.pending, name: '待处理' },
{ value: this.alertData.processing, name: '处理中' },
{ value: this.alertData.completed, name: '已完成' }
],
label: {
formatter: '{b}: {d}%'
}
}
]
};
alertPieChart.setOption(options);
},
initAlertTrendChart() {
const alertTrendChart = echarts.init(this.$refs.alertTrendChart);
const options = {
xAxis: {
type: 'category',
data: ['2024-07-16', '2024-07-17', '2024-07-18', '2024-07-19', '2024-07-20', '2024-07-21', '2024-07-22']
},
yAxis: {
type: 'value'
},
series: [
{
name: '待处理',
data: [0, 0, 0, 0, 0, 0, 0],
type: 'line',
smooth: true
},
{
name: '处理中',
data: [0, 0, 0, 0, 0, 0, 0],
type: 'line',
smooth: true
},
{
name: '已完成',
data: [0, 0, 0, 0, 0, 0, 0],
type: 'line',
smooth: true
}
]
};
alertTrendChart.setOption(options);
}
}
};
</script>
<style scoped>
.statistics-analysis {
padding: 20px;
background-color: #f5f7fa;
}
.breadcrumb {
margin-bottom: 20px;
}
.filter-form {
margin-bottom: 20px;
}
.card-header {
font-size: 18px;
font-weight: bold;
}
.demo-form-inline .el-form-item {
margin-right: 20px;
}
.el-form-item .el-select,
.el-form-item .el-date-picker {
width: 200px;
}
.el-button {
margin-right: 10px;
}
.device-management {
margin-bottom: 20px;
}
.status-info {
background-color: #fff;
padding: 10px;
border-radius: 10px;
text-align: center;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
height: 100px;
}
.status-title {
font-size: 14px;
color: #999;
}
.status-value {
font-size: 20px;
font-weight: bold;
margin-top: 5px;
}
.chart-row {
margin-top: 20px;
}
.chart {
width: 100%;
height: 200px;
}
.status-percentage {
margin-top: 20px;
font-size: 14px;
text-align: center;
}
.status-percentage .dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
}
.status-percentage .online {
background-color: #67c23a;
}
.status-percentage .abnormal {
background-color: #f56c6c;
}
.chart-container {
margin-top: 20px;
}
.alert-management {
margin-bottom: 20px;
}
.alert-info {
background-color: #fff;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.alert-info.pending {
background: linear-gradient(135deg, #64b5f6 0%, #bbdefb 100%);
}
.alert-info.processing {
background: linear-gradient(135deg, #9575cd 0%, #d1c4e9 100%);
}
.alert-info.completed {
background: linear-gradient(135deg, #81c784 0%, #c8e6c9 100%);
}
.alert-title {
font-size: 14px;
color: #999;
}
.alert-value {
font-size: 20px;
font-weight: bold;
margin-top: 5px;
}
</style>

View File

@ -0,0 +1,71 @@
<template>
<div class="system-setting">
<el-row>
<el-col :span="3">
<el-menu
:default-openeds="['1', '2', '3', '4']"
router
class="el-menu-vertical-demo"
background-color="#1f2d3d"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-menu"></i>
<span>层级管理</span>
</template>
<el-menu-item index="/levelList">层级列表</el-menu-item>
<el-menu-item index="/levelTree">层级树</el-menu-item>
</el-submenu>
<el-menu-item index="/siteManagement">
<i class="el-icon-s-platform"></i>
<span slot="title">站点管理</span>
</el-menu-item>
<el-submenu index="3">
<template slot="title">
<i class="el-icon-setting"></i>
<span>设备管理</span>
</template>
<el-menu-item index="/serverManagement">服务器管理</el-menu-item>
<el-menu-item index="/cameraManagement">摄像头管理</el-menu-item>
</el-submenu>
<el-menu-item index="/userManagement">
<i class="el-icon-user"></i>
<span slot="title">用户管理</span>
</el-menu-item>
</el-menu>
</el-col>
<el-col :span="21">
<div class="router-view-container">
<router-view></router-view>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: "SystemSetting",
};
</script>
<style scoped>
.system-setting {
height: 100%;
}
.el-menu-vertical-demo {
height: 100%;
border-right: none;
}
.router-view-container {
margin: 20px 30px 20px 10px;
padding: 20px;
background-color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
height: calc(100vh - 100px); /* 调整高度以适应页面 */
}
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h2>用户管理</h2>
<!-- 模拟静态数据内容 -->
</div>
</template>
<script>
export default {
name: "UserManagement",
};
</script>
<style scoped>
/* 添加需要的样式 */
</style>

21
src/hooks/useDog.ts Normal file
View File

@ -0,0 +1,21 @@
import {reactive} from "vue"
import axios from "axios"
export default function (){
let dogList = reactive([
'https://images.dog.ceo/breeds/pembroke/n02113023_1774.jpg'
])
async function addDog(){
try {
let result = await axios.get('https://dog.ceo/api/breeds/image/random')
console.log(result.data.message)
dogList.push(result.data.message)
} catch (error) {
console.log(error)
}
}
return {dogList,addDog}
}

18
src/hooks/useSum.ts Normal file
View File

@ -0,0 +1,18 @@
import {ref,onMounted} from "vue"
import axios from "axios"
export default function (){
let sum = ref(0)
function changeSum(){
sum.value++
}
onMounted(() => {
changeSum()
})
return {sum,changeSum}
}

17
src/main.ts Normal file
View File

@ -0,0 +1,17 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as echarts from 'echarts'
import axios from 'axios';
const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.config.globalProperties.$echarts = echarts
app.mount("#app");

75
src/router/index.ts Normal file
View File

@ -0,0 +1,75 @@
// 第一步引入createRouter
import { createRouter, createWebHistory,createWebHashHistory} from 'vue-router'
// 引入路由组件
import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import News from '@/components/News.vue'
import AlertManagement from '@/components/AlertManagement.vue'
import RealTimeMonitoring from '@/components/RealTimeMonitoring.vue'
import StatisticsAnalysis from '@/components/StatisticsAnalysis.vue'
import SystemSettings from '@/components/SystemSettings.vue'
import LevelList from '@/components/LevelList.vue'
import LevelTree from '@/components/LevelTree.vue'
import Region1 from '@/components/Region1.vue'
import Region2 from '@/components/Region2.vue'
import SiteManagement from '@/components/SiteManagement.vue'
import DeviceManagement from '@/components/DeviceManagement.vue'
import ServerManagement from '@/components/ServerManagement.vue'
import CameraManagement from '@/components/CameraManagement.vue'
import UserManagement from '@/components/UserManagement.vue'
// 第二步,创建路由求
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/news', component: News },
{ path: '/about', component: About },
{ path: '/alert-management', name: 'AlertManagement', component: AlertManagement },
{ path: '/real-time-monitoring', name: 'RealTimeMonitoring', component: RealTimeMonitoring },
{ path: '/statistics-analysis', name: 'StatisticsAnalysis', component: StatisticsAnalysis },
{ path: '/system-settings', name: 'SystemSettings', component: SystemSettings },
{ path: '/levelList', name: 'LevelList', component: LevelList },
{ path: '/region1', name: 'Region1', component: Region1 },
{ path: '/region2', name: 'Region2', component: Region2 },
{ path: '/siteManagement', name: 'SiteManagement', component: SiteManagement },
{ path: '/deviceManagement', name: 'DeviceManagement', component: DeviceManagement },
{ path: '/serverManagement', name: 'ServerManagement', component: ServerManagement },
{ path: '/cameraManagement', name: 'CameraManagement', component: CameraManagement },
{ path: '/userManagement', name: 'UserManagement', component: UserManagement },
]
})
// const routes = [
// { path: '/', component: Home },
// { path: '/news', component: News },
// { path: '/about', component: About },
// { path: '/alert-management', name: 'AlertManagement', component: AlertManagement },
// { path: '/real-time-monitoring', name: 'RealTimeMonitoring', component: RealTimeMonitoring },
// { path: '/statistics-analysis', name: 'StatisticsAnalysis', component: StatisticsAnalysis },
// { path: '/system-settings', name: 'SystemSettings', component: SystemSettings },
// { path: '/levelList', name: 'LevelList', component: LevelList },
// { path: '/region1', name: 'Region1', component: Region1 },
// { path: '/region2', name: 'Region2', component: Region2 },
// { path: '/siteManagement', name: 'SiteManagement', component: SiteManagement },
// { path: '/deviceManagement', name: 'DeviceManagement', component: DeviceManagement },
// { path: '/serverManagement', name: 'ServerManagement', component: ServerManagement },
// { path: '/cameraManagement', name: 'CameraManagement', component: CameraManagement },
// { path: '/userManagement', name: 'UserManagement', component: UserManagement },
// ]
// const router = createRouter({
// history: createWebHistory(),
// routes
// })
// const routes = createRouter({
// history: createWebHistory(process.env.BASE_URL),
// router
// })
// 暴漏出去router
export default router

158
superbox.js Normal file
View File

@ -0,0 +1,158 @@
import axios from "axios";
export const getSuperboxApiConfig = (address) => {
const PORT = 8000;
const BASE_ROUTE = "/api/v1";
const LOGIN_ROUTE = BASE_ROUTE + "/auth/login";
const LOGOUT_ROUTE = BASE_ROUTE + "/auth/logout";
const CAMERA_ROUTE = BASE_ROUTE + "/camera/cameras";
const EVENTS_ROUTE = BASE_ROUTE + "/event/events";
const ALGORITHM_ROUTE = BASE_ROUTE + "/algorithms";
let addr = address;
if (!addr) {
addr = window.location.host.replace(/:\d+/, "");
}
const superboxAddress = "http://" + addr + ":" + PORT.toString();
return {
base: superboxAddress + BASE_ROUTE,
login: superboxAddress + LOGIN_ROUTE,
logout: superboxAddress + LOGOUT_ROUTE,
cameras: superboxAddress + CAMERA_ROUTE,
events: superboxAddress + EVENTS_ROUTE,
algorithms: superboxAddress + ALGORITHM_ROUTE,
};
/*return {
base: "/api/v1",
login: "/api/v1/auth/login",
logout: "/api/v1/auth/logout",
cameras: "/api/v1/camera/cameras",
events: "/api/v1/event/events",
};*/
};
axios.defaults.withCredentials = true;
export const login = (username, password, address = null) => {
return new Promise((resolve, reject) => {
const api = getSuperboxApiConfig(address);
axios
.post(
api.login,
{
username: username,
password: password,
cookieless: false,
},
{
headers: {
"Content-Type": "application/json",
},
}
)
.then((res) => {
if (res.data.err.ec === 0) {
resolve(res.data.ret.token);
}
reject(res.data.err);
})
.catch((err) => {
reject(err);
});
});
};
export const getCameras = (token, address = null) => {
return new Promise((resolve, reject) => {
const api = getSuperboxApiConfig(address);
axios
.get(api.cameras, {
withCredentials: true,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (res.data.err.ec === 0) {
resolve(res.data.ret.results);
}
reject(res.data.err);
})
.catch((err) => {
reject(err);
});
});
};
export const getEvents = (token, address = null) => {
return new Promise((resolve, reject) => {
const api = getSuperboxApiConfig(address);
axios
.get(api.events, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (res.data.err.ec === 0) {
resolve(res.data.ret.results);
}
reject(res.data.err);
})
.catch((err) => {
reject(err);
});
});
};
export const getAlgorithms = (token, address = null) => {
return new Promise((resolve, reject) => {
const api = getSuperboxApiConfig(address);
axios
.get(api.algorithms, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (res.data.err.ec === 0) {
resolve(res.data.ret);
}
reject(res.data.err);
})
.catch((err) => {
reject(err);
});
});
};
var codeNameMap = {};
export const initCodeNameMap = async (token, address = null) => {
try {
const algorithms = await getAlgorithms(token, address);
algorithms.forEach((algorithm) => {
codeNameMap[algorithm.code_name] = algorithm.name;
});
return true;
} catch (err) {
console.log(err);
return false;
}
};
export const codenameTranslate = (codeName) => {
return codeNameMap[codeName];
};
export default {
login,
getCameras,
getEvents,
getSuperboxApiConfig,
initCodeNameMap,
codenameTranslate,
};

14
tsconfig.app.json Normal file
View File

@ -0,0 +1,14 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

16
tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
],
"compilerOptions": {
"types": ["element-plus/global"]
}
}

19
tsconfig.node.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

37
vite.config.ts Normal file
View File

@ -0,0 +1,37 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
import { fileURLToPath, URL } from 'node:url'
import { createProxyMiddleware } from 'http-proxy-middleware'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueSetupExtend(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server:{
// host:'192.168.28.44',
// host:'127.0.0.1',
// host: '192.168.28.32',
port: 8090,
open:true,
cors: true,
// proxy: {
// '/api': "192.168.28.44:8000"
// }
proxy: {
'/api': {
target: 'http://127.0.0.1:8000', // 目标 API 地址
changeOrigin: true, // 开启跨域
rewrite: path => path.replace('^/api/', '')
}
}
}
})

View File

@ -0,0 +1,21 @@
// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "file:///D:/java/demo/turing/vue_one/node_modules/vite/dist/node/index.js";
import vue from "file:///D:/java/demo/turing/vue_one/node_modules/@vitejs/plugin-vue/dist/index.mjs";
import vueSetupExtend from "file:///D:/java/demo/turing/vue_one/node_modules/vite-plugin-vue-setup-extend/dist/index.mjs";
var __vite_injected_original_import_meta_url = "file:///D:/java/demo/turing/vue_one/vite.config.ts";
var vite_config_default = defineConfig({
plugins: [
vue(),
vueSetupExtend()
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", __vite_injected_original_import_meta_url))
}
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxqYXZhXFxcXGRlbW9cXFxcdHVyaW5nXFxcXHZ1ZV9vbmVcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkQ6XFxcXGphdmFcXFxcZGVtb1xcXFx0dXJpbmdcXFxcdnVlX29uZVxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRDovamF2YS9kZW1vL3R1cmluZy92dWVfb25lL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXG5cbmltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gJ3ZpdGUnXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcbmltcG9ydCB2dWVTZXR1cEV4dGVuZCBmcm9tICd2aXRlLXBsdWdpbi12dWUtc2V0dXAtZXh0ZW5kJ1xuXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW1xuICAgIHZ1ZSgpLFxuICAgIHZ1ZVNldHVwRXh0ZW5kKCksXG4gIF0sXG4gIHJlc29sdmU6IHtcbiAgICBhbGlhczoge1xuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcbiAgICB9XG4gIH1cbn0pXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQTZRLFNBQVMsZUFBZSxXQUFXO0FBRWhULFNBQVMsb0JBQW9CO0FBQzdCLE9BQU8sU0FBUztBQUNoQixPQUFPLG9CQUFvQjtBQUo0SSxJQUFNLDJDQUEyQztBQU94TixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxJQUFJO0FBQUEsSUFDSixlQUFlO0FBQUEsRUFDakI7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMLEtBQUssY0FBYyxJQUFJLElBQUksU0FBUyx3Q0FBZSxDQUFDO0FBQUEsSUFDdEQ7QUFBQSxFQUNGO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K

View File

@ -0,0 +1,21 @@
// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "file:///D:/java/demo/turing/vue_one/node_modules/vite/dist/node/index.js";
import vue from "file:///D:/java/demo/turing/vue_one/node_modules/@vitejs/plugin-vue/dist/index.mjs";
import vueSetupExtend from "file:///D:/java/demo/turing/vue_one/node_modules/vite-plugin-vue-setup-extend/dist/index.mjs";
var __vite_injected_original_import_meta_url = "file:///D:/java/demo/turing/vue_one/vite.config.ts";
var vite_config_default = defineConfig({
plugins: [
vue(),
vueSetupExtend()
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", __vite_injected_original_import_meta_url))
}
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxqYXZhXFxcXGRlbW9cXFxcdHVyaW5nXFxcXHZ1ZV9vbmVcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkQ6XFxcXGphdmFcXFxcZGVtb1xcXFx0dXJpbmdcXFxcdnVlX29uZVxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRDovamF2YS9kZW1vL3R1cmluZy92dWVfb25lL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXG5cbmltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gJ3ZpdGUnXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcbmltcG9ydCB2dWVTZXR1cEV4dGVuZCBmcm9tICd2aXRlLXBsdWdpbi12dWUtc2V0dXAtZXh0ZW5kJ1xuXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW1xuICAgIHZ1ZSgpLFxuICAgIHZ1ZVNldHVwRXh0ZW5kKCksXG4gIF0sXG4gIHJlc29sdmU6IHtcbiAgICBhbGlhczoge1xuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcbiAgICB9XG4gIH1cbn0pXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQTZRLFNBQVMsZUFBZSxXQUFXO0FBRWhULFNBQVMsb0JBQW9CO0FBQzdCLE9BQU8sU0FBUztBQUNoQixPQUFPLG9CQUFvQjtBQUo0SSxJQUFNLDJDQUEyQztBQU94TixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxJQUFJO0FBQUEsSUFDSixlQUFlO0FBQUEsRUFDakI7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMLEtBQUssY0FBYyxJQUFJLElBQUksU0FBUyx3Q0FBZSxDQUFDO0FBQUEsSUFDdEQ7QUFBQSxFQUNGO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K

View File

@ -0,0 +1,26 @@
// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "file:///D:/java/demo/turing/vue_one/node_modules/vite/dist/node/index.js";
import vue from "file:///D:/java/demo/turing/vue_one/node_modules/@vitejs/plugin-vue/dist/index.mjs";
import vueSetupExtend from "file:///D:/java/demo/turing/vue_one/node_modules/vite-plugin-vue-setup-extend/dist/index.mjs";
var __vite_injected_original_import_meta_url = "file:///D:/java/demo/turing/vue_one/vite.config.ts";
var vite_config_default = defineConfig({
plugins: [
vue(),
vueSetupExtend()
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", __vite_injected_original_import_meta_url))
}
},
server: {
host: "127.0.0.1",
port: 5137,
open: true
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxqYXZhXFxcXGRlbW9cXFxcdHVyaW5nXFxcXHZ1ZV9vbmVcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkQ6XFxcXGphdmFcXFxcZGVtb1xcXFx0dXJpbmdcXFxcdnVlX29uZVxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRDovamF2YS9kZW1vL3R1cmluZy92dWVfb25lL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXG5cbmltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gJ3ZpdGUnXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcbmltcG9ydCB2dWVTZXR1cEV4dGVuZCBmcm9tICd2aXRlLXBsdWdpbi12dWUtc2V0dXAtZXh0ZW5kJ1xuXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW1xuICAgIHZ1ZSgpLFxuICAgIHZ1ZVNldHVwRXh0ZW5kKCksXG4gIF0sXG4gIHJlc29sdmU6IHtcbiAgICBhbGlhczoge1xuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcbiAgICB9XG4gIH0sXG4gIHNlcnZlcjp7XG4gICAgaG9zdDonMTI3LjAuMC4xJyxcbiAgICBwb3J0OjUxMzcsXG4gICAgb3Blbjp0cnVlXG4gIH1cbn0pXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQTZRLFNBQVMsZUFBZSxXQUFXO0FBRWhULFNBQVMsb0JBQW9CO0FBQzdCLE9BQU8sU0FBUztBQUNoQixPQUFPLG9CQUFvQjtBQUo0SSxJQUFNLDJDQUEyQztBQU94TixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxJQUFJO0FBQUEsSUFDSixlQUFlO0FBQUEsRUFDakI7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMLEtBQUssY0FBYyxJQUFJLElBQUksU0FBUyx3Q0FBZSxDQUFDO0FBQUEsSUFDdEQ7QUFBQSxFQUNGO0FBQUEsRUFDQSxRQUFPO0FBQUEsSUFDTCxNQUFLO0FBQUEsSUFDTCxNQUFLO0FBQUEsSUFDTCxNQUFLO0FBQUEsRUFDUDtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==