src/api/trApi.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/menu/TimeLine.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/monifangzhen/schemeCard.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/store/simulation.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/left/CitySim.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/left/KGSimOption/RealTimeSimulation.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/trApi.js
@@ -81,6 +81,28 @@ throw error; } } // 实时模拟的结果 export async function getSimresult(ids) { try { const res = await instance.get(`/simu/results?id=${ids}`); return res.data; } catch (error) { console.error("Error deleting simulation data:", error); throw error; } } // 结束实时模拟 export async function stopSim(ids) { try { const res = await instance.get(`/simu/stop?id=${ids}`); return res.data; } catch (error) { console.error("Error deleting simulation data:", error); throw error; } } // ************************************************************************************************************** // 解析json获取泥石流参数 export function parseWaterSimulationData(jsonData) { src/components/menu/TimeLine.vue
@@ -2,20 +2,30 @@ <div class="timeline-container"> <div class="controls"> <div class="control-btn" @click="skipBackward"> <img src="@/assets/img/timeline/left.png" class="fas fa-step-backward" /> <img src="@/assets/img/timeline/left.png" class="fas fa-step-backward" /> </div> <div class="control-btn play-btn" @click="togglePlay"> <img v-show="isPlaying" src="@/assets/img/timeline/stop.png" /> <img v-show="!isPlaying" src="@/assets/img/timeline/start.png" /> </div> <div class="control-btn" @click="skipForward"> <img src="@/assets/img/timeline/right.png" class="fas fa-step-forward" /> <img src="@/assets/img/timeline/right.png" class="fas fa-step-forward" /> </div> <div class="speed-control" v-show="speedShow"> <div @click="toggleSpeedMenu">{{ playbackRate }}X</div> <div class="speed-menu" v-show="showSpeedMenu"> <div v-for="rate in playbackRates" :key="rate" @click.capture="setPlaybackRate(rate)" :class="{ active: playbackRate === rate }"> <div v-for="rate in playbackRates" :key="rate" @click.capture="setPlaybackRate(rate)" :class="{ active: playbackRate === rate }" > {{ rate }}X </div> </div> @@ -25,19 +35,33 @@ <div class="timeline"> <div class="dates"> <div class="current-date">当前播放时间:{{ currentPlayingTime }}</div> <div v-for="(date, index) in visibleDates" :key="index" class="date-label"> <div v-for="(date, index) in visibleDates" :key="index" class="date-label" > <!-- {{ formatDate(date) }} --> </div> <div> 专题渲染: <el-switch v-model="isColorRenderEnabled" @change="handleColorRenderChange" style="margin-top: -3px" :disabled="!isPlaying || !isWaterPrimitiveCreated" /> <el-switch v-model="isColorRenderEnabled" @change="handleColorRenderChange" style="margin-top: -3px" :disabled="!isPlaying || !isWaterPrimitiveCreated" /> <!-- active-text="开" inactive-text="关" --> </div> </div> <div class="timeline-track" ref="timelineTrack" @click="seekToPosition"> <div class="timeline-progress" :style="{ width: progressPercentage + '%' }"></div> <div class="timeline-cursor" :style="{ left: progressPercentage + '%' }"></div> <div class="timeline-progress" :style="{ width: progressPercentage + '%' }" ></div> <div class="timeline-cursor" :style="{ left: progressPercentage + '%' }" ></div> <div class="scale-markers"> <div class="scale-marker" style="left: 0%"></div> <div class="scale-marker" style="left: 25%"></div> @@ -46,8 +70,12 @@ <div class="scale-marker" style="left: 100%"></div> </div> <div class="time-markers"> <div v-for="(time, index) in timeMarkers" :key="index" class="time-marker" :style="{ left: `${index * 25}%`, transform: 'translateX(-50%)' }"> <div v-for="(time, index) in timeMarkers" :key="index" class="time-marker" :style="{ left: `${index * 25}%`, transform: 'translateX(-50%)' }" > <div class="date-part">{{ time.split(" ")[0] }}</div> <div class="time-part">{{ time.split(" ")[1] }}</div> </div> @@ -56,27 +84,38 @@ </div> <div> <div style="display: flex"> <ratelevel ref="ratelevelRef" :playing-time="sendCurrentPlayingTime" @finish-calculation="handleFinishCalculation" style=" <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=" " /> <crossanalysis ref="crossRef" style=" margin-top: 12px; margin-left: 16px; margin-right: 20px; justify-content: flex-end; " /> " /> </div> <el-button @click="handleBack" style=" <el-button @click="handleBack" style=" margin-top: 3px; margin-left: 28px; margin-right: 10px; width: 75%; height: 30%; ">结束模拟</el-button> " >结束模拟</el-button > </div> </div> </template> @@ -107,9 +146,9 @@ updateWaterColor, } from "@/utils/water"; import mapUtils from "@/utils/tools.js"; import { fetchWaterSimulationData } from "@/api/trApi.js"; import { fetchWaterSimulationData, stopSim } from "@/api/trApi.js"; import { EventBus } from "@/eventBus"; import { ElMessage } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus"; // 状态管理器 import { useSimStore } from "@/store/simulation"; import { storeToRefs } from "pinia"; @@ -278,7 +317,8 @@ const startPlayback = () => { clearInterval(playInterval); if (selectedScheme.value.type === 2) { // 新建方案中的实时模拟不能倍速 if (selectedScheme.value.type === 2 && simStore.rePlayList.length == 0) { // 类型为 2:每 5 秒跳动一次 playInterval = setInterval(() => { const fiveSeconds = 5; @@ -541,9 +581,9 @@ // 全局状态记录 const colorState = { currentColor: "#F5F0E6", // 当前颜色 currentAlpha: -0.3, // 当前透明度 colorStages: null, // 预计算的颜色阶段时间点 maxColorTime: null // 记录达到最深颜色时的时间点 currentAlpha: -0.3, // 当前透明度 colorStages: null, // 预计算的颜色阶段时间点 maxColorTime: null, // 记录达到最深颜色时的时间点 }; // 预计算颜色阶段时间点 @@ -556,17 +596,17 @@ { hex: "#D4B483", luminance: 184.0 }, // stage 2 { hex: "#B78B6A", luminance: 148.4 }, // stage 3 { hex: "#8B5A3A", luminance: 101.0 }, // stage 4 { hex: "#744C33", luminance: 84.5 }, // stage 5 { hex: "#5D3D2C", luminance: 68.1 } // stage 6 { hex: "#744C33", luminance: 84.5 }, // stage 5 { hex: "#5D3D2C", luminance: 68.1 }, // stage 6 ]; const alphaStops = [ -0.2, // stage 0 -0.3, // stage 1 -0.4, // stage 2 -0.5, // stage 3 -0.6, // stage 4 -0.7, // stage 5 -0.8 // stage 6 -0.2, // stage 0 -0.3, // stage 1 -0.4, // stage 2 -0.5, // stage 3 -0.6, // stage 4 -0.7, // stage 5 -0.8, // stage 6 ]; // 累计降雨量阈值(mm) const R_THRESHOLDS = [0, 200, 240, 280, 310, 350]; // 共6个阶段对应6个阈值 @@ -579,7 +619,7 @@ const total = rainTotalInfo.value[i].total; // 使用 total 替代 intensity timeTotals.push({ time, total total, }); } @@ -594,7 +634,7 @@ startTime: time, color: COLOR_STOPS[stage].hex, alpha: alphaStops[stage], threshold threshold, }; break; } @@ -606,7 +646,7 @@ startTime: 0, color: COLOR_STOPS[0].hex, alpha: alphaStops[0], threshold: 0 threshold: 0, }; colorState.colorStages = stages; @@ -623,7 +663,10 @@ // 查找当前时间点所属的阶段 let currentStage = 0; for (let i = colorState.colorStages.length - 1; i >= 0; i--) { if (colorState.colorStages[i] && currentTime.value >= colorState.colorStages[i].startTime) { if ( colorState.colorStages[i] && currentTime.value >= colorState.colorStages[i].startTime ) { currentStage = i; break; } @@ -631,15 +674,21 @@ // 记录达到最深颜色的时间点 if (currentStage >= colorState.colorStages.length - 1) { if (colorState.maxColorTime === null || currentTime.value > colorState.maxColorTime) { if ( colorState.maxColorTime === null || currentTime.value > colorState.maxColorTime ) { colorState.maxColorTime = currentTime.value; } } // 判断是否需要强制更新颜色 const isTimeGoingBackward = currentTime.value < colorState.lastTime; const isBeforeMaxColorTime = colorState.maxColorTime !== null && currentTime.value <= colorState.maxColorTime; const shouldForceUpdate = isForceUpdate && (isTimeGoingBackward || isBeforeMaxColorTime); const isBeforeMaxColorTime = colorState.maxColorTime !== null && currentTime.value <= colorState.maxColorTime; const shouldForceUpdate = isForceUpdate && (isTimeGoingBackward || isBeforeMaxColorTime); // 更新颜色逻辑 if (shouldForceUpdate || isTimeGoingBackward) { @@ -652,7 +701,9 @@ const newAlpha = colorState.colorStages[currentStage].alpha; // 只应用更暗的颜色和更低的透明度 if (calculateLuminance(newColor) < calculateLuminance(colorState.currentColor)) { if ( calculateLuminance(newColor) < calculateLuminance(colorState.currentColor) ) { colorState.currentColor = newColor; } if (newAlpha < colorState.currentAlpha) { @@ -717,7 +768,8 @@ // 判断是否需要强制更新颜色 const isGoingBackward = newTime < currentTime.value; const isBeforeMaxColor = colorState.maxColorTime !== null && newTime <= colorState.maxColorTime; const isBeforeMaxColor = colorState.maxColorTime !== null && newTime <= colorState.maxColorTime; const shouldForceUpdate = isGoingBackward || isBeforeMaxColor; currentTime.value = newTime; @@ -910,16 +962,25 @@ ); const jsonFetch = ref(null); const currentReplayIndex = ref(0); // 当前播放的rePlayList索引 // 提取为独立函数 async function initializeSimulationData(force = false) { async function initializeSimulationData(replayItem = null) { try { const schemeInfo = selectedScheme.value; serviceInfo = schemeInfo.serviceName; if (schemeInfo.type == 2) { speedShow.value = false; jsonFetch.value = layerDate.value; if ( replayItem || (simStore.rePlayList && simStore.rePlayList.length != 0) ) { jsonFetch.value = replayItem || simStore.rePlayList[currentReplayIndex.value]; speedShow.value = true; } else { jsonFetch.value = layerDate.value; speedShow.value = false; } } else { getRainfallData(); speedShow.value = true; @@ -962,11 +1023,36 @@ } } // 挂载时调用 onMounted(async () => { // 因为这个函数实时模拟监听也需要使用,所以封装了一个函数 await initializeSimulationData(); }); // 播放完成后的回调 function handlePlayFinished() { if (selectedScheme.value.type !== 2) return; finishPlay.value = false; currentReplayIndex.value++; if (currentReplayIndex.value < simStore.rePlayList.length) { console.log(currentReplayIndex.value); // 自动播放下一个 initializeSimulationData(simStore.rePlayList[currentReplayIndex.value]); togglePlay(); shouldAutoPlay.value = false; } else { // 所有项目播放完成 currentReplayIndex.value = 0; // 重置索引 isPlaying.value = false; // 停止播放 } } // 监听播放完成事件 watch( () => finishPlay.value, (newVal) => { if (newVal && selectedScheme.value.type === 2) { handlePlayFinished(); } } ); const shouldAutoPlay = ref(false); // 监听 layerDate 变化后标记准备播放 watch( @@ -986,6 +1072,13 @@ shouldAutoPlay.value = false; } }); // 挂载时调用 onMounted(async () => { // 因为这个函数实时模拟监听也需要使用,所以封装了一个函数 await initializeSimulationData(); }); // 根据返回数据的个数去渲染时间轴 function updateTimelineRange() { if (waterTimestamps.value.length > 0) { @@ -1002,28 +1095,63 @@ }); const { endSimulate } = inject("simulateActions"); function handleBack() { endSimulate(); // 停止实时模拟定时器 EventBus.emit("close-time"); async function handleBack() { // 实时模拟弹窗确认是返回方案列表还是停止模拟 if (selectedScheme.value.type === 2) { try { await ElMessageBox.confirm("方案未停止时结束模拟后,后台将停止计算", { confirmButtonText: "返回列表", cancelButtonText: "结束模拟", type: "warning", }); // 用户点击了确认,这里不执行任何操作,仅关闭对话框 } catch (error) { stopSim(selectedScheme.value.id).then((res) => { if (res.code == 404) { ElMessage.warning("该服务已停止"); } else { ElMessage.success("服务正在停止中"); } }); // return; } } // 不管type是不是2,最终都执行结束模拟的操作 endSimulation(); } async function endSimulation() { EventBus.emit("close-time"); endSimulate(); isWaterPrimitiveCreated.value = false; // 结束计算和停止拾取 if (ratelevelRef.value) { ratelevelRef.value.endCalculation(); ratelevelRef.value.stopPicking(); } // 清除点 if (crossRef.value) { crossRef.value.clearPoints(); console.log("执行删除点功能"); } emit("isColorRender", false); // 延迟删除雨量图层 setTimeout(() => { mapUtils.delRain(); }, 3000); destoryWaterPrimitive(); // 发送事件隐藏相关信息 EventBus.emit("hide-schemeInfo"); EventBus.emit("clear-water-depth"); EventBus.emit("clear-water-velocity"); ElMessage({ message: "模拟进程正在关闭中...", type: "success" }); } </script> src/components/monifangzhen/schemeCard.vue
@@ -29,6 +29,9 @@ @click="startPlay(item)" >进入模拟</el-button > <el-button size="small" v-show="item.type == 2" @click="rePlay(item)" >历史回放</el-button > <!-- :disabled="item.status !== 2" --> </div> </div> @@ -50,7 +53,15 @@ <script setup> import { EventBus } from "@/eventBus"; // 引入事件总线 import { onMounted, ref, watch, defineEmits, onUnmounted } from "vue"; import { nextTick, onMounted, ref, watch, defineEmits, onUnmounted, inject, } from "vue"; import dayjs from "dayjs"; import { initeWaterPrimitiveView } from "@/utils/water"; import Message from "@/components/tools/Message.vue"; @@ -61,11 +72,11 @@ import { ElMessage, ElMessageBox } from "element-plus"; const emit = defineEmits(["start", "end", "reset", "closeBtn"]); import { getRegionData, getSimData, deleteSimData, getSimStart, getSimDataById, getSimresult, } from "@/api/trApi.js"; const simStore = useSimStore(); @@ -101,8 +112,9 @@ // 实时模拟五分钟请求一次的定时器 const realTimeSimInterval = ref(null); const { startSimulate, endSimulate } = inject("simulateActions"); async function startPlay(item) { console.log(item, "item"); if (item.status === 2) { ElMessage.warning("当前方案正在分析中,无法进入模拟!"); return; @@ -131,7 +143,7 @@ currentScheme.value = item; schemeInfoShow.value = true; emit("closeBtn", false); emit("start"); startSimulate(); return; } @@ -151,47 +163,35 @@ return; } // 处理 type == 2 的情况(实时模拟) if (item.type === 2) { // 清除已有定时器,防止重复启动 if (realTimeSimInterval.value) { clearInterval(realTimeSimInterval.value); } // 即刻执行一次 await executeRealTimeSimulation(item); // 每隔 5 分钟执行一次 realTimeSimInterval.value = setInterval(() => { executeRealTimeSimulation(item); }, 5 * 60 * 1000); // 5分钟 return; } // 默认情况:有服务名称 simStore.setSelectedScheme(item); } // 封装实时模拟的异步操作 async function executeRealTimeSimulation(item) { try { const ress = await getSimStart(item.id); const res = await getSimDataById(item.id); item.serviceName = res.data[0]?.serviceName || null; simStore.setSelectedScheme(item); getScheme(); if (ress.code === 200) { simStore.layerDate = ress.data; initeWaterPrimitiveView(); emit("start"); } } catch (e) { console.error("实时模拟获取模拟数据失败:", e); } // 实时模拟历史回放 function rePlay(item) { // 当前选中的方案 simStore.setSelectedScheme(item); // 拿id去请求results接口,如果长度不为0,则可以进行历史回放 getSimresult(item.id) .then((res) => { if (res.code == 500) { // 如果长度为0,提示用户并且不进行后续操作 ElMessage.warning("提示:没有可回放的数据!"); return; // 阻止后续操作 } else { simStore.rePlayList = res.data; console.log(simStore.rePlayList, "lisi"); } // 使用 nextTick 确保 DOM 更新后再执行后续操作 nextTick(() => { initeWaterPrimitiveView(); startSimulate(); }); }) .catch((error) => { console.log("请求失败:", error); // 错误处理 }); } function handleBack(value) { @@ -221,10 +221,10 @@ item.result == "创建仿真" || item.result == "完成" || item.result == "-1" || item.result == null item.result == "停止" || item.result == "进行中" ); simAPIStore.shouldPoll = !shouldStop; // 修改 Pinia 状态 console.log(shouldStop, "aaaaaaaaaaaaaaaa"); // 3. 如果需要停止 if (shouldStop) { if (intervalId) { src/store/simulation.js
@@ -2,6 +2,8 @@ import { defineStore } from 'pinia' import { ref } from 'vue' export const useSimStore = defineStore('simulation', () => { // 历史回放列表 const rePlayList = ref([]) // 北京市所有村的code const townCodeAll = ref([]) // 实时模拟最新的layer @@ -47,10 +49,22 @@ // 降雨单位 const intensityUnit = ref() const setSelectedScheme = (scheme) => { selectedScheme.value = scheme rainFalls.value = JSON.parse(scheme.data).rainfalls intensityUnit.value = JSON.parse(scheme.data).intensityUnit } selectedScheme.value = scheme; try { const parsedData = JSON.parse(scheme.data); // 只有当 rainfalls 和 intensityUnit 存在且非空时才赋值 if (parsedData.rainfalls && parsedData.intensityUnit) { rainFalls.value = parsedData.rainfalls; intensityUnit.value = parsedData.intensityUnit; } else { console.warn("缺少必要的 rainfalls 或 intensityUnit 字段"); } } catch (error) { console.error("解析 scheme.data 出错", error); } }; const clearSelectedScheme = () => { selectedScheme.value = null } @@ -200,6 +214,7 @@ devices, frameNum, layerDate, rePlayList, // 方案相关方法 setSchemCard, src/views/left/CitySim.vue
@@ -495,7 +495,9 @@ : undefined, })); console.log(rawRainFallList, "原始降雨数据"); // 更新 forms.rainFallList,可用于图表显示等用途 forms.rainFallList = rawRainFallList; // 判断是否为整小时数据(即相邻时间间隔是否为整小时) const isHourlyData = checkIfHourlyData(rawRainFallList); @@ -513,9 +515,6 @@ intensity: item.intensity, })); } // 更新 forms.rainFallList,可用于图表显示等用途 forms.rainFallList = rawRainFallList; // 计算起始时间和结束时间(毫秒数) const firstTime = parseDateTime(hourlyRainfallList[0]?.time); src/views/left/KGSimOption/RealTimeSimulation.vue
@@ -81,7 +81,8 @@ import { useSimStore } from "@/store/simulation.js"; import { EventBus } from "@/eventBus"; // 引入事件总线 import { getDeviceInfoSHG, getYLJData } from "@/api/hpApi"; import { getSimStart, getSimDataById } from "@/api/trApi"; import { getSimStart, getSimDataById, getSimresult } from "@/api/trApi"; import { ControlSchemeType } from "@/assets/js/lib-pixelstreamingfrontend.esm"; // 获取 Store 实例 const simAPIStore = SimAPIStore(); @@ -219,6 +220,8 @@ // 实时模拟定时器 let pollingInterval = null; // 用于记录上次数据条数 let lastDataLength = 0; async function startPlay() { // 开始模拟前需要先保存方案 @@ -246,30 +249,27 @@ }); try { // 调用求解器并初始化模拟 const resStart = await getSimStart(schemeId); // 启动模拟 await getSimStart(schemeId); // 请求完成后关闭加载提示 loadingMessage.close(); if (resStart.code === 200) { const res = await getSimDataById(schemeId); simStore.setSelectedScheme(res.data[0]); simStore.layerDate = resStart.data; initeWaterPrimitiveView(); // 首次请求延迟 90s setTimeout(async () => { try { startSimulate(); // 这里可能会报错 } catch (error) { console.error("调用 startSimulate 出错:", error); } const res = await getSimresult(schemeId); console.log(res.data, "实时模拟 - 初始结果"); // 开始轮询任务:每 5 分钟调用一次 getSimStart 并更新方案数据 startPolling(schemeId); } else { ElMessage.error(resStart.message || "调用求解器失败"); } if (res.data.length > 0) { handleNewData(res.data, schemeId); } // 显示结果并开始轮询 loadingMessage.close(); startPolling(schemeId); } catch (error) { console.error("首次请求模拟结果失败", error); loadingMessage.close(); } }, 3 * 60 * 1000); // 1.5 分钟后第一次请求 } catch (error) { loadingMessage.close(); ElMessage.error("请求失败:" + (error.message || "未知错误")); @@ -277,27 +277,85 @@ } } // 启动轮询函数 // 定时五分钟请求 function startPolling(schemeId) { stopPolling(); // 避免重复启动 stopPolling(); // 确保不会重复启动 pollingInterval = setInterval(async () => { try { const resStart = await getSimStart(schemeId); const res = await getSimresult(schemeId); if (resStart.code === 200) { const res = await getSimDataById(schemeId); simStore.setSelectedScheme(res.data[0]); // 更新方案数据 simStore.layerDate = resStart.data; // 更新 layer 数据 if (res.data && res.data.length > 0) { if (res.data.length === lastDataLength) { console.log("主轮询:无新数据,切换为 10 秒高频轮询"); console.log("轮询获取最新数据成功"); } else { console.warn("轮询请求失败:", resStart.message); clearInterval(pollingInterval); pollingInterval = null; startFastPolling(schemeId); // 启动高频轮询 } else { handleNewData(res.data, schemeId); } } } catch (error) { console.error("轮询请求异常:", error); console.error("轮询获取模拟结果失败", error); } }, 5 * 60 * 1000); // 每 5 分钟执行一次 }, 5.6 * 60 * 1000); // 每 5.5 分钟执行一次 } let fastPollingInterval = null; // 如果五分钟没拿到最新的数据,则开启十秒钟调用一次,拿到最新的数据就停止 function startFastPolling(schemeId) { fastPollingInterval = setInterval(async () => { try { const res = await getSimresult(schemeId); if (res.data && res.data.length > 0) { if (res.data.length !== lastDataLength) { console.log("高频轮询:检测到新数据,恢复主轮询"); clearInterval(fastPollingInterval); fastPollingInterval = null; handleNewData(res.data, schemeId); startPolling(schemeId); // 重新启动主轮询 } } } catch (error) { console.error("高频轮询获取模拟结果失败", error); } }, 10 * 1000); // 每 10 秒执行一次 } // 拿取最新的layer.json存储到pinia中 async function handleNewData(dataArray, schemeId) { // 拿服务名称 const res = await getSimDataById(schemeId); simStore.setSelectedScheme(res.data[0]); // 更新方案数据 const latestItem = dataArray[dataArray.length - 1]; const currentLength = dataArray.length; if (currentLength <= lastDataLength) { console.log("本轮无新数据(长度未变化)"); return; } // 更新标识 lastDataLength = currentLength; // 执行更新逻辑 console.log("检测到新数据,更新中..."); console.log(latestItem, "last"); simStore.layerDate = latestItem; initeWaterPrimitiveView(); try { startSimulate(); } catch (error) { console.error("调用 startSimulate 出错:", error); } } // 停止轮询函数 @@ -305,8 +363,14 @@ if (pollingInterval) { clearInterval(pollingInterval); pollingInterval = null; console.log("轮询已停止"); } if (fastPollingInterval) { clearInterval(fastPollingInterval); fastPollingInterval = null; } console.log("轮询已停止"); } EventBus.on("close-time", () => {