|
@@ -0,0 +1,586 @@
|
|
|
|
|
+<!-- 坝体断面渗压可视化:浸润线抛物线 + 实际水位浮动 -->
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div ref="sectionRef" class="dam-seepage-section">
|
|
|
|
|
+ <!-- 阈值图例 -->
|
|
|
|
|
+ <div class="legend-row">
|
|
|
|
|
+ <div class="legend-item">
|
|
|
|
|
+ <span class="legend-line legend-line-measured"></span>
|
|
|
|
|
+ <span class="legend-label">实测线</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="legend-item">
|
|
|
|
|
+ <span class="legend-line legend-line-design"></span>
|
|
|
|
|
+ <span class="legend-label">设计线</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="legend-item">
|
|
|
|
|
+ <span class="legend-line legend-line-warning"></span>
|
|
|
|
|
+ <span class="legend-label">警戒线</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="legend-item">
|
|
|
|
|
+ <span class="legend-dot legend-dot-gauge"></span>
|
|
|
|
|
+ <span class="legend-label">渗压计</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 指标行 -->
|
|
|
|
|
+ <div class="stats-row">
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <span class="stat-label">库水位</span>
|
|
|
|
|
+ <span class="stat-value" style="color:#00d4ff;">{{ currentWaterLevel.toFixed(1) }}</span>
|
|
|
|
|
+ <span class="stat-unit">m</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-divider"></div>
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <span class="stat-label">设计水位</span>
|
|
|
|
|
+ <span class="stat-value" style="color:#fbbf24;">{{ designWaterLevel }}</span>
|
|
|
|
|
+ <span class="stat-unit">m</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-divider"></div>
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <span class="stat-label">实测渗压</span>
|
|
|
|
|
+ <span class="stat-value" :style="{ color: phreaticStatusColor }">{{ currentPhreaticRatio.toFixed(0) }}%</span>
|
|
|
|
|
+ <span class="stat-unit">阈值</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Canvas 绘制区 -->
|
|
|
|
|
+ <div class="canvas-wrapper">
|
|
|
|
|
+ <canvas ref="canvasRef" :width="cw" :height="ch"></canvas>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 渗压计数据表格 -->
|
|
|
|
|
+ <div class="gauge-table">
|
|
|
|
|
+ <table>
|
|
|
|
|
+ <colgroup>
|
|
|
|
|
+ <col style="width:38px" />
|
|
|
|
|
+ <col style="width:auto" />
|
|
|
|
|
+ <col style="width:68px" />
|
|
|
|
|
+ <col style="width:68px" />
|
|
|
|
|
+ <col style="width:68px" />
|
|
|
|
|
+ <col style="width:44px" />
|
|
|
|
|
+ </colgroup>
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>编号</th>
|
|
|
|
|
+ <th>位置</th>
|
|
|
|
|
+ <th>实测(m)</th>
|
|
|
|
|
+ <th>设计(m)</th>
|
|
|
|
|
+ <th>警戒(m)</th>
|
|
|
|
|
+ <th>状态</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr v-for="g in gauges" :key="g.id" :class="g.status">
|
|
|
|
|
+ <td>{{ g.id }}</td>
|
|
|
|
|
+ <td class="td-left">{{ g.pos }}</td>
|
|
|
|
|
+ <td>{{ g.measured.toFixed(1) }}</td>
|
|
|
|
|
+ <td>{{ g.design }}</td>
|
|
|
|
|
+ <td>{{ g.warning }}</td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <span :class="['status-tag', g.status]">
|
|
|
|
|
+ {{ { normal: '正常', warn: '注意', danger: '超限' }[g.status] }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'DamSeepageSection',
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ cw: 320,
|
|
|
|
|
+ ch: 150,
|
|
|
|
|
+ scale: 1,
|
|
|
|
|
+ animFrame: null,
|
|
|
|
|
+ time: 0,
|
|
|
|
|
+ // 水位参数(相对坝基高程,单位:m)
|
|
|
|
|
+ baseWaterLevel: 8.5, // 基准水位
|
|
|
|
|
+ designWaterLevel: 10.2, // 设计水位
|
|
|
|
|
+ warningWaterLevel: 11.5, // 警戒水位
|
|
|
|
|
+ waveAmplitude: 0.3, // 波动幅度
|
|
|
|
|
+ // 渗压计数据(沿坝体水平分布)
|
|
|
|
|
+ gauges: [
|
|
|
|
|
+ { id: 'P05', pos: '坝体上游', xRatio: 0.30, measured: 8.2, design: 8.5, warning: 9.8, status: 'normal' },
|
|
|
|
|
+ { id: 'P06', pos: '坝体中部', xRatio: 0.47, measured: 6.8, design: 7.0, warning: 8.5, status: 'normal' },
|
|
|
|
|
+ { id: 'P07', pos: '坝体中后', xRatio: 0.64, measured: 4.5, design: 5.2, warning: 6.8, status: 'normal' },
|
|
|
|
|
+ { id: 'P08', pos: '坝体下游', xRatio: 0.82, measured: 2.8, design: 3.5, warning: 5.0, status: 'normal' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ // 浸润线抛物线控制点(基准值)
|
|
|
|
|
+ phreaticControlPoints: [
|
|
|
|
|
+ { x: 0.20, y: 8.5 }, // 起始点(上游坝面附近)
|
|
|
|
|
+ { x: 0.32, y: 7.2 },
|
|
|
|
|
+ { x: 0.47, y: 5.8 },
|
|
|
|
|
+ { x: 0.62, y: 4.2 },
|
|
|
|
|
+ { x: 0.77, y: 2.8 },
|
|
|
|
|
+ { x: 0.90, y: 1.5 }, // 结束点(下游坝面附近)
|
|
|
|
|
+ ],
|
|
|
|
|
+ // 设计浸润线控制点(设计值)
|
|
|
|
|
+ designControlPoints: [
|
|
|
|
|
+ { x: 0.20, y: 10.2 },
|
|
|
|
|
+ { x: 0.32, y: 8.5 },
|
|
|
|
|
+ { x: 0.47, y: 6.8 },
|
|
|
|
|
+ { x: 0.62, y: 5.0 },
|
|
|
|
|
+ { x: 0.77, y: 3.2 },
|
|
|
|
|
+ { x: 0.90, y: 1.8 },
|
|
|
|
|
+ ],
|
|
|
|
|
+ // 警戒浸润线控制点
|
|
|
|
|
+ warningControlPoints: [
|
|
|
|
|
+ { x: 0.20, y: 11.5 },
|
|
|
|
|
+ { x: 0.32, y: 9.8 },
|
|
|
|
|
+ { x: 0.47, y: 8.0 },
|
|
|
|
|
+ { x: 0.62, y: 6.2 },
|
|
|
|
|
+ { x: 0.77, y: 4.2 },
|
|
|
|
|
+ { x: 0.90, y: 2.5 },
|
|
|
|
|
+ ],
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ currentWaterLevel() {
|
|
|
|
|
+ // 基准水位 + 正弦波动模拟水位浮动
|
|
|
|
|
+ return this.baseWaterLevel + Math.sin(this.time * 0.02) * this.waveAmplitude
|
|
|
|
|
+ },
|
|
|
|
|
+ // 当前浸润线相对设计值的比例
|
|
|
|
|
+ currentPhreaticRatio() {
|
|
|
|
|
+ const currentMaxY = Math.max(...this.phreaticControlPoints.map(p => p.y))
|
|
|
|
|
+ const designMaxY = Math.max(...this.designControlPoints.map(p => p.y))
|
|
|
|
|
+ return (currentMaxY / designMaxY) * 85 + Math.sin(this.time * 0.015) * 5
|
|
|
|
|
+ },
|
|
|
|
|
+ phreaticStatusColor() {
|
|
|
|
|
+ const r = this.currentPhreaticRatio
|
|
|
|
|
+ if (r < 85) return '#22c55e'
|
|
|
|
|
+ if (r < 100) return '#fbbf24'
|
|
|
|
|
+ return '#ef4444'
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ this.resizeCanvas()
|
|
|
|
|
+ this.startAnimation()
|
|
|
|
|
+ window.addEventListener('resize', this.resizeCanvas)
|
|
|
|
|
+ },
|
|
|
|
|
+ beforeUnmount() {
|
|
|
|
|
+ if (this.animFrame) cancelAnimationFrame(this.animFrame)
|
|
|
|
|
+ window.removeEventListener('resize', this.resizeCanvas)
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ resizeCanvas() {
|
|
|
|
|
+ const el = this.$refs.sectionRef
|
|
|
|
|
+ if (!el) return
|
|
|
|
|
+ const w = el.clientWidth - 12
|
|
|
|
|
+ if (w <= 0) return
|
|
|
|
|
+ const base = 320
|
|
|
|
|
+ this.scale = w / base
|
|
|
|
|
+ this.cw = Math.round(w)
|
|
|
|
|
+ this.ch = Math.round(150 * this.scale)
|
|
|
|
|
+ },
|
|
|
|
|
+ startAnimation() {
|
|
|
|
|
+ const draw = () => {
|
|
|
|
|
+ // 如果 canvas 还未初始化(v-show 隐藏时),每帧重试
|
|
|
|
|
+ if (this.cw <= 0) this.resizeCanvas()
|
|
|
|
|
+ this.time++
|
|
|
|
|
+ this.updateGaugeStatus()
|
|
|
|
|
+ this.drawCanvas()
|
|
|
|
|
+ this.animFrame = requestAnimationFrame(draw)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.animFrame = requestAnimationFrame(draw)
|
|
|
|
|
+ },
|
|
|
|
|
+ updateGaugeStatus() {
|
|
|
|
|
+ const wl = this.currentWaterLevel
|
|
|
|
|
+ this.gauges.forEach(g => {
|
|
|
|
|
+ // 每个渗压计随水位浮动
|
|
|
|
|
+ const fluctuation = Math.sin(this.time * 0.01 + g.xRatio * 5) * 0.15
|
|
|
|
|
+ g.measured = this.getPhreaticY(g.xRatio) + fluctuation
|
|
|
|
|
+ if (g.measured >= g.warning) g.status = 'danger'
|
|
|
|
|
+ else if (g.measured >= g.design) g.status = 'warn'
|
|
|
|
|
+ else g.status = 'normal'
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ // 通过贝塞尔插值获取浸润线某点的Y值
|
|
|
|
|
+ getPhreaticY(xRatio, points) {
|
|
|
|
|
+ const pts = points || this.phreaticControlPoints
|
|
|
|
|
+ if (xRatio <= pts[0].x) return pts[0].y
|
|
|
|
|
+ if (xRatio >= pts[pts.length - 1].x) return pts[pts.length - 1].y
|
|
|
|
|
+ for (let i = 0; i < pts.length - 1; i++) {
|
|
|
|
|
+ if (xRatio >= pts[i].x && xRatio <= pts[i + 1].x) {
|
|
|
|
|
+ const t = (xRatio - pts[i].x) / (pts[i + 1].x - pts[i].x)
|
|
|
|
|
+ // 余弦插值使曲线更平滑(抛物线效果)
|
|
|
|
|
+ const st = (1 - Math.cos(t * Math.PI)) / 2
|
|
|
|
|
+ return pts[i].y + (pts[i + 1].y - pts[i].y) * st
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return pts[pts.length - 1].y
|
|
|
|
|
+ },
|
|
|
|
|
+ // 绘制浸润线(平滑抛物线)
|
|
|
|
|
+ drawPhreaticLine(ctx, points, color, dash, label) {
|
|
|
|
|
+ const W = this.cw
|
|
|
|
|
+ const H = this.ch
|
|
|
|
|
+ const s = this.scale
|
|
|
|
|
+ const pad = { left: 45 * s, right: 10 * s, top: 12 * s, bottom: 20 * s }
|
|
|
|
|
+ const drawW = W - pad.left - pad.right
|
|
|
|
|
+ const drawH = H - pad.top - pad.bottom
|
|
|
|
|
+ const maxY = 14
|
|
|
|
|
+
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.setLineDash(dash || [])
|
|
|
|
|
+ ctx.strokeStyle = color
|
|
|
|
|
+ ctx.lineWidth = dash ? 1.8 * s : 2.5 * s
|
|
|
|
|
+ ctx.shadowColor = color
|
|
|
|
|
+ ctx.shadowBlur = dash ? 0 : 4 * s
|
|
|
|
|
+
|
|
|
|
|
+ // 抛物线插值:贝塞尔曲线
|
|
|
|
|
+ const segments = 40
|
|
|
|
|
+ const step = (points[points.length - 1].x - points[0].x) / segments
|
|
|
|
|
+ let first = true
|
|
|
|
|
+ for (let i = 0; i <= segments; i++) {
|
|
|
|
|
+ const x = points[0].x + i * step
|
|
|
|
|
+ const y = this.getPhreaticY(x, points)
|
|
|
|
|
+ const px = pad.left + (x / 1.0) * drawW
|
|
|
|
|
+ const py = pad.top + (1 - y / maxY) * drawH
|
|
|
|
|
+ if (first) { ctx.moveTo(px, py); first = false }
|
|
|
|
|
+ else ctx.lineTo(px, py)
|
|
|
|
|
+ }
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+ },
|
|
|
|
|
+ drawCanvas() {
|
|
|
|
|
+ const canvas = this.$refs.canvasRef
|
|
|
|
|
+ if (!canvas) return
|
|
|
|
|
+ const ctx = canvas.getContext('2d')
|
|
|
|
|
+ const W = this.cw
|
|
|
|
|
+ const H = this.ch
|
|
|
|
|
+ const s = this.scale
|
|
|
|
|
+ const pad = { left: 45 * s, right: 10 * s, top: 12 * s, bottom: 20 * s }
|
|
|
|
|
+ const drawW = W - pad.left - pad.right
|
|
|
|
|
+ const drawH = H - pad.top - pad.bottom
|
|
|
|
|
+ const maxY = 14
|
|
|
|
|
+ const fs = 14 * s // base font size
|
|
|
|
|
+
|
|
|
|
|
+ ctx.clearRect(0, 0, W, H)
|
|
|
|
|
+
|
|
|
|
|
+ // --- 背景网格 ---
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 212, 255, 0.08)'
|
|
|
|
|
+ ctx.lineWidth = 0.5
|
|
|
|
|
+ for (let i = 0; i <= 5; i++) {
|
|
|
|
|
+ const y = pad.top + (i / 5) * drawH
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.moveTo(pad.left, y)
|
|
|
|
|
+ ctx.lineTo(W - pad.right, y)
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+ }
|
|
|
|
|
+ // Y轴标签(高程)
|
|
|
|
|
+ ctx.fillStyle = 'rgba(123, 190, 246, 0.6)'
|
|
|
|
|
+ ctx.font = `${fs}px sans-serif`
|
|
|
|
|
+ ctx.textAlign = 'right'
|
|
|
|
|
+ for (let i = 0; i <= 5; i++) {
|
|
|
|
|
+ const val = maxY - (i / 5) * maxY
|
|
|
|
|
+ const y = pad.top + (i / 5) * drawH
|
|
|
|
|
+ ctx.fillText(val.toFixed(1), pad.left - 6 * s, y + 4 * s)
|
|
|
|
|
+ }
|
|
|
|
|
+ // Y轴标题
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.translate(12 * s, pad.top + drawH / 2)
|
|
|
|
|
+ ctx.rotate(-Math.PI / 2)
|
|
|
|
|
+ ctx.fillStyle = 'rgba(123, 190, 246, 0.5)'
|
|
|
|
|
+ ctx.font = `${fs}px sans-serif`
|
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
|
+ ctx.fillText('水位(m)', 0, 0)
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+
|
|
|
|
|
+ // --- 坝体轮廓 ---
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ const damLeft = pad.left + drawW * 0.20
|
|
|
|
|
+ const damRight = pad.left + drawW * 0.92
|
|
|
|
|
+ const damTop = pad.top + drawH * 0.08
|
|
|
|
|
+ const damBottom = pad.top + drawH
|
|
|
|
|
+
|
|
|
|
|
+ // 坝体主体(梯形)
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.moveTo(damLeft, damTop)
|
|
|
|
|
+ ctx.lineTo(damRight, damTop)
|
|
|
|
|
+ ctx.lineTo(damRight + drawW * 0.05, damBottom)
|
|
|
|
|
+ ctx.lineTo(damLeft - drawW * 0.02, damBottom)
|
|
|
|
|
+ ctx.closePath()
|
|
|
|
|
+ ctx.fillStyle = 'rgba(30, 64, 175, 0.25)'
|
|
|
|
|
+ ctx.fill()
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 212, 255, 0.3)'
|
|
|
|
|
+ ctx.lineWidth = 1.5 * s
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+
|
|
|
|
|
+ // 坝体填充纹理(水平线)
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.rect(damLeft, damTop, damRight - damLeft + drawW * 0.05, damBottom - damTop)
|
|
|
|
|
+ ctx.clip()
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 212, 255, 0.04)'
|
|
|
|
|
+ ctx.lineWidth = 0.5
|
|
|
|
|
+ for (let i = 0; i < 20; i++) {
|
|
|
|
|
+ const y = damTop + (i / 20) * (damBottom - damTop)
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.moveTo(damLeft, y)
|
|
|
|
|
+ ctx.lineTo(damRight + drawW * 0.05, y)
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+ }
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+
|
|
|
|
|
+ // --- 坝顶标注 ---
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.fillStyle = 'rgba(123, 190, 246, 0.6)'
|
|
|
|
|
+ ctx.font = `${fs}px sans-serif`
|
|
|
|
|
+ ctx.textAlign = 'left'
|
|
|
|
|
+ ctx.fillText('上游', damLeft, damTop - 5 * s)
|
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
|
+ ctx.fillText('坝顶', (damLeft + damRight) / 2, damTop - 5 * s)
|
|
|
|
|
+ ctx.textAlign = 'right'
|
|
|
|
|
+ ctx.fillText('下游', damRight, damTop - 5 * s)
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+
|
|
|
|
|
+ // --- 库区水体(上游侧),带波动效果 ---
|
|
|
|
|
+ const wl = this.currentWaterLevel
|
|
|
|
|
+ const wlY = pad.top + (1 - wl / maxY) * drawH
|
|
|
|
|
+ // 水位线必须在坝顶以下
|
|
|
|
|
+ const finalWlY = Math.min(wlY, damTop + 5)
|
|
|
|
|
+
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ // 水体渐变
|
|
|
|
|
+ const waterGrad = ctx.createLinearGradient(damLeft - drawW * 0.3, finalWlY, damLeft - drawW * 0.3, damBottom)
|
|
|
|
|
+ waterGrad.addColorStop(0, 'rgba(0, 150, 255, 0.35)')
|
|
|
|
|
+ waterGrad.addColorStop(1, 'rgba(0, 80, 180, 0.5)')
|
|
|
|
|
+ ctx.fillStyle = waterGrad
|
|
|
|
|
+
|
|
|
|
|
+ // 水体路径:上游侧矩形 + 波动水面线
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ const waterLeft = pad.left
|
|
|
|
|
+ ctx.moveTo(waterLeft, finalWlY)
|
|
|
|
|
+ // 波浪线
|
|
|
|
|
+ for (let x = waterLeft; x <= damLeft; x += 3) {
|
|
|
|
|
+ const waveY = finalWlY + Math.sin((x + this.time * 2) * 0.05) * 2.5
|
|
|
|
|
+ ctx.lineTo(x, waveY)
|
|
|
|
|
+ }
|
|
|
|
|
+ ctx.lineTo(damLeft, damBottom)
|
|
|
|
|
+ ctx.lineTo(waterLeft, damBottom)
|
|
|
|
|
+ ctx.closePath()
|
|
|
|
|
+ ctx.fill()
|
|
|
|
|
+
|
|
|
|
|
+ // 水位线(亮线)
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ // 水面的波浪亮线,只在水体区域绘制
|
|
|
|
|
+ for (let x = waterLeft; x <= damLeft; x += 2) {
|
|
|
|
|
+ const waveY = finalWlY + Math.sin((x + this.time * 2) * 0.05) * 2.5
|
|
|
|
|
+ if (x === waterLeft) ctx.moveTo(x, waveY)
|
|
|
|
|
+ else ctx.lineTo(x, waveY)
|
|
|
|
|
+ }
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 212, 255, 0.7)'
|
|
|
|
|
+ ctx.lineWidth = 1.5
|
|
|
|
|
+ ctx.shadowColor = '#00d4ff'
|
|
|
|
|
+ ctx.shadowBlur = 6
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+
|
|
|
|
|
+ // --- 浸润线(3条:实测、设计、警戒) ---
|
|
|
|
|
+ // 1. 警戒浸润线(虚线)
|
|
|
|
|
+ this.drawPhreaticLine(ctx, this.warningControlPoints, 'rgba(239, 68, 68, 0.5)', [5, 4], '警戒线')
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 设计浸润线(虚线)
|
|
|
|
|
+ this.drawPhreaticLine(ctx, this.designControlPoints, 'rgba(251, 191, 36, 0.6)', [4, 3], '设计线')
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 实测浸润线(实线,带发光)
|
|
|
|
|
+ // 实时调整实测线:受水位波动影响
|
|
|
|
|
+ const wlFactor = wl / this.baseWaterLevel
|
|
|
|
|
+ const adjustedPoints = this.phreaticControlPoints.map(p => ({
|
|
|
|
|
+ x: p.x,
|
|
|
|
|
+ y: p.y * wlFactor,
|
|
|
|
|
+ }))
|
|
|
|
|
+ this.drawPhreaticLine(ctx, adjustedPoints, '#00d4ff', [], '实测线')
|
|
|
|
|
+
|
|
|
|
|
+ // --- 渗压计点位 ---
|
|
|
|
|
+ this.gauges.forEach(g => {
|
|
|
|
|
+ const px = pad.left + (g.xRatio / 1.0) * drawW
|
|
|
|
|
+ const py = pad.top + (1 - g.measured / maxY) * drawH
|
|
|
|
|
+
|
|
|
|
|
+ // 渗压计圆点
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.arc(px, py, 5, 0, Math.PI * 2)
|
|
|
|
|
+ const dotColor = g.status === 'danger' ? '#ef4444' : g.status === 'warn' ? '#fbbf24' : '#22c55e'
|
|
|
|
|
+ ctx.fillStyle = dotColor
|
|
|
|
|
+ ctx.fill()
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 30, 60, 0.8)'
|
|
|
|
|
+ ctx.lineWidth = 2
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+ // 发光
|
|
|
|
|
+ ctx.shadowColor = dotColor
|
|
|
|
|
+ ctx.shadowBlur = 6
|
|
|
|
|
+ ctx.fill()
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+
|
|
|
|
|
+ // 标签
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.fillStyle = '#e0fcff'
|
|
|
|
|
+ ctx.font = `${fs}px sans-serif`
|
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
|
+ ctx.fillText(g.id, px, py - 10 * s)
|
|
|
|
|
+ ctx.fillStyle = 'rgba(123, 190, 246, 0.6)'
|
|
|
|
|
+ ctx.font = `${fs - 1}px sans-serif`
|
|
|
|
|
+ ctx.fillText(g.measured.toFixed(1) + 'm', px, py + 16 * s)
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // --- 底部X轴标注 ---
|
|
|
|
|
+ ctx.save()
|
|
|
|
|
+ ctx.fillStyle = 'rgba(123, 190, 246, 0.5)'
|
|
|
|
|
+ ctx.font = `${fs}px sans-serif`
|
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
|
+
|
|
|
|
|
+ ctx.restore()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.dam-seepage-section {
|
|
|
|
|
+ background: rgba(0, 20, 40, 0.5);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ border: 1px solid rgba(0, 212, 255, 0.12);
|
|
|
|
|
+ padding: 4px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 2px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 图例 */
|
|
|
|
|
+.legend-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+}
|
|
|
|
|
+.legend-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+.legend-line {
|
|
|
|
|
+ width: 22px;
|
|
|
|
|
+ height: 3px;
|
|
|
|
|
+ border-radius: 1px;
|
|
|
|
|
+}
|
|
|
|
|
+.legend-line-measured { background: #00d4ff; }
|
|
|
|
|
+.legend-line-design { background: #fbbf24; }
|
|
|
|
|
+.legend-line-warning { background: #ef4444; }
|
|
|
|
|
+.legend-dot {
|
|
|
|
|
+ width: 8px;
|
|
|
|
|
+ height: 8px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+}
|
|
|
|
|
+.legend-dot-gauge { background: #22c55e; }
|
|
|
|
|
+.legend-label {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: rgba(123, 190, 246, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* Canvas 容器 */
|
|
|
|
|
+.canvas-wrapper {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.canvas-wrapper canvas {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 指标行 */
|
|
|
|
|
+.stats-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 0;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+.stat-item {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 3px;
|
|
|
|
|
+ background: rgba(0, 20, 40, 0.5);
|
|
|
|
|
+ border: 1px solid rgba(0, 212, 255, 0.1);
|
|
|
|
|
+ border-radius: 3px;
|
|
|
|
|
+ padding: 3px 6px;
|
|
|
|
|
+}
|
|
|
|
|
+.stat-divider {
|
|
|
|
|
+ width: 4px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.stat-label {
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: rgba(123, 190, 246, 0.7);
|
|
|
|
|
+}
|
|
|
|
|
+.stat-value {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ min-width: 24px;
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+}
|
|
|
|
|
+.stat-unit {
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: rgba(123, 190, 246, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 渗压计表格 */
|
|
|
|
|
+.gauge-table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ overflow-x: auto;
|
|
|
|
|
+}
|
|
|
|
|
+.gauge-table table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ border-collapse: collapse;
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.gauge-table th {
|
|
|
|
|
+ color: rgba(123, 190, 246, 0.8);
|
|
|
|
|
+ background: rgba(0, 20, 40, 0.4);
|
|
|
|
|
+ padding: 3px 6px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ border-bottom: 1px solid rgba(0, 212, 255, 0.1);
|
|
|
|
|
+ font-weight: normal;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+}
|
|
|
|
|
+.gauge-table td {
|
|
|
|
|
+ color: #e0fcff;
|
|
|
|
|
+ padding: 2px 6px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ border-bottom: 1px solid rgba(0, 212, 255, 0.05);
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ line-height: 1.8;
|
|
|
|
|
+}
|
|
|
|
|
+.gauge-table td.td-left {
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+.gauge-table tr.danger td {
|
|
|
|
|
+ background: rgba(239, 68, 68, 0.08);
|
|
|
|
|
+}
|
|
|
|
|
+.gauge-table tr.warn td {
|
|
|
|
|
+ background: rgba(251, 191, 36, 0.06);
|
|
|
|
|
+}
|
|
|
|
|
+.status-tag {
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ padding: 1px 6px;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+}
|
|
|
|
|
+.status-tag.normal { color: #22c55e; background: rgba(34, 197, 94, 0.15); }
|
|
|
|
|
+.status-tag.warn { color: #fbbf24; background: rgba(251, 191, 36, 0.15); }
|
|
|
|
|
+.status-tag.danger { color: #ef4444; background: rgba(239, 68, 68, 0.15); }
|
|
|
|
|
+</style>
|