驾驶舱

This commit is contained in:
1378012178@qq.com 2025-08-30 16:56:52 +08:00
parent 2bef563b0a
commit 4dba88a958
5 changed files with 1188 additions and 107 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,124 @@
<template>
<!-- 城区折线图 -->
<a-modal title="详情" width="80vw" v-if="cqzxtVisible" :visible="cqzxtVisible"
@cancel="handleCqzxtCancel" :okButtonProps="{ class: { 'jee-hidden': true } }" cancelText="关闭">
<ZxtCQ ref="cqzxtRef"></ZxtCQ>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineExpose } from 'vue';
import ZxtCQ from './ZxtCQ.vue'
const title = ref<string>('');
const width = ref<number>(800);
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
const cqzxtVisible = ref(false)
const cqzxtRef = ref()
const jxzxtVisible = ref(false)
const jxzxtRef = ref()
/**
* 新增
*/
function add() {
title.value = '新增';
visible.value = true;
nextTick(() => {
registerForm.value.add();
});
}
/**
* 编辑
* @param record
*/
function edit(record) {
title.value = disableSubmit.value ? '详情' : '编辑';
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
emit('success');
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
visible.value = false;
}
function cqDetail(record) {
cqzxtVisible.value = true
nextTick(() => {
cqzxtRef.value.init(record)
})
}
function handleCqzxtCancel() {
cqzxtVisible.value = false
}
function jxDetail(record) {
jxzxtVisible.value = true
nextTick(() => {
jxzxtRef.value.init(record)
})
}
function handleJxzxtCancel() {
jxzxtVisible.value = false
}
defineExpose({
add,
edit,
disableSubmit,
cqDetail,
jxDetail,
});
</script>
<style>
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
.full-modal {
.ant-modal {
max-width: 100%;
top: 0;
padding-bottom: 0;
margin: 0;
}
.ant-modal-content {
display: flex;
flex-direction: column;
height: calc(100vh);
}
.ant-modal-body {
flex: 1;
}
}
</style>

View File

