This commit is contained in:
tcy
2025-09-19 10:20:18 +08:00
committed by re-JZzzz
44 changed files with 3949 additions and 91 deletions

View File

@ -5,7 +5,7 @@ VITE_APP_TITLE = 新能源场站智慧运维平台
VITE_APP_ENV = 'development'
# 开发环境
VITE_APP_BASE_API = 'http://192.168.110.180:18899'
VITE_APP_BASE_API = 'http://192.168.110.149:18899'
# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

BIN
src/assets/demo/archive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

BIN
src/assets/demo/chi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

BIN
src/assets/demo/down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

BIN
src/assets/demo/health.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

BIN
src/assets/demo/nowifi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

BIN
src/assets/demo/people.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

BIN
src/assets/demo/qin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

BIN
src/assets/demo/que.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

BIN
src/assets/demo/rebot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

BIN
src/assets/demo/time.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

BIN
src/assets/demo/tui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

BIN
src/assets/demo/up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

BIN
src/assets/demo/wifi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

View File

@ -0,0 +1,198 @@
<template>
<div class="chart-container">
<!--组件温度 图表内容区域 -->
<div ref="chartRef" class="chart-content"></div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
// 图表DOM引用
const chartRef = ref(null);
// 图表实例
let chartInstance = null;
// 初始化图表
const initChart = () => {
if (chartRef.value && !chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
const option = {
xAxis: {
type: "category",
data: ["09-04", "09-05", "09-06", "09-07", "09-08", "09-09", "09-10"],
axisTick: {
show: false // 去除刻度线
}
},
yAxis: {
type: "value",
splitLine: {
lineStyle: {
color: '#f0f0f0',
type: 'dashed'
}
}
},
legend: {
show: true,
icon: 'square',
left: '2%',
itemWidth: 10,
itemHeight: 10,
itemAlign: 'middle', // 设置图例项垂直居中
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
series: [
{
name: "维护提醒",
data: [120, 200, 150, 80, 70, 110, 130],
type: "bar",
itemStyle: {
color: "rgb(0, 179, 255)",
},
},
{
name: "数据异常",
data: [80, 170, 100, 50, 90, 140, 170],
type: "bar",
itemStyle: {
color: "rgb(22, 93, 255)",
},
},
{
name: "信号减弱",
data: [60, 140, 100, 120, 110, 100, 130],
type: "bar",
itemStyle: {
color: "rgb(255, 153, 0)",
},
},
{
name: "温度过高",
data: [60, 140, 100, 120, 110, 100, 130],
type: "bar",
itemStyle: {
color: "rgb(250, 220, 25)",
},
},
{
name: "通讯中断",
data: [60, 140, 100, 120, 110, 100, 130],
type: "bar",
itemStyle: {
color: "rgb(251, 62, 122)",
}
}
],
};
chartInstance.setOption(option);
};
// 响应窗口大小变化
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 生命周期钩子
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
};
});
</script>
<style scoped>
.chart-container {
background-color: #fff;
border-radius: 8px;
overflow: hidden;
height: 400px;
width: 100%;
padding: 10px;
box-sizing: border-box;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
}
.chart-header h2 {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
}
.chart-content {
width: 100%;
height: calc(100% - 54px);
padding: 10px;
box-sizing: border-box;
}
@media (max-width: 768px) {
.chart-container {
height: 350px;
}
}
@media (max-width: 480px) {
.chart-container {
height: 300px;
}
.chart-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.chart-actions {
width: 100%;
display: flex;
justify-content: space-between;
}
.chart-actions button {
margin: 0;
flex: 1;
margin-right: 5px;
}
.chart-actions button:last-child {
margin-right: 0;
}
}
.model {
padding: 20px;
background-color: rgba(242, 248, 252, 1);
}
</style>

View File

@ -0,0 +1,172 @@
<template>
<div class="chart-container">
<!--组件温度 图表内容区域 -->
<div ref="chartRef" class="chart-content"></div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
// 图表DOM引用
const chartRef = ref(null);
// 图表实例
let chartInstance = null;
// 初始化图表
const initChart = () => {
if (chartRef.value && !chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
const option = {
tooltip: {
trigger: 'item'
},
grid: {
left: '0%',
right: '20%',
bottom: '0%',
top: '0%',
containLabel: true
},
legend: {
top: 'middle',
orient: 'vertical',
right: '5%', // 调整图例位置,使其更靠近左侧
itemWidth: 15,
itemHeight: 15,
},
series: [
{
type: 'pie',
radius: '80%',
label: {
show: false
},
color: [
'rgb(0, 179, 255)', // 提示信息
'rgb(45, 214, 131)', // 一般告警
'rgb(255, 208, 35)', // 重要告警
'rgb(227, 39, 39)' // 严重告警
],
data: [
{ value: 1048, name: '提示信息' },
{ value: 735, name: '一般告警' },
{ value: 580, name: '重要告警' },
{ value: 484, name: '严重告警' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
chartInstance.setOption(option);
};
// 响应窗口大小变化
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 生命周期钩子
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
};
});
</script>
<style scoped>
.chart-container {
background-color: #fff;
border-radius: 8px;
overflow: hidden;
height: 150px;
width: 100%;
padding: 5px;
box-sizing: border-box;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
}
.chart-header h2 {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
}
.chart-content {
width: 100%;
height: 100%;
padding: 5px;
box-sizing: border-box;
}
@media (max-width: 768px) {
.chart-container {
height: 350px;
}
}
@media (max-width: 480px) {
.chart-container {
height: 300px;
}
.chart-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.chart-actions {
width: 100%;
display: flex;
justify-content: space-between;
}
.chart-actions button {
margin: 0;
flex: 1;
margin-right: 5px;
}
.chart-actions button:last-child {
margin-right: 0;
}
}
.model {
padding: 20px;
background-color: rgba(242, 248, 252, 1);
}
</style>

View File

@ -0,0 +1,382 @@
<template>
<el-table :data="alarmLevels" :border="false" style="width: 100%">
<el-table-column prop="levelName" label="级别名称" align="center">
<template #default="scope">
<span :class="['level-name', `level-${scope.row.level}`]">{{ scope.row.levelName }}</span>
</template>
</el-table-column>
<el-table-column prop="description" label="标识含义" align="center"></el-table-column>
<el-table-column prop="priority" label="优先级" width="100">
<template #default="scope">
<el-tag :type="getPriorityType(scope.row.priority)">{{ scope.row.priority }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="responseTime" label="响应时间" align="center">
<template #default="scope">
<span style="color: #186DF5;">{{ scope.row.responseTime }}</span>
</template>
</el-table-column>
<el-table-column prop="processingMethod" label="处理方式" align="center">
<template #default="scope">
<div class="process-methods">
<el-tag size="small" v-for="method in scope.row.processingMethod" :key="method" :type="getMethodType(method)">{{ method }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="enabled" label="是否启用" width="100" align="center">
<template #default="scope">
<el-switch v-model="scope.row.enabled" active-color="#13ce66" inactive-color="#ff4949" @change="handleEnabledChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right" align="center">
<template #default="scope">
<el-button link type="primary" @click="handleConfig(scope.row)">配置</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 配置对话框 -->
<el-dialog v-model="configDialogVisible" title="告警配置" width="600px">
<div v-if="currentConfigData">
<h3 class="config-title">{{ currentConfigData.levelName }} - 详细配置</h3>
<el-form ref="configFormRef" :model="currentConfigData" label-width="120px">
<el-form-item label="告警声音">
<el-select v-model="currentConfigData.alarmSound" placeholder="请选择告警声音">
<el-option label="默认声音" value="default" />
<el-option label="紧急声音" value="urgent" />
<el-option label="普通声音" value="normal" />
</el-select>
</el-form-item>
<el-form-item label="通知方式">
<el-checkbox-group v-model="currentConfigData.notificationMethods">
<el-checkbox label="短信" />
<el-checkbox label="邮件" />
<el-checkbox label="站内信" />
</el-checkbox-group>
</el-form-item>
<el-form-item label="告警持续时间">
<el-input-number v-model="currentConfigData.duration" :min="1" :max="60" label="分钟" />
</el-form-item>
<el-form-item label="自动处理">
<el-switch v-model="currentConfigData.autoProcess" />
</el-form-item>
<el-form-item label="处理说明" v-if="currentConfigData.autoProcess">
<el-input v-model="currentConfigData.processDescription" type="textarea" placeholder="请输入自动处理说明" />
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="configDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfigSave">保存配置</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
// 定义告警等级类型
interface AlarmLevel {
id: number;
levelName: string;
description: string;
priority: string;
responseTime: string;
processingMethod: string[];
enabled: boolean;
level: number; // 用于样式区分
}
// 定义配置数据类型
interface ConfigData extends AlarmLevel {
alarmSound: string;
notificationMethods: string[];
duration: number;
autoProcess: boolean;
processDescription: string;
}
// 模拟数据
const alarmLevels = ref<AlarmLevel[]>([
{
id: 1,
levelName: '严重告警',
description: '系统或应用出现严重故障',
priority: '一级',
responseTime: '15分钟以内',
processingMethod: ['系统锁定', '声光报警', '短信通知'],
enabled: true,
level: 1
},
{
id: 2,
levelName: '重要告警',
description: '系统或应用出现严重故障',
priority: '二级',
responseTime: '30分钟以内',
processingMethod: ['声光报警', '短信通知', '系统记录'],
enabled: true,
level: 2
},
{
id: 3,
levelName: '一般告警',
description: '非关键性故障或潜在风险',
priority: '三级',
responseTime: '120分钟以内',
processingMethod: ['短信通知', '系统记录'],
enabled: true,
level: 3
},
{
id: 4,
levelName: '提示信息',
description: '系统或应用非关键性变化或即将达到阈值的状态',
priority: '四级',
responseTime: '24小时以内',
processingMethod: ['短信通知'],
enabled: false,
level: 4
}
]);
// 对话框相关状态
const configDialogVisible = ref(false);
const configFormRef = ref<any>();
const currentConfigData = ref<ConfigData | null>(null);
// 获取优先级对应的标签类型
const getPriorityType = (priority: string) => {
const priorityMap: Record<string, string> = {
'一级': 'danger',
'二级': 'warning',
'三级': 'success',
'四级': 'primary'
};
return priorityMap[priority] || 'default';
};
// 获取处理方式对应的标签类型
const getMethodType = (method: string) => {
const methodMap: Record<string, string> = {
'系统锁定': 'danger',
'声光报警': 'warning',
'短信通知': 'primary',
'邮件通知': 'info',
'系统记录': 'success'
};
return methodMap[method] || 'info';
};
// 处理启用状态变更
const handleEnabledChange = (row: AlarmLevel) => {
ElMessage.success(`${row.levelName} ${row.enabled ? '已启用' : '已禁用'}`);
// 这里可以添加保存到后端的逻辑
};
// 打开配置对话框
const handleConfig = (row: AlarmLevel) => {
// 构建配置数据
currentConfigData.value = {
...row,
alarmSound: 'default',
notificationMethods: ['短信'],
duration: 30,
autoProcess: false,
processDescription: ''
};
configDialogVisible.value = true;
};
// 保存配置
const handleConfigSave = () => {
if (currentConfigData.value) {
// 找到对应的告警等级并更新
const index = alarmLevels.value.findIndex(item => item.id === currentConfigData.value!.id);
if (index !== -1) {
alarmLevels.value[index] = {
...alarmLevels.value[index],
enabled: currentConfigData.value!.enabled
};
}
ElMessage.success('配置保存成功');
configDialogVisible.value = false;
}
};
// 删除告警等级
const handleDelete = (id: number) => {
ElMessageBox.confirm('确定要删除该告警等级吗?', '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const index = alarmLevels.value.findIndex(item => item.id === id);
if (index !== -1) {
alarmLevels.value.splice(index, 1);
ElMessage.success('删除成功');
}
}).catch(() => {
// 用户取消删除
});
};
</script>
<style scoped lang="scss">
.level-set-container {
padding: 20px;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.level-name {
font-weight: 500;
padding: 2px 6px 2px 18px;
border-radius: 3px;
transition: all 0.3s ease;
position: relative;
}
.level-name::before {
content: '';
position: absolute;
left: 4px;
top: 50%;
transform: translateY(-50%);
width: 8px;
height: 8px;
border-radius: 50%;
}
.level-1::before {
background-color: #ff4949;
}
.level-2::before {
background-color: #f7ba1e;
}
.level-3::before {
background-color: #13ce66;
}
.level-4::before {
background-color: #1890ff;
}
.level-name:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.level-1 {
color: #ff4949;
}
.level-2 {
color: #f7ba1e;
}
.level-3 {
color: #13ce66;
}
.level-4 {
color: #1890ff;
}
.process-methods {
display: flex;
gap: 6px;
flex-wrap: wrap;
padding: 4px 0;
}
/* 优化表格样式 */
:deep(.el-table) {
border-radius: 8px;
overflow: hidden;
}
:deep(.el-table th) {
background-color: #fafafa;
font-weight: 600;
color: #303133;
border-bottom: 1px solid #ebeef5;
}
:deep(.el-table tr:hover > td) {
background-color: #f0f9ff !important;
}
:deep(.el-table__row:nth-child(even)) {
background-color: #fafafa;
}
/* 优化按钮和操作列 */
:deep(.el-button--text) {
transition: all 0.3s ease;
padding: 4px 12px;
border-radius: 4px;
}
:deep(.el-button--text:hover) {
background-color: rgba(0, 0, 0, 0.05);
}
/* 优化对话框样式 */
.config-title {
margin-bottom: 20px;
color: #303133;
font-size: 16px;
font-weight: 500;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* 优化表单样式 */
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-form-item__label) {
color: #606266;
font-weight: 500;
}
:deep(.el-select),
:deep(.el-input),
:deep(.el-input-number) {
width: 100%;
}
/* 响应式调整 */
@media (max-width: 768px) {
.level-set-container {
padding: 15px;
}
:deep(.el-table) {
font-size: 12px;
}
}
</style>

View File

@ -0,0 +1,321 @@
<template>
<div class="total-view-dashboard">
<!-- 今日报警总数 -->
<div class="total-view-card blue-border">
<div class="total-content">
<div class="content-row">
<div class="left-section">
<div class="total-header">
<span class="total-title">今日报警总数</span>
</div>
<div class="total-number">28</div>
</div>
<div class="icon-section">
<el-icon class="total-icon blue">
<img src="@/assets/demo/health.png" alt="">
</el-icon>
</div>
</div>
<div class="total-comparison">
<el-icon class="trend-icon green">
<img src="/src/assets/demo/up.png" alt="上升">
</el-icon>
<span class="comparison-text green">8</span>
<span class="period-text">较上月同期</span>
</div>
</div>
</div>
<!-- 未处理报警 -->
<div class="total-view-card purple-border">
<div class="total-content">
<div class="content-row">
<div class="left-section">
<div class="total-header">
<span class="total-title">未处理报警</span>
</div>
<div class="total-number">8</div>
</div>
<div class="icon-section">
<el-icon class="total-icon purple">
<img src="@/assets/demo/sms-tracking.png" alt="">
</el-icon>
</div>
</div>
<div class="total-comparison">
<el-icon class="trend-icon green">
<img src="/src/assets/demo/up.png" alt="上升">
</el-icon>
<span class="comparison-text green">8</span>
<span class="period-text">较上月同期</span>
</div>
</div>
</div>
<!-- 已处理报警 -->
<div class="total-view-card green-border">
<div class="total-content">
<div class="content-row">
<div class="left-section">
<div class="total-header">
<span class="total-title">已处理报警</span>
</div>
<div class="total-number">20</div>
</div>
<div class="icon-section">
<el-icon class="total-icon green">
<img src="@/assets/demo/archive.png" alt="">
</el-icon>
</div>
</div>
<div class="total-comparison">
<el-icon class="trend-icon green">
<img src="/src/assets/demo/up.png" alt="上升">
</el-icon>
<span class="comparison-text green">8</span>
<span class="period-text">较上月同期</span>
</div>
</div>
</div>
<!-- 严重报警 -->
<div class="total-view-card orange-border">
<div class="total-content">
<div class="content-row">
<div class="left-section">
<div class="total-header">
<span class="total-title">严重报警</span>
</div>
<div class="total-number">3</div>
</div>
<div class="icon-section">
<el-icon class="total-icon orange">
<img src="@/assets/demo/mouse-square.png" alt="">
</el-icon>
</div>
</div>
<div class="total-comparison">
<el-icon class="trend-icon green">
<img src="/src/assets/demo/up.png" alt="上升">
</el-icon>
<span class="comparison-text green">8</span>
<span class="period-text">较上月同期</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
.total-view-dashboard {
display: flex;
gap: 16px;
width: 100%;
flex-wrap: wrap;
}
.total-view-card {
display: flex;
align-items: center;
padding: 20px 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
position: relative;
flex: 1;
min-width: 200px;
height: 150px;
transition: all 0.3s ease;
overflow: hidden;
}
.total-view-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* 左侧边框样式 - 使用伪元素创建与指定内容高度一致的边框 */
.total-view-card::before {
content: '';
position: absolute;
left: 0;
top: 42px;
width: 4px;
height: 45px;
border-radius: 0 2px 2px 0;
transition: height 0.3s ease;
}
.total-view-card:hover::before {
height: 80px;
}
.blue-border::before {
background-color: #0080FC;
}
.blue-border {
background-color: #EAF5FF;
}
/* 添加卡片背景渐变效果 */
.total-view-card::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.3) 100%);
pointer-events: none;
}
.purple-border::before {
background-color: #722ed1;
}
.purple-border {
background-color: #F3EDFF;
}
.green-border::before {
background-color: #009B72;
}
.green-border {
background-color: #E8FFF9;
}
.orange-border::before {
background-color: #fa8c16;
}
.orange-border {
background-color: #FFF6EC;
}
.total-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
min-width: 0;
}
.content-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.left-section {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.icon-section {
display: flex;
align-items: center;
justify-content: center;
margin-left: 12px;
}
.total-header {
display: flex;
align-items: center;
}
.total-title {
font-size: 14px;
color: #606266;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.total-icon {
width: 40px;
height: 40px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.total-icon.blue {
background-color: #DBEEFF;
color: #1890ff;
}
.total-icon.purple {
background-color: #E9DEFF;
color: #722ed1;
}
.total-icon.green {
background-color: #CEFFF2;
color: #52c41a;
}
.total-icon.orange {
background-color: #FFEBD3;
color: #fa8c16;
}
.total-number {
font-size: 18px;
font-weight: 600;
color: #303133;
line-height: 1;
}
.total-comparison {
display: flex;
align-items: center;
gap: 8px;
height: 16px;
}
.trend-icon {
width: 16px;
height: 16px;
}
.trend-icon.green {
color: #52c41a;
}
.comparison-text {
font-size: 12px;
}
.comparison-text.green {
color: #52c41a;
}
.period-text {
font-size: 12px;
color: #909399;
}
@media screen and (max-width: 1200px) {
.total-view-dashboard {
flex-wrap: wrap;
}
.total-view-card {
flex: 0 0 calc(50% - 8px);
}
}
@media screen and (max-width: 768px) {
.total-view-card {
flex: 0 0 100%;
}
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<div class="model">
<!-- 标题栏 -->
<el-row>
<el-col :span="12">
<TitleComponent title="报警管理" subtitle="配置新能源厂站的报警级别、类型及相关规则" />
</el-col>
</el-row>
<!-- 第一行报警管理和报警级别分布 -->
<el-row :gutter="20" class="content-row">
<el-col :span="16">
<el-card shadow="hover" class="custom-card">
<TitleComponent title="报警管理" :font-level="2" />
<totalView />
</el-card>
</el-col>
<el-col :span="8">
<!-- 报警级别分布 -->
<el-card shadow="hover" class="custom-card">
<TitleComponent title="报警级别分布" :font-level="2" />
<levelPie />
</el-card>
</el-col>
</el-row>
<!-- 第二行报警趋势分析 -->
<el-row :gutter="20" class="content-row">
<el-col :span="24">
<el-card shadow="hover" class="custom-card">
<TitleComponent title="报警趋势分析" :font-level="2" />
<fenxiBar />
</el-card>
</el-col>
</el-row>
<!-- 第三行报警级别设置 -->
<el-row :gutter="20" class="content-row">
<el-col :span="24">
<el-card shadow="hover" class="custom-card">
<TitleComponent title="报警级别设置" :font-level="2" />
<levelSet />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import TitleComponent from '@/components/TitleComponent/index.vue';
import levelPie from '@/views/integratedManage/alarmManage/components/levelPie.vue'
import fenxiBar from '@/views/integratedManage/alarmManage/components/fenxiBar.vue'
import totalView from '@/views/integratedManage/alarmManage/components/totalView.vue';
import levelSet from '@/views/integratedManage/alarmManage/components/levelSet.vue';
</script>
<style scoped>
.model {
padding: 20px 15px;
background-color: rgba(242, 248, 252, 1);
}
.content-row {
margin-bottom: 20px;
}
.custom-card {
border-radius: 8px;
transition: all 0.3s ease;
border: none;
}
.custom-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* 响应式布局调整 */
@media (max-width: 1200px) {
.content-row {
margin-bottom: 15px;
}
}
@media (max-width: 768px) {
.model {
padding: 15px 10px;
}
.content-row {
margin-bottom: 10px;
}
}
</style>

View File

@ -0,0 +1,327 @@
<template>
<div class="chart-container">
<!-- 图表标题和时间范围选择器 -->
<div class="chart-header">
<h2>出勤趋势分析</h2>
<div class="chart-actions">
<button @click="timeRange = 'week'" :class="{ active: timeRange === 'week' }">每周</button>
<button @click="timeRange = 'month'" :class="{ active: timeRange === 'month' }">每月</button>
</div>
</div>
<!-- 图表内容区域 -->
<div ref="chartRef" class="chart-content"></div>
</div>
</template>
<script setup>
import { ref, onMounted, computed, watch } from 'vue';
import * as echarts from 'echarts';
// 接收从父组件传入的数据
const props = defineProps({
attendData: {
type: Object,
default: () => ({
week: {
xAxis: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
actualCount: [40, 20, 30, 15, 22, 63, 58],
expectedCount: [100, 556, 413, 115, 510, 115, 317]
},
month: {
xAxis: ['第1周', '第2周', '第3周', '第4周'],
actualData: [280, 360, 320, 400],
theoreticalData: [300, 400, 350, 450]
}
})
}
});
// 图表DOM引用
const chartRef = ref(null);
// 图表实例
let chartInstance = null;
// 时间范围状态
const timeRange = ref('week');
// 根据时间范围计算当前显示的数据
const chartData = computed(() => {
const dataForRange = props.attendData[timeRange.value] || props.attendData.week;
// 处理字段名称差异
if (timeRange.value === 'week') {
return {
xAxis: dataForRange.xAxis || [],
actualCount: dataForRange.actualCount || [],
expectedCount: dataForRange.expectedCount || []
};
} else {
return {
xAxis: dataForRange.xAxis || [],
actualCount: dataForRange.actualData || [],
expectedCount: dataForRange.theoreticalData || []
};
}
});
// 定义颜色常量
const ACTUAL_COUNT_COLOR = '#029CD4'; // 蓝色 - 实际人数
const EXPECTED_COUNT_COLOR = '#0052D9'; // 蓝色 - 应出勤人数
// 初始化图表
const initChart = () => {
if (chartRef.value && !chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
// 使用计算后的数据
const { xAxis, actualCount, expectedCount } = chartData.value;
const option = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255,255,255,1)',
borderColor: '#ddd',
borderWidth: 1,
textStyle: {
color: '#333',
fontSize: 14
},
formatter: function(params) {
const actualCount = params[0].value;
const expectedCount = params[1].value;
return `
<div style="padding: 5px;">
<div style="color: ${params[0].color};">实际人数: ${actualCount}</div>
<div style="color: ${params[1].color};">应出勤人数: ${expectedCount}</div>
</div>
`;
}
},
legend: {
top: 30,
left: 'center',
itemWidth: 10,
itemHeight: 10,
itemGap: 25,
data: ['实际人数', '应出勤人数'],
textStyle: {
color: '#666',
fontSize: 12
}
},
grid: {
top: '30%',
right: '10%',
bottom: '10%',
left: '6%',
containLabel: true
},
xAxis: {
data: xAxis,
type: 'category',
boundaryGap: true,
axisLabel: {
textStyle: {
color: '#666',
fontSize: 12
}
},
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#ddd'
}
}
},
yAxis: [
{
type: 'value',
name: '人数',
nameTextStyle: {
color: '#666',
fontSize: 12
},
interval: 100,
axisLabel: {
textStyle: {
color: '#666',
fontSize: 12
}
},
axisTick: {
show: false
},
axisLine: {
show: false
},
splitLine: {
lineStyle: {
color: '#f0f0f0',
type: 'dashed'
}
}
}
],
series: [
{
name: '实际人数',
type: 'bar',
barWidth: '40%',
itemStyle: {
color: ACTUAL_COUNT_COLOR
},
data: actualCount
},
{
name: '应出勤人数',
type: 'line',
showSymbol: false,
symbol: 'circle',
symbolSize: 6,
emphasis: {
showSymbol: true,
symbolSize: 10
},
lineStyle: {
width: 2,
color: EXPECTED_COUNT_COLOR
},
itemStyle: {
color: EXPECTED_COUNT_COLOR,
borderColor: '#fff',
borderWidth: 2
},
data: expectedCount
}
]
};
chartInstance.setOption(option);
};
// 响应窗口大小变化
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 监听时间范围变化,更新图表
watch(timeRange, () => {
initChart();
});
// 监听数据变化,更新图表
watch(() => props.attendData, () => {
initChart();
}, { deep: true });
// 生命周期钩子
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
};
});
</script>
<style scoped>
.chart-container {
background-color: #fff;
border-radius: 8px;
overflow: hidden;
height: 500px;
width: 100%;
padding: 10px;
box-sizing: border-box;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
}
.chart-header h2 {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
}
.chart-actions button {
background: none;
border: 1px solid #e0e0e0;
padding: 5px 12px;
border-radius: 4px;
margin-left: 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
}
.chart-actions button.active {
background-color: #1890ff;
color: white;
border-color: #1890ff;
}
.chart-content {
width: 100%;
height: calc(100% - 54px);
padding: 10px;
}
@media (max-width: 768px) {
.chart-container {
height: 450px;
}
}
@media (max-width: 480px) {
.chart-container {
height: 400px;
}
.chart-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.chart-actions {
width: 100%;
display: flex;
justify-content: space-between;
}
.chart-actions button {
margin: 0;
flex: 1;
margin-right: 5px;
}
.chart-actions button:last-child {
margin-right: 0;
}
}
.model {
padding: 20px;
background-color: rgba(242, 248, 252, 1);
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<div class="box">
<div class="total">
<div class="infoBox">
<div class="date text-color">2025-08-26</div>
<div class="temperature text-color">28</div>
<div class="role text-color">中午好管理员</div>
<div class="cycle text-color">加入项目已经89天</div>
</div>
<img src="@/assets/demo/icTicket.png" alt="" class="imgbox">
</div>
</div>
</template>
<style scoped lang="scss">
.total {
width: 100%;
position: relative;
overflow: hidden;
.imgbox {
position: absolute;
top: 60px;
left: 210px;
}
.infoBox {
height: 217px;
border-radius: 12px;
padding: 30px;
background: rgba(24, 109, 245, 1);
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
.text-color {
color: rgba(255, 255, 255, 1);
}
.date {
font-size: 16px;
}
.temperature {
font-weight: 600;
font-size: 28px;
}
.role {
font-size: 24px;
}
.cycle {
font-size: 16px;
}
}
}
</style>

View File

@ -0,0 +1,211 @@
<template>
<div class="box">
<div class="chart-header">
<TitleComponent title="审批" :font-level="2" />
<span>更多</span>
</div>
<div class="approval-content">
<div
v-for="(item, index) in approvalData"
:key="index"
class="approval-item"
>
<div class="approval-left">
<div class="approval-icon">
<img :src="item.iconPath" :alt="item.type">
</div>
<div class="approval-info">
<div class="info">
<div class="type">{{ item.type }}</div>
<div class="day">{{ item.days }}</div>
</div>
<div class="info1">
<div class="time">
<img src="@/assets/demo/time.png" alt="时间">
<span>{{ item.timeRange }}</span>
</div>
<div class="people">
<img src="@/assets/demo/people.png" alt="人员">
<span>{{ item.people }}</span>
</div>
</div>
</div>
</div>
<div class="approval-tag">
<el-tag :type="item.statusType">{{ item.status }}</el-tag>
</div>
</div>
</div>
</div>
</template>
<script setup>
import TitleComponent from '@/components/TitleComponent/index.vue';
const approvalData = [
{
type: '事假',
days: 1,
timeRange: '09.14-09.15',
people: '水泥班组-王五',
status: '待审批',
statusType: 'primary',
iconPath: '/src/assets/demo/approval.png'
},
{
type: '病假',
days: 2,
timeRange: '09.14-09.15',
people: '水泥班组-王五',
status: '待审批',
statusType: 'primary',
iconPath: '/src/assets/demo/approval.png'
},
{
type: '调休',
days: 1,
timeRange: '09.14-09.15',
people: '水泥班组-王五',
status: '待审批',
statusType: 'primary',
iconPath: '/src/assets/demo/approval.png'
},
{
type: '事假',
days: 1,
timeRange: '09.14-09.15',
people: '水泥班组-王五',
status: '待审批',
statusType: 'primary',
iconPath: '/src/assets/demo/approval.png'
},
{
type: '事假',
days: 1,
timeRange: '09.14-09.15',
people: '水泥班组-王五',
status: '已通过',
statusType: 'success',
iconPath: '/src/assets/demo/approval.png'
}
];
</script>
<style scoped lang="scss">
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.chart-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #333;
}
.chart-header span {
color: #186DF5;
font-size: 14px;
cursor: pointer;
}
.approval-content {
background-color: white;
.approval-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: #F2F8FC;
border-radius: 8px;
margin-bottom: 12px;
border: 1px solid #F2F3F5;
transition: all 0.3s ease;
}
.approval-item:hover {
border-color: #E4E6EB;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.approval-left {
display: flex;
align-items: center;
flex: 1;
}
.approval-icon {
width: 48px;
height: 48px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
position: relative;
overflow: hidden;
background: #186DF5;
}
.approval-icon img {
width: 26px;
height: 26px;
}
.approval-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.info {
display: flex;
justify-content: space-between;
align-items: center;
width: 90px;
}
.info .type {
font-size: 14px;
font-weight: 500;
color: #333;
}
.info .day {
font-size: 14px;
color: #666;
}
.info1 {
display: flex;
align-items: center;
gap: 16px;
}
.info1 .time,
.info1 .people {
display: flex;
align-items: center;
font-size: 12px;
color: rgba(113, 128, 150, 1);
}
.info1 img {
width: 12px;
height: 12px;
margin-right: 4px;
}
.approval-tag {
margin-left: 16px;
}
.approval-tag .el-tag {
padding: 4px 12px;
font-size: 12px;
border-radius: 4px;
}
}
</style>

View File

@ -0,0 +1,320 @@
<template>
<div class="box">
<div class="chart-header">
<TitleComponent title="日历" :font-level="2" />
</div>
<div class="calendar-container">
<div class="calendar-header">
<el-button size="small" type="text" @click="prevMonth">
<el-icon><ArrowLeft /></el-icon>
</el-button>
<span class="current-month">{{ currentYear }} {{ currentMonthName }}</span>
<el-button size="small" type="text" @click="nextMonth">
<el-icon><ArrowRight /></el-icon>
</el-button>
</div>
<div class="calendar-weekdays">
<span v-for="day in weekdays" :key="day" class="weekday">{{ day }}</span>
</div>
<div class="calendar-days">
<!-- 上月剩余天数 -->
<div v-for="(day, index) in prevMonthDays" :key="'prev-' + index" class="day prev-month-day">
{{ day }}
</div>
<!-- 当月天数 -->
<div
v-for="day in currentMonthDays"
:key="day"
class="day current-month-day"
:class="{
'current-day': isCurrentDay(day),
'selected-day': isSelectedDay(day)
}"
@click="selectDay(day)"
>
{{ day }}
<!-- 今天有红点标记 -->
<span v-if="isToday(day)" class="today-marker"></span>
<!-- 考勤状态标记 -->
<span v-if="getAttendanceStatus(day)" class="attendance-marker" :class="getAttendanceStatus(day)"></span>
</div>
<!-- 下月开始天数 -->
<div v-for="(day, index) in nextMonthDays" :key="'next-' + index" class="day next-month-day">
{{ day }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import TitleComponent from '@/components/TitleComponent/index.vue';
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
import { ref, computed } from 'vue';
import { ElMessage } from 'element-plus';
// 初始化当前日期
const today = new Date();
const currentDate = ref(new Date(2025, 8, 27)); // 2025年9月27日截图中显示的日期
const selectedDate = ref(new Date(2025, 8, 27));
// 模拟考勤数据
const attendanceData = ref({
2025: {
9: {
1: 'normal',
4: 'late',
8: 'absent',
10: 'leave',
15: 'normal',
20: 'normal',
25: 'late',
27: 'normal'
}
}
});
// 星期几的显示
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
// 计算属性
const currentYear = computed(() => currentDate.value.getFullYear());
const currentMonth = computed(() => currentDate.value.getMonth());
const currentMonthName = computed(() => monthNames[currentMonth.value]);
// 获取当月的天数
const currentMonthDays = computed(() => {
return new Date(currentYear.value, currentMonth.value + 1, 0).getDate();
});
// 获取当月第一天是星期几0-60是星期日
const firstDayOfMonth = computed(() => {
return new Date(currentYear.value, currentMonth.value, 1).getDay();
});
// 获取上月剩余天数
const prevMonthDays = computed(() => {
const days = [];
const prevMonth = new Date(currentYear.value, currentMonth.value, 0).getDate(); // 上月最后一天
for (let i = firstDayOfMonth.value - 1; i >= 0; i--) {
days.push(prevMonth - i);
}
return days;
});
// 获取下月开始天数
const nextMonthDays = computed(() => {
const days = [];
const totalDays = prevMonthDays.value.length + currentMonthDays.value;
const nextDays = 35 - totalDays; // 显示5周共35天
for (let i = 1; i <= nextDays; i++) {
days.push(i);
}
return days;
});
// 方法
const prevMonth = () => {
currentDate.value = new Date(currentYear.value, currentMonth.value - 1, 1);
};
const nextMonth = () => {
currentDate.value = new Date(currentYear.value, currentMonth.value + 1, 1);
};
const selectDay = (day) => {
selectedDate.value = new Date(currentYear.value, currentMonth.value, day);
// 显示选择的日期和考勤状态
let message = `Selected: ${currentMonthName.value} ${day}, ${currentYear.value}`;
const status = getAttendanceStatus(day);
if (status) {
const statusMap = {
normal: '正常',
late: '迟到',
absent: '缺勤',
leave: '请假'
};
message += ` - 考勤状态: ${statusMap[status] || '未知'}`;
}
ElMessage.success(message);
};
// 获取考勤状态
const getAttendanceStatus = (day) => {
if (attendanceData.value[currentYear.value] &&
attendanceData.value[currentYear.value][currentMonth.value + 1] &&
attendanceData.value[currentYear.value][currentMonth.value + 1][day]) {
return attendanceData.value[currentYear.value][currentMonth.value + 1][day];
}
return null;
};
const isToday = (day) => {
return day === today.getDate() &&
currentMonth.value === today.getMonth() &&
currentYear.value === today.getFullYear();
};
const isCurrentDay = (day) => {
return day === today.getDate() &&
currentMonth.value === today.getMonth() &&
currentYear.value === today.getFullYear();
};
const isSelectedDay = (day) => {
return day === selectedDate.value.getDate() &&
currentMonth.value === selectedDate.value.getMonth() &&
currentYear.value === selectedDate.value.getFullYear();
};
</script>
<style scoped lang="scss">
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.chart-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #333;
}
.chart-header span {
color: #186DF5;
font-size: 14px;
cursor: pointer;
}
.calendar-container {
background: white;
border-radius: 8px;
padding: 16px;
// border: 1px solid #F2F3F5;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 8px 0;
}
.current-month {
font-size: 16px;
font-weight: 500;
color: #333;
}
.calendar-weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
margin-bottom: 8px;
}
.weekday {
text-align: center;
font-size: 14px;
color: #666;
font-weight: 500;
padding: 8px 0;
}
.calendar-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
height: 350px;
}
.day {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 40px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.prev-month-day,
.next-month-day {
color: #C0C4CC;
}
.current-month-day {
color: #333;
}
.current-month-day:hover {
background-color: #ECF5FF;
color: #186DF5;
}
.current-day {
position: relative;
}
.today-marker {
position: absolute;
bottom: 4px;
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #FF6B3B;
}
.selected-day {
background-color: #186DF5;
color: white !important;
font-weight: 500;
}
.selected-day:hover {
background-color: #4096ff;
color: white !important;
}
// 考勤状态标记样式
.attendance-marker {
position: absolute;
bottom: 2px;
width: 6px;
height: 6px;
border-radius: 50%;
}
.attendance-marker.normal {
background-color: #52c41a;
}
.attendance-marker.late {
background-color: #faad14;
}
.attendance-marker.absent {
background-color: #f5222d;
}
.attendance-marker.leave {
background-color: #1890ff;
}
</style>

View File

@ -0,0 +1,71 @@
<template>
<div class="box">
<TitleComponent title="今日出勤" :font-level="2" />
<div class="todayAttend">
<div class="todayAttendItem">
<img src="@/assets/demo/qin.png" alt="" width="30px" height="30px">
<div class="todayAttendItemInfo">
<span class="todayAttendItemTitle">出勤</span>
<span class="todayAttendItemNum">150</span>
</div>
</div>
<div class="todayAttendItem">
<img src="@/assets/demo/qin.png" alt="" width="30px" height="30px">
<div class="todayAttendItemInfo">
<span class="todayAttendItemTitle">迟到</span>
<span class="todayAttendItemNum">150</span>
</div>
</div>
<div class="todayAttendItem">
<img src="@/assets/demo/qin.png" alt="" width="30px" height="30px">
<div class="todayAttendItemInfo">
<span class="todayAttendItemTitle">早退</span>
<span class="todayAttendItemNum">150</span>
</div>
</div>
<div class="todayAttendItem">
<img src="@/assets/demo/qin.png" alt="" width="30px" height="30px">
<div class="todayAttendItemInfo">
<span class="todayAttendItemTitle">缺勤</span>
<span class="todayAttendItemNum">150</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import TitleComponent from '@/components/TitleComponent/index.vue';
</script>
<style scoped lang="scss">
.todayAttend {
display: flex;
justify-content: space-between;
align-items: center;
}
.todayAttendItem {
width: 110px;
height: 100px;
background: #E5F0FF;
padding: 5px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-around;
flex-direction: column;
.todayAttendItemInfo{
display: flex;
justify-content: space-around;
align-items: center;
.todayAttendItemTitle {
color: rgba(113, 128, 150, 1);
font-size: 12px;
}
.todayAttendItemNum {
font-size: 16px;
color: rgba(0, 30, 59, 1);
}
}
}
</style>

View File

@ -0,0 +1,320 @@
<template>
<div class="total-view-container">
<div class="total-view-content">
<!-- 总出勤人数 -->
<div class="stats-card">
<div class="stats-card-header">
<span class="stats-title">总出勤人数</span>
<span class="stats-change positive"> 8.2% 较昨日同期</span>
</div>
<div class="stats-card-body">
<div class="stats-value">248</div>
<div class="stats-chart">
<div ref="attendanceChart" class="chart-container"></div>
</div>
</div>
</div>
<!-- 调休 -->
<div class="stats-card">
<div class="stats-card-header">
<span class="stats-title">调休</span>
<span class="stats-change positive"> 8.2% 较上月同期</span>
</div>
<div class="stats-card-body">
<div class="stats-value">8</div>
<div class="stats-chart">
<div ref="restChart" class="chart-container"></div>
</div>
</div>
</div>
<!-- 本月请假 -->
<div class="stats-card">
<div class="stats-card-header">
<span class="stats-title">本月请假</span>
<span class="stats-change negative"> 10% 较昨日同期</span>
</div>
<div class="stats-card-body">
<div class="stats-value">24</div>
<div class="stats-chart">
<div ref="leaveChart" class="chart-container"></div>
</div>
</div>
</div>
<!-- 平均出勤率 -->
<div class="stats-card">
<div class="stats-card-header">
<span class="stats-title">平均出勤率</span>
<span class="stats-change positive"> 10% 较昨日同期</span>
</div>
<div class="stats-card-body">
<div class="stats-value">96.8%</div>
<div class="stats-chart">
<div ref="rateChart" class="chart-container"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
// 图表引用
const attendanceChart = ref(null);
const restChart = ref(null);
const leaveChart = ref(null);
const rateChart = ref(null);
// 初始化图表
const initCharts = () => {
// 总出勤人数图表 - 条形图
const attendanceChartInstance = echarts.init(attendanceChart.value);
attendanceChartInstance.setOption({
tooltip: { show: false },
grid: { top: 0, bottom: 0, left: -70, right: 0, containLabel: true },
xAxis: {
type: 'category',
data: ['', '', '', '', '', '', ''],
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false }
},
yAxis: {
type: 'value',
show: false
},
series: [{
data: [150, 230, 224, 218, 135, 300, 220],
type: 'bar',
barWidth: 10,
itemStyle: {
color: '#FF7D00',
borderRadius: [10, 10, 0, 0] // 柱状图圆角
},
emphasis: {
focus: 'series'
}
}]
});
// 调休图表 - 折线图
const restChartInstance = echarts.init(restChart.value);
restChartInstance.setOption({
tooltip: { show: false },
grid: { top:10, bottom: 0, left: -30, right: 0, containLabel: true },
xAxis: {
type: 'category',
data: ['', '', '', '', '', '', '', '', '', ''],
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false }
},
yAxis: {
type: 'value',
show: false
},
series: [{
data: [120, 200, 150, 80, 70, 110, 130, 150, 160, 180],
type: 'line',
smooth: true,
showSymbol: false,
lineStyle: {
width: 3, // 折线图线条加粗
color: '#52C41A'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(82, 196, 26, 0.2)'
}, {
offset: 1,
color: 'rgba(82, 196, 26, 0.01)'
}])
}
}]
});
// 本月请假图表 - 折线图
const leaveChartInstance = echarts.init(leaveChart.value);
leaveChartInstance.setOption({
tooltip: { show: false },
grid: { top: 10, bottom: 0, left: -30, right: 0, containLabel: true },
xAxis: {
type: 'category',
data: ['', '', '', '', '', '', '', '', '', ''],
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false }
},
yAxis: {
type: 'value',
show: false
},
series: [{
data: [80, 150, 120, 200, 180, 130, 160, 190, 140, 100],
type: 'line',
smooth: true,
showSymbol: false,
lineStyle: {
width: 3, // 折线图线条加粗
color: '#F5222D'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(245, 34, 45, 0.2)'
}, {
offset: 1,
color: 'rgba(245, 34, 45, 0.01)'
}])
}
}]
});
// 平均出勤率图表 - 折线图
const rateChartInstance = echarts.init(rateChart.value);
rateChartInstance.setOption({
tooltip: { show: false },
grid: { top: 0, bottom: 0, left: -30, right: 0, containLabel: true },
xAxis: {
type: 'category',
data: ['', '', '', '', '', '', '', '', '', ''],
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false }
},
yAxis: {
type: 'value',
show: false
},
series: [{
data: [90, 92, 91, 93, 95, 94, 96, 95, 97, 98],
type: 'line',
smooth: true,
showSymbol: false,
lineStyle: {
width: 3, // 折线图线条加粗
color: '#186DF5'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(24, 109, 245, 0.2)'
}, {
offset: 1,
color: 'rgba(24, 109, 245, 0.01)'
}])
}
}]
});
// 响应窗口大小变化
window.addEventListener('resize', () => {
attendanceChartInstance.resize();
restChartInstance.resize();
leaveChartInstance.resize();
rateChartInstance.resize();
});
};
// 组件挂载后初始化图表
onMounted(() => {
initCharts();
});
</script>
<style scoped lang="scss">
.total-view-container {
background-color: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.total-view-content {
display: flex;
justify-content: space-between;
gap: 0;
}
.stats-card {
flex: 1;
padding: 16px;
background-color: #fff;
border-radius: 0;
border: none;
border-right: 1px dashed #E4E7ED;
}
.stats-card:last-child {
border-right: none;
}
.stats-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.stats-title {
font-size: 14px;
color: #606266;
}
.stats-change {
font-size: 12px;
}
.stats-change.positive {
color: #52C41A;
}
.stats-change.negative {
color: #F5222D;
}
.stats-card-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.stats-value {
font-size: 24px;
font-weight: 600;
color: #303133;
}
.stats-chart {
width: 100%;
height: 60px;
}
.chart-container {
width: 100%;
height: 100%;
}
// 响应式布局
@media screen and (max-width: 1200px) {
.total-view-content {
flex-wrap: wrap;
}
.stats-card {
width: calc(50% - 10px);
}
}
@media screen and (max-width: 768px) {
.stats-card {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,158 @@
<template>
<div class="model">
<!-- 标题栏 -->
<el-row :gutter="24">
<el-col :span="12">
<TitleComponent title="考勤管理" subtitle="项目出勤情况、人员排班及请假调休管理" />
</el-col>
<!-- 外层col控制整体宽度并右对齐同时作为flex容器 -->
<el-col :span="12" style="display: flex; justify-content: flex-end; align-items: center;">
<!-- 子col1下拉 -->
<el-col :span="4">
<el-select placeholder="选择电站">
<el-option label="所有电站" value="all"></el-option>
</el-select>
</el-col>
<!-- 子col2下拉框容器 -->
<el-col :span="4">
<el-select placeholder="日期范围">
<el-option label="所有月份" value="all"></el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-button type="primary">
导出数据
<el-icon class="el-icon--right">
<UploadFilled />
</el-icon>
</el-button>
</el-col>
</el-col>
</el-row>
<el-row>
<!-- 左侧 -->
<el-col :span="17">
<el-row>
<el-col :span="24">
<totalView ></totalView>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<attendTrend :attendData="attendData"></attendTrend>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
</el-col>
</el-row>
</el-col>
<!-- 右侧 -->
<el-col :span="7">
<!-- hello -->
<el-row>
<el-col :span="24">
<infoBox></infoBox>
</el-col>
</el-row>
<!-- 日历 -->
<el-row>
<el-col :span="24">
<el-card>
<calendar></calendar>
<todayAttend></todayAttend>
<approval></approval>
</el-card>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import infoBox from '@/views/integratedManage/attendManage/components/infoBox.vue'
import attendTrend from '@/views/integratedManage/attendManage/components/attendTrend.vue'
import todayAttend from '@/views/integratedManage/attendManage/components/leftBox/todayAttend.vue'
import approval from '@/views/integratedManage/attendManage/components/leftBox/approval.vue'
import calendar from '@/views/integratedManage/attendManage/components/leftBox/calendar.vue'
import totalView from '@/views/integratedManage/attendManage/components/totalView.vue'
const attendData = ref(
{
week: {
xAxis: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
actualCount: [40, 20, 30, 15, 22, 63, 58, 43, 39, 36],
expectedCount: [100, 556, 413, 115, 510, 115, 317, 118, 14, 7]
},
month: {
xAxis: ['第1周', '第2周', '第3周', '第4周'],
actualData: [280, 360, 320, 400],
theoreticalData: [300, 400, 350, 450]
},
}
)
// 创建itembox数据数组用于循环渲染
const itemBoxData = ref([
{
title: '总发电量',
value: '2,456.8',
unit: 'KWh',
growth: '2.5',
growthLabel: '较昨日',
color: '#186DF5',
chartType: 'bar',
power: '',
iconSrc: '/src/assets/demo/shandian.png',
type: 'up',
chartData: [30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]
},
{
title: '平均效率',
value: '18.7',
unit: '%',
growth: '2.5',
growthLabel: '较昨日',
color: '#00B87A',
chartType: 'line',
power: '',
iconSrc: '/src/assets/demo/huojian.png',
type: 'up',
chartData: [30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]
},
{
title: '设备温度',
value: '43.5',
unit: '℃',
growth: '2.5',
growthLabel: '较昨日',
color: '#FFC300',
chartType: 'line',
power: '',
iconSrc: '/src/assets/demo/wendu.png',
type: 'up',
chartData: [30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]
},
{
title: '系统可用性',
value: '18.7',
unit: '%',
growth: '2.5',
growthLabel: '较昨日',
color: '#7948EA',
chartType: 'line',
power: '',
iconSrc: '/src/assets/demo/use.png',
type: 'up',
chartData: [30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]
}
]);
</script>
<style scoped lang="scss">
.model {
padding: 20px 15px;
background-color: rgba(242, 248, 252, 1);
}
</style>

View File

@ -0,0 +1,433 @@
<template>
<div class="manage-form-container">
<!-- 搜索和筛选区域 -->
<div class="search-filter-section">
<el-row gutter="12" align="middle">
<el-col :span="2">
<el-select v-model="searchForm.deviceType" placeholder="设备类型" clearable>
<el-option label="全部类型" value="" />
<el-option label="逆变器" value="逆变器" />
<el-option label="传感器" value="传感器" />
<el-option label="电表" value="电表" />
<el-option label="摄像头" value="摄像头" />
<el-option label="控制器" value="控制器" />
</el-select>
</el-col>
<el-col :span="2">
<el-select v-model="searchForm.status" placeholder="设备状态" clearable>
<el-option label="全部状态" value="" />
<el-option label="正常" value="normal" />
<el-option label="异常" value="abnormal" />
<el-option label="中断" value="interrupt" />
</el-select>
</el-col>
<el-col :span="2">
<el-select v-model="searchForm.protocol" placeholder="通讯状态" clearable>
<el-option label="全部状态" value="" />
<el-option label="Modbus TCP" value="Modbus TCP" />
<el-option label="其他协议" value="其他" />
</el-select>
</el-col>
<el-col :span="2">
<el-select v-model="searchForm.station" placeholder="所属电站" clearable>
<el-option label="全部电站" value="" />
<el-option label="兴电基站1" value="兴电基站1" />
<el-option label="兴电基站2" value="兴电基站2" />
<el-option label="兴电基站3" value="兴电基站3" />
<el-option label="兴电基站4" value="兴电基站4" />
<el-option label="兴电基站5" value="兴电基站5" />
</el-select>
</el-col>
<el-col :span="2">
<el-button type="primary" @click="handleSearch" style="width: 100%">
<el-icon><Search /></el-icon>
搜索
</el-button>
</el-col>
<el-col :span="3">
<el-button type="primary" @click="handleAddDevice" style="width: 100%">
<el-icon><CirclePlus /></el-icon>
添加设备
</el-button>
</el-col>
<el-col :span="3">
<el-button type="primary" @click="handleBatchConfig" style="width: 100%">
<el-icon><Setting /></el-icon>
批量配置
</el-button>
</el-col>
</el-row>
</div>
<!-- 设备信息表格 -->
<el-table v-loading="loading" :data="deviceList" style="width: 100%">
<el-table-column prop="deviceId" label="设备ID" min-width="120" align="center" />
<el-table-column prop="deviceName" label="设备名称" min-width="120" align="center" />
<el-table-column prop="deviceType" label="类型" min-width="100" align="center">
<template #default="scope">
<el-tag :type="getDeviceTypeTagType(scope.row.deviceType)" :effect="'light'">
{{ scope.row.deviceType }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="station" label="所属电站" min-width="120" align="center" />
<el-table-column prop="protocol" label="通讯协议" min-width="100" align="center" />
<el-table-column prop="ipAddress" label="IP地址" min-width="120" align="center" />
<el-table-column prop="lastOnlineTime" label="最后在线时间" min-width="150" align="center" />
<el-table-column prop="status" label="状态" min-width="80">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)" :effect="light">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="150" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleDetails(scope.row)" size="small">
查看
</el-button>
<el-button type="primary" link @click="handleConfig(scope.row)" size="small">
配置
</el-button>
<el-button type="primary" link @click="handleDelete(scope.row)" size="small">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<div class="pagination-container">
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]" layout="prev, pager, next, jumper"
:total="pagination.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
</div>
</template>
<script setup>
import { Search, CirclePlus, Setting } from '@element-plus/icons-vue';
import { ref, reactive } from 'vue';
// 搜索表单数据
const searchForm = reactive({
deviceType: '',
station: '',
protocol: '',
status: '',
keyword: ''
});
// 表格加载状态
const loading = ref(false);
// 分页数据
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 545
});
// 设备列表数据
const deviceList = ref([
{
deviceId: 'WO-2023-0620-056',
deviceName: '逆变器-01',
deviceType: '逆变器',
station: '兴电基站1',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'normal'
},
{
deviceId: 'WO-2023-0620-057',
deviceName: '温度传感器-45',
deviceType: '传感器',
station: '兴电基站2',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'interrupt'
},
{
deviceId: 'WO-2023-0620-058',
deviceName: '智能电表-03',
deviceType: '电表',
station: '兴电基站3',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'abnormal'
},
{
deviceId: 'WO-2023-0620-059',
deviceName: '监控摄像头-02',
deviceType: '摄像头',
station: '兴电基站4',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'normal'
},
{
deviceId: 'WO-2023-0620-060',
deviceName: '控制器-07',
deviceType: '控制器',
station: '兴电基站5',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'normal'
},
{
deviceId: 'WO-2023-0620-061',
deviceName: '逆变器-02',
deviceType: '逆变器',
station: '兴电基站1',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'normal'
},
{
deviceId: 'WO-2023-0620-062',
deviceName: '电流传感器-08',
deviceType: '传感器',
station: '兴电基站1',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'normal'
},
{
deviceId: 'WO-2023-0620-063',
deviceName: '多功能电表-12',
deviceType: '电表',
station: '兴电基站1',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'normal'
},
{
deviceId: 'WO-2023-0620-064',
deviceName: '门禁摄像头-05',
deviceType: '摄像头',
station: '兴电基站1',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'normal'
},
{
deviceId: 'WO-2023-0620-065',
deviceName: '开关控制器-15',
deviceType: '控制器',
station: '兴电基站1',
protocol: 'Modbus TCP',
ipAddress: '192.168.1.101',
lastOnlineTime: '2023-06-30 17:00',
status: 'normal'
}
]);
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
normal: '正常',
interrupt: '中断',
abnormal: '异常'
};
return statusMap[status] || status;
};
// 获取状态标签类型
const getStatusTagType = (status) => {
const typeMap = {
normal: 'success',
interrupt: 'warning',
abnormal: 'danger'
};
return typeMap[status] || 'default';
};
// 获取设备类型标签类型
const getDeviceTypeTagType = (deviceType) => {
const typeMap = {
'逆变器': 'primary',
'传感器': 'success',
'电表': 'warning',
'摄像头': 'info',
'控制器': 'danger'
};
return typeMap[deviceType] || 'default';
};
// 处理搜索
const handleSearch = () => {
loading.value = true;
// 模拟搜索请求
setTimeout(() => {
loading.value = false;
ElMessage.success('搜索成功');
// 实际项目中这里应该调用API获取数据
}, 500);
};
// 处理添加设备
const handleAddDevice = () => {
// 实际项目中这里应该打开添加设备的弹窗或跳转到添加页面
ElMessage.success('打开添加设备窗口');
};
// 处理批量配置
const handleBatchConfig = () => {
// 实际项目中这里应该打开批量配置的弹窗
ElMessage.success('打开批量配置窗口');
};
// 处理查看详情
const handleDetails = (row) => {
// 实际项目中这里应该打开设备详情的弹窗或跳转到详情页面
ElMessage.success(`查看设备${row.deviceCode}详情`);
};
// 处理配置
const handleConfig = (row) => {
// 实际项目中这里应该打开设备配置的弹窗
ElMessage.success(`配置设备${row.deviceCode}`);
};
// 处理删除
const handleDelete = (row) => {
ElMessageBox.confirm(
`确定要删除设备${row.deviceCode}吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(() => {
// 实际项目中这里应该调用API删除设备
ElMessage.success('删除成功');
})
.catch(() => {
ElMessage.info('已取消删除');
});
};
// 处理分页大小变化
const handleSizeChange = (size) => {
pagination.pageSize = size;
// 实际项目中这里应该重新请求数据
};
// 处理分页页码变化
const handleCurrentChange = (current) => {
pagination.currentPage = current;
// 实际项目中这里应该重新请求数据
};
</script>
<style scoped>
.manage-form-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
min-height: 100%;
}
.search-filter-section {
margin-bottom: 24px;
padding: 16px;
background-color: #f5f7fa;
border-radius: 8px;
}
/* 下拉选择框样式优化 */
:deep(.el-select) {
width: 100%;
}
:deep(.el-input__wrapper) {
border-radius: 6px;
}
/* 按钮样式优化 */
:deep(.el-button) {
border-radius: 6px;
font-size: 14px;
transition: all 0.3s ease;
}
:deep(.el-button--primary) {
background-color: #1890ff;
border-color: #1890ff;
}
:deep(.el-button--primary:hover) {
background-color: #40a9ff;
border-color: #40a9ff;
}
/* 响应式设计 */
@media screen and (max-width: 1200px) {
.search-filter-section .el-col {
margin-bottom: 12px;
}
}
@media screen and (max-width: 768px) {
.search-filter-section {
padding: 12px;
}
.search-filter-section .el-row {
display: flex;
flex-direction: column;
}
.search-filter-section .el-col {
width: 100% !important;
margin-bottom: 12px;
}
}
.action-buttons {
margin-bottom: 20px;
display: flex;
gap: 10px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
align-items: center;
}
/* 表格样式优化 */
:deep(.el-table) {
border-radius: 8px;
overflow: hidden;
}
:deep(.el-table__header-wrapper) {
background-color: #fafafa;
}
:deep(.el-table__row:hover) {
background-color: #f5f7fa;
}
/* 分页样式优化 */
:deep(.el-pagination) {
display: flex;
justify-content: flex-end;
}
</style>

View File

@ -0,0 +1,214 @@
<template>
<div class="chart-container">
<!--组件温度 图表内容区域 -->
<div ref="chartRef" class="chart-content"></div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
// 图表DOM引用
const chartRef = ref(null);
// 图表实例
let chartInstance = null;
// 初始化图表
const initChart = () => {
if (chartRef.value && !chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
legend: {
show: true,
left: '8%',
icon: 'square',
itemWidth: 12,
itemHeight: 12,
textStyle: {
color: '#4E5969'
}
},
xAxis: {
type: 'category',
axisTick: {
show: false
},
axisLabel: {
textStyle: {
color: '#4E5969'
}
},
axisLine: {
lineStyle: {
color: '#EAEBF0'
}
},
data: ['9-12', '9-13', '9-14', '9-15', '9-16', '9-17', '9-18']
},
yAxis: {
type: 'value',
max: 150,
interval: 50,
axisLabel: {
textStyle: {
color: '#4E5969'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#EAEBF0',
type: 'dashed'
}
}
},
series: [
{
name: '正常',
data: [20, 10, 50, 80, 70, 10, 30],
type: 'bar',
stack: 'one',
color: '#7339F5',
itemStyle: {
// borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 1)', //同背景色一样
barBorderRadius: 8
},
barWidth: '20',
},
{
name: '中断',
data: [80, 30, 50, 80, 70, 10, 30],
type: 'bar',
stack: 'one', //堆叠
color: '#FF8A00',
itemStyle: {
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 1)', //同背景色一样
barBorderRadius: 8
},
},
{
name: '异常',
data: [50, 30, 50, 80, 70, 10, 30],
type: 'bar',
stack: 'one', //堆叠
color: '#DE4848',
itemStyle: {
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 1)', //同背景色一样
barBorderRadius: 8
},
barWidth: '12',
}
]
};
chartInstance.setOption(option);
};
// 响应窗口大小变化
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 生命周期钩子
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
};
});
</script>
<style scoped>
.chart-container {
background-color: #fff;
border-radius: 8px;
overflow: hidden;
height: 400px;
width: 100%;
padding: 5px;
box-sizing: border-box;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
}
.chart-header h2 {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
}
.chart-content {
width: 100%;
height: 100%;
padding: 5px;
box-sizing: border-box;
}
@media (max-width: 768px) {
.chart-container {
height: 350px;
}
}
@media (max-width: 480px) {
.chart-container {
height: 300px;
}
.chart-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.chart-actions {
width: 100%;
display: flex;
justify-content: space-between;
}
.chart-actions button {
margin: 0;
flex: 1;
margin-right: 5px;
}
.chart-actions button:last-child {
margin-right: 0;
}
}
</style>

View File

@ -0,0 +1,202 @@
<template>
<div class="chart-container">
<!--组件温度 图表内容区域 -->
<div ref="chartRef" class="chart-content"></div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
// 图表DOM引用
const chartRef = ref(null);
// 图表实例
let chartInstance = null;
// 初始化图表
const initChart = () => {
if (chartRef.value && !chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
const option = {
tooltip: {
trigger: 'item',
formatter: function(params) {
// 定义名称映射关系
const nameMap = {
'提示信息': '设备正常',
'一般告警': '设备中断',
'重要告警': '设备异常'
};
// 使用ECharts提供的百分比值
const percentage = params.percent.toFixed(1);
// 返回格式化后的文本
return `${nameMap[params.name]}: ${params.value}台 (${percentage}%)`;
}
},
grid: {
left: '0%',
right: '20%',
bottom: '0%',
top: '0%',
containLabel: true
},
legend: {
top: 'middle',
orient: 'vertical',
right: '5%', // 调整图例位置,使其更靠近左侧
itemWidth: 15,
itemHeight: 15,
formatter: function(name) {
// 定义名称映射关系
const nameMap = {
'提示信息': '设备正常',
'一般告警': '设备中断',
'重要告警': '设备异常'
};
// 定义数值映射关系
const valueMap = {
'提示信息': 28,
'一般告警': 45,
'重要告警': 55
};
// 返回格式化后的文本
return `${nameMap[name] || name}(${valueMap[name]})`;
}
},
series: [
{
type: 'pie',
radius: ['40%', '70%'],
center:['40%','50%'],
label: {
show: false
},
labelLine: {
show: true
},
color: [
'#43CF7C', // 设备正常
'#00B3FF', // 设备中断
'#FB3E7A', // 设备异常
],
data: [
{ value: 28, name: '提示信息' },
{ value: 45, name: '一般告警' },
{ value: 55, name: '重要告警' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
chartInstance.setOption(option);
};
// 响应窗口大小变化
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 生命周期钩子
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
};
});
</script>
<style scoped>
.chart-container {
background-color: #fff;
border-radius: 8px;
overflow: hidden;
height: 250px;
width: 100%;
padding: 5px;
box-sizing: border-box;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
}
.chart-header h2 {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
}
.chart-content {
width: 100%;
height: 100%;
padding: 5px;
box-sizing: border-box;
}
@media (max-width: 768px) {
.chart-container {
height: 350px;
}
}
@media (max-width: 480px) {
.chart-container {
height: 300px;
}
.chart-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.chart-actions {
width: 100%;
display: flex;
justify-content: space-between;
}
.chart-actions button {
margin: 0;
flex: 1;
margin-right: 5px;
}
.chart-actions button:last-child {
margin-right: 0;
}
}
.model {
padding: 20px;
background-color: rgba(242, 248, 252, 1);
}
</style>

View File

@ -0,0 +1,152 @@
<template>
<div class="chart-container">
<el-row style="padding: 0 0 0 20px;box-sizing: border-box;">
<el-col :span="6">
<div class="item-box">
<div class="item-icon">
<img src="@/assets/demo/rebot.png" alt="">
</div>
<div class="item-title">设备总数</div>
<div class="item-value">128</div>
<div class="item-unit"></div>
<div class="item-trend">
<img src="@/assets/demo/up.png" alt="">
<span class="trend-num">8</span>
<span class="trend-des">较上月同期</span>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="item-box">
<div class="item-icon">
<img src="@/assets/demo/wifi.png" alt="">
</div>
<div class="item-title">设备总数</div>
<div class="item-value">128</div>
<div class="item-unit"></div>
<div class="item-trend">
<img src="@/assets/demo/up.png" alt="">
<span class="trend-num">8</span>
<span class="trend-des">较上月同期</span>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="item-box">
<div class="item-icon">
<img src="@/assets/demo/wifiwarn.png" alt="">
</div>
<div class="item-title">设备总数</div>
<div class="item-value">128</div>
<div class="item-unit"></div>
<div class="item-trend">
<img src="@/assets/demo/up.png" alt="">
<span class="trend-num">8</span>
<span class="trend-des">较上月同期</span>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="item-box">
<div class="item-icon">
<img src="@/assets/demo/nowifi.png" alt="">
</div>
<div class="item-title">设备总数</div>
<div class="item-value">128</div>
<div class="item-unit"></div>
<div class="item-trend">
<img src="@/assets/demo/up.png" alt="" class="trend-icon">
<span class="trend-num">8</span>
<span class="trend-des">较上月同期</span>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<style scoped lang="scss">
.item-box {
padding: 20px;
width: 216px;
height: 282px;
border-radius: 10px;
// border: 1px solid red;
background: rgba(255, 255, 255, 1);
display: flex;
flex-direction: column;
justify-content: space-between;
.item-icon {
width: 49.94px;
height: 49.91px;
border-radius: 8px;
background: rgba(24, 109, 245, 1);
display: flex;
align-items: center;
justify-content: center;
}
.item-title {
width: 129.85px;
height: 21.21px;
/** 文本1 */
font-size: 16px;
font-weight: 500;
letter-spacing: 0px;
line-height: 23.17px;
color: rgba(182, 182, 182, 1);
text-align: left;
vertical-align: top;
}
.item-value {
border-radius: 10px;
background: rgba(255, 255, 255, 1);
font-size: 24px;
font-weight: 400;
letter-spacing: 0px;
line-height: 34.75px;
color: rgba(0, 0, 0, 1);
text-align: left;
vertical-align: top;
}
.item-unit {
width: 38.71px;
height: 21.21px;
opacity: 1;
/** 文本1 */
font-size: 16px;
font-weight: 500;
letter-spacing: 0px;
line-height: 23.17px;
color: rgba(182, 182, 182, 1);
text-align: left;
vertical-align: top;
}
.item-trend {
.trend-num {
font-size: 14px;
font-weight: 500;
letter-spacing: 0px;
line-height: 14.48px;
color: rgba(0, 184, 122, 1);
text-align: center;
vertical-align: middle;
margin-right: 10px;
margin-left: 10px;
}
.trend-des {
font-size: 14px;
font-weight: 500;
letter-spacing: 0px;
line-height: 14.48px;
color: rgba(154, 154, 154, 1);
text-align: left;
vertical-align: middle;
}
}
}
</style>

View File

@ -0,0 +1,132 @@
<template>
<div class="model">
<!-- 标题栏 -->
<el-row :gutter="24">
<el-col :span="12">
<TitleComponent title="报警管理" subtitle="配置新能源厂站的报警级别、类型及相关规则" />
</el-col>
<!-- 外层col控制整体宽度并右对齐同时作为flex容器 -->
<el-col :span="12" style="display: flex; justify-content: flex-end; align-items: center;">
<el-col :span="4">
<el-button type="primary">
导出数据
<el-icon class="el-icon--right">
<UploadFilled />
</el-icon>
</el-button>
</el-col>
</el-col>
</el-row>
<!-- 第一行报警管理和报警级别分布 -->
<el-row :gutter="20" class="content-row equal-height-row">
<el-col :span="16">
<el-card shadow="hover" class="custom-card">
<totalView />
</el-card>
</el-col>
<el-col :span="8">
<!-- 报警级别分布 -->
<el-card shadow="hover" class="custom-card">
<TitleComponent title="报警级别分布" :font-level="2" />
<statusPie />
</el-card>
</el-col>
</el-row>
<!-- 第二行报警趋势分析 -->
<el-row :gutter="20" class="content-row">
<el-col :span="24">
<el-card shadow="hover" class="custom-card">
<TitleComponent title="报警趋势分析" :font-level="2" />
<stateTrend />
</el-card>
</el-col>
</el-row>
<!-- 第三行报警管理表单 -->
<el-row :gutter="20" class="content-row">
<el-col :span="24">
<el-card shadow="hover" class="custom-card">
<TitleComponent title="报警管理表单" :font-level="2" />
<manageForm />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import TitleComponent from '@/components/TitleComponent/index.vue';
import totalView from '@/views/integratedManage/stateManage/components/totalView.vue';
import stateTrend from '@/views/integratedManage/stateManage/components/stateTrend.vue'
import statusPie from '@/views/integratedManage/stateManage/components/statusPie.vue'
import manageForm from '@/views/integratedManage/stateManage/components/manageForm.vue';
</script>
<style scoped>
.model {
padding: 20px 15px;
background-color: rgba(242, 248, 252, 1);
}
.content-row {
margin-bottom: 20px;
}
.custom-card {
border-radius: 8px;
transition: all 0.3s ease;
border: none;
}
.custom-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.equal-height-row {
display: flex;
align-items: stretch;
}
.equal-height-row .el-col {
display: flex;
}
.equal-height-row .custom-card {
flex: 1;
display: flex;
flex-direction: column;
}
.equal-height-row .el-card__body {
flex: 1;
display: flex;
flex-direction: column;
}
/* 响应式布局调整 */
@media (max-width: 1200px) {
.content-row {
margin-bottom: 15px;
}
.equal-height-row {
flex-direction: column;
}
.equal-height-row .el-col {
width: 100%;
margin-bottom: 20px;
}
}
@media (max-width: 768px) {
.model {
padding: 15px 10px;
}
.content-row {
margin-bottom: 10px;
}
}
</style>

View File

@ -18,8 +18,12 @@
<div class="metric-value">{{ props.dashboardData.todayAlarmTotal }}</div>
<div class="metric-label">今日报警总数</div>
<div class="metric-change">较上周 <span :class="props.dashboardData.updates.todayAlarmTotal.type">
{{ props.dashboardData.updates.todayAlarmTotal.type === 'up' ? '↑' : '↓' }}{{ props.dashboardData.updates.todayAlarmTotal.value }}
</span></div>
<img v-if="props.dashboardData.updates.todayAlarmTotal.type === 'up'" src="/src/assets/demo/up.png"
class="trend-icon" alt="上升">
<img v-else src="/src/assets/demo/down.png" class="trend-icon" alt="下降">{{
props.dashboardData.updates.todayAlarmTotal.value }}
</span>
</div>
</div>
</el-col>
@ -29,8 +33,11 @@
<div class="metric-value">{{ props.dashboardData.unhandledAlarms }}</div>
<div class="metric-label">未处理报警</div>
<div class="metric-change">较上周 <span :class="props.dashboardData.updates.unhandledAlarms.type">
{{ props.dashboardData.updates.unhandledAlarms.type === 'up' ? '↑' : '↓' }}{{ props.dashboardData.updates.unhandledAlarms.value }}
</span></div>
<img v-if="props.dashboardData.updates.unhandledAlarms.type === 'up'" src="/src/assets/demo/up.png"
class="trend-icon" alt="上升">
<img v-else src="/src/assets/demo/down.png" class="trend-icon" alt="下降">{{
props.dashboardData.updates.unhandledAlarms.value }}
</span></div>
</div>
</el-col>
@ -40,8 +47,11 @@
<div class="metric-value">{{ props.dashboardData.handledAlarms }}</div>
<div class="metric-label">已处理报警</div>
<div class="metric-change">较上周 <span :class="props.dashboardData.updates.handledAlarms.type">
{{ props.dashboardData.updates.handledAlarms.type === 'up' ? '↑' : '↓' }}{{ props.dashboardData.updates.handledAlarms.value }}
</span></div>
<img v-if="props.dashboardData.updates.handledAlarms.type === 'up'" src="/src/assets/demo/up.png"
class="trend-icon" alt="上升">
<img v-else src="/src/assets/demo/down.png" class="trend-icon" alt="下降">{{
props.dashboardData.updates.handledAlarms.value }}
</span></div>
</div>
</el-col>
@ -50,9 +60,15 @@
<div class="metric-card">
<div class="metric-value">{{ props.dashboardData.avgProcessTime }}</div>
<div class="metric-label">平均处理时长</div>
<div class="metric-change">较上周 <span :class="props.dashboardData.updates.avgProcessTime.type">
{{ props.dashboardData.updates.avgProcessTime.type === 'up' ? '↑' : '↓' }}{{ props.dashboardData.updates.avgProcessTime.value }}
</span></div>
<div class="metric-change">
较上周
<span :class="props.dashboardData.updates.avgProcessTime.type">
<img v-if="props.dashboardData.updates.avgProcessTime.type === 'up'" src="/src/assets/demo/up.png"
class="trend-icon" alt="上升">
<img v-else src="/src/assets/demo/down.png" class="trend-icon" alt="下降">{{
props.dashboardData.updates.avgProcessTime.value }}
</span>
</div>
</div>
</el-col>
</el-row>
@ -67,7 +83,7 @@
<el-option label="近90天" value="90days" />
</el-select>
</div>
<el-col :span="24">
<div class="trend-section">
<el-row :gutter="20">
@ -76,11 +92,17 @@
<div class="chart-container">
<div class="chart-left-right-layout">
<div class="chart-info-container">
<div class="chart-title">报警数量() </div>
<div class="chart-title">报警数量() </div>
<div class="chart-total">{{ props.chartData.totals.alarmCount }}</div>
<div class="chart-value">较昨日 <span :class="props.chartData.dailyChanges.alarmCount.type">
{{ props.chartData.dailyChanges.alarmCount.type === 'up' ? '↑' : '↓' }}{{ props.chartData.dailyChanges.alarmCount.value }}
</span></div>
<div class="chart-value">
<span>较昨日</span>
<img v-if="props.chartData.dailyChanges.processEfficiency.type === 'up'"
src="/src/assets/demo/up.png" class="trend-icon" alt="上升">
<img v-else src="/src/assets/demo/down.png" class="trend-icon" alt="下降">
<span :class="props.chartData.dailyChanges.processEfficiency.type">
{{ props.chartData.dailyChanges.processEfficiency.value }}
</span>
</div>
</div>
<div ref="alarmCountRef" class="chart-content"></div>
</div>
@ -93,9 +115,15 @@
<div class="chart-info-container">
<div class="chart-title">报警处理效率(%)</div>
<div class="chart-total">{{ props.chartData.totals.processEfficiency }}</div>
<div class="chart-value">较昨日 <span :class="props.chartData.dailyChanges.processEfficiency.type">
{{ props.chartData.dailyChanges.processEfficiency.type === 'up' ? '↑' : '↓' }}{{ props.chartData.dailyChanges.processEfficiency.value }}
</span></div>
<div class="chart-value">
<span>较昨日</span>
<img v-if="props.chartData.dailyChanges.processEfficiency.type === 'up'"
src="/src/assets/demo/up.png" class="trend-icon" alt="上升">
<img v-else src="/src/assets/demo/down.png" class="trend-icon" alt="下降">
<span :class="props.chartData.dailyChanges.processEfficiency.type">
{{ props.chartData.dailyChanges.processEfficiency.value }}
</span>
</div>
</div>
<div ref="processEfficiencyRef" class="chart-content"></div>
</div>
@ -141,7 +169,7 @@ const props = defineProps({
processEfficiency: '89%'
},
dailyChanges: {
alarmCount: { value: '0.9%', type: 'down' },
alarmCount: { value: '0.9%', type: 'up' },
processEfficiency: { value: '0.9%', type: 'down' }
}
})
@ -172,7 +200,7 @@ const initCharts = () => {
trigger: 'axis',
},
grid: {
left: '-45px',
left: '-38px',
right: '0%',
bottom: '0%',
top: '0%',
@ -248,7 +276,7 @@ const initCharts = () => {
trigger: 'axis',
},
grid: {
left: '-45px',
left: '-38px',
right: '0%',
bottom: '0%',
top: '0%',
@ -352,7 +380,7 @@ onUnmounted(() => {
width: 100%;
height: 100%;
background: #fff;
padding:0 20px;
padding: 0 20px;
box-sizing: border-box;
}
@ -411,11 +439,18 @@ onUnmounted(() => {
}
.up {
color: #ff4d4f;
color: #00B87A;
}
.down {
color: #52c41a;
color: #ff4d4f;
}
.trend-icon {
// width: 12px;
// height: 12px;
margin-right: 2px;
vertical-align: middle;
}
.trend-container {
@ -436,7 +471,7 @@ onUnmounted(() => {
}
.trend-header {
align-items: center;
}
@ -483,6 +518,24 @@ onUnmounted(() => {
font-size: 12px;
color: #999;
margin-bottom: 0;
display: flex;
align-items: center;
gap: 4px;
line-height: 1;
}
.chart-value span {
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.chart-value .trend-icon {
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
margin: 0;
}
.chart-content {

View File

@ -68,9 +68,9 @@ const dashboardData = ref({
handledAlarms: 16,
avgProcessTime: '42分钟',
updates: {
todayAlarmTotal: { value: '4.2%', type: 'down' },
unhandledAlarms: { value: '5%', type: 'up' },
handledAlarms: { value: '8%', type: 'down' },
todayAlarmTotal: { value: '4.2%', type: 'up' },
unhandledAlarms: { value: '5%', type: 'down' },
handledAlarms: { value: '8%', type: 'up' },
avgProcessTime: { value: '10%', type: 'down' }
}
});
@ -84,7 +84,7 @@ const chartData = ref({
processEfficiency: '89%'
},
dailyChanges: {
alarmCount: { value: '0.9%', type: 'down' },
alarmCount: { value: '0.9%', type: 'up' },
processEfficiency: { value: '0.9%', type: 'down' }
}
});
@ -155,7 +155,7 @@ onMounted(() => {
</script>
<style scoped lang="scss">
.model {
padding: 0px 15px;
padding: 20px 15px;
background-color: rgba(242, 248, 252, 1);
}
</style>

View File

@ -6,14 +6,8 @@
<div class="card-title">总发电量</div>
<div class="card-value">{{ props.statusData.totalPower }}</div>
<div class="card-change positive">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18"
height="18" viewBox="0 0 18 18" fill="none">
<path
d="M15.15 5.77505L15.15 5.70006L15.15 5.62505L15.075 5.62505C15.075 5.62505 15 5.62505 14.925 5.55005L11.25 5.55005C10.875 5.55005 10.575 5.85005 10.575 6.22505C10.575 6.60006 10.875 6.90005 11.25 6.90005L13.125 6.90005L9.52501 10.5L7.72501 8.70005C7.35 8.32505 6.60001 8.32505 6.225 8.70005L3.075 11.85C2.85 12.075 2.85 12.525 3.075 12.75C3.22501 12.9001 3.37501 12.975 3.525 12.975C3.67501 12.975 3.82501 12.975 3.975 12.75L6.9 9.82505L8.7 11.625C9.07501 12.0001 9.825 12.0001 10.2 11.625L13.95 7.87505L13.95 9.75006C13.95 10.1251 14.25 10.4251 14.625 10.4251C15 10.4251 15.3 10.1251 15.3 9.75006L15.3 6.37506L15.3 6.15006L15.15 5.77505Z"
fill="#00B87A">
</path>
</svg>
{{ props.statusData.totalPowerChange }} 较上周
<img src="/src/assets/demo/up.png" alt="" class="change-icon">
<span>{{ props.statusData.totalPowerChange }} 较上周</span>
</div>
</div>
<div class="card-right">
@ -37,14 +31,8 @@
<div class="card-title">系统效率</div>
<div class="card-value">{{ props.statusData.efficiency }}</div>
<div class="card-change negative">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18"
height="18" viewBox="0 0 18 18" fill="none">
<path
d="M15.15 12.2252L15.15 12.3002L15.15 12.3752L15.075 12.3752C15.075 12.3752 15 12.3752 14.925 12.4502L11.25 12.4502C10.875 12.4502 10.575 12.1502 10.575 11.7752C10.575 11.4002 10.875 11.1002 11.25 11.1002L13.125 11.1002L9.52501 7.5002L7.72501 9.3002C7.35 9.67519 6.60001 9.67519 6.225 9.3002L3.075 6.1502C2.85 5.9252 2.85 5.4752 3.075 5.2502C3.22501 5.10019 3.37501 5.0252 3.525 5.0252C3.67501 5.0252 3.82501 5.0252 3.975 5.2502L6.9 8.1752L8.7 6.3752C9.07501 6.00019 9.825 6.00019 10.2 6.3752L13.95 10.1252L13.95 8.25019C13.95 7.87519 14.25 7.57519 14.625 7.57519C15 7.57519 15.3 7.87519 15.3 8.25019L15.3 11.6252L15.3 11.8502L15.15 12.2252Z"
fill="#E32727">
</path>
</svg>
{{ props.statusData.efficiencyChange }} 较上周
<img src="/src/assets/demo/down.png" alt="" class="change-icon">
<span>{{ props.statusData.efficiencyChange }} 较上周</span>
</div>
</div>
<div class="card-right">
@ -65,14 +53,8 @@
<div class="card-title">组件温度</div>
<div class="card-value">{{ props.statusData.temperature }}</div>
<div class="card-change positive">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18"
height="18" viewBox="0 0 18 18" fill="none">
<path
d="M15.15 5.77505L15.15 5.70006L15.15 5.62505L15.075 5.62505C15.075 5.62505 15 5.62505 14.925 5.55005L11.25 5.55005C10.875 5.55005 10.575 5.85005 10.575 6.22505C10.575 6.60006 10.875 6.90005 11.25 6.90005L13.125 6.90005L9.52501 10.5L7.72501 8.70005C7.35 8.32505 6.60001 8.32505 6.225 8.70005L3.075 11.85C2.85 12.075 2.85 12.525 3.075 12.75C3.22501 12.9001 3.37501 12.975 3.525 12.975C3.67501 12.975 3.82501 12.975 3.975 12.75L6.9 9.82505L8.7 11.625C9.07501 12.0001 9.825 12.0001 10.2 11.625L13.95 7.87505L13.95 9.75006C13.95 10.1251 14.25 10.4251 14.625 10.4251C15 10.4251 15.3 10.1251 15.3 9.75006L15.3 6.37506L15.3 6.15006L15.15 5.77505Z"
fill="#00B87A">
</path>
</svg>
{{ props.statusData.temperatureChange }} 较上周
<img src="/src/assets/demo/up.png" alt="" class="change-icon">
<span>{{ props.statusData.temperatureChange }} 较上周</span>
</div>
</div>
<div class="card-right">
@ -93,14 +75,8 @@
<div class="card-title">日照强度</div>
<div class="card-value">{{ props.statusData.sunlight }}</div>
<div class="card-change positive">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18"
height="18" viewBox="0 0 18 18" fill="none">
<path
d="M15.15 5.77505L15.15 5.70006L15.15 5.62505L15.075 5.62505C15.075 5.62505 15 5.62505 14.925 5.55005L11.25 5.55005C10.875 5.55005 10.575 5.85005 10.575 6.22505C10.575 6.60006 10.875 6.90005 11.25 6.90005L13.125 6.90005L9.52501 10.5L7.72501 8.70005C7.35 8.32505 6.60001 8.32505 6.225 8.70005L3.075 11.85C2.85 12.075 2.85 12.525 3.075 12.75C3.22501 12.9001 3.37501 12.975 3.525 12.975C3.67501 12.975 3.82501 12.975 3.975 12.75L6.9 9.82505L8.7 11.625C9.07501 12.0001 9.825 12.0001 10.2 11.625L13.95 7.87505L13.95 9.75006C13.95 10.1251 14.25 10.4251 14.625 10.4251C15 10.4251 15.3 10.1251 15.3 9.75006L15.3 6.37506L15.3 6.15006L15.15 5.77505Z"
fill="#00B87A">
</path>
</svg>
{{ props.statusData.sunlightChange }} 较上周
<img src="/src/assets/demo/up.png" alt="" class="change-icon">
<span>{{ props.statusData.sunlightChange }} 较上周</span>
</div>
</div>
<div class="card-right">
@ -206,7 +182,7 @@ const props = defineProps({
margin-top: 4px;
&.positive {
color: #67c23a;
color: #00B87A;
}
&.negative {
@ -216,7 +192,7 @@ const props = defineProps({
.change-icon {
font-size: 10px;
margin-right: 2px;
margin-right: 5px;
}
.card-icon {

View File

@ -142,7 +142,7 @@ onUnmounted(() => {
.chart-content {
width: 100%;
height: calc(100% - 80px);
min-height: 200px;
min-height: 220px;
}
@media (max-width: 768px) {

View File

@ -185,7 +185,7 @@ onUnmounted(() => {
.chart-content {
width: 100%;
height: calc(100% - 80px);
min-height: 200px;
min-height: 220px;
}
@media (max-width: 768px) {

View File

@ -104,7 +104,7 @@ const mockData = ref({
totalPower: '3,456.8KWh',
totalPowerChange: '8.2%',
efficiency: '18.7%',
efficiencyChange: '-0.3%',
efficiencyChange: '0.3%',
temperature: '42.3°C',
temperatureChange: '2.1°C',
sunlight: '865 W/m²',

View File

@ -8,7 +8,8 @@
</div>
<div class="power-amount">{{ value || '2,456.8' }} <span>{{ unit || 'KWh' }}</span></div>
<div class="power-growth">
<span class="growth-value">{{ growth || '+2.5%' }}</span>
<img :src="type === 'up' ? '/src/assets/demo/up.png' : '/src/assets/demo/down.png'" alt="">
<span :class="type === 'up' ? 'up' : 'down'">{{ growth+'%' || '2.5'+'%' }}</span>
<span class="growth-label">{{ growthLabel || '较昨日' }}</span>
</div>
</div>
@ -37,6 +38,14 @@ const props = defineProps({
type: String,
default: '平均效率'
},
type: {
type: String,
default: ''
},
type: {
type: String,
default: ''
},
value: {
type: String,
default: '2,456.8'
@ -247,11 +256,14 @@ onUnmounted(() => {
flex-shrink: 0;
}
.growth-value {
font-size: 14px;
color: #13C2C2;
.up{
font-size: 14px;
color:#00B87A;
}
.down{
font-size: 14px;
color:#ff4d4f;
}
.growth-label {
font-size: 12px;
color: #999;

View File

@ -43,25 +43,20 @@
</el-row>
<!-- 数据展示-->
<el-row :gutter="24">
<el-col :span="6">
<itembox title="总发电量" value="2,456.8" unit="KWh" growth="+2.5%" growthLabel="较昨日" color="#186DF5"
chartType="bar" power="" icon-src="/src/assets/demo/shandian.png"
:chartData="[30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]"></itembox>
</el-col>
<el-col :span="6">
<itembox title="平均效率" value="18.7" unit="%" growth="+2.5%" growthLabel="较昨日" color="#00B87A"
chartType="line" icon-src="/src/assets/demo/huojian.png"
:chartData="[30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]"></itembox>
</el-col>
<el-col :span="6">
<itembox title="设备温度" value="43.5" unit="℃" growth="+2.5%" growthLabel="较昨日" color="#FFC300"
chartType="line" icon-src="/src/assets/demo/wendu.png"
:chartData="[30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]"></itembox>
</el-col>
<el-col :span="6">
<itembox title="系统可用性" value="18.7" unit="%" growth="+2.5%" growthLabel="较昨日" color="#7948EA"
chartType="line" icon-src="/src/assets/demo/use.png"
:chartData="[30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]"></itembox>
<el-col :span="6" v-for="(item, index) in itemBoxData" :key="index">
<itembox
:title="item.title"
:value="item.value"
:unit="item.unit"
:growth="item.growth"
:growthLabel="item.growthLabel"
:color="item.color"
:chartType="item.chartType"
:power="item.power"
:icon-src="item.iconSrc"
:type="item.type"
:chartData="item.chartData">
</itembox>
</el-col>
</el-row>
<!-- 第一行图表 -->
@ -159,6 +154,62 @@ const fenxiLineData = ref({
}
});
// 创建itembox数据数组用于循环渲染
const itemBoxData = ref([
{
title: '总发电量',
value: '2,456.8',
unit: 'KWh',
growth: '2.5',
growthLabel: '较昨日',
color: '#186DF5',
chartType: 'bar',
power: '',
iconSrc: '/src/assets/demo/shandian.png',
type: 'up',
chartData: [30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]
},
{
title: '平均效率',
value: '18.7',
unit: '%',
growth: '2.5',
growthLabel: '较昨日',
color: '#00B87A',
chartType: 'line',
power: '',
iconSrc: '/src/assets/demo/huojian.png',
type: 'up',
chartData: [30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]
},
{
title: '设备温度',
value: '43.5',
unit: '℃',
growth: '2.5',
growthLabel: '较昨日',
color: '#FFC300',
chartType: 'line',
power: '',
iconSrc: '/src/assets/demo/wendu.png',
type: 'up',
chartData: [30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]
},
{
title: '系统可用性',
value: '18.7',
unit: '%',
growth: '2.5',
growthLabel: '较昨日',
color: '#7948EA',
chartType: 'line',
power: '',
iconSrc: '/src/assets/demo/use.png',
type: 'up',
chartData: [30, 50, 40, 60, 80, 70, 100, 90, 85, 75, 65, 55]
}
]);
const tableData = ref([
{ time: '00:00', irradiance: 0, powerGeneration: 0.0, efficiency: 24.5, moduleTemperature: 23.5, inverterTemperature: 21.2, status: '停机' },
{ time: '08:00', irradiance: 12.5, powerGeneration: 17.2, efficiency: 28.1, moduleTemperature: 26.3, inverterTemperature: 20.3, status: '正常' },