| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- <template>
- <!-- 设备温度应力趋势卡片,通过父组件 visible 控制显隐 -->
- <div class="middle-area-content" v-if="visible">
- <div class="data-card">
- <div class="card-header">
- <!-- 标题:选中设备时显示"设备名 数据",否则显示默认标题 -->
- <h3 class="card-title">{{ device ? device.name + ' 数据' : '温度与应力监测' }}</h3>
- <div class="header-actions">
- <!-- 选中设备时显示设备路径面包屑 -->
- <span class="update-time" v-if="device">{{ breadcrumb }}</span>
- <span class="update-time" v-else>单位:温度(℃) / 应力(MPa)</span>
- <!-- 日期范围选择器:开始 / 结束 -->
- <span class="panel-date-label">开始</span>
- <input class="panel-date-input" type="date" v-model="startDate" @change="renderChart" />
- <span class="panel-date-label">结束</span>
- <input class="panel-date-input" type="date" v-model="endDate" @change="renderChart" />
- <!-- 关闭按钮(X),触发父组件 close 事件 -->
- <svg class="panel-close-btn" @click="$emit('close')" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#7bbef6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
- </div>
- </div>
- <div class="card-body chart-body">
- <div class="monitor-chart-box">
- <div class="monitor-chart-label">温度 / 应力趋势</div>
- <!-- ECharts 渲染容器:去掉固定宽高,用 100% 占满父容器 -->
- <div class="temp-chart-wrapper"><div id="tempStressChart" style="width:100%;height:200px;"></div></div>
- </div>
- <!-- 摘要数据卡片:当前温度、最大应力、平均温度、平均应力 -->
- <div class="water-stats-row" style="margin-top:4px;">
- <div class="water-stat-card">
- <div class="water-stat-label">当前温度</div>
- <div class="water-stat-value">28.6</div>
- </div>
- <div class="water-stat-card">
- <div class="water-stat-label">最大应力</div>
- <div class="water-stat-value">2.45</div>
- </div>
- <div class="water-stat-card">
- <div class="water-stat-label">平均温度</div>
- <div class="water-stat-value">23.8</div>
- </div>
- <div class="water-stat-card">
- <div class="water-stat-label">平均应力</div>
- <div class="water-stat-value">1.82</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import * as echarts from "echarts";
- export default {
- name: "TempStressCard",
- props: {
- device: { type: Object, default: null },
- breadcrumb: { type: String, default: "" },
- visible: { type: Boolean, default: false },
- },
- data() {
- return {
- startDate: "2026-01-01",
- endDate: "2026-12-31",
- chart: null,
- };
- },
- watch: {
- visible(val) {
- if (val) {
- this.startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
- this.endDate = new Date().toISOString().slice(0, 10);
- this.$nextTick(() => this.renderChart());
- } else {
- if (this.chart) {
- this.chart.dispose();
- this.chart = null;
- }
- }
- },
- },
- mounted() {
- window.addEventListener("resize", this.handleResize);
- },
- beforeUnmount() {
- window.removeEventListener("resize", this.handleResize);
- if (this.chart) this.chart.dispose();
- },
- methods: {
- handleResize() {
- if (!this.chart) return;
- this.chart.resize();
- },
- renderChart() {
- if (!echarts || !this.visible) return;
- if (this.chart) this.chart.dispose();
- const el = document.getElementById("tempStressChart");
- if (!el) return;
- const wrapper = el.parentElement;
- const dpr = window.devicePixelRatio || 1;
- // 让图表容器 100% 占满父容器
- el.style.width = "100%";
- el.style.height = "200px";
- this.chart = echarts.init(el, null, {
- renderer: "canvas",
- devicePixelRatio: dpr
- });
- const start = new Date(this.startDate);
- const end = new Date(this.endDate);
- const hours = Math.max(1, Math.round((end - start) / (60 * 60 * 1000)) + 1);
- const timeList = [];
- const tempData = [];
- const stressData = [];
- const seed = this.device ? this.device.name.charCodeAt(0) + this.device.name.length : 42;
- for (let i = 0; i < hours; i++) {
- const d = new Date(start.getTime() + i * 60 * 60 * 1000);
- const pad = n => String(n).padStart(2, "0");
- timeList.push((d.getMonth() + 1) + "/" + pad(d.getDate()) + " " + pad(d.getHours()) + ":00");
- const hourAngle = (d.getHours() / 24) * 2 * Math.PI;
- const dayOffset = i * 0.15;
- tempData.push(+(22 + 5 * Math.sin(hourAngle + dayOffset + seed) + 3 * Math.cos(dayOffset * 0.3) + (Math.random() - 0.5) * 1.5).toFixed(1));
- stressData.push(+(1.8 + 0.4 * Math.cos(hourAngle * 0.5 + dayOffset * 0.2 + seed) + 0.3 * Math.sin(dayOffset * 0.5) + (Math.random() - 0.5) * 0.2).toFixed(3));
- }
- const labelInterval = Math.max(1, Math.floor(hours / 12));
- this.chart.setOption({
- animation: false,
- tooltip: { trigger: "axis" },
- legend: {
- data: ["温度(℃)", "应力(MPa)"],
- textStyle: { color: "#7bbef6", fontSize: 10 },
- bottom: 2, // 图例紧贴底部,不占额外空间
- itemWidth: 10,
- itemHeight: 8,
- },
- // 👇 关键修改:扩大 grid 边距,让图表填满容器
- grid: {
- left: 40, // 给左Y轴留出空间
- right: 40, // 给右Y轴留出空间
- top: 15, // 顶部留少量空间
- bottom: 30, // 给X轴和图例留出空间
- containLabel: false // 取消自动包含标签,避免额外留白
- },
- xAxis: {
- type: "category",
- boundaryGap: false,
- data: timeList,
- axisLine: { lineStyle: { color: "#7bbef6" } },
- axisLabel: {
- color: "#7bbef6",
- fontSize: 8,
- interval: labelInterval,
- rotate: 0 // 保持水平不倾斜
- },
- },
- yAxis: [
- {
- type: "value",
- name: "温度(℃)",
- nameTextStyle: { color: "#62f6fb", fontSize: 10 },
- axisLine: { lineStyle: { color: "#62f6fb" } },
- axisLabel: { color: "#62f6fb", fontSize: 9 },
- splitLine: { lineStyle: { color: "rgba(123,190,246,0.15)" } },
- },
- {
- type: "value",
- name: "应力(MPa)",
- nameTextStyle: { color: "#f97316", fontSize: 10 },
- axisLine: { lineStyle: { color: "#f97316" } },
- axisLabel: { color: "#f97316", fontSize: 9 },
- splitLine: { show: false },
- },
- ],
- dataZoom: [
- { type: "inside", start: 0, end: 100 },
- ],
- series: [
- {
- name: "温度(℃)",
- type: "line",
- data: tempData,
- smooth: true,
- symbol: "none",
- lineStyle: { color: "#62f6fb", width: 1.5 },
- itemStyle: { color: "#62f6fb" },
- areaStyle: { color: "rgba(98,246,251,0.1)" },
- },
- {
- name: "应力(MPa)",
- type: "line",
- yAxisIndex: 1,
- data: stressData,
- smooth: true,
- symbol: "none",
- lineStyle: { color: "#f97316", width: 1.5 },
- itemStyle: { color: "#f97316" },
- areaStyle: { color: "rgba(249,115,22,0.08)" },
- },
- ],
- });
- },
- },
- };
- </script>
- <style scoped>
- .data-card { width: 100%; background: rgba(0,20,40,0.7); border-radius: 4px; overflow: hidden; box-shadow: 0 0 10px rgba(0,212,255,0.2); }
- .card-header { height: 42px; background-image: url("/src/assets/images/数据小标题.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; display: flex; align-items: flex-start; justify-content: space-between; padding: 4px 16px 0; }
- .card-title { font-size: var(--fs-card-title); font-weight: bold; color: #e0fcff; margin: 0; text-shadow: 0 0 5px rgba(0,212,255,0.5); padding-left: 20px; }
- .header-actions { display: flex; align-items: flex-start; gap: 10px; }
- .update-time { color: #7bbef6; font-size: 11px; font-family: monospace; }
- .card-body { padding: 8px; min-height: auto; font-size: 13px; line-height: 1.5; margin-top: -6px; }
- .card-body.chart-body { padding: 2px 8px 4px; }
- .monitor-chart-box { background: rgba(0,20,40,0.4); border-radius: 4px; border: 1px solid rgba(0,212,255,0.15); padding: 6px; }
- .monitor-chart-label { color: #62f6fb; font-size: var(--fs-label); font-weight: bold; margin-bottom: 2px; }
- .temp-chart-wrapper { width: 100%; }
- #tempStressChart { display: block; margin: 0 auto; }
- .water-stats-row { display: flex; gap: 6px; }
- .water-stat-card { flex: 1; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 3px 10px; background-image: url("/src/assets/images/卡片背景2.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; height: 42px; }
- .water-stat-label { color: #62f6fb; font-size: var(--fs-label); margin-bottom: 0; text-align: left; line-height: 1.2; white-space: nowrap; }
- .water-stat-value { color: #ffffff; font-size: var(--fs-value-xl); font-weight: bold; text-shadow: 0 0 6px rgba(0,212,255,0.5); line-height: 1.2; }
- .panel-date-label { color: #7bbef6; white-space: nowrap; }
- .panel-date-input { background: rgba(0,40,80,0.7); border: 1px solid rgba(0,212,255,0.25); border-radius: 3px; color: #e0fcff; font-weight: 300; padding: 0 6px; outline: none; cursor: pointer; }
- .panel-date-input::-webkit-calendar-picker-indicator { filter: invert(0.7); cursor: pointer; }
- .panel-close-btn { cursor: pointer; flex-shrink: 0; transition: opacity 0.15s; }
- .panel-close-btn:hover { opacity: 0.7; }
- </style>
|