@ -0,0 +1,292 @@
<template>
<div>
<span class="close-btn2" @click="closeModal"><span style="color: white;">×</span></span>
</div>
<div class="wrap">
<!-- 页面顶部中间显示 公司/锅炉房/换热站 -->
<div class="title">{{ headerText }}</div>
<!-- Tabs -->
<a-tabs v-model:activeKey="activeTab" centered @change="onTabChange">
<a-tab-pane key="temp" tab="温度指标">
<div ref="tempChartRef" class="chart-box"></div>
</a-tab-pane>
<a-tab-pane key="press" tab="压力指标">
<div ref="pressChartRef" class="chart-box"></div>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineProps, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { useMessage } from '/@/hooks/web/useMessage';
import { list } from '/@/views/heating/history/HeatanalysisHistory.api';
const emit = defineEmits(['close']);
const props = defineProps({});
const { createMessage } = useMessage();
const activeTab = ref('temp'); // tab
// 7
const end = new Date();
end.setHours(23, 59, 59, 999);
const start = new Date(end);
start.setDate(end.getDate() - 6);
start.setHours(0, 0, 0, 0);
const pad = (n: number) => String(n).padStart(2, '0');
function formatDateTime(d: Date) {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}
const tempChartRef = ref<HTMLElement | null>(null);
const pressChartRef = ref<HTMLElement | null>(null);
let tempChart: echarts.ECharts | null = null;
let pressChart: echarts.ECharts | null = null;
let tempOption: any = null;
let pressOption: any = null;
const headerText = ref(''); //
function safeNum(v: any) {
if (v === null || v === undefined || v === '') return null;
const n = Number(v);
return Number.isNaN(n) ? null : n;
}
function buildHeader(records: any[], record_: any) {
const r = (records && records.length > 0) ? records[0] : record_;
//
const names = [r.view001Name, r.view002Name, r.view004Name].filter(v => v && v !== '');
//
return names.join(' → ');
}
function initChartsOptions(records: any[]) {
records.sort((a: any, b: any) => new Date(a.datatime).getTime() - new Date(b.datatime).getTime());
const xAxis = records.map((r: any) => r.datatime);
const tempSeries = [
{ name: '一次供水温度', key: 'view005', color: '#00c6ff' }, // 使
{ name: '一次回水温度', key: 'view006', color: '#ff6a00' },
{ name: '二次供水温度', key: 'view009', color: '#ff007f' },
{ name: '二次回水温度', key: 'view010', color: '#6a5acd' },
].map(s => ({
name: s.name,
type: 'line',
showSymbol: false,
connectNulls: false,
lineStyle: { width: 2 }, // 线
itemStyle: {
color: s.color, // 线
},
data: records.map((r: any) => safeNum(r[s.key])),
}));
const pressSeries = [
{ name: '一次供水压力', key: 'view007', color: '#00c6ff' },
{ name: '一次回水压力', key: 'view008', color: '#ff6a00' },
{ name: '二次供水压力', key: 'view011', color: '#ff007f' },
{ name: '二次回水压力', key: 'view012', color: '#6a5acd' },
].map(s => ({
name: s.name,
type: 'line',
showSymbol: false,
connectNulls: false,
lineStyle: { width: 2 }, // 线
itemStyle: {
color: s.color, // 线
},
data: records.map((r: any) => safeNum(r[s.key])),
}));
const baseOption = {
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
legend: { top: 36, left: 'center', textStyle: { color: '#ffffff' } }, // 线legend
grid: { left: '5%', right: '5%', bottom: 70, top: 70, containLabel: true },
xAxis: {
type: 'category',
data: xAxis,
boundaryGap: false,
axisLabel: {
interval: 0,
formatter: (val: string) => val.replace(' ', '\n'),
color: '#ffffff' // x
},
},
yAxis: { type: 'value', scale: true, axisLabel: { color: '#ffffff' } }, // y
};
tempOption = { ...baseOption, title: { text: '温度指标', left: 'center', top: 8, textStyle: { color: '#ffffff' } }, series: tempSeries };
pressOption = { ...baseOption, title: { text: '压力指标', left: 'center', top: 8, textStyle: { color: '#ffffff' } }, series: pressSeries };
}
let resizeAdded = false;
function ensureResizeListener() {
if (resizeAdded) return;
const onResize = () => {
tempChart?.resize();
pressChart?.resize();
};
window.addEventListener('resize', onResize);
onUnmounted(() => {
window.removeEventListener('resize', onResize);
});
resizeAdded = true;
}
function init(record_: any) {
const params = {
pageNo: 1,
pageSize: -1,
regionType: '城区',
view001: record_.view001,
view002: record_.view002,
view004: record_.view004 || -1,
SDateStr: formatDateTime(start),
EDateStr: formatDateTime(end),
};
list(params).then((res: any) => {
const records = (res && res.records) ? res.records : [];
headerText.value = buildHeader(records, record_);
initChartsOptions(records);
nextTick(() => {
// tab
if (tempChartRef.value) {
tempChart = echarts.init(tempChartRef.value);
tempChart.setOption(tempOption);
}
ensureResizeListener();
});
}).catch(err => {
console.error(err);
createMessage.error('获取数据失败');
});
}
// tab init
function onTabChange(key: string) {
nextTick(() => {
if (key === 'temp') {
if (!tempChart && tempChartRef.value) {
tempChart = echarts.init(tempChartRef.value);
tempChart.setOption(tempOption);
}
tempChart?.resize();
} else if (key === 'press') {
if (!pressChart && pressChartRef.value) {
pressChart = echarts.init(pressChartRef.value);
pressChart.setOption(pressOption);
}
pressChart?.resize();
}
});
}
function closeModal() {
emit('close')
}
defineExpose({ init });
onUnmounted(() => {
if (tempChart) { tempChart.dispose(); tempChart = null; }
if (pressChart) { pressChart.dispose(); pressChart = null; }
});
</script>
<style lang="less" scoped>
.close-btn2 {
position: absolute;
top: 10px;
right: 10px;
float: right;
color: white;
cursor: pointer;
z-index: 299;
}
.wrap {
width: 100%;
margin: 0 auto;
background: linear-gradient(135deg, rgba(20, 35, 55, 0.85), rgba(0, 20, 40, 0.7));
/* 渐变背景色 */
padding: 20px;
border-radius: 15px;
/* 增加圆角 */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
/* 添加阴影 */
backdrop-filter: blur(10px);
/* 背景模糊效果 */
}
.title {
text-align: center;
font-size: 24px;
font-weight: 700;
margin: 20px 0;
color: #ffffff;
background: linear-gradient(135deg, #66d9ff, #ff8c00);
/* 渐变文字 */
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.5);
/* 增加文字的荧光效果 */
}
.chart-box {
width: 100%;
height: 60vh;
background-color: rgba(0, 0, 0, 0.3);
/* 深色背景让图表突出 */
border-radius: 12px;
/* 圆角效果 */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
/* 给图表增加阴影 */
overflow: hidden;
}
a-tabs {
background-color: rgba(0, 0, 0, 0.5);
/* Tab 背景色 */
border-radius: 8px;
}
a-tab-pane {
background-color: rgba(255, 255, 255, 0.836);
/* Tab 选项卡背景 */
border-radius: 8px;
}
a-tab-pane .ant-tabs-tab {
font-size: 16px;
font-weight: bold;
color: #ffffff;
/* 确保未选中的 Tab 文字为白色 */
padding: 10px 20px;
}
a-tab-pane .ant-tabs-tab-active {
background-color: #ff8c00;
/* 激活的Tab颜色 */
color: #ffffff;
}
a-tab-pane .ant-tabs-tab:hover {
color: #ffffff;
/* hover时也为白色 */
}
:deep .ant-tabs-tab-btn {
color: white;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB