123
wangjuncheng
2025-06-16 4b7b8185bdce4272cd5f256fcc777056f54add6d
123
已添加4个文件
已修改9个文件
1071 ■■■■■ 文件已修改
src/api/trApi.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/menu/TimeLine.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/menu/flowRate_waterLevel.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/menu/flowRate_waterLevel多点测量.vue 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/DangerAssess.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/WaterDepthContent.vue 306 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/WaterVelocityContent.vue 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/flowRateTab.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/schemeCard.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/schemeInfo.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/tools/Tools.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/tools.js 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/water.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/trApi.js
@@ -107,7 +107,9 @@
      flowUrl: jsonData.flowUrl,
      waterUrl: jsonData.waterUrl,
      version: jsonData.version,
      waterTimestamps: waterTimestamps, // åŽŸå§‹æ—¶é—´æ•°ç»„
      waterTimestamps: waterTimestamps,
      watersMaxHeight: jsonData.waters.maxHeight,
      watersMinHeight: jsonData.waters.minHeight,
    };
  } catch (error) {
    console.error("解析水模拟数据时出错:", error);
@@ -124,7 +126,7 @@
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const jsonData = await response.json(); // è§£æž JSON æ•°æ®
    console.log(jsonData, "jsonjsonjsonjson");
    // console.log(jsonData, "jsonjsonjsonjson");
    return parseWaterSimulationData(jsonData); // è°ƒç”¨è§£æžå‡½æ•°
  } catch (error) {
    console.error("请求或解析数据时出错:", error);
src/components/menu/TimeLine.vue
@@ -58,10 +58,12 @@
        <ratelevel ref="ratelevelRef" :playing-time="sendCurrentPlayingTime"
          @finish-calculation="handleFinishCalculation"
          style="margin-top: 12px; margin-left: 28px; margin-right: 10px;justify-content: flex-end;" />
        <crossanalysis ref="crossRef" style="margin-top: 12px; margin-left: 16px; margin-right: 20px;justify-content: flex-end;" />
        <crossanalysis ref="crossRef"
          style="margin-top: 12px; margin-left: 16px; margin-right: 20px;justify-content: flex-end;" />
      </div>
      <el-button @click="handleBack" style="margin-top: 3px; margin-left: 28px; margin-right: 10px;width: 75%;height: 30%;">结束模拟</el-button>
      <el-button @click="handleBack"
        style="margin-top: 3px; margin-left: 28px; margin-right: 10px;width: 75%;height: 30%;">结束模拟</el-button>
    </div>
  </div>
@@ -136,8 +138,8 @@
  rainColor: "#99B3CC",
  rainDensity: 30 // é›¨çš„密度
});
let minFlowRate =ref()
let maxFlowRate =ref()
let minFlowRate = ref()
let maxFlowRate = ref()
// è®¡ç®—属性
const progressPercentage = computed(
  () => (currentTime.value / duration.value) * 100
@@ -167,8 +169,8 @@
        // baseUrl: `/simu/c2h1dc`,
        interval: intervalMap[playbackRate.value],
        colorRender: isColorRenderEnabled.value,
        minFlowRate:0.1,
        maxFlowRate:10,
        minFlowRate,
        maxFlowRate,
      });
      isWaterPrimitiveCreated.value = true;
    } else {
@@ -545,12 +547,13 @@
    // å½“前方案的所有信息
    const schemeInfo = selectedScheme.value;
    serviceInfo = schemeInfo.serviceName;
    // minFlowRate = schemeInfo.最小水深
    // maxFlowRate = schemeInfo.最大水深
    // console.log('获取到的 serviceName:', serviceInfo);
    getRainfallData()
    // æ ¹æ®layer.json去获取时间轴信息
    const { waterTimestamps: timestamps } = await fetchWaterSimulationData(serviceInfo);
    const { waterTimestamps: timestamps, watersMaxHeight, watersMinHeight } = await fetchWaterSimulationData(serviceInfo);
    console.log('当前方案下的最大水位深度和最小水位深度',watersMaxHeight,watersMinHeight);
    // çŽ°åœ¨æ˜¯æŒ‰ç…§æ€»å…±æœ‰å¤šå°‘ä¸ªç‚¹æ¥æ¸²æŸ“æ—¶é—´è½´
    if (timestamps) {
      waterTimestamps.value = timestamps;
@@ -561,6 +564,8 @@
        "YYYY-MM-DD HH:mm:ss"
      );
    }
    minFlowRate = watersMinHeight
    maxFlowRate = watersMaxHeight
  } catch (error) {
    console.error("Error loading water simulation data:", error);
    ElMessage({
@@ -596,7 +601,7 @@
  if (crossRef.value) {
    crossRef.value.clearPoints();
    console.log('执行删除点功能');
  }
  emit("isColorRender", false);
  setTimeout(() => {
@@ -604,6 +609,8 @@
  }, 3000);
  destoryWaterPrimitive();
  EventBus.emit("hide-schemeInfo");
  EventBus.emit("clear-water-depth");
  EventBus.emit("clear-water-velocity");
  ElMessage({ message: "模拟进程正在关闭中...", type: "success" });
}
</script>
src/components/menu/flowRate_waterLevel.vue
@@ -53,7 +53,8 @@
import { useSimStore } from "@/store/simulation";
import { storeToRefs } from "pinia";
const simStore = useSimStore();
const { selectedScheme } = storeToRefs(simStore);
const { selectedScheme, currentInfo } = storeToRefs(simStore);
import { EventBus } from "@/eventBus";
const pickedPoints = ref([]);
const handler = ref(null);
@@ -89,11 +90,16 @@
    const schemeInfo = selectedScheme.value;
    serviceInfo = schemeInfo.serviceName;
    // å¦‚果已有选中点,则先清除
    if (pickedPoints.value.length >= 1) {
        endCalculation(); // æ¸…除所有已有点
    }
    // åˆ›å»º label å®žä½“
    const labelEntity = viewer.entities.add({
        position: point.cartesian,
        label: {
            text: `测量点 ${pickedPoints.value.length + 1}\næ°´æ·±: ç­‰å¾…启动...\n流速: ç­‰å¾…启动...`,
            text: `测量点\næ°´æ·±: ç­‰å¾…启动...\n流速: ç­‰å¾…启动...`,
            font: 'bold 14pt monospace',
            style: Cesium.LabelStyle.FILL_AND_OUTLINE,
            fillColor: Cesium.Color.YELLOW,
@@ -109,6 +115,7 @@
            pixelOffsetScaleByDistance: new Cesium.NearFarScalar(100, 1.0, 5000, 0.3),
        }
    });
    const groundPosition = Cesium.Cartesian3.fromRadians(
        point.longitude * Math.PI / 180,
        point.latitude * Math.PI / 180,
@@ -128,16 +135,21 @@
            distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 5000)
        }
    });
    // å­˜å‚¨å®žä½“引用之前就更新 currentInfo
    currentInfo.value = {
        longitude: point.longitude,
        latitude: point.latitude,
        serviceInfo: serviceInfo
    };
    // è¯·æ±‚数据并更新 label
    getFlowRateInfo(point.longitude, point.latitude, displayTime).then(result => {
        updateLabel(pickedPoints.value.length - 1, result.depth, result.velocity);
        updateLabel(pickedPoints.value.length, result.depth, result.velocity);
    });
    // å­˜å‚¨å®žä½“引用
    pickedPoints.value.push({
        labelEntity,
        cylinderEntity, // ä½¿ç”¨åœ†æŸ±ä»£æ›¿ line å’Œ circle
        cylinderEntity,
        longitude: point.longitude,
        latitude: point.latitude
    });
@@ -199,18 +211,19 @@
);
async function updateAllLabels() {
    for (const pointInfo of pickedPoints.value) {
    for (let i = 0; i < pickedPoints.value.length; i++) {
        const pointInfo = pickedPoints.value[i];
        if (!pointInfo || !pointInfo.labelEntity) continue;
        const result = await getFlowRateInfo(pointInfo.longitude, pointInfo.latitude, currentTime.value);
        updateLabel(pointInfo, result.depth, result.velocity);
        updateLabel(i, result.depth, result.velocity);
    }
}
function updateLabel(pointInfo, depth, velocity) {
    if (pointInfo.labelEntity && pointInfo.labelEntity.label) {
function updateLabel(index, depth, velocity) {
    const pointInfo = pickedPoints.value[index];
    if (pointInfo && pointInfo.labelEntity && pointInfo.labelEntity.label) {
        pointInfo.labelEntity.label.text = `
测量点 ${pickedPoints.value.findIndex(p => p === pointInfo) + 1}
测量点
æ°´æ·±: ${depth} m
流速: ${velocity} m/s
`.trim();
@@ -222,6 +235,9 @@
        if (pointInfo.cylinderEntity) viewer.entities.remove(pointInfo.cylinderEntity);
    });
    pickedPoints.value = [];
    EventBus.emit("clear-water-depth");
    EventBus.emit("clear-water-velocity");
}
defineExpose({
@@ -236,7 +252,7 @@
        serviceName: serviceInfo
    };
    return getFlowRate(params).then(data => {
        // console.log('获取到的数据:', data);
        console.log('获取到的数据:', data);
        if (data && data.code === 200) {
            return {
                depth: data.data.depth.toFixed(2),
src/components/menu/flowRate_waterLevel¶àµã²âÁ¿.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,254 @@
<template>
    <div style="display: flex;justify-content: space-between;">
        <el-tooltip class="box-item" effect="dark" placement="top" show-after="1000">
            <template #content>
                æ°´æ·±/流速分析功能说明:
                <br /><br />
                ðŸ”¹ ç‚¹å‡»æŒ‰é’®åˆ‡æ¢çŠ¶æ€ï¼š
                <br />
                &nbsp;&nbsp;&nbsp;&nbsp;- ç™½è‰²ï¼šå…³é—­æ‹¾å–功能(不可选点)
                <br />
                &nbsp;&nbsp;&nbsp;&nbsp;- é»„色:开启拾取功能(可点击地图选择分析点)
                <br /><br />
                ðŸ”¹ ä½¿ç”¨æµç¨‹ï¼š
                <br />
                &nbsp;&nbsp;&nbsp;&nbsp;1. ç‚¹å‡»æŒ‰é’®åˆ‡æ¢ä¸ºé»„色状态 âžœ å¼€å§‹æ‹¾å–坐标点
                <br />
                &nbsp;&nbsp;&nbsp;&nbsp;2. åœ¨åœ°å›¾ä¸Šç‚¹å‡»æ‰€éœ€ä½ç½® âžœ æ·»åŠ æ°´æ·±/流速分析点
                <br />
                &nbsp;&nbsp;&nbsp;&nbsp;3. å®Œæˆé€‰ç‚¹åŽï¼Œè¯·å°†æŒ‰é’®åˆ‡å›žç™½è‰² âžœ å…³é—­æ‹¾å–功能
                <br /><br />
                âš ï¸ æ¸©é¦¨æç¤ºï¼š
                <br />
                &nbsp;&nbsp;&nbsp;&nbsp;使用完毕请务必关闭拾取功能,避免误操作影响其他功能。
            </template>
            <div @click="togglePick" :class="['pick-button', { active: isPickingActive }]">
                <img v-if="!isPickingActive" src="@/assets/img/timeline/流速.png" style="width: 28px;height: 28px;" />
                <img v-else src="@/assets/img/timeline/已流速.png" style="width: 28px;height: 28px;" />
            </div>
        </el-tooltip>
        <el-tooltip class="box-item" effect="dark" placement="top" show-after="1000">
            <template #content>
                æ¸…除所有分析坐标点及分析结果:
                <br /><br />
                ðŸ” åŠŸèƒ½è¯´æ˜Žï¼š
                <br />
                &nbsp;&nbsp;&nbsp;&nbsp;点击后将移除地图上的所有水深/流速分析点以及相关分析图表
                <br /><br />
                âš ï¸ æ¸©é¦¨æç¤ºï¼š
                <br />
                &nbsp;&nbsp;&nbsp;&nbsp;此操作会清空当前分析进度,请确认后再执行
            </template>
            <div @click="endCalculation">
                <img src="@/assets/img/timeline/清除.png" style="width: 26px;height: 26px;" />
            </div>
        </el-tooltip>
    </div>
</template>
<script setup>
import { defineProps, watch, ref, onMounted, defineExpose } from "vue";
import { ElMessage } from 'element-plus';
import { getFlowRate } from "@/api/trApi.js";
import { useSimStore } from "@/store/simulation";
import { storeToRefs } from "pinia";
const simStore = useSimStore();
const { selectedScheme } = storeToRefs(simStore);
const pickedPoints = ref([]);
const handler = ref(null);
const isPickingActive = ref(false);
const currentTime = ref(0);
let serviceInfo = ref(null);
const props = defineProps({
    playingTime: {
        type: String,
        required: true
    }
});
const viewer = window.viewer;
function getPickPosition(windowPosition) {
    if (!viewer) return null;
    viewer.scene.globe.depthTestAgainstTerrain = true;
    const cartesian = viewer.scene.pickPosition(windowPosition);
    if (!cartesian) return null;
    const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
    cartographic.height += 110.0;
    return {
        cartesian: Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, cartographic.height),
        longitude: Cesium.Math.toDegrees(cartographic.longitude),
        latitude: Cesium.Math.toDegrees(cartographic.latitude),
        height: cartographic.height
    };
}
function addPointToViewer(point, index) {
    const displayTime = currentTime.value || "未设置时间";
    const schemeInfo = selectedScheme.value;
    serviceInfo = schemeInfo.serviceName;
    // åˆ›å»º label å®žä½“
    const labelEntity = viewer.entities.add({
        position: point.cartesian,
        label: {
            text: `测量点 ${pickedPoints.value.length + 1}\næ°´æ·±: ç­‰å¾…启动...\n流速: ç­‰å¾…启动...`,
            font: 'bold 14pt monospace',
            style: Cesium.LabelStyle.FILL_AND_OUTLINE,
            fillColor: Cesium.Color.YELLOW,
            outlineColor: Cesium.Color.BLACK,
            outlineWidth: 2,
            verticalOrigin: Cesium.VerticalOrigin.CENTER,
            horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
            backgroundColor: Cesium.Color.fromCssColorString('rgba(0,0,0,0.7)'),
            backgroundPadding: new Cesium.Cartesian2(10, 10),
            showBackground: true,
            scale: 1,
            distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 5000),
            pixelOffsetScaleByDistance: new Cesium.NearFarScalar(100, 1.0, 5000, 0.3),
        }
    });
    const groundPosition = Cesium.Cartesian3.fromRadians(
        point.longitude * Math.PI / 180,
        point.latitude * Math.PI / 180,
        0
    );
    const cylinderEntity = viewer.entities.add({
        position: groundPosition, // åº•部位置
        cylinder: {
            length: 100.0,
            topRadius: 1.0,
            bottomRadius: 1.0,
            material: Cesium.Color.YELLOW,
            outline: true,
            outlineColor: Cesium.Color.YELLOW,
            slices: 32,
            heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND,
            distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 5000)
        }
    });
    // è¯·æ±‚数据并更新 label
    getFlowRateInfo(point.longitude, point.latitude, displayTime).then(result => {
        updateLabel(pickedPoints.value.length - 1, result.depth, result.velocity);
    });
    // å­˜å‚¨å®žä½“引用
    pickedPoints.value.push({
        labelEntity,
        cylinderEntity, // ä½¿ç”¨åœ†æŸ±ä»£æ›¿ line å’Œ circle
        longitude: point.longitude,
        latitude: point.latitude
    });
}
function initPickHandler() {
    if (!viewer?.scene?.canvas) return;
    if (handler.value) {
        handler.value.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
        handler.value.destroy();
        handler.value = null;
    }
    handler.value = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.value.setInputAction((movement) => {
        const position = getPickPosition(movement.position);
        if (position) {
            const index = pickedPoints.value.length;
            addPointToViewer(position, index);
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
}
function startPicking() {
    if (!handler.value) {
        initPickHandler();
    }
    isPickingActive.value = true;
    ElMessage.success(`开始--流量流速--拾取坐标功能,请点击地图选择点位!选取完请及时关闭,避免影响其他功能!`);
}
function stopPicking() {
    if (handler.value) {
        handler.value.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
        handler.value.destroy();
        handler.value = null;
    }
    isPickingActive.value = false;
}
function togglePick() {
    if (isPickingActive.value) {
        stopPicking();
        isPickingActive.value = false;
        ElMessage.info('已关闭--流量流速--拾取点坐标功能!');
    } else {
        startPicking();
        isPickingActive.value = true;
    }
}
watch(
    () => props.playingTime,
    async (newVal, oldVal) => {
        if (newVal !== oldVal) {
            currentTime.value = newVal || "未设置时间";
            await updateAllLabels();
        }
    },
    { immediate: true }
);
async function updateAllLabels() {
    for (const pointInfo of pickedPoints.value) {
        if (!pointInfo || !pointInfo.labelEntity) continue;
        const result = await getFlowRateInfo(pointInfo.longitude, pointInfo.latitude, currentTime.value);
        updateLabel(pointInfo, result.depth, result.velocity);
    }
}
function updateLabel(pointInfo, depth, velocity) {
    if (pointInfo.labelEntity && pointInfo.labelEntity.label) {
        pointInfo.labelEntity.label.text = `
测量点 ${pickedPoints.value.findIndex(p => p === pointInfo) + 1}
æ°´æ·±: ${depth} m
流速: ${velocity} m/s
`.trim();
    }
}
function endCalculation() {
    pickedPoints.value.forEach(pointInfo => {
        if (pointInfo.labelEntity) viewer.entities.remove(pointInfo.labelEntity);
        if (pointInfo.cylinderEntity) viewer.entities.remove(pointInfo.cylinderEntity);
    });
    pickedPoints.value = [];
}
defineExpose({
    endCalculation,
    stopPicking
});
function getFlowRateInfo(lon, lat, time) {
    const params = {
        lon: lon,
        lat: lat,
        time: time,
        serviceName: serviceInfo
    };
    return getFlowRate(params).then(data => {
        // console.log('获取到的数据:', data);
        if (data && data.code === 200) {
            return {
                depth: data.data.depth.toFixed(2),
                velocity: data.data.velocity.toFixed(2)
            };
        } else {
            return { depth: 'N/A', velocity: 'N/A' };
        }
    }).catch(error => {
        console.error('获取数据时发生错误:', error);
        return { depth: 'N/A', velocity: 'N/A' };
    });
}
</script>
<style lang="less" scoped></style>
src/components/monifangzhen/DangerAssess.vue
@@ -1,7 +1,7 @@
<template>
    <div class="right">
        <div class="right-top">
            <span>模拟结果</span>
            <span>水深流速结果</span>
        </div>
        <div class="right-content" >
            <div class="listinfo-title">受灾统计</div>
src/components/monifangzhen/WaterDepthContent.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,306 @@
<template>
  <div class="water-depth-content">
    <!-- æç¤ºä¿¡æ¯ -->
    <div v-if="showSelectPrompt" style=" font-weight: bold;">
      è¯·åœ¨åœ°å›¾ä¸­é€‰å–唯一测量点
    </div>
    <!-- æ­£å¸¸å†…容 -->
    <div v-else>
      <!-- ä½ç½®ä¿¡æ¯ -->
      <div class="location-info">
        <h3>位置信息</h3>
        <p class="coordinates">
          ç»åº¦ï¼š <strong>{{ safeCurrentInfo.longitude.toFixed(3) }}&nbsp;&nbsp;&nbsp;&nbsp;</strong>
          çº¬åº¦ï¼š<strong>{{ safeCurrentInfo.latitude.toFixed(3) }}</strong>
        </p>
      </div>
      <!-- æ°´æ·±ä¿¡æ¯ -->
      <div class="depth-info">
        <h3>水深信息 (m)</h3>
        <p>
          å¹³å‡ï¼š<strong>2.45</strong>&nbsp;&nbsp;
          æœ€å¤§ï¼š<strong>3.10</strong>&nbsp;&nbsp;
          æœ€å°ï¼š<strong>1.80</strong>
        </p>
      </div>
    </div>
    <div class="chart-placeholder">
      <div ref="chartDom" style="width: 100%; height:300px;"></div>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, watch, computed, onBeforeUnmount, onBeforeMount } from 'vue';
import * as echarts from 'echarts';
import { useSimStore } from "@/store/simulation";
import { storeToRefs } from "pinia";
import { getFlowRate } from "@/api/trApi.js";
import { EventBus } from "@/eventBus"; // å¼•入事件总线
const simStore = useSimStore();
const { currentInfo } = storeToRefs(simStore);
// å®šä¹‰å›¾è¡¨çš„ DOM å…ƒç´ å¼•用
const chartDom = ref(null);
let myChart = null;
// è®¡ç®—属性:安全获取经纬度
const safeCurrentInfo = computed(() => {
  const info = currentInfo.value || {};
  return {
    longitude: info.longitude !== undefined ? info.longitude : 0.0,
    latitude: info.latitude !== undefined ? info.latitude : 0.0
  };
});
// åˆ¤æ–­æ˜¯å¦éœ€è¦æ˜¾ç¤ºé€‰æ‹©æç¤º
const showSelectPrompt = computed(() => {
  const info = safeCurrentInfo.value;
  return info.longitude === 0.0 && info.latitude === 0.0;
});
// é˜²æŠ–函数
function debounce(func, wait) {
  let timeout;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args);
    }, wait);
  };
}
// ç®€åŒ–数据源
function generateSampleData() {
  const now = new Date();
  const data = [];
  // ç”Ÿæˆ7个数据点,每10分钟一个
  for (let i = 99; i >= 0; i--) {
    const time = new Date(now.getTime() - i * 10 * 60 * 1000);
    // éšæœºç”Ÿæˆ1.8-3.1之间的水深数据
    const depth = (1.8 + Math.random() * 1.3).toFixed(2);
    data.push({
      time: time,
      value: parseFloat(depth)
    });
  }
  return data;
}
function initChart(chart) {
  const data = generateSampleData();
  const option = {
    title: {
      text: '水深变化趋势',
      textStyle: {
        color: '#fff',
        fontSize: 16
      },
      left: '-1%' // å’Œç»„ä»¶ A å¯¹é½
    },
    tooltip: {
      trigger: 'axis',
      formatter: (params) => {
        const date = new Date(params[0].value[0]);
        return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}<br/>水深:${params[0].value[1]} m`;
      },
      backgroundColor: 'rgba(0,0,0,0.7)',
      textStyle: {
        color: '#fff',
        fontSize: 14
      }
    },
    xAxis: {
      type: 'time',
      name: '', // å޻除X轴单位名称
      axisLabel: {
        color: '#fff',
        fontSize: 12,
        formatter: function (value) {
          const date = new Date(value);
          return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
        },
        interval: 'auto', // è‡ªåŠ¨è®¡ç®—é—´éš”é˜²æ­¢é‡å 
        rotate: 45 // å€¾æ–œ45度防止重叠
      },
      nameTextStyle: {
        color: '#fff',
        fontSize: 14
      }
    },
    yAxis: {
      type: 'value',
      name: 'æ°´æ·±(m)', // å•位简写为小写m
      min: 1.5,
      max: 3.5,
      axisLabel: {
        color: '#fff',
        fontSize: 12
      },
      nameTextStyle: {
        color: '#fff',
        fontSize: 14
      }
    },
    series: [{
      name: 'æ°´æ·±',
      type: 'line',
      data: data.map(item => [item.time, item.value]),
      showSymbol: true,
      lineStyle: {
        color: '#f97316', // æ”¹ä¸ºæ©™è‰²ï¼Œä¸Žæµé€Ÿä¸€è‡´
        width: 2
      },
      itemStyle: {
        color: '#f97316'
      },
      smooth: false
    }],
    // dataZoom: [{
    //   type: 'slider',
    //   start: 0,
    //   end: 100,
    //   bottom: '0%', // ä¸‹ç§»ç¼©æ”¾æŽ§ä»¶
    //   textStyle: {
    //     color: '#fff',
    //     fontSize: 12
    //   },
    //   filterMode: 'filter'
    // }],
    // å…¨å±€æ–‡å­—样式
    textStyle: {
      color: '#fff',
      fontSize: 14
    },
    animation: false
  };
  chart.setOption(option);
  window.addEventListener('resize', debounce(() => {
    chart.resize();
  }, 200));
}
watch(
  () => currentInfo.value,
  (newVal) => {
    if (!newVal || showSelectPrompt.value || !chartDom.value) return;
    console.log('经度:', newVal.longitude);
    console.log('纬度:', newVal.latitude);
    console.log('服务名称:', newVal.serviceInfo);
    // é”€æ¯æ—§å›¾è¡¨
    if (myChart) {
      myChart.dispose();
    }
    // åˆ›å»ºæ–°å›¾è¡¨
    myChart = echarts.init(chartDom.value);
    initChart(myChart);
  },
  { deep: true }
);
onMounted(() => {
  if (!showSelectPrompt.value && chartDom.value) {
    myChart = echarts.init(chartDom.value);
    initChart(myChart);
  }
});
onBeforeMount(() => {
  EventBus.on('clear-water-depth', clear);
});
onBeforeUnmount(() => {
  if (myChart) {
    myChart.dispose();
    myChart = null;
  }
  EventBus.off('clear-water-depth', clear);
});
function clear() {
  // æ¸…空 store ä¸­çš„经纬度信息
  currentInfo.value.longitude = 0.0;
  currentInfo.value.latitude = 0.0;
  // æ¸…除 ECharts å›¾è¡¨
  if (myChart) {
    myChart.clear(); // æ¸…除图表数据和图形
    myChart.dispose(); // é”€æ¯å®žä¾‹ï¼Œé‡Šæ”¾èµ„源
    myChart = null;
  }
}
function getFlowRateInfo(lon, lat, serviceInfo) {
  const params = {
    lon: lon,
    lat: lat,
    serviceName: serviceInfo
  };
  return getFlowRate(params).then(data => {
    console.log('获取到的数据:', data);
    if (data && data.code === 200) {
      return {
        depth: data.data.depth.toFixed(2),
        velocity: data.data.velocity.toFixed(2)
      };
    } else {
      return { depth: 'N/A', velocity: 'N/A' };
    }
  }).catch(error => {
    console.error('获取数据时发生错误:', error);
    return { depth: 'N/A', velocity: 'N/A' };
  });
}
function resizeChart() {
  if (myChart) {
    myChart.resize();
  }
}
defineExpose({ resizeChart });
</script>
<style scoped>
.water-depth-content {
  padding-left: 8px;
  padding-top: 8px;
  border-radius: 6px;
}
.chart-placeholder {
  margin-top: 10px;
}
.location-info,
.depth-info {
  margin-bottom: 10px;
}
.coordinates {
  font-size: 15px;
  color: #fff;
  user-select: all;
  display: inline-block;
  border-radius: 4px;
  transition: background-color 0.2s ease;
}
.depth-info p {
  font-size: 14px;
  color: #fff;
}
.depth-info strong {
  color: #fff;
  margin: 0 4px;
}
</style>
src/components/monifangzhen/WaterVelocityContent.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,300 @@
<template>
  <div class="water-velocity-content">
    <!-- æç¤ºä¿¡æ¯ -->
    <div v-if="showSelectPrompt" style=" font-weight: bold;">
      è¯·åœ¨åœ°å›¾ä¸­é€‰å–唯一测量点
    </div>
    <!-- æ­£å¸¸å†…容 -->
    <div v-else>
      <!-- ä½ç½®ä¿¡æ¯ -->
      <div class="location-info">
        <h3>位置信息</h3>
        <p class="coordinates">
          ç»åº¦ï¼š <strong>{{ safeCurrentInfo.longitude.toFixed(3) }}&nbsp;&nbsp;&nbsp;&nbsp;</strong>
          çº¬åº¦ï¼š<strong>{{ safeCurrentInfo.latitude.toFixed(3) }}</strong>
        </p>
      </div>
      <!-- æµé€Ÿä¿¡æ¯ -->
      <div class="velocity-info">
        <h3>流速信息 (m/s)</h3>
        <p>
          å¹³å‡ï¼š<strong>1.25</strong>&nbsp;&nbsp;
          æœ€å¤§ï¼š<strong>1.80</strong>&nbsp;&nbsp;
          æœ€å°ï¼š<strong>0.70</strong>
        </p>
      </div>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="chart-placeholder">
      <div ref="chartDom" style="width: 100%; height: 300px;"></div>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, watch, computed, onBeforeUnmount,onBeforeMount } from 'vue';
import * as echarts from 'echarts';
import { useSimStore } from "@/store/simulation";
import { storeToRefs } from "pinia";
import { EventBus } from "@/eventBus"; // å¼•入事件总线
const simStore = useSimStore();
const { currentInfo } = storeToRefs(simStore);
// å®šä¹‰å›¾è¡¨çš„ DOM å…ƒç´ å¼•用
const chartDom = ref(null);
let myChart = null;
// è®¡ç®—属性:安全获取经纬度
const safeCurrentInfo = computed(() => {
  const info = currentInfo.value || {};
  return {
    longitude: info.longitude !== undefined ? info.longitude : 0.0,
    latitude: info.latitude !== undefined ? info.latitude : 0.0
  };
});
// åˆ¤æ–­æ˜¯å¦éœ€è¦æ˜¾ç¤ºé€‰æ‹©æç¤º
const showSelectPrompt = computed(() => {
  const info = safeCurrentInfo.value;
  return info.longitude === 0.0 && info.latitude === 0.0;
});
// é˜²æŠ–函数
function debounce(func, wait) {
  let timeout;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args);
    }, wait);
  };
}
// ç®€åŒ–数据源
function generateSampleData() {
  const now = new Date();
  const data = [];
  // ç”Ÿæˆ7个数据点,每10分钟一个
  for (let i = 100; i >= 0; i--) {
    const time = new Date(now.getTime() - i * 10 * 60 * 1000);
    // éšæœºç”Ÿæˆ0.7-1.8之间的流速数据
    const velocity = (0.7 + Math.random() * 3).toFixed(2);
    data.push({
      time: time,
      value: parseFloat(velocity)
    });
  }
  return data;
}
function initChart(chart) {
  const data = generateSampleData();
  const option = {
    // grid: {
    //   top: '5%',     // ä¸Šè¾¹è·å‡å°‘ => æŠ˜çº¿å›¾å˜é«˜
    //   bottom: '%',  // ä¸‹è¾¹è·å‡å°‘ => æŠ˜çº¿å›¾å˜é«˜
    //   left: '10%',    // å·¦è¾¹è·å‡å°‘ => æŠ˜çº¿å›¾å˜å®½
    //   right: '10%'    // å³è¾¹è·å‡å°‘ => æŠ˜çº¿å›¾å˜å®½
    // },
    title: {
      text: '流速变化趋势',
      textStyle: {
        color: '#fff',
        fontSize: 16
      },
      // å°†æ ‡é¢˜å‘左移动 1%
      left: '-1%'
    },
    tooltip: {
      trigger: 'axis',
      formatter: (params) => {
        const date = new Date(params[0].value[0]);
        return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}<br/>流速:${params[0].value[1]} m/s`;
      },
      backgroundColor: 'rgba(0,0,0,0.7)',
      textStyle: {
        color: '#fff',
        fontSize: 14
      }
    },
    xAxis: {
      type: 'time',
      // å–消 X è½´å•位
      name: '',
      axisLabel: {
        color: '#fff',
        fontSize: 12,
        formatter: function (value) {
          const date = new Date(value);
          return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
        },
        interval: 'auto', // è‡ªåŠ¨è®¡ç®—é—´éš”é˜²æ­¢é‡å 
        rotate: 45 // å€¾æ–œ45度防止重叠
      },
      nameTextStyle: {
        color: '#fff',
        fontSize: 14
      }
    },
    yAxis: {
      type: 'value',
      name: '流速(m/s)',
      min: 0.5, // è®¾ç½®æœ€å°å€¼
      max: 6.0, // è®¾ç½®æœ€å¤§å€¼
      axisLabel: {
        color: '#fff',
        fontSize: 12,
      },
      nameTextStyle: {
        color: '#fff',
        fontSize: 14
      },
    },
    series: [{
      name: '流速',
      type: 'line',
      data: data.map(item => [item.time, item.value]),
      showSymbol: true,
      lineStyle: {
        color: '#f97316', // æ©™è‰²
        width: 2
      },
      itemStyle: {
        color: '#f97316'
      },
      smooth: true // å¹³æ»‘曲线
    }],
    // dataZoom: [{
    //   type: 'slider',
    //   start: 0,
    //   end: 100,
    //   textStyle: {
    //     color: '#fff',
    //     fontSize: 12
    //   },
    //   filterMode: 'filter', // é¿å…é‡å¤æ¸²æŸ“
    //   // ç¼©æ”¾æŽ§ä»¶å‘下移动 2%
    //   bottom: '0%'
    // }],
    // å…¨å±€æ–‡å­—样式
    textStyle: {
      color: '#fff',
      fontSize: 14
    },
    animation: false // ç¦ç”¨åŠ¨ç”»ï¼Œæé«˜æ€§èƒ½
  };
  chart.setOption(option);
  // æ·»åŠ é˜²æŠ–çš„resize监听
  window.addEventListener('resize', debounce(() => {
    chart.resize();
  }, 200));
}
watch(
  () => currentInfo.value,
  (newVal) => {
    if (!newVal || showSelectPrompt.value || !chartDom.value) return;
    console.log('经度:', newVal.longitude);
    console.log('纬度:', newVal.latitude);
    console.log('服务名称:', newVal.serviceInfo);
    // é”€æ¯æ—§å›¾è¡¨
    if (myChart) {
      myChart.dispose();
    }
    // åˆ›å»ºæ–°å›¾è¡¨
    myChart = echarts.init(chartDom.value);
    initChart(myChart);
  },
  { deep: true }
);
onMounted(() => {
  if (!showSelectPrompt.value && chartDom.value) {
    myChart = echarts.init(chartDom.value);
    initChart(myChart);
  }
});
onBeforeMount(() => {
  EventBus.on('clear-water-velocity', clear);
});
onBeforeUnmount(() => {
  if (myChart) {
    myChart.dispose();
    myChart = null;
  }
  EventBus.off('clear-water-velocity', clear);
});
function clear() {
  // æ¸…空 store ä¸­çš„经纬度信息
  currentInfo.value.longitude = 0.0;
  currentInfo.value.latitude = 0.0;
  // æ¸…除 ECharts å›¾è¡¨
  if (myChart) {
    myChart.clear(); // æ¸…除图表数据和图形
    myChart.dispose(); // é”€æ¯å®žä¾‹ï¼Œé‡Šæ”¾èµ„源
    myChart = null;
  }
}
// æ·»åŠ  resizeChart æ–¹æ³•
function resizeChart() {
  if (myChart) {
    myChart.resize();
  }
}
// æš´éœ²æ–¹æ³•给父组件调用
defineExpose({ resizeChart });
</script>
<style scoped>
.water-velocity-content {
  padding-left: 8px;
  padding-top: 8px;
  padding-bottom: -8px;
  border-radius: 6px;
}
.location-info,
.velocity-info {
  margin-bottom: 10px;
}
.coordinates {
  font-size: 15px;
  color: #fff;
  user-select: all;
  display: inline-block;
  border-radius: 4px;
  transition: background-color 0.2s ease;
}
.velocity-info p {
  font-size: 14px;
  color: #fff;
}
.velocity-info strong {
  color: #fff;
  margin: 0 4px;
}
.chart-placeholder {
  overflow: hidden;
  height: 284px;
  /* margin-top: 10px; */
}
</style>
src/components/monifangzhen/flowRateTab.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
<template>
  <div class="right-top">
    <span>模拟结果</span>
  </div>
  <el-tabs v-model="activeTab" class="full-width-tabs" @tab-change="handleTabChange">
    <el-tab-pane label="æ°´æ·±" name="depth">
      <WaterDepthContent ref="depthRef"/>
    </el-tab-pane>
    <el-tab-pane label="流速" name="velocity">
      <WaterVelocityContent ref="velocityRef" />
    </el-tab-pane>
  </el-tabs>
</template>
<script setup>
import { ref } from 'vue'
import { nextTick } from 'vue'
import WaterDepthContent from './WaterDepthContent.vue'
import WaterVelocityContent from './WaterVelocityContent.vue'
const activeTab = ref('depth')
const depthRef = ref(null)
const velocityRef = ref(null)
const handleTabChange = () => {
  nextTick(() => {
    if (activeTab.value === 'depth' && depthRef.value?.resizeChart) {
      depthRef.value.resizeChart()
    } else if (activeTab.value === 'velocity' && velocityRef.value?.resizeChart) {
      velocityRef.value.resizeChart()
    }
  })
}
</script>
<style scoped>
.full-width-tabs {
  width: 100%;
}
</style>
<style scoped>
.full-width-tabs :deep(.el-tabs__header) {
  margin: 0;
}
.full-width-tabs :deep(.el-tabs__nav) {
  width: 100%;
}
.full-width-tabs :deep(.el-tabs__item) {
  width: 50%;
  text-align: center;
  padding: 0;
  font-size: 16px;
  font-weight: bold;
  color: #fff;
}
.full-width-tabs :deep(.el-tabs__item.is-active) {
  color: #61f7d4;
}
.full-width-tabs :deep(.el-tabs__active-bar) {
  background-color: #61f7d4;
}
</style>
src/components/monifangzhen/schemeCard.vue
@@ -37,6 +37,10 @@
      :selectedScheme="currentScheme"
      @back="handleBack"
    />
    <flowRateTab
    v-if="schemeInfoShow">
      123
    </flowRateTab>
  </div>
  <Message
    @close="close"
@@ -55,6 +59,7 @@
import { useSimStore } from "@/store/simulation.js";
import { SimAPIStore } from "@/store/simAPI";
import schemeInfo from "@/components/monifangzhen/schemeInfo.vue";
import flowRateTab from "@/components/monifangzhen/flowRateTab.vue";
import { ElMessage, ElMessageBox } from "element-plus";
const emit = defineEmits(["start", "end", "reset", "closeBtn"]);
import {
src/components/monifangzhen/schemeInfo.vue
@@ -236,7 +236,6 @@
  margin-bottom: 20px;
  border-radius: 8px;
  color: white;
  .left-top {
    display: flex;
    justify-content: space-between;
@@ -264,7 +263,7 @@
  }
  .details {
    padding: 8px;
    padding-left: 8px;
    .input-group {
      display: flex;
src/components/tools/Tools.vue
@@ -233,12 +233,8 @@
      }
    },
    æ—¥ç…§åˆ†æž: () => {
      showSunAnalysis.value = !showSunAnalysis.value
      if (showSunAnalysis.value) {
        // mapUtils.AnalysisSunshine()
      } else {
        // mapUtils.disableAtmosphere()
      }
      window.Viewer = earthCtrl.viewer;
      mapUtils.AnalysisSunshine()
    },
    æ–‡å­—标绘: () => mapUtils.CreateLabel("label", true),
src/utils/tools.js
@@ -674,8 +674,6 @@
  
  // é˜´å½±åˆ†æž
  AnalysisSunshine() {
    if (!layerIsOpen) {
      layerIsOpen = true;
      let currentTime = viewer.clock.currentTime.clone();
      let startTime = viewer.clock.startTime.clone();
      let stopTime = viewer.clock.stopTime.clone();
@@ -684,7 +682,7 @@
        type: 2,
        title: "阴影分析",
        shade: false,
        area: ["350px", "540px"],
        area: ["350px", "350px"],
        offset: "r",
        skin: "other-class",
        content: SmartEarthRootUrl + "Workers/analysis/AnalysisShadow.html",
@@ -697,7 +695,6 @@
          Viewer.shadowLayer = false;
        }
      });
    }
  },
  transformCartesianToCoord(position) {
    const cartographic = SmartEarth.Cesium.Cartographic.fromCartesian(position);
src/utils/water.js
@@ -38,30 +38,46 @@
    "#ffe930", "#fdd10a", "#feb652", "#fd7f06",
    "#fe2b07", "#4d0a08"
  ];
  // æž„造 waterHeightLevels:从 min åˆ° max ä½¿ç”¨æŒ‡æ•°å¢žé•¿ï¼Œç¡®ä¿å°å€¼åŒºåŸŸæ›´å¯†é›†
  const levelCount = colorStops.length; // ä¿æŒå’Œé¢œè‰²æ•°é‡ä¸€è‡´
  const levelCount = colorStops.length;
  const minAllowed = 0.01; // æœ€å°å…è®¸å€¼
  const threshold = 1;     // å°å€¼ä¸Žå¤§å€¼åˆ†ç•Œç‚¹
  let effectiveMin = Math.max(minFlowRate, minAllowed); // æœ€å°ä¸èƒ½å°äºŽ 0.01
  const waterHeightLevels = [];
  // æŒ‡æ•°å¢žé•¿å…¬å¼ï¼šy = base^x
  const base = Math.exp(Math.log(maxFlowRate / minFlowRate) / (levelCount - 1));
  // åˆ†ä¸¤æ®µæž„造高度数组
  for (let i = 0; i < levelCount; i++) {
    const ratio = i / (levelCount - 1); // 0 ~ 1
    // ä½¿ç”¨æŒ‡æ•°æ’值,保证低值区域更密集
    const height = minFlowRate * Math.pow(base, i);
    const color = colorStops[i];
    let ratio = i / (levelCount - 1); // 0 ~ 1
    let height;
    if (ratio <= 0.5) {
      // å‰åŠæ®µï¼šä½Žå€¼åŒºåŸŸï¼Œä½¿ç”¨å¼ºæŒ‡æ•°å¢žé•¿ï¼Œä»Ž effectiveMin åˆ° threshold
      const localRatio = ratio * 2; // æ˜ å°„到 0~1
      const expRatio = Math.pow(localRatio, 2); // æ›´å¼ºè°ƒä½Žå€¼åŒºåŸŸå¯†åº¦
      height = effectiveMin + (threshold - effectiveMin) * expRatio;
    } else {
      // åŽåŠæ®µï¼šé«˜å€¼åŒºåŸŸï¼Œä»Ž threshold åˆ° maxFlowRate,使用指数增长
      const localRatio = (ratio - 0.5) * 2; // æ˜ å°„到 0~1
      const expBase = Math.exp(Math.log(maxFlowRate / threshold) / 1);
      height = threshold * Math.pow(expBase, localRatio);
    }
    waterHeightLevels.push({
      height: height.toFixed(2), // å¯é€‰ï¼šä¿ç•™ä¸¤ä½å°æ•°
      color
      height: parseFloat(height.toFixed(2)), // ä¿ç•™ä¸¤ä½å°æ•°
      color: colorStops[i]
    });
  }
  waterLegendData.value = waterHeightLevels
  console.log(waterLegendData.value,'图里数据');
  waterLegendData.value = waterHeightLevels;
  console.log(waterLegendData.value, '图例数据');
  // åˆ›å»ºå›¾å±‚
  water = earthCtrl.simulate.createWaterSimulateLayer({
    baseUrl,
    interval,
    color: new SmartEarth.Cesium.Color.fromCssColorString("#D4F2E7"),
    color: SmartEarth.Cesium.Color.fromCssColorString("#D4F2E7"),
    loop: false,
    callback: timeCallback,
    alphaByDepth: -0.3,
@@ -70,7 +86,7 @@
  });
  console.log(
    `仿真模拟参数:请求路径 ${baseUrl}, å¸§é—´é—´éš” ${interval}ms, æ˜¯å¦å¼€å¯ä¸“题渲染 ${colorRender},图例参数${waterHeightLevels}`
    `仿真模拟参数:请求路径 ${baseUrl}, å¸§é—´é—´éš” ${interval}ms, æ˜¯å¦å¼€å¯ä¸“题渲染 ${colorRender}`
  );
}
/**