Update .gitignore and add files
This commit is contained in:
130
platform/src/components3/gu4.vue
Normal file
130
platform/src/components3/gu4.vue
Normal file
@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="echarts-container">
|
||||
<div ref="chartRef" style="width: 550px; height: 454px;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, onBeforeUnmount, watch} from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import axios from 'axios';
|
||||
|
||||
const chartRef = ref(null);
|
||||
let chartInstance = null;
|
||||
const faultData = ref([]);
|
||||
|
||||
const colorMap = {
|
||||
'其他故障': '#0EA5E9',
|
||||
'离线故障': '#F87171', // 粉调红
|
||||
'传感器故障': '#FBBF24', // 奶油橙
|
||||
|
||||
};
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value || !faultData.value.length) return;
|
||||
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
// 为数据添加颜色配置
|
||||
const coloredData = faultData.value.map(item => ({
|
||||
...item,
|
||||
itemStyle: {
|
||||
color: colorMap[item.name] || '#999' // 默认灰色
|
||||
}
|
||||
}));
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '故障类型分布',
|
||||
left: 'left',
|
||||
top: 10,
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#333'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
bottom: '5%',
|
||||
left: 'center',
|
||||
data: coloredData.map(item => item.name)
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '故障类型分布',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: coloredData
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
chartInstance.setOption(option);
|
||||
window.addEventListener('resize', handleResize);
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
chartInstance?.resize();
|
||||
};
|
||||
|
||||
// 获取故障类型数据
|
||||
const fetchFaultData = async () => {
|
||||
try {
|
||||
const response = await axios.get('http://localhost:5000/fault-types');
|
||||
if (response.data.success) {
|
||||
faultData.value = response.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取故障类型数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchFaultData();
|
||||
watch(faultData, () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
initChart();
|
||||
}, {deep: true});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
chartInstance?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.echarts-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
right: 5px;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
163
platform/src/components3/gu5.vue
Normal file
163
platform/src/components3/gu5.vue
Normal file
@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div class="echarts-container">
|
||||
<div ref="chartRef" style="width: 670px; height: 440px;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, onBeforeUnmount} from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const chartRef = ref(null);
|
||||
let chartInstance = null;
|
||||
|
||||
// 手动设置故障时段数据 (6点1个,18点1个,22点1个)
|
||||
const faultTimeData = ref([
|
||||
{name: '00:00-02:00', value: 0},
|
||||
{name: '02:00-04:00', value: 0},
|
||||
{name: '04:00-06:00', value: 1}, // 6点一个故障
|
||||
{name: '06:00-08:00', value: 0},
|
||||
{name: '08:00-10:00', value: 0},
|
||||
{name: '10:00-12:00', value: 0},
|
||||
{name: '12:00-14:00', value: 0},
|
||||
{name: '14:00-16:00', value: 0},
|
||||
{name: '16:00-18:00', value: 0},
|
||||
{name: '18:00-20:00', value: 0},
|
||||
{name: '20:00-22:00', value: 1}, // 22点一个故障
|
||||
{name: '22:00-24:00', value: 0}
|
||||
]);
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '系统故障时段分布统计',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#333'
|
||||
},
|
||||
subtext: '24小时故障发生情况',
|
||||
subtextStyle: {
|
||||
fontSize: 14,
|
||||
color: '#666'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
formatter: '时段: {b}<br/>故障次数: {c}次',
|
||||
backgroundColor: 'rgba(50,50,50,0.9)',
|
||||
borderColor: '#333',
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '10%',
|
||||
right: '5%',
|
||||
bottom: '8%',
|
||||
top: '20%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: faultTimeData.value.map(item => item.name),
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
rotate: 30,
|
||||
fontSize: 12,
|
||||
margin: 15
|
||||
},
|
||||
axisLine: {lineStyle: {color: '#666'}},
|
||||
axisTick: {alignWithLabel: true, length: 5}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '故障次数',
|
||||
nameLocation: 'middle',
|
||||
nameGap: 40,
|
||||
nameTextStyle: {fontSize: 14, padding: [0, 0, 10, 0]},
|
||||
axisLabel: {
|
||||
fontSize: 12,
|
||||
formatter: function (value) {
|
||||
return value; // 确保Y轴只显示整数
|
||||
}
|
||||
},
|
||||
min: 0, // 最小值设为0
|
||||
max: 5, // 最大值设为5
|
||||
interval: 1, // 间隔设为1
|
||||
splitLine: {lineStyle: {type: 'dashed'}}
|
||||
},
|
||||
series: [{
|
||||
name: '故障次数',
|
||||
type: 'bar',
|
||||
barWidth: '50%',
|
||||
data: faultTimeData.value.map(item => item.value),
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{offset: 0, color: '#ff9e9e'},
|
||||
{offset: 0.5, color: '#ff6b6b'},
|
||||
{offset: 1, color: '#ff3d3d'}
|
||||
]),
|
||||
borderRadius: [6, 6, 0, 0]
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{offset: 0, color: '#ff6b6b'},
|
||||
{offset: 0.7, color: '#ff3d3d'},
|
||||
{offset: 1, color: '#cc2a2a'}
|
||||
]),
|
||||
shadowColor: 'rgba(255, 100, 100, 0.5)',
|
||||
shadowBlur: 10,
|
||||
shadowOffsetY: 3
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter: '{c}',
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
color: '#ff3d3d'
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
chartInstance.setOption(option);
|
||||
window.addEventListener('resize', handleResize);
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
chartInstance?.resize();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
chartInstance?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.echarts-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 47px;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
363
platform/src/components3/gu6.vue
Normal file
363
platform/src/components3/gu6.vue
Normal file
@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<div class="fault-management-container">
|
||||
<div class="toolbar">
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchQuery"
|
||||
placeholder="搜索设备ID或名称"
|
||||
class="search-box"
|
||||
/>
|
||||
<div class="status-buttons">
|
||||
<button
|
||||
class="status-button"
|
||||
:class="{ active: selectedStatus === 'all' }"
|
||||
@click="handleStatusChange('all')"
|
||||
>
|
||||
全部
|
||||
</button>
|
||||
<button
|
||||
class="status-button"
|
||||
:class="{ active: selectedStatus === 'pending' }"
|
||||
@click="handleStatusChange('pending')"
|
||||
>
|
||||
待处理
|
||||
</button>
|
||||
<button
|
||||
class="status-button"
|
||||
:class="{ active: selectedStatus === 'in-progress' }"
|
||||
@click="handleStatusChange('in-progress')"
|
||||
>
|
||||
处理中
|
||||
</button>
|
||||
<button
|
||||
class="status-button"
|
||||
:class="{ active: selectedStatus === 'resolved' }"
|
||||
@click="handleStatusChange('resolved')"
|
||||
>
|
||||
已解决
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="fault-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>故障信息</th>
|
||||
<th>设备ID</th>
|
||||
<th>设备名称</th>
|
||||
<th>时间信息</th>
|
||||
<th>相关人员</th>
|
||||
<th>处理状态</th>
|
||||
<th style="position: relative;right: -20px">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="fault in filteredFaults" :key="fault.id">
|
||||
<td>{{ fault.faultInfo || '无' }}</td>
|
||||
<td>{{ fault.deviceId }}</td>
|
||||
<td>{{ fault.deviceName }}</td>
|
||||
<td>{{ formatTimestamp(fault.timestamp) }}</td>
|
||||
<td>{{ fault.assignedTo || '未指派' }}</td>
|
||||
<td>
|
||||
<span :class="['status', fault.statusClass]">{{ fault.status }}</span>
|
||||
</td>
|
||||
<td v-if="fault.status === '功能故障' || fault.status === '离线故障'">
|
||||
<button
|
||||
class="notify-button"
|
||||
@click="notifyResponsible(fault)"
|
||||
>
|
||||
通知负责人
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination">
|
||||
<span>
|
||||
显示 {{ (currentPage - 1) * pageSize + 1 }} 到
|
||||
10 条,共 10 条记录
|
||||
</span>
|
||||
<div class="page-buttons">
|
||||
<button @click="prevPage" :disabled="currentPage === 1"><</button>
|
||||
<button
|
||||
v-for="page in totalPages"
|
||||
:key="page"
|
||||
:class="{ active: currentPage === page }"
|
||||
@click="currentPage = page"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
<button @click="nextPage" :disabled="currentPage === totalPages">></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FaultManagement',
|
||||
data() {
|
||||
return {
|
||||
searchQuery: '',
|
||||
selectedStatus: 'all',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
faults: [],
|
||||
total: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredFaults() {
|
||||
return this.faults.slice(
|
||||
(this.currentPage - 1) * this.pageSize,
|
||||
this.currentPage * this.pageSize
|
||||
);
|
||||
},
|
||||
totalPages() {
|
||||
return Math.ceil(this.total / this.pageSize);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchFaults() {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:5000/fault-list?page=${this.currentPage}&size=${this.pageSize}&search=${this.searchQuery}&status=${this.selectedStatus}`);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
this.faults = data.data;
|
||||
this.total = data.total;
|
||||
} else {
|
||||
console.error('获取故障列表失败:', data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('网络请求失败:', error);
|
||||
}
|
||||
},
|
||||
handleStatusChange(status) {
|
||||
this.selectedStatus = status;
|
||||
this.currentPage = 1;
|
||||
this.fetchFaults();
|
||||
},
|
||||
prevPage() {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--;
|
||||
this.fetchFaults();
|
||||
}
|
||||
},
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.currentPage++;
|
||||
this.fetchFaults();
|
||||
}
|
||||
},
|
||||
formatTimestamp(timestamp) {
|
||||
if (!timestamp) return '-';
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString();
|
||||
},
|
||||
async notifyResponsible(fault) {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:5000/api/fault/notify/${fault.id}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP错误 ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
// 邮件发送成功,根据日志记录状态显示提示
|
||||
if (data.logStatus === 'success') {
|
||||
this.$message.success('通知已发送并记录');
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '通知已发送,但记录日志失败',
|
||||
description: '邮件已送达,系统日志可能不完整'
|
||||
});
|
||||
}
|
||||
this.fetchFaults(); // 刷新数据
|
||||
} else {
|
||||
// 邮件发送失败或其他错误
|
||||
let errorMsg = data.message;
|
||||
if (data.emailStatus === 'failed') {
|
||||
errorMsg = '邮件发送失败,请检查邮箱配置';
|
||||
}
|
||||
this.$message.error(errorMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('通知请求失败:', error);
|
||||
this.$message.error('通知处理异常,请重试');
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchFaults();
|
||||
},
|
||||
watch: {
|
||||
searchQuery() {
|
||||
this.currentPage = 1;
|
||||
this.fetchFaults();
|
||||
},
|
||||
selectedStatus() {
|
||||
this.currentPage = 1;
|
||||
this.fetchFaults();
|
||||
},
|
||||
currentPage() {
|
||||
this.fetchFaults();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fault-management-container {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
|
||||
width: 1390px; /* 固定容器宽度 */
|
||||
margin: 0 auto;
|
||||
padding-right: 10px; /* 增加右内边距 */
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 300px;
|
||||
height: 35px;
|
||||
font-size: 17px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.status-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-right: 15px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.status-button {
|
||||
width: 100px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: #f0f0f0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-button.active {
|
||||
background-color: #1a73e8;
|
||||
color: #fff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
width: 100%; /* 改为100%自适应 */
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.fault-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
|
||||
.fault-table th, .fault-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
/* 让单元格内容垂直居中 */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.fault-table tbody tr::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20px; /* 左边留白20px */
|
||||
right: 20px; /* 统一右边留白20px */
|
||||
height: 1px;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/* 调整列宽分配(新增第7列后重新计算) */
|
||||
.fault-table th:nth-child(1), .fault-table td:nth-child(1) { width: 13%; } /* 故障信息 */
|
||||
.fault-table th:nth-child(2), .fault-table td:nth-child(2) { width: 15%; } /* 设备ID */
|
||||
.fault-table th:nth-child(3), .fault-table td:nth-child(3) { width: 12%; } /* 设备名称 */
|
||||
.fault-table th:nth-child(4), .fault-table td:nth-child(4) { width: 18%; } /* 时间信息 */
|
||||
.fault-table th:nth-child(5), .fault-table td:nth-child(5) { width: 12%; } /* 相关人员 */
|
||||
.fault-table th:nth-child(6), .fault-table td:nth-child(6) { width: 10%; } /* 处理状态 */
|
||||
.fault-table th:nth-child(7), .fault-table td:nth-child(7) { width: 5%; } /* 操作列 */
|
||||
|
||||
.status {
|
||||
padding: 5px 10px;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status.pending { background-color: #ffcccc; color: #ff3d3d; }
|
||||
.status.in-progress { background-color: #cce5ff; color: #1a73e8; }
|
||||
.status.resolved { background-color: #ccffcc; color: #28a745; }
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.page-buttons { display: flex; gap: 5px; }
|
||||
|
||||
.page-buttons button {
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: #f0f0f0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.page-buttons button.active {
|
||||
background-color: #1a73e8;
|
||||
color: #fff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 通知按钮样式 */
|
||||
.notify-button {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #1a73e8;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
color: #1a73e8;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: background-color 0.2s;
|
||||
white-space: nowrap;
|
||||
/* 关键:用 Flex 布局让按钮在单元格内居中 */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.notify-button:hover {
|
||||
background-color: #e3f2fd;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user