Compare commits

...

3 Commits

4 changed files with 274 additions and 201 deletions

View File

@ -1,49 +1,67 @@
DEMO
===========================
# 预警系统 (pre-warning-system)
###########环境依赖
node.js
基于Vue 3 + Vite构建的仓库预警管理系统提供实时监控、数据可视化和异常预警功能。
###########部署步骤
1. 添加系统环境变量
export $PORTAL_VERSION="production" // production, test, dev
## 功能特点
- 实时数据监控仪表盘
- 多维度数据可视化图表
- 异常事件预警与通知
- 历史数据查询与分析
- 用户权限管理
## 技术栈
- **前端框架**: Vue 3 (Composition API + <script setup>)
- **构建工具**: Vite
- **路由管理**: Vue Router
- **状态管理**: Vuex
- **UI组件**: element-plus (v2.10.1) 和 tdesign-vue-next (v1.13.2)
- **图表可视化**: echarts (v5.6.0) 和 @antv/g6 (v4.8.24)
2. npm install //安装node运行环境
## 项目结构
```
/src
/views # 页面组件
/components # 通用组件
/router # 路由配置
/store # 状态管理
/assets # 静态资源
/escel # 数据文件
```
3. npm run dev //前端编译
## 安装与启动
4. 启动两个配置(已forever为例)
eg: forever start app-service.js
forever start logger-service.js
### 环境要求
- Node.js 14.0.0+
- npm 6.0.0+ 或 yarn 1.22.0+
### 安装依赖
```bash
npm install
# 或
yarn install
```
###########目录结构描述
├── Readme.md // help
├── app // 应用
├── config // 配置
│ ├── default.json
│ ├── dev.json // 开发环境
│ ├── experiment.json // 实验
│ ├── index.js // 配置控制
│ ├── local.json // 本地
│ ├── production.json // 生产环境
│ └── test.json // 测试环境
├── data
├── doc // 文档
├── environment
├── gulpfile.js
├── locales
├── logger-service.js // 启动日志配置
├── node_modules
├── package.json
├── app-service.js // 启动应用配置
├── static // web静态资源加载
│ └── initjson
│ └── config.js // 提供给前端的配置
├── test
├── test-service.js
└── tools
### 开发环境启动
```bash
npm run dev
# 或
yarn dev
```
### 生产环境构建
```bash
npm run build
# 或
yarn build
```
## 配置说明
系统配置文件位于 `src/config` 目录下可根据实际需求修改API地址、预警阈值等参数。
## 数据导入
1. 将数据文件放入 `/escel` 目录
2. 在系统设置中选择导入对应文件
3. 系统将自动解析并更新数据库
## 许可证
[MIT](LICENSE)

View File

@ -1,10 +1,14 @@
<template>
<div :class="containerClass" ref="scrollContainer">
<div v-for="(item, index) in displayData" :key="index" class="containner4-data"
:class="{ 'temporary-style': item.showTemporaryStyle }">
<img :src="item.avatar" alt="人物头像" />
<p1 style="margin-left: 10px;">{{ item.commenter }}</p1>
<p style="margin-left: 10px;">{{ item.comment }}</p>
<div class="scroll-content">
<div v-for="(item, index) in displayData" :key="index" class="containner4-data"
:class="{ 'temporary-style': item.showTemporaryStyle }">
<img :src="item.avatar" alt="人物头像" />
<div class="data-content">
<div class="commenter">{{ item.commenter }}</div>
<div class="comment">{{ item.comment }}</div>
</div>
</div>
</div>
</div>
</template>
@ -42,7 +46,6 @@ const initDisplayData = () => {
//
const startAutoScroll = () => {
if (!scrollContainer.value) return;
scrollTimer.value = setInterval(() => {
scrollContainer.value.scrollTop += 1;
if (scrollContainer.value.scrollTop >= scrollContainer.value.scrollHeight / 2) {
@ -93,32 +96,91 @@ onBeforeUnmount(() => {
<style scoped>
.containner4-alldata {
height: 330px;
overflow: hidden;
padding: 10px;
font-weight: 300;
height: 300px;
overflow-x: hidden;
overflow-y: auto;
padding: 15px;
background-color: rgba(4, 20, 33, 0.5);
border-radius: 12px;
}
.containner4-alldata::-webkit-scrollbar {
width: 6px;
}
.containner4-alldata::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
.containner4-alldata::-webkit-scrollbar-thumb {
background: rgba(78, 162, 245, 0.5);
border-radius: 3px;
}
.containner4-alldata::-webkit-scrollbar-thumb:hover {
background: rgba(78, 162, 245, 0.8);
}
.scroll-content {
display: flex;
flex-direction: column;
gap: 10px;
}
.containner4-data {
color: rgba(0, 0, 0, 0.8);
font-family: "PingFang SC";
color: #E1F4FF;
font-family: "PingFang SC", sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 00;
line-height: normal;
font-weight: 200;
line-height: 22px;
display: flex;
align-items: center;
letter-spacing: 0.14px;
margin-left: 25px;
margin-top: 25px;
background-image: linear-gradient(to right,#4ea2f599, #646464);
border-radius: 8px;
padding: 12px 16px;
background-image: linear-gradient(135deg, #4ea2f566, #1C588F66);
border-radius: 10px;
cursor: pointer;
}
.containner4-alldata .text-content {
margin-left: 100px;
}
.containner4-data img{
width: 24px;
height: px;
border-radius:88px ;
margin-top: 10px;
margin-left: 10px;
width: 90%;
max-width: 90%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
border: 1px solid rgba(78, 162, 245, 0.3);
}
.containner4-data:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(78, 162, 245, 0.3);
background-image: linear-gradient(135deg, #4ea2f588, #1C588F88);
}
.containner4-data img {
width: 32px;
height: 32px;
border-radius: 8px;
margin-right: 12px;
object-fit: cover;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.data-content {
display: flex;
flex-direction: column;
overflow: hidden;
}
.commenter {
font-weight: 500;
color: #FFFFFF;
margin-bottom: 4px;
}
.comment {
font-size: 13px;
color: rgba(225, 244, 255, 0.9);
max-width: 80%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -2,23 +2,15 @@
<template>
<div style="width: 100%; height: 100%;">
<div class="graph-box" id="graph-container"></div>
<div class="slider-box">
<div class="slider-container">
<div class="play-controls">
<button @click="togglePlay" class="play-button">
{{ isPlaying ? '暂停' : '播放' }}
</button>
</div>
<el-slider style="" v-model="state.sliderValue" @input="sliderChange" :marks="state.marks" />
</div>
</div>
</div>
</template>
<script setup>
import axios from "axios";
import { onMounted,onUnmounted, reactive,ref } from "vue";
@ -27,7 +19,6 @@ import { ElLoading } from 'element-plus';
const isPlaying = ref(false);
const playTimer = ref(null);
const state = reactive({
graph: null,
marks: {
@ -65,15 +56,6 @@ const setStateSliderValue = (value) => {
sliderChange(value);
};
//
const togglePlay = () => {
if (isPlaying.value) {
stopAutoPlay();
} else {
startAutoPlay();
}
};
const startAutoPlay = () => {
isPlaying.value = true;
// mark
@ -84,13 +66,12 @@ const startAutoPlay = () => {
if (currentIndex < markKeys.length - 1) {
currentIndex++;
} else {
stopAutoPlay();
return;
currentIndex = 0; //
}
const nextValue = markKeys[currentIndex];
state.sliderValue = nextValue;
sliderChange(nextValue);
}, 3000); // 3
sliderChange(nextValue, true); // isAuto
}, 6000); // 6
};
const stopAutoPlay = () => {
@ -101,16 +82,17 @@ const stopAutoPlay = () => {
}
};
const sliderChange = (val) => {
const sliderChange = (val, isAuto = false) => {
//
if (isPlaying.value) {
stopAutoPlay();
}
if (state.marks[val]) {
//
if (!isAuto && isPlaying.value) {
stopAutoPlay();
}
if (state.marks[val]) {
getData(state.marks[val]);
}
}
}
/**
* 根据日期获取图谱数据
@ -222,6 +204,7 @@ onUnmounted(() => {
});
onMounted(() => {
startAutoPlay(); //
getData('2024-01-03')
})
</script>
@ -260,9 +243,11 @@ onMounted(() => {
.slider-box {
flex: 1;
margin-top: 0;
width: 90%;
width: 95%;
margin-top: -230px;
margin-left: 10px;
flex-direction: column;
color: #fff;
font-size: 16px;
}
</style>

View File

@ -7,7 +7,6 @@
<div class="tooltip-containner-data">
<li><img :src="currentItem.img" alt="" style=""></li>
<li v-if="currentItem.title.includes('中国海警首次登检菲律宾运补船只')"><chart32_-inspection /></li>
<<<<<<< HEAD
<li v-else-if="currentItem.title.includes('中方回应菲称我海警挥舞刀具')"> <Chart33_WavingBlades /></li>
<li v-else-if="currentItem.title.includes('中国海警夺回菲方盗窃赃物')"><Chart34_RecoveredGoods /></li>
<li v-else-if="currentItem.title.includes('菲自曝被中国海警缴枪的是顶级特种部队')"><Chart35_SpecialForces /></li>
@ -16,22 +15,11 @@
<li style="margin-left: 10px; margin-top:20px;"><img src="../assets/images/logo/point.png"
alt="" style="margin-bottom: -7px;">最初首发者&nbsp;&nbsp;&nbsp;{{ currentItem.earler }} </li>
<li style="margin-left: 10px;"><img src="../assets/images/logo/point.png" alt="" style="margin-bottom: -7px;">积极评论者{{ currentItem.comenter
=======
<li v-else-if="currentItem.title.includes('中方回应菲称我海警挥舞刀具')">
<Chart33_WavingBlades />
</li>
<li v-else-if="currentItem.title.includes('中国海警夺回菲方盗窃赃物')">
<Chart34_RecoveredGoods />
</li>
<li style="margin-left: 10px; margin-top: 20px;"><img src="../assets/images/logo/point.png"
alt="">最初首发者&nbsp;&nbsp;&nbsp;{{ currentItem.earler }} </li>
<li style="margin-left: 10px;"><img src="../assets/images/logo/point.png" alt="">积极评论者{{ currentItem.comenter
>>>>>>> 33171231818e20d0f810df4ae56f3620727af4df
}}</li>
<li style="margin-left: 200px;margin-top: -45px;"><img src="../assets/images/logo/point.png" alt="" style="margin-bottom: -7px;">锚点用户{{
currentItem.keyuser }}</li>
<li style="margin-left: 200px;;"><img src="../assets/images/logo/point.png" alt="" style="margin-bottom: -7px;">积极转发者&nbsp;&nbsp;&nbsp;{{
<li style="margin-left: 220px;margin-top: -45px;"><img src="../assets/images/logo/point.png" alt="" style="margin-bottom: -7px;">积极转发者{{
currentItem.switcher }}</li>
<li style="margin-left: 220px;;"><img src="../assets/images/logo/point.png" alt="" style="margin-bottom: -7px;">锚点用户&nbsp;&nbsp;&nbsp;{{
currentItem.keyuser}}</li>
</div>
<img src="../assets/images/logo/cancel.png" alt=""
style="float: right;margin-right: 10px;margin-top: -520px;cursor: pointer;" @click="showTooltip = false">
@ -122,12 +110,10 @@
</div>
<div class="suggestion-containner">
<ul>
<!-- 只显示第一个按钮 -->
<li v-for="(item, index) in filteredData" :key="item.id" @click="openDetailModal(item)"
@mouseenter="handleMouseEnter(item.id)" @mouseleave="handleMouseLeave()"
:class="{ 'hover-item': hoverItemId === item.id }">
<img :src="item.avatar">
<img :src="item.avatar" style="width: 38px;margin-left: 20px;margin-top: 5px;border-radius: 5px;">
<!-- 显示其他信息 -->
<div class="span-data">
<span class="span-1">{{ item.name }}</span>
@ -135,9 +121,10 @@
<span class="span-3">推荐监控频率{{ item.transmit }}</span>
</div>
<button v-if="index === 0"
style="background-image: url('src/assets/images/logo/定位.png'); background-size: cover; background-repeat: no-repeat; background-position: center;margin-left: 340px;margin-top: -100px;width: 20px;height: 20px;border-radius: 0px;border: 0;cursor: pointer;"
style="background-image: url('src/assets/images/logo/定位.png'); background-size: cover; background-repeat: no-repeat; background-position: center;margin-left: 370px;margin-top: -90px;width: 20px;height: 20px;border-radius: 0px;border: 0;cursor: pointer;"
@click.stop="handleLocateMark"></button>
</li>
<li v-if="index < filteredData.length - 1" class="divider"></li>
</ul>
</div>
</div>
@ -239,11 +226,8 @@ import { useActionStore } from '@/store';
import Chart32_Inspection from '@/components/Chart32_Inspection(1).vue';
import Chart33_WavingBlades from '@/components/Chart33_WavingBlades(1).vue';
import Chart34_RecoveredGoods from '@/components/Chart34_RecoveredGoods(1).vue';
<<<<<<< HEAD
import Chart35_SpecialForces from '@/components/Chart35_SpecialForces(1).vue';
import Chart36_Dialogue from '@/components/Chart36_Dialogue(1).vue';
=======
>>>>>>> 33171231818e20d0f810df4ae56f3620727af4df
let myChart = null;
@ -1071,9 +1055,40 @@ const updateChart = () => {
type: 'pie',
radius: ['40%', '70%'],
center: ['40%', '50%'],
padAngle: 5,
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderRadius: 0,
borderColor: '#fff',
borderWidth: 1
},
label: {
show: false,
position: 'outside'
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold',
color: '#fff'
}
},
labelLine: {
show: false
},
data: data
}
,
{
name: currentChartType.value === 'registration' ? '注册时间分布' : '行为模式分布',
type: 'pie',
radius: ['20%', '30%'],
center: ['40%', '50%'],
padAngle: 10,
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 0,
borderColor: '#fff',
borderWidth: 2
},
@ -1093,8 +1108,7 @@ const updateChart = () => {
show: false
},
data: data
}
]
}]
});
};
@ -1169,10 +1183,20 @@ onMounted(() => {
trigger: 'axis'
},
legend: {
data: ['活跃预警事件', '高风险事件']
data: ['活跃预警事件', '高风险事件'],
textStyle: {
fontSize: 14, //
fontWeight: 'bold', // (normal/bold/bolder/lighter/100-900)
color: '#E1F4FF', //
fontFamily: '微软雅黑, Arial, sans-serif', //
fontWeight:200,
fontStyle: 'normal' // (normal/italic/oblique)
},
},
grid: {
left: '5%',
top: '30%', //
bottom: '1%',
containLabel: true
},
toolbox: {
@ -1195,9 +1219,9 @@ onMounted(() => {
// margin:15,
textStyle: {
color: '#a8aab0',
fontStyle: 'normal',
fontFamily: '微软雅黑',
fontSize: 12,
fontStyle: 'normal',
fontFamily: '微软雅黑',
fontSize: 12,
},
formatter: function (params) {
var newParamsName = "";
@ -1228,16 +1252,15 @@ onMounted(() => {
show: false,
},
axisLine: {//线
lineStyle: {
color: '#E5E9ED',
// opacity:0.2
}
show: false
},
splitLine: { // grid 线
show: false,
show: true,
lineStyle: {
color: '#E5E9ED',
// opacity:0.1
color: 'rgba(229, 233, 237, 0.3)', //
type: 'dashed', // 线
width: 1, // 线
opacity: 0.7 // 线
}
}
},
@ -1263,8 +1286,10 @@ onMounted(() => {
splitLine: {
show: true,
lineStyle: {
color: '#E5E9ED',
// opacity:0.1
color: 'rgba(229, 233, 237, 0.3)', //
type: 'dashed', // 线
width: 1, // 线
opacity: 0.7 // 线
}
}
@ -1495,12 +1520,11 @@ onUnmounted(() => {
.containner4 {
width: 457px;
height: 394px;
margin-left: 16px;
margin-left: 10px;
margin-top: 30px;
background-color: #04142166;
border-style: solid;
border-width: 0px;
border-image: linear-gradient(to bottom, #3AA1F8, #3AA1F833) 1;
border-radius: 2px;
/* 添加内阴影 */
box-shadow: 0px 0px 18px 0px #0A2E55 inset;
@ -1511,9 +1535,9 @@ onUnmounted(() => {
}
.temporary-style {
background-image: linear-gradient(to right, #003F7D, #5DB9FF7A);
border-image: linear-gradient(to bottom, #95CEFF, #3AA1F833);
border: 0px solid;
background-image: linear-gradient(to right, #003F7D, #003F7D);
border-image: linear-gradient(to bottom, #003F7D, #003F7D);
border: 1px solid;
border-radius: 3px;
cursor: pointer;
}
@ -1652,23 +1676,17 @@ onUnmounted(() => {
}
.suggestion li {
width: 360px;
height: 48px;
width: 395px;
height: 52px;
color: #FFFFFF;
margin-top: 15px;
margin-left: 30px;
border-radius: 8px;
}
.suggestion li img {
width: 48px;
height: 48px;
margin-right: 10px;
border-radius: 4px;
margin-bottom: 15px;
margin-left: 10px;
border-radius: 5px;
}
.suggestion li:hover {
background-image: linear-gradient(to bottom, #07293D, #050B23);
background-image: linear-gradient(to bottom, #07293D, #07293D);
}
.suggestion-containner {
@ -1695,20 +1713,20 @@ onUnmounted(() => {
display: flex;
flex-direction: column;
margin-left: 80px;
margin-top: -50px;
margin-top: -45px;
}
.span-1 {
font-family: OPPOSans;
font-weight: 300;
font-size: 16px;
margin-top: -5px;
font-size: 14px;
margin-left: 1px;
}
.span-2 {
font-family: PingFang SC;
font-weight: 200;
font-size: 14px;
font-weight: 100;
font-size: 12px;
margin-top: 5px;
}
@ -1720,7 +1738,13 @@ onUnmounted(() => {
margin-left: 150px;
margin-top: -20px;
}
.divider {
height: 1px; /* 必须设置高度才能显示 */
background-color: #e5e5e5; /* 确保颜色与背景有对比度 */
margin: 8px 0; /* 上下间距 */
list-style: none; /* 移除默认列表样式 */
width: 100%; /* 确保宽度足够 */
}
.suggestions li {
padding: 8px;
cursor: pointer;
@ -1806,43 +1830,6 @@ onUnmounted(() => {
height: 200px;
max-height: 200px;
}
.color-block1 {
width: 10px;
height: 10px;
background-color: #D83D6C;
display: inline-block;
margin-right: 5px;
vertical-align: middle;
}
.color-block2 {
width: 10px;
height: 10px;
background-color: #00CEFF;
display: inline-block;
margin-right: 5px;
vertical-align: middle;
}
.color-block3 {
width: 10px;
height: 10px;
background-color: #D3ADF7;
display: inline-block;
margin-right: 5px;
vertical-align: middle;
}
.color-block4 {
width: 10px;
height: 10px;
background-color: #1890FF;
display: inline-block;
margin-right: 5px;
vertical-align: middle;
}
.intime li {
list-style-type: none;
padding-left: 0;
@ -1875,7 +1862,7 @@ onUnmounted(() => {
}
.containner3-img {
margin-top: 70px;
margin-top:5px;
}
.focus-events {
@ -1979,7 +1966,7 @@ onUnmounted(() => {
background-color: #04142166;
border-style: solid;
border-width: 0px;
border-image: linear-gradient(to bottom, #67A4E199, #3AA1F833) 1;
border-radius: 2px;
/* 添加内阴影 */
box-shadow: 0px 0px 18px 0px #0A2E55 inset;
@ -2339,21 +2326,42 @@ onUnmounted(() => {
}
.chart-tabs button {
width: 100px;
height: 30px;
width: 80px;
height: 25px;
margin-top: 60px;
margin-left: 10px;
border: none;
border-radius: 15px;
background-color: #333;
background-color: #04142166;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
font-family: OPPOSans;
font-weight: 300;
font-size: 14px;
color: #E1F4FF;
border: 1px solid #1C588F;
line-height: 18px;
letter-spacing: 0%;
text-align: center;
display: inline-block;
vertical-align: middle;
cursor: pointer;
}
/* 为第一个按钮设置左侧圆角 */
.chart-tabs button:first-child {
border-radius: 8px 0 0 8px;
}
/* 为最后一个按钮设置右侧圆角 */
.chart-tabs button:last-child {
border-radius: 0 8px 8px 0;
}
/* 为中间按钮取消所有圆角 */
.chart-tabs button:not(:first-child):not(:last-child) {
border-radius: 0;
}
.chart-tabs button.active-tab {
margin-top: 60px;
background-color: #1890ff;
background-color: #236291;
box-shadow: 0 0 10px rgba(24, 144, 255, 0.5);
}
@ -2361,7 +2369,7 @@ onUnmounted(() => {
#categoryChart {
width: 80%;
height: 400px;
margin-left: -40px;
}