From f6bc35f8843bd36af9f5c897e097d6e7e499fd33 Mon Sep 17 00:00:00 2001 From: blak-kong <546598185@qq.com> Date: Wed, 27 Mar 2024 10:22:32 +0800 Subject: [PATCH] we200 --- src/app.config.ts | 2 +- src/components/bluetoot/connection/we200.js | 100 + src/moduleIOT/pages/iotCarePlan/WE200.less | 1 + src/moduleIOT/pages/iotCarePlan/WE200.tsx | 2562 +++++++++++++++++++ src/pages/index/index.tsx | 5 + 5 files changed, 2669 insertions(+), 1 deletion(-) create mode 100644 src/components/bluetoot/connection/we200.js create mode 100644 src/moduleIOT/pages/iotCarePlan/WE200.less create mode 100644 src/moduleIOT/pages/iotCarePlan/WE200.tsx diff --git a/src/app.config.ts b/src/app.config.ts index 2f1c6bc..ac0e79f 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -79,7 +79,7 @@ export default defineAppConfig({ subPackages: [ { root: "moduleIOT", - pages: ["pages/iotCarePlan/WL200", "pages/iotCarePlan/FR200"], + pages: ["pages/iotCarePlan/WL200", "pages/iotCarePlan/FR200", "pages/iotCarePlan/WE200",], }, { root: "recoding", diff --git a/src/components/bluetoot/connection/we200.js b/src/components/bluetoot/connection/we200.js new file mode 100644 index 0000000..8975fa1 --- /dev/null +++ b/src/components/bluetoot/connection/we200.js @@ -0,0 +1,100 @@ +/**蓝牙命令合集*/ +export const we200BleCommand = { + /**发送配对码*/ + match: { + commandType: "BleMatch", + bleCommandType: "SendMatchCode", + }, + + InfoQuery: { + /**查询版本指令*/ + versionInfo: { + commandType: "InfoQuery", + infoQueryType: "versionInfo", + otaDeviceType: "FR200", + }, + /**时间同步*/ + timeSync: { + commandType: "InfoQuery", + infoQueryType: "timeSync", + date: new Date(), + }, + /**查询离线记录概要*/ + clockSummary: { + commandType: "InfoQuery", + infoQueryType: "offlineClockInInfo", + dataType: "summary", + }, + /**查询离线明细第一条*/ + clockDetail: { + commandType: "InfoQuery", + infoQueryType: "offlineClockInInfo", + dataType: "detail", + dataIndex: 0, + }, + }, +}; + +/**控制设备命令合集*/ +export const we200DeviceControlCommand = { + /**查询设备状态指令*/ + queryDeviceStatus: { + commandType: "DeviceStatusSync", + deviceSyncCommandType: "onlySyncStatusToDevice", + }, + /**控制设备暂停 pause暂停 working启动 end关闭*/ + pause: { + commandType: "DeviceControl", + workStatus: "pause", + }, + standby: { + commandType: "DeviceControl", + workStatus: "standby", + }, + end: { + commandType: "DeviceControl", + workStatus: "end", + }, + + /** + * '进入水分测试模式' = 'switchTestMode', + * '启动水分测试模式' = 'startTestMode', + * + * face: "01", 面部 + * eyes: "02", 眼部 + * + * nasolabialFold: "03", 法令纹Pro + * mandibularLine: "04", 下颌线Pro + * led: "05", led + * headLiftingPro: "09", 抬头纹Pro + * + * moistureTest: "06", 水分测试 + * maskPenetration: "07",面膜促渗 + * essence: "08", 精华模式 + * + * neck: "0A", 颈纹 + * partition: "11", 分区模式 废弃VM1001 + * ignore: "255", 不改变/无模式 + */ + + // 普通工作模式 + work: { + commandType: "DeviceControl", + workStatus: "working", + workMode: "ignore", // face + }, + // 切换/进入水分测试 + switchTestMode: { + commandType: "DeviceControl", + workStatus: "working", + workMode: "moistureTest", + testStatus: "standby", + }, + // 启动水分测试 + startTestMode: { + commandType: "DeviceControl", + workStatus: "working", + workMode: "moistureTest", + testStatus: "start", + }, +}; diff --git a/src/moduleIOT/pages/iotCarePlan/WE200.less b/src/moduleIOT/pages/iotCarePlan/WE200.less new file mode 100644 index 0000000..231581f --- /dev/null +++ b/src/moduleIOT/pages/iotCarePlan/WE200.less @@ -0,0 +1 @@ +@import url(./WL200.less); diff --git a/src/moduleIOT/pages/iotCarePlan/WE200.tsx b/src/moduleIOT/pages/iotCarePlan/WE200.tsx new file mode 100644 index 0000000..d9e50e2 --- /dev/null +++ b/src/moduleIOT/pages/iotCarePlan/WE200.tsx @@ -0,0 +1,2562 @@ +import Taro from "@tarojs/taro"; +import dayjs from "dayjs"; +import classnames from "classnames"; +import { debounce } from "lodash"; + +import React, { + Component, + PropsWithChildren, + useEffect, + useState, +} from "react"; +/*** redux ***/ +import { connect } from "react-redux"; +/*** redux end ***/ +import { + Block, + View, + Text, + Image, + Video, + Input, + Button, +} from "@tarojs/components"; + +import { sendCommand } from "@/utils/bluetoothWXAPI"; +import { + deviceCommandSamples, + bleCommandSamples, +} from "@/components/bluetoot/connection/wl200"; + +import { minSecToS, s_to_ms, s_to_hms, sleep, s_to_s } from "@/utils/util"; +// import { DeviceToolKit as DeviceToolKitWE100 } from "@flossom-npm/iot-translater-we100"; +import { + DeviceToolKit as DeviceToolKitWM, + TResponseFromDevice as TResponseFromDeviceWM, +} from "@flossom-npm/iot-translater"; +import commandMap from "@/utils/commandMap"; +import { Popup } from "@antmjs/vantui"; +import { fr200BleCommand } from "@/components/bluetoot/connection/fr200"; + +import { go, getStorageSync, setStorageSync, msg } from "@/utils/traoAPI"; +import { InstrumentInfo } from "@/utils/Interface"; + +/* 公共组件 */ +import Navbar from "@/components/navbar/navbar"; +import PopupCountdown from "@/components/popup/popup-countdown"; +import PopupStepTips from "@/components/popup/popup-step-tips"; +import PopupConfirm from "@/components/popup/popup-confirm"; +import PopupAlert from "@/components/popup/popup-alert"; +import PopupStatus from "@/components/popup/popup-status"; +import PopupInstrumentUploadTips from "@/components/popup/popup-instrument-upload-tips"; +import ConnectionBluetoot from "@/components/bluetoot/connection"; +/* 公共组件 END */ + +/* 本页组件 */ +import ElectricityView from "./components/ElectricityView/Electricity"; +import ModeListView from "./components/ModeList/FR200"; +import Footer from "./components/Footer/FR200"; +import WaterTest from "./components/WaterTest/index"; +/* 本页组件 END */ + +import Echarts from "./components/Echart"; +import EchartsFullScean from "./components/EchartFullScean"; + +import Gears from "./components/Gears"; + +import "./FR200.less"; + +import BluetoothContainer from "./components/Bluetoot/FR200"; + +const deviceToolKitInstanceFR200 = new DeviceToolKitWM("FR200"); +let deviceToolKitInstance = deviceToolKitInstanceFR200; + +let currentTimeTimer: any = null; // 当前项目时间定时器 +let CountdownTimer: any = null; +let timer: any = null; +let showTipsTimer: any = null; +let loadingTipsTimer: any = null; // 蓝牙连接提示 +// 设备运行时间校准频率,每多少秒校准一次 +const TIME_CALIBRATION_FREQUENCY = 5; + +const MODE_WORKING_ENUM = { + STANDBY: "standby", // 待命 + WORKING: "working", // 工作 + PAUSE: "pause", + END: "end", +}; + +// 不同模式启动前的倒计时时间 +let CountDownTime = {}; + +let DeviceSyncData = { + totalWorkingMinutes: 0, + totalWorkingSeconds: 0, +}; + +class IotCarePlanWE200 extends Component { + constructor(props) { + super(props); + this.state = { + name: "WE200", + title: "WE200", // 页面标题 + // 当前设备 + currentDevice: { + name: "", + model: "", + }, + + /** 连接设备 */ + hasVersion: false, // 是否已查询到版本号 + basicModeList: [], //模式列表 + modelActiveIndex: 0, //模式下标 + sliderProgress: 22, + DeviceConnectStatus: 1, // 设备连接状态 0未连接 1已连接 + Electricity: 4, // WL200电量 + matrixElectricity: 4, // WE200发箍电量 + + workMode: "", //当前模式 + // 挡位调节组件参数 + GearData: [ + { name: "额头", forehead: 5, Total: 10 }, + { name: "左脸颊", forehead: 6, Total: 10 }, + { name: "右脸颊", forehead: 2, Total: 10 }, + ], + waterStepList: [ + { + value: "Step1", + name: "额头", + finish: false, + schedule: 0, + color: "#c2e5f3", + forehead: 0, + }, + { + value: "Step2", + name: "左脸颊", + finish: false, + schedule: 0, + color: "#c2e5f3", + forehead: 0, + }, + { + value: "Step3", + name: "右脸颊", + finish: false, + schedule: 0, + color: "#c2e5f3", + forehead: 0, + }, + ], + waterStepIndex: 0, + // 进度条定时器 + timerIdSchedule: null, + timerIdSuccess: null, + + currentShowDialog: "", + showVideoPlayBtn: true, // 视频播放按钮 + duration: 0, // 视频总时长 + hadShowBreakTips: false, // 是否展示过支架断开提示 + + isConnectShow: false, // 是否弹出连蓝牙弹窗:在蓝牙断开时弹出 + /** 连接设备 End */ + + /** 护理过程 */ + currentGear: 5, // 默认档位 + isRuningTest: 1, // 是否正在测试 + isShowStepTips: false, // 是否显示介绍步骤弹窗 + isConnectionBlutoot: true, // 是否已连接蓝牙 + isShowNurse: true, // 是否开始并显示护理 FR200默认已经开始准备护理 + isStopNurse: false, // 是否暂停护理 + isEndNurse: false, // 是否结束护理 + errorTips: "", // 错误提示 + /** 护理过程 END*/ + + // 模式列表 + currentWorkModeType: 1, // 现在的模式类型 + currentVideoSrc: "", // 现在模式的视频地址 + isModeLock: false, // 模式是否锁定 + isSwitchActiveMode: false, // 是否显示弹窗切换模式 + ModeList: [], + ModeType: "all", // all 1基础护理 2专区护理 3专研促渗 4敏期护理 5智能测肤 + ActiveModeItem: { + openSourceData: [], + }, // 当前选中模式 + SwitchActiveModeItem: {}, // 切换选中模式 + ModeID: "mode_", // 模式KEY + activeModeID: "", // 当前选中模式ID:用于高亮 + ModeStepIndex: 0, // 当前护理功效步骤:每个步骤时间不定,所以时间另外计算,根据步骤显示 + ModeStepTimeArray: [], // 护理功效时间步骤,用于切换显示GIF + + // TestModeStepIndex: 0, // 水分测试步骤 + EssenceStepIndex: 0, // 精华促渗步骤 + MaskModeStepIndex: 0, // 面膜促渗步骤 + + EssenceBuzzingIndex: 0, // 精华蜂鸣 + EssenceVibrateIndex: 0, // 精华震动 + MaskModeBuzzingIndex: 0, // 面膜蜂鸣 + MaskModeVibrateIndex: 0, // 面膜震动 + + currentServiceData: { + // 当前展示的开启暂停GIF: 因为时间判断不方便,所以单独领出来 + startSource: "", + stopSource: "", + }, + + // 倒计时 + isShowCountdown: false, // 倒计时弹窗 + countdown: 3, + // 是否结束护理 + isEndCarePlan: false, + currentTime: "10:00", // 倒计时时间:WR200以视频为准 + currentVideoTime: "10:00", // 当前视频时间 + + // 护理时间不够 + isNotEnoughTime: false, + // 通用错误提示 + isShowErrorTipsText: false, // 护理模式切换错误弹窗 + errorTipsText: "", // 护理模式切换错误提示 + + isShowNursingSuccess: false, // 护理成功弹窗 + isShowTipsSave: false, // 切换模式时,提示是否保存部分护理记录 + + // 初次护理弹窗 + isFirstTipShow: false, + nurseInfo: [], + + // 上一次护理记录未生成,是否继续连接设备 + isShowReReadRecordConnect: false, + + // isFirstEntryMode: false, // 模式首次打开 + + isShowHistoryMsg: false, // 是否显示正在同步历史 + showEcharts: false, + echartsData: "", //传给echarts图表的数据 + series: [ + { + data: [2, 3, 5, 3, 5, 6, 8, 5, 6, 4], + type: "line", + smooth: true, + z: 1, + areaStyle: {}, + color: "red", + }, + { + data: [2, 3, 5, 3, 5, 6, 8, 5, 6, 4], + type: "line", + smooth: true, + symbolSize: 10, + lineStyle: { + color: "#ff8410", + width: 1, + }, + itemStyle: { + color: "#ff8410", + }, + }, + { + name: "a", + data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + type: "bar", + barWidth: 22, + z: 2, + stack: "x", + visualMap: false, + itemStyle: { + color: "#ffcf56", + }, + }, + { + name: "b", + data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + type: "bar", + z: 2, + stack: "x", + visualMap: false, + itemStyle: { + color: "#febb22", + }, + }, + { + name: "c", + data: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + type: "bar", + z: 2, + stack: "x", + visualMap: false, + itemStyle: { + color: "#ffac28", + }, + }, + { + name: "d", + data: [0, 0, 1, 0, 1, 1, 1, 1, 1, 1], + type: "bar", + z: 2, + stack: "x", + visualMap: false, + itemStyle: { + color: "#ff8410", + }, + }, + { + name: "e", + data: [0, 0, 1, 0, 1, 1, 1, 1, 1, 0], + type: "bar", + z: 2, + stack: "x", + visualMap: false, + itemStyle: { + color: "#f85804", + }, + }, + { + name: "f", + data: [0, 0, 0, 0, 0, 1, 1, 0, 1, 0], + type: "bar", + z: 2, + stack: "x", + visualMap: false, + itemStyle: { + color: "#e02e14", + }, + }, + { + name: "h", + data: [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + type: "bar", + z: 2, + stack: "x", + visualMap: false, + itemStyle: { + color: "#b30016", + }, + }, + { + name: "i", + data: [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + type: "bar", + z: 2, + stack: "x", + visualMap: false, + itemStyle: { + color: "#750010", + }, + }, + ], + }; + } + bluetoothContainer: any = null; + isFullScreen: boolean = false; + // 不涉及渲染的页面变量 + isRuning: any = true; // 设备默认运行中:fr200贴脸就会自动开始工作 + jsonStatus: any = {}; // 同步设备返回数据,用于结束 + workJsonStatus: any = {}; // 同步工作中的仪器 + tempModeCurrent: any = {}; // 临时保存的当前模式 + elapsedTime: any = 0; // 设备已运行时间 + workStatus: any = ""; // 工作状态 + FR200NursingHistory: any = null; // 护理缓存历史 + hadCheckReport = false; // 是否已检查仪器护理记录 + hadGotInstrumentHistoryData = false; // 是否已缓存仪器历史数据 + hadLoadedPage = false; // 判断是否首次进入页面 + + // FR200同步状态 + FR200Status: any = { + gear: 1, // 1-5 挡位 + isCharging: false, // 是否充电 + joulePerSecond: 0, // 每秒焦耳 0-10 + nasolabialOrMandibularOutput: false, // 鼻唇或下颌输出 + partition: 0, // 分割? + pointOutChangeSide: false, // 交叉输出点 + impedance: 107, // 阻抗/能量等级:1档<200 200<2档<280 280<3档<360 后面以此类推每加一档+80抗阻 + }; + // 1档 等于0<200,2档等于200<280,3档等于280<360 + /** FR200模式类型:名称 */ + ModeTypeArray: string[] = [ + "all", + "base", + "eyes", + "zone", + "permeation", + "sensitive", + "intelligence", + ]; + + /** 基础版:脸部/眼部/PRO 设备使用时,会自动开启暂停 */ + BaseModeType: string[] = [ + "face", + "eyes", + "nasolabialFold", + "mandibularLine", + "headLiftingPro", + ]; + + async onLoad(option) { + console.log(option, "跳转过来的数据"); + if (option.modeId) { + this.setState({ activeModeID: option.modeId }); + } + // 保持屏幕常亮 + Taro.setKeepScreenOn({ + keepScreenOn: true, + }); + this.initData(); + this.getInstrumentClockSummary(); + this.getInstrumentClockDetail(); + } + componentDidMount() {} + + componentWillUnmount() {} + + componentDidShow() { + console.log("页面显示了"); + + if (!this.hadLoadedPage) { + this.hadLoadedPage = true; // 二次进入页面(非首次进入) + return; + } + + this.getFR200NursingHistory(); + // 重置初始值,每次进入页面重新检查面罩护理记录 + this.hadCheckReport = false; + this.hadGotInstrumentHistoryData = false; + } + + componentDidHide() { + console.log("Hide"); + // 页面隐藏后,下次显示需要重新检查记录 + this.hadCheckReport = false; + } + + async initData() { + let obj = getStorageSync("instrument_detail"); + if (obj) { + this.setState({ + currentDevice: obj, + title: obj.name, + }); + + await this.GetModeList(obj.id); + + // 如果不存在设备模式值,则判断为首次进入,弹窗提示 + let isFirstTipShow = getStorageSync("first_instrument_" + obj.id); + if (!isFirstTipShow) { + this.firstNurseInfo(); + } + } + + // 开发者工具 + const platform = Taro.getSystemInfoSync().platform; + if (platform !== "devtools") { + // 仅手机端初始化蓝牙 + this.init(); + } + } + + async init() { + // 查询离线记录汇总 + const queryInstructionParams = { + commandType: "InfoQuery", + infoQueryType: "offlineClockInInfo", + dataType: "summary", + }; + let commandBuffer = deviceToolKitInstance.toBleCommand( + queryInstructionParams as any + ); + sendCommand({ value: commandBuffer }).then((res) => { + console.log("查询查询离线记录汇总 参数为=>", queryInstructionParams); + }); + + // 监听蓝牙连接状态改变 + Taro.onBLEConnectionStateChange(this.listener); + this.bluetoothContainer = new BluetoothContainer( + this.props.bluetoothInfo, + deviceToolKitInstance, + this + ); + this.bluetoothContainer.notifyBLECharacteristicValueChange(); + + setTimeout(() => { + this.handleWorkStatus(false, MODE_WORKING_ENUM.STANDBY); + }, 1000); + } + listener = (res) => { + console.log("listener res", res); + if (res?.connected) return; + // 蓝牙未连接才执行下面逻辑 + Taro.offBLECharacteristicValueChange((res) => { + console.log("offBLECharacteristicValueChange", res); + }); + clearTimeout(loadingTipsTimer); + console.log(commandMap.WL200Command, "监听到蓝牙断开, 打开断开提示"); + + this.workStatus = ""; + // 显示蓝牙断开弹窗 + this.setState({ + isConnectShow: true, // 打开蓝牙链接弹窗 + isConnectionBlutoot: false, // 断开蓝牙 + // isShowCountdown: false, // 关闭倒计时,防止倒计时还在运行 + }); + }; + + // 获取模式列表 + GetModeList = async (id) => { + let params = { + instrumentId: id, + }; + let res = await InstrumentInfo.modeInfoList(params); + + if (res.data.code === 200) { + if (res.data.data.length > 0) { + this.setState({ + ModeList: res.data.data, // 模式列表 + ActiveModeItem: res.data.data[0], // 让模式列表正常显示 + ModeType: this.ModeTypeArray[res.data.data[0].modeClass], + }); + + if (this.state.activeModeID != "") { + let res1 = res.data.data.find((e) => e.id == this.state.activeModeID); + setTimeout(() => { + this.modeCurrentFun(res1); + }, 100); + } else { + setTimeout(() => { + this.modeCurrentFun(res.data.data[0]); + }, 100); + } + let res1 = res.data.data.find((e) => e.id == this.state.activeModeID); + setTimeout(() => { + this.modeCurrentFun(res1); + }, 100); + } else { + this.setState({ ModeList: [] }); + } + } + }; + + /** + * 倒计时弹窗 + * param 重置倒计时 + * callback 回调函数 + */ + showCountdownFun(count = 3, callback: any = null) { + this.setState({ + countdown: count, + }); + setTimeout(() => { + clearInterval(CountdownTimer); + this.setState({ + isShowCountdown: true, + }); + CountdownTimer = setInterval(() => { + if (this.state.countdown === 0) { + clearInterval(CountdownTimer); + this.setState({ + isShowCountdown: false, + }); + if (callback) callback(); + } else { + this.setState({ + countdown: this.state.countdown - 1, + }); + } + }, 1000); + }, 0); + } + + /** + * @name 选中/切换护理模式 + * */ + modeCurrentFun = async (data, isNotCheck = false) => { + let { isShowNurse, activeModeID } = this.state; + // 护理检查改变模式,是否提示切换护理模式 + // isNotCheck为真时,不进行校验,直接切换 + this.tempModeCurrent = data; + + // 仅在未开始护理前,切换模式的时候提示模式弹窗 + // FR200默认开始护理 + if (!isShowNurse) { + this.openStepTips(); + } + + // 如果按钮不可点击则报错,内部自带检查底部按钮函数 + // this.onEmitErrorTips(); + if (!isNotCheck) { + let isReturn = this.modeRuningChange(); + if (isReturn) return; + } + + // 根据模式,动态设置底部按钮样式 + let currentWorkModeType = 1; + if (data.modeType === "moistureTest") { + currentWorkModeType = 3; + } else if ( + data.modeType === "maskPenetration" || + data.modeType === "essence" || + data.modeType === "led" + ) { + currentWorkModeType = 2; + } + + this.setState({ + ActiveModeItem: data, + activeModeID: data.id, + ModeID: "mode_" + data.id, + ModeStepIndex: 0, + waterStepIndex: 0, // 水分测试步骤 + EssenceStepIndex: 0, // 精华促渗步骤 + MaskModeStepIndex: 0, // 面膜促渗步骤 + ModeType: this.ModeTypeArray[data.modeClass], + currentWorkModeType, + }); + + // 切换模式时:重新设置视频地址 + this.VideoSrcLoad(data.modeVideo); + + // 开发中,暂时允许直接切换 + // 每次切换模式时清空一下历史数据 + this.changeItemUpdateFR200NursingHistory(); + this.stepNext(); // 仅切换模式,不执行开始逻辑 + + // FR200水分测试不可自动运行,需手动点击开始测试,手动启动检测 + // 其他模式可以自动运行 + if (data.modeType !== "moistureTest") { + setTimeout(() => { + this.onNursingTap("switch"); + }, 800); + } + }; + /** 设备运行中切换模式 */ + modeRuningChange() { + // 运行中切换模式逻辑 + // 当且仅当,护理模式时长大于设备最低时常时,提示是否保存护理记录。 + // 若不满足,直接切换 + if ( + this.workStatus === MODE_WORKING_ENUM.PAUSE || + this.workStatus === MODE_WORKING_ENUM.WORKING + ) { + const { totalWorkingMinutes, totalWorkingSeconds } = DeviceSyncData; + const totalTime = totalWorkingMinutes * 60 + totalWorkingSeconds; // 设备时间 + let { ActiveModeItem } = this.state; + if (!ActiveModeItem || totalTime === 0) { + return false; + } + + if (this.isRuning && this.state.DeviceConnectStatus == 1) { + // 提示切换护理模式 + if (this.isCheckNurseTime()) { + // 满足时间条件,提示是否保存部分护理记录 + this.judgementWorkStatus( + MODE_WORKING_ENUM.PAUSE, + this.state.ActiveModeItem?.modeType + ); + this.setState({ + isShowTipsSave: true, + }); + return true; + } + } + } + return false; + } + // 加减组件- 减号操作 + handleMinus = async (data, inde) => { + let GearData = this.state.GearData; + if (GearData[inde].forehead !== 1) { + GearData[inde].forehead = GearData[inde].forehead - 1; + this.setState({ GearData }); + } + }; + // 加减组件- 加号操作 + handleAdd = async (data, inde) => { + let GearData = this.state.GearData; + if (GearData[inde].forehead !== GearData[inde].Total) { + GearData[inde].forehead = GearData[inde].forehead + 1; + this.setState({ GearData }); + } + }; + + /** 切换护理模式 */ + switchModeCurrentFun = async (data) => { + this.setState({ + SwitchActiveModeItem: data, + activeModeID: data.id, + ModeID: "mode_" + data.id, + }); + }; + // 打开模式切换弹窗 + openModeSwitch = () => { + console.log("openModeSwitch"); + this.setState({ + isSwitchActiveMode: true, + }); + }; + // 取消并关闭切换护理模式弹窗 + cancelModeSwitchBtn = () => { + this.setState({ + isSwitchActiveMode: false, + }); + }; + // 弹窗确定切换护理模式 + confirmModeSwitchBtn = () => { + let { SwitchActiveModeItem } = this.state; + this.cancelModeSwitchBtn(); + this.modeCurrentFun(SwitchActiveModeItem); + + this.setState({ + ModeType: this.ModeTypeArray[SwitchActiveModeItem.modeClass], + }); + }; + + stepNext = () => { + let modeClass = this.state.ActiveModeItem.modeClass; + this.workStatus = "pause"; + this.isRuning = true; // 暂停也是运行中 + this.setState({ + ModeType: this.ModeTypeArray[modeClass], + isShowNurse: true, + isStopNurse: true, + }); + + setTimeout(() => { + this.handleWorkStatus(false, MODE_WORKING_ENUM.STANDBY); + }); + }; + + /** 开始护理按钮:点击开始,页面进行到下一步 */ + onStartNurse = async () => { + this.stepNext(); + + setTimeout(() => { + this.onNursingTap(); + // // 倒计时弹窗: 倒计时完成后,自动开始,并判断弹窗 + // let downNum = CountDownTime[this.state.ActiveModeItem.modeType] || 3; + // this.showCountdownFun(downNum, () => {}); + }, 500); + + // 如果检查失败,则报错 + this.onEmitErrorTips(); + }; + /** 完成护理,跳转到水分 */ + onsuccess = async () => { + let waterStepList = this.state.waterStepList; + this.setState({ + isShowNursingSuccess: true, + }); + // return; + let { currentDevice, ActiveModeItem } = this.state; + let params: any = {}; + + params = { + instrumentId: currentDevice.id, + instrumentName: currentDevice.name, + modeId: ActiveModeItem.id, + modeName: ActiveModeItem.modeName, + nursingTime: s_to_hms(this.elapsedTime), + nursingData: JSON.stringify({ + GearData: [...waterStepList], + }), + }; + + // params = { ...params, ...nursingData }; + let res: any = await InstrumentInfo.apiNursingLog.addLog( + JSON.stringify(params) + ); + setTimeout(async () => { + this.setState({ + isShowNursingSuccess: false, + }); + let date = new Date(); + + let year = date.getFullYear(); + let month = (date.getMonth() + 1).toString().padStart(2, "0"); + let day = date.getDate().toString().padStart(2, "0"); + + let formattedDate = `${year}.${month}.${day}`; + + this.moistureTest( + params.nursingData, + formattedDate, + ActiveModeItem.id, + currentDevice.id + ); + }, 2000); + }; + + async moistureTest( + nursingData, + formattedDate, + ActiveModeItemId, + currentDeviceId + ) { + let data = { + queryDate: formattedDate, + instrumentId: currentDeviceId, + }; + let res = await InstrumentInfo.fr200.moistureTest(data); + let echartsData = res.data.rows; + for (let i = 0; i < echartsData.length; i++) { + for (let j = i + 1; j < echartsData.length; j++) { + if ( + echartsData[i].createTime.split(" ")[0] == + echartsData[j].createTime.split(" ")[0] + ) { + let result = + Date.parse(echartsData[i].createTime) - + Date.parse(echartsData[j].createTime); + if (result < 0) { + echartsData.splice(i, 1); + } else { + echartsData.splice(j, 1); + } + } + } + } + let gears: any = []; + let eDate: any = []; + echartsData?.map((item) => { + const result = item.createTime.split(" ")[0].substring(5); + eDate.push(result); + item.nursingData = JSON.parse(item.nursingData); + let level: any = 0; + item.nursingData?.GearData?.map((gear) => { + level = level + gear.forehead; + }); + level = Math.floor(level / 3); + gears.push(level); + }); + echartsData = { + gears, + eDate, + }; + setStorageSync("moistureEachtsData", JSON.stringify(echartsData)); + let report = true; + // go(`/recoding/pages/moisture_test_report/moisture_test_report?data=${allData.nursingData}&date=${allData.createTime}&modeId=${allData.modeId}&id=${allData.instrumentId}&echartsData=${JSON.stringify(echartsData)}`); + go( + `/recoding/pages/moisture_test_report/moisture_test_report?data=${nursingData}&date=${formattedDate}&modeId=${ActiveModeItemId}&id=${currentDeviceId}&echartsData=${JSON.stringify( + echartsData + )}&report=${report}` + ); + } + + /** + * @name 不可切换光照提示 + * @description 检测紧贴肌肤 + */ + onEmitErrorTips = async () => { + setTimeout(() => { + let { ActiveModeItem } = this.state; + + // 按钮不可点击时,提示报错 + this.showTips("检测到您的设备没有紧贴肌肤,请紧贴肌肤后重新尝试"); + }); + }; + updata() { + let that = this; + let stop = 0; + let time = setInterval(function () { + stop++; + let series = JSON.parse(JSON.stringify(that.state.series)); + let num = Math.floor(Math.random() * 9); + let count = 0; + series.map((item) => { + if (item.type === "line") { + item.data.splice(0, 1); + item.data.push(num); + } + if (item.type === "bar") { + count++; + item.data.splice(0, 1); + if (count <= num) { + item.data.push(1); + } else { + item.data.push(0); + } + } + }); + + // 更新图表数据 + that.setState({ series }); + if (stop >= 20) { + clearInterval(time); + } + }, 1000); + } + + full() { + this.setState({ isFullScreen: !this.state.isFullScreen }); + } + + /** 切换光照 */ + onSwitchChange = async () => { + let { isStopNurse, ActiveModeItem } = this.state; + + console.log("切换光照,", ActiveModeItem); + if (ActiveModeItem.modeType === "led") { + this.onSwitchChangeLED(); + return; + } + + if (isStopNurse) { + // 开始光照逻辑 + this.onNursingTap(); + this.switchVideoPlay(); // 开始 + } else { + // 暂停光照逻辑 + this.handleWorkStatus(false, MODE_WORKING_ENUM.PAUSE); + this.switchVideoPause(); // 暂停 + } + this.setState({ + isStopNurse: !isStopNurse, + }); + }; + /** LED切换模式 */ + onSwitchChangeLED = () => { + let { isStopNurse } = this.state; + if (isStopNurse) { + // 开始光照逻辑 + this.onNursingTap(); + this.switchVideoPlay(); // 开始 + } else { + // 暂停光照逻辑 + let sendParams: any = { + ...deviceCommandSamples.pause, + workMode: "led", // 使用模式 + workStatus: "standby", + }; + const pauseArrayBuffer = deviceToolKitInstance.toBleCommand( + sendParams as any + ); + sendCommand({ + value: pauseArrayBuffer, + }).then(() => { + this.workStatus = "pause"; + this.resetTimer(); + console.info(`发送暂停指令成功 参数为 =>`, sendParams); + }); + // this.handleWorkStatus(false, MODE_WORKING_ENUM.PAUSE); + this.switchVideoPause(); // 暂停 + } + this.setState({ + isStopNurse: !isStopNurse, + }); + }; + + /** + * @name 每次进入设备运行页,打开首个模式的介绍弹窗 + */ + openStepTips = () => { + let isFirstEntryModeNot = getStorageSync( + "isFirstEntryMode_" + this.state.currentDevice.id + ); + // 1.如果没有持久化不再提示,每次进入都会弹窗提示 + if (!isFirstEntryModeNot) { + // 2.必须要有数据才弹窗 + if (this.state.ActiveModeItem.openSourceData.length > 0) { + this.setState({ isShowStepTips: true }); + } + } + }; + closeStepTips = (data) => { + if (data.isLocal) { + setStorageSync("isFirstEntryMode_" + this.state.currentDevice.id, true); // 关闭首次进入弹窗 + } + this.setState({ isShowStepTips: false }); + }; + + /** 蓝牙连接相关 */ + switchBLEMatch = (jsonStatus: any) => { + console.log("蓝牙连接相关", jsonStatus); + switch (jsonStatus.bleCommandType) { + // 如果设备配对链接发送配对码的时候,设备应答小程序配对码是否正确。 + case "SendMatchCode": + if (jsonStatus.matchedSuccess) { + console.log("设备配对成功"); + this.setState({ + DeviceConnectStatus: 1, + }); + } + break; + } + }; + + /**监听关机事件*/ + onEndDevice = () => { + this.rmFR200NursingHistory(this.FR200NursingHistory, true); + // 判断护理时间,如果不足,则提示不足 + if (!this.isCheckNurseTime()) { + this.setState({ isNotEnoughTime: true }); + } else { + this.endNurseFun(); + } + }; + + /** 同步设备运行信息:运行时间 */ + updateDeviceSyncData = (newData, jsonStatus) => { + DeviceSyncData = { + ...DeviceSyncData, + ...newData, + }; + if (newData.hasOwnProperty("totalWorkingSeconds")) { + this.renderDeviceStatus.renderWorkTime(jsonStatus); + } + }; + + // 页面同步护理剩余时间 + renderDeviceStatus = { + renderWorkTime: (jsonStatus) => { + const { totalWorkingMinutes, totalWorkingSeconds } = DeviceSyncData; + let { ActiveModeItem, currentTime } = this.state; + const totalTime = totalWorkingMinutes * 60 + totalWorkingSeconds; + // console.log("仪器上报的已经运行的总秒数", totalTime); + // console.log("时间校准频率,默认5秒一次", TIME_CALIBRATION_FREQUENCY); + //对比仪器上报运行的总秒数 和小程序页面运行的已经运行的总秒数,如果不一致就进行校准 + let sceneTime = ActiveModeItem?.breakTimeStr + ? minSecToS(ActiveModeItem.breakTimeStr) + : minSecToS(this.state.currentVideoTime); // 场景时间 + + // console.log("场景时间 sceneTime", sceneTime); + // console.log("当前显示时间 currentTime", currentTime); + console.log("设备运行时间 totalTime", totalTime); + + // 更新界面倒计时 + this.resetTimer(); + + if ( + sceneTime > totalTime && + this.isRuning && + this.state.DeviceConnectStatus == 1 + ) { + // 界面倒计时同步设备时间 + const t = sceneTime - totalTime; // 场景时间 - 已运行时间 = 剩余时间 + this.setState({ + currentTime: s_to_ms(t), + }); + } else { + this.setState({ + currentTime: "00:00", + }); + this.judgementWorkStatus(MODE_WORKING_ENUM.END, jsonStatus.workMode); + } + + // 每次同步后,更新历史缓存 + setTimeout(() => { + this.updateFR200NursingHistory(null, jsonStatus); + }, 100); + }, + }; + + // 仪器开始倒计时 + setLoadingTips(time) { + this.setState({ + countdown: time, + }); + if (time >= 0) { + loadingTipsTimer = setTimeout(() => { + this.setLoadingTips(--time); + }, 1000); + } else { + // 停止倒计时 + // that.data.startSettingCountDown = false; + this.setState({ + isShowCountdown: false, + }); + } + } + + /** + * 设备上报不同状态 + * params 工作状态 工作模式 响应状态 + */ + judgementWorkStatus(nWorkStatus, nWorkMode) { + const { ActiveModeItem, ModeList, currentVideoTime } = this.state; + const opts: any = {}; + // ActiveModeItem + let nowModeItem; + if (nWorkMode) { + nowModeItem = ModeList.find((item) => { + return item.modeType === nWorkMode; + }); + } + opts.workStatus = nWorkStatus; + + let nowCurrentTime = currentVideoTime; + // 完成重连同步则删除重连时间字段 + if (ActiveModeItem?.breakTimeStr) { + nowCurrentTime = ActiveModeItem?.breakTimeStr; + } + + const statusF = { + sleep: () => { + this.setState({ + isShowCountdown: false, + }); + }, + standby: () => { + this.setState({ + isShowCountdown: false, + }); + }, + setting: () => { + this.isRuning = true; + this.setState({ + title: "正在护理", + isStopNurse: false, + }); + if (nowModeItem) { + opts.currentTime = nowCurrentTime; + } + // 倒计时loading + // if (!this.state.isShowCountdown) { + // this.setState({ + // isShowCountdown: true, + // }); + // this.setLoadingTips(CountDownTime[workMode] || 6); + // } + }, + working: () => { + this.setState({ + title: "正在护理", + isStopNurse: false, + isShowCountdown: false, + }); + this.closeTips(); + }, + pause: () => { + clearInterval(currentTimeTimer); + this.setState({ + isShowCountdown: false, + }); + this.setState({ + title: "暂停护理", + isStopNurse: true, + }); + }, + end: () => { + // 已进入了报告阶段, 防止重复进入, 主要防止在手动点击结束护理接收到仪器消息 + console.log("END 护理结束"); + clearInterval(currentTimeTimer); + + this.endnursing(true); + }, + }; + statusF[nWorkStatus] && statusF[nWorkStatus](); + if (Object.keys(opts).length) { + this.setState(opts); + } + } + + /** + * 保存护理报告 + * 1.是否跳转 2.数据 + * */ + saveNurseReport = async (isJump = true, from) => { + this.endNurseFun(); + }; + + /** + * 结束护理 + * param isAuto 是否仪器自动结束 + */ + endnursing = (isAuto) => { + if (isAuto == true) { + // 仪器自动上报完成, 直接上报并跳转报告页 + clearInterval(currentTimeTimer); + const isEnough = this.isCheckNurseTime(); + if (isEnough) { + this.saveNurseReport(true, "endnursing"); + } + } else { + // 手动点击结束, 弹出弹窗, 看看是否需要结束 + this.onEndPlan(); + } + }; + + // 重置并同步计时器 + resetTimer = () => { + // 切换模式后, 需要重新设置计时器, 以防进行中的计时器 + currentTimeTimer && clearInterval(currentTimeTimer); + currentTimeTimer = setInterval(() => { + let { + DeviceConnectStatus, + currentTime, + ActiveModeItem, + currentVideoTime, + } = this.state; + if ( + this.workStatus == MODE_WORKING_ENUM.WORKING && + this.isRuning && + DeviceConnectStatus == 1 + ) { + let totalSeconds = ActiveModeItem?.breakTimeStr + ? minSecToS(ActiveModeItem.breakTimeStr) + : minSecToS(currentVideoTime); + // 现在的倒计时剩余时间:同步时检查是否断开重连,如果是,则使用断开的剩余时长,进行倒计时计算 + let currentSeconds = minSecToS(currentTime); + let checkTime = totalSeconds - currentSeconds; + + // 缓存经过的时间:用于接口提交 + this.elapsedTime = checkTime; + // fr200不需要加上中断的时间 + // 如果存在中断时间,则要加上间隔的时间 + // if (ActiveModeItem?.breakTimeStr) { + // let intervalTime = + // minSecToS(ActiveModeItem.modeTimeStr) - + // minSecToS(ActiveModeItem.breakTimeStr); + // this.elapsedTime += intervalTime; + // } + console.log("this.elapsedTime", this.elapsedTime); + + // 判断剩余时间是否大于1 + if (currentSeconds >= 1) { + // 小程序显示倒计时 + this.setState({ + currentTime: s_to_ms(--currentSeconds), + }); + + // 根据不同的模式,切换步骤到下一步 + if (ActiveModeItem.modeType === "essence") { + this.essencePenetrationNext(); + } else if (ActiveModeItem.modeType === "maskPenetration") { + this.maskPenetrationNext(); + } + } else { + clearInterval(currentTimeTimer); + this.setState({ + currentTime: "00:00", + waterStepIndex: 0, // 水分测试步骤 + EssenceStepIndex: 0, // 精华促渗步骤 + MaskModeStepIndex: 0, // 面膜促渗步骤 + }); + this.saveNurseReport(true, "setTimer"); // 保存护理计划,并且结束 + } + } + }, 1000); + }; + + /** + * @name 水分测试下一步,手动调用 + * @description 步骤+1并设置视频 + */ + waterTestNext(index) { + let ActiveModeItem = this.state.ActiveModeItem; + if (index === 0 && ActiveModeItem.stepOneVideo) { + this.VideoSrcLoad(ActiveModeItem.stepOneVideo); + } else if (index === 1 && ActiveModeItem.stepTwoVideo) { + this.VideoSrcLoad(ActiveModeItem.stepTwoVideo); + } else if (index === 2 && ActiveModeItem.stepThreeVideo) { + this.VideoSrcLoad(ActiveModeItem.stepThreeVideo); + } + } + + /** + * @name 面膜促渗下一步,根据时间自动调用 + * @description 1.切换步骤 2.检测记录蜂鸣/震动的时间节点与步骤 + * */ + maskPenetrationNext() { + let { + MaskModeStepIndex, + MaskModeBuzzingIndex, + MaskModeVibrateIndex, + ActiveModeItem, + } = this.state; + + // 模式多个步骤节点切换 + // 已运行时间达到下一节点时,切换 + let GearLength = ActiveModeItem.modeGear.length; + if (GearLength && MaskModeStepIndex < GearLength) { + let GearTime = minSecToS(ActiveModeItem.modeGear[MaskModeStepIndex].time); + if (this.elapsedTime > GearTime) { + // 已运行时间达到下一节点,且存在下一节点,步骤切换时更新 + if (MaskModeStepIndex < GearLength) { + let index = MaskModeStepIndex + 1; // 提前步骤+1 + this.setState({ + MaskModeStepIndex: index, + }); + this.FR200AutoChangeGear(index); + } + } + } + + let BuzzingLength = ActiveModeItem.modeBuzzing.length; + if (BuzzingLength && MaskModeStepIndex < GearLength) { + let BuzzingTime = minSecToS( + ActiveModeItem.modeBuzzing[MaskModeStepIndex].time + ); + if (this.elapsedTime > BuzzingTime) { + if (MaskModeBuzzingIndex < BuzzingLength) { + let index = MaskModeBuzzingIndex + 1; // 提前步骤+1 + this.setState({ + MaskModeBuzzingIndex: index, + }); + this.FR200Buzzing(); + } + } + } + + let VibrateLength = ActiveModeItem.modeVibrate.length; + if (VibrateLength && MaskModeStepIndex < VibrateLength) { + let VibrateTime = minSecToS( + ActiveModeItem.modeVibrate[MaskModeStepIndex].time + ); + if (this.elapsedTime > VibrateTime) { + if (MaskModeVibrateIndex < VibrateLength) { + let index = MaskModeVibrateIndex + 1; // 提前步骤+1 + this.setState({ + MaskModeVibrateIndex: index, + }); + this.FR200Vibrate(); + } + } + } + } + + /** @name 精华促渗下一步,根据时间自动调用 */ + essencePenetrationNext() { + let { + EssenceStepIndex, + EssenceBuzzingIndex, + EssenceVibrateIndex, + ActiveModeItem, + } = this.state; + + // 模式多个步骤节点切换 + // 已运行时间达到下一节点时,切换 + let GearLength = ActiveModeItem.modeGear.length; + if (GearLength && EssenceBuzzingIndex < GearLength) { + let GearTime = minSecToS( + ActiveModeItem.modeGear[EssenceBuzzingIndex].time + ); + if (this.elapsedTime > GearTime) { + // 已运行时间达到下一节点,且存在下一节点,步骤切换时更新 + if (EssenceStepIndex < GearLength) { + let index = EssenceStepIndex + 1; // 提前步骤+1 + this.setState({ + EssenceStepIndex: index, + }); + this.FR200AutoChangeGear(index); + } + } + } + + let BuzzingLength = ActiveModeItem.modeBuzzing.length; + if (BuzzingLength && EssenceBuzzingIndex < BuzzingLength) { + let BuzzingTime = minSecToS( + ActiveModeItem.modeBuzzing[EssenceBuzzingIndex].time + ); + if (this.elapsedTime > BuzzingTime) { + if (EssenceBuzzingIndex < BuzzingLength) { + let index = EssenceBuzzingIndex + 1; // 提前步骤+1 + this.setState({ + EssenceBuzzingIndex: index, + }); + this.FR200Buzzing(); + } + } + } + + let VibrateLength = ActiveModeItem.modeVibrate.length; + if (VibrateLength && EssenceBuzzingIndex < VibrateLength) { + let VibrateTime = minSecToS( + ActiveModeItem.modeBuzzing[EssenceBuzzingIndex].time + ); + if (this.elapsedTime > VibrateTime) { + if (EssenceVibrateIndex < VibrateLength) { + let index = EssenceVibrateIndex + 1; // 提前步骤+1 + this.setState({ + EssenceVibrateIndex: index, + }); + this.FR200Vibrate(); + } + } + } + } + + /**切换挡位发起蜂鸣:可以切换到现在的挡位?*/ + FR200Buzzing = () => { + console.log("FR200Buzzing 蜂鸣", this.elapsedTime); + const { ActiveModeItem, currentGear } = this.state; + let sendParams: any = { + ...deviceCommandSamples.pause, + workMode: ActiveModeItem.modeType, // 使用模式 + workStatus: "working", + gear: currentGear, + }; + + if (currentGear > 1) { + sendParams.gear = currentGear - 1; + let pauseArrayBuffer = deviceToolKitInstance.toBleCommand( + sendParams as any + ); + sendCommand({ + value: pauseArrayBuffer, + }); + } else { + sendParams.gear = currentGear + 1; + let pauseArrayBuffer = deviceToolKitInstance.toBleCommand( + sendParams as any + ); + sendCommand({ + value: pauseArrayBuffer, + }); + } + setTimeout(() => { + sendParams.gear = currentGear; + let pauseArrayBuffer = deviceToolKitInstance.toBleCommand( + sendParams as any + ); + sendCommand({ + value: pauseArrayBuffer, + }); + }, 300); + }; + + /** + * @name FR200自动切换挡位 + * @description 需要传参索引 + */ + FR200AutoChangeGear = (index: number = 0) => { + console.log("FR200AutoChangeGear 切换挡位", this.elapsedTime); + const { ActiveModeItem, GearData } = this.state; + let gear = GearData[index].forehead; + + // 同步挡位 + this.setState({ + currentGear: gear, + }); + + let sendParams: any = { + ...deviceCommandSamples.pause, + workMode: ActiveModeItem.modeType, // 使用模式 + workStatus: "working", + gear: gear, + }; + const pauseArrayBuffer = deviceToolKitInstance.toBleCommand( + sendParams as any + ); + sendCommand({ + value: pauseArrayBuffer, + }).then(() => { + this.workStatus = "working"; + this.resetTimer(); + }); + }; + + /** + * @name FR200震动 + * @description 同步设备工作却不改变档位,仅震动 + */ + FR200Vibrate = () => { + console.log("FR200Vibrate FR200震动", this.elapsedTime); + const { ActiveModeItem, currentGear } = this.state; + + let sendParams: any = { + ...deviceCommandSamples.pause, + workMode: ActiveModeItem.modeType, // 使用模式 + workStatus: "working", + gear: currentGear, + }; + const pauseArrayBuffer = deviceToolKitInstance.toBleCommand( + sendParams as any + ); + sendCommand({ + value: pauseArrayBuffer, + }); + }; + + executePromises = async () => { + let waterStepList = this.state.waterStepList; + let waterStepIndex = this.state.waterStepIndex; + let that = this; + await new Promise((resolve) => { + setTimeout(() => { + waterStepList[waterStepIndex].schedule = 100; + that.setState({ + waterStepList, + }); + + resolve(); + }, 3000); + }).then(() => { + return new Promise((resolve) => { + setTimeout(() => { + waterStepList[waterStepIndex].finish = true; + that.setState({ + waterStepList, + }); + resolve(); + }, 2000); + }); + }); + }; + + // 检测并控制工作状态 + handleWorkStatus = async (isBtnClick: boolean, workStatus) => { + const { DeviceConnectStatus, ActiveModeItem } = this.state; + let newWorkStatus = + workStatus || + (this.workStatus == MODE_WORKING_ENUM.WORKING ? "pause" : "working"); + + let sendParams: any = { + ...deviceCommandSamples.pause, + workMode: ActiveModeItem.modeType, // 使用模式 + workStatus: newWorkStatus, + }; + + // 水分测试需要特殊处理 + // 水分测试准备 水分测试工作 水分测试启动 + if (ActiveModeItem.modeType === "moistureTest") { + let that = this; + sendParams.testStatus = "standby"; // 切换为准备 + + // 3秒定时器,逻辑3秒把进度条弄成100,再加2秒获取最后结果 + if (isBtnClick) { + that.setState({ + isRuningTest: 2, + }); + this.executePromises(); + + sendParams.testStatus = "start"; // 点击开始再开始 + console.log("点击开始", isBtnClick); + } + } + + // 面膜促渗和精华促渗 + if ( + ActiveModeItem.modeType === "maskPenetration" || + ActiveModeItem.modeType === "essence" + ) { + sendParams.gear = this.state.currentGear; // 点击开始再开始 + } + + console.log("准备发送自定义或工作指令", ActiveModeItem, sendParams); + const pauseArrayBuffer = deviceToolKitInstance.toBleCommand( + sendParams as any + ); + sendCommand({ + value: pauseArrayBuffer, + }).then(() => { + this.workStatus = newWorkStatus; + this.resetTimer(); + console.info( + `handleWorkStatus 发送${newWorkStatus}指令成功 参数为 =>`, + sendParams + ); + }); + }; + + /** + * @name 点击开始护理 + * @params type 传值 switch 则用于区分是否切换模式的启动,如果是WL200的切换模式,则倒计时 + */ + onNursingTap(type = "") { + // 如果已禁止运行,则停止执行后续逻辑 + if (this.state.isRuningTest === 2) return; + // 防止多次点击 + if (this.state.hadClickStart) return; + this.setState({ + hadClickStart: true, + }); + setTimeout(() => { + this.setState({ + hadClickStart: false, + }); + }, 500); + + const { DeviceConnectStatus } = this.state; + + if (DeviceConnectStatus != 1) { + console.log("DeviceConnectStatus 开始处", DeviceConnectStatus); + this.showTips("检测到FR200未连接成功,请确认FR200开机并佩戴"); + return; + } + + // 开始执行护理 + this.workStatus = MODE_WORKING_ENUM.WORKING; // 不管当前什么状态,直接设为工作状态 + this.handleWorkStatus(true, MODE_WORKING_ENUM.WORKING); + this.setState({ + isStopNurse: false, + }); + } + + // 结束护理 + async endNurseFun() { + if (this.isCheckNurseTime()) { + await this.PostNursingLogClock(); + this.handleWorkStatus(false, "end"); + } else { + // 时间不满足,直接提交回到主页 + this.handleWorkStatus(false, "end"); + this.setState({ + isEndCarePlan: false, + isNotEnoughTime: true, + }); + } + } + + /** 检查时间是否达标仪器最低护理时间 */ + isCheckNurseTime() { + const { currentDevice, currentVideoTime } = this.state; + let sceneTime = minSecToS(currentVideoTime); + const timeRemaining = sceneTime - minSecToS(this.state.currentTime); // 当前模式已运行时间 + + let nursingTimeStr = currentDevice?.nursingTimeStr; + let nursingTime = nursingTimeStr ? minSecToS(nursingTimeStr) : 60; // 设备生成护理记录至少需要运行时间 + + console.log("检查已运行时间", timeRemaining, nursingTime); + if (timeRemaining >= nursingTime) { + return true; + } else { + return false; + } + } + /*** 护理记录 START ***/ + /** 小程序查询护理记录概要 */ + getInstrumentClockSummary() { + this.hadGotInstrumentHistoryData = true; + console.log("发送指令clockSummary 获取设备护理概要"); + setTimeout(() => { + const queryClockSummary = deviceToolKitInstance.toBleCommand({ + ...(fr200BleCommand.InfoQuery.clockSummary as any), + }); + sendCommand({ + value: queryClockSummary, + }); + }, 1000); + } + /** 小程序查询最近一条护理详情 */ + getInstrumentClockDetail() { + this.hadGotInstrumentHistoryData = true; + console.log("发送指令clockDetail 查询最近一条护理详情"); + setTimeout(() => { + const queryClockSummary = deviceToolKitInstance.toBleCommand({ + ...(fr200BleCommand.InfoQuery.clockDetail as any), + }); + sendCommand({ + value: queryClockSummary, + }); + }, 2000); + } + + /** + * @title 检查护理记录 + * @description + * 1.判断是否存在工作状态:如果不存在,则等待两秒用于连接设备与赋值,再执行后面的代码 + * + * 2.判断是否已存在缓存的护理记录:如果没有历史,则缓存 + * + * 3.判断是否当天(如果不是当天,则删除记录) + * + * 4.判断设备状态-未运行/已完成/待机 + * 4-1.已有缓存护理记录,判断ID一致,同步时间。若主动结束,需判断时间是否满足仪器最低护理时间,满足直接跳转护理报告页;不满足需提示不满足,回到首页 + * 4-2.已有缓存护理记录,判断ID不一致(同步异常)。直接提交,固定设置为一分钟。 + * + * 5.判断设备状态-运行中 + * 正常执行逻辑 + * + * */ + checkInstrumentRecord = async (jsonStatus: any) => { + console.log("检查护理记录"); + let { currentDevice, ActiveModeItem, ModeList, currentVideoTime } = + this.state; + + await sleep(2); + + let isSyncHistory = Taro.getStorageSync("isSyncHistory"); + if (isSyncHistory) { + this.setState({ isShowHistoryMsg: false }); + Taro.removeStorageSync("isSyncHistory"); + } + console.log( + "this.workJsonStatus", + this.workJsonStatus, + this.workJsonStatus.workMode + ); + + if (this.workJsonStatus.workMode) { + // FR200可能要判断是否水分测试 Test + // console.log("现在运行的模式:", this.workJsonStatus.workMode); + } + + // 2.判断是否已存在缓存的护理记录:如果没有历史,则缓存 + let workStatus = this.workJsonStatus.workStatus; + let FR200NursingHistory = this.FR200NursingHistory; + if (!this.FR200NursingHistory) { + console.log("小程序缓存没有数据, 忽略"); + if ( + workStatus == MODE_WORKING_ENUM.WORKING || + workStatus == MODE_WORKING_ENUM.PAUSE + ) { + // 缓存没有数据, 要存缓存 + this.setFR200NursingHistory(jsonStatus); + } + return; + } + + // 3.判断是否当天(如果不是当天,则删除记录) + if (!dayjs().isSame(FR200NursingHistory?.createDate, "day")) { + console.log("小程序缓存有数据,但是不是当天数据,忽略"); + this.rmFR200NursingHistory(FR200NursingHistory); + return; + } + + // 仪器缓存模式,判断是否存在于现有模式中 + let recordModeItem = ModeList.find((item) => { + return item.id == FR200NursingHistory.modeId; + }); + if (!FR200NursingHistory || !recordModeItem) { + console.log("仪器有数据, 但是缓存没有数据, 忽略"); + return; + } + + let historyElapsedTime = + minSecToS(currentVideoTime) - minSecToS(FR200NursingHistory.currentTime); + + this.elapsedTime = + this.elapsedTime > historyElapsedTime + ? this.elapsedTime + : historyElapsedTime; + // 4.判断设备状态-未运行/已完成/待机 + if ( + workStatus == MODE_WORKING_ENUM.STANDBY || + workStatus == MODE_WORKING_ENUM.END || + !workStatus + ) { + // 判断id是否一致, 一致的话则生成护理报表 + if (jsonStatus.id == FR200NursingHistory.id) { + console.log("id一致, 设备没有运行/已完成/待机"); + let totalSeconds = jsonStatus.totalSeconds; // 从仪器上获取的使用时间 + + let nursingTimeStr = currentDevice?.nursingTimeStr; + let nursingTime = nursingTimeStr ? minSecToS(nursingTimeStr) : 60; // 设备生成护理记录至少需要运行时间 + + if (totalSeconds < nursingTime) { + // 护理时间不足 + this.setState({ isNotEnoughTime: true }); + this.rmFR200NursingHistory(FR200NursingHistory); + return; + } + + // 小程序时间和设备时间,谁大用谁 + let timeValue = + totalSeconds > this.elapsedTime + ? s_to_hms(totalSeconds) + : s_to_hms(this.elapsedTime); + + let params = { + instrumentId: currentDevice.id, + instrumentName: currentDevice.name, + modeId: ActiveModeItem.id, + modeName: ActiveModeItem.modeName, + nursingTime: timeValue, + }; + this.handleWorkStatus(false, "end"); + let res: any = await this.PostNursingLogClock(params); + console.log("res", res); + this.rmFR200NursingHistory(FR200NursingHistory); + } else { + // ID不一致,同步异常,统一提交一分钟 + let params = { + instrumentId: currentDevice.id, + instrumentName: currentDevice.name, + modeId: ActiveModeItem.id, + modeName: ActiveModeItem.modeName, + nursingTime: "00:01:00", + }; + this.handleWorkStatus(false, "end"); + let res: any = await this.PostNursingLogClock(params); + console.log("res", res); + this.rmFR200NursingHistory(FR200NursingHistory); + } + } else { + console.log("id一致, 设备运行中或暂停"); + // 5.判断设备状态-运行中 + // 同步时间 + if (jsonStatus.id == FR200NursingHistory.id) { + if (FR200NursingHistory.currentTime) { + this.isRuning = true; + this.resetTimer(); + } + } + } + }; + + /** 获取小程序本地缓存的历史记录 */ + getFR200NursingHistory() { + this.FR200NursingHistory = Taro.getStorageSync("FR200NursingHistory"); + console.log( + this.FR200NursingHistory, + "获取本地数据++++++++++++++++++++++++++++++++++++++++++" + ); + + // 是否同步历史记录 + let isSyncHistory = Taro.getStorageSync("isSyncHistory"); + if (isSyncHistory) { + let ActiveModeItem = this.FR200NursingHistory.ActiveModeItem; + // 直接进入开始护理状态 + this.setState({ + isShowNurse: true, + isShowHistoryMsg: true, + videoTime: this.FR200NursingHistory.videoTime, + tempModeCurrent: ActiveModeItem, + ActiveModeItem: ActiveModeItem, + activeModeID: ActiveModeItem.id, + ModeID: "mode_" + ActiveModeItem.id, + currentTime: this.FR200NursingHistory.currentTime, + }); + } + } + /** 设置WL200护理历史 */ + setFR200NursingHistory = (jsonStatus: any) => { + let { currentDevice, ActiveModeItem } = this.state; + const params = { + createDate: dayjs().format("YYYY-MM-DD"), + workMode: jsonStatus.workMode, + instrumentId: currentDevice.id, + instrumentName: currentDevice.name, + modeId: ActiveModeItem.id, + modeName: ActiveModeItem.modeName, + id: dayjs().format("YYYY-MM-DD HH:mm:ss"), + neededTotalSeconds: jsonStatus.neededTotalSeconds, + jsonStatus, + ActiveModeItem: this.state.ActiveModeItem, + }; + this.FR200NursingHistory = JSON.parse(JSON.stringify(params)); + Taro.setStorageSync("FR200NursingHistory", params); + console.log("保存setFR200NursingHistory"); + }; + /** 更新WL200护理历史运行时间 */ + updateFR200NursingHistory = (data: any = null, jsonStatus: any = null) => { + this.FR200NursingHistory = Taro.getStorageSync("FR200NursingHistory"); + + if (this.FR200NursingHistory) { + let params: any = this.FR200NursingHistory; + + // 设置当前时间 + params.currentTime = this.state.currentTime; + // params.videoTime; + + // 设置正确封面 + if (!data) { + if (jsonStatus) { + // 缓存每秒数据 + if (!params.dataArray) params.dataArray = []; + params.dataArray.push(jsonStatus); + params.jsonStatus = jsonStatus; + params.workMode = jsonStatus?.workMode; + params.modeId = this.state.ActiveModeItem.id; + params.modeName = this.state.ActiveModeItem.modeName; + + console.log(jsonStatus, 555555555555); + } + } else { + params.jsonStatus = jsonStatus; + params.workMode = jsonStatus?.workMode; + params.modeId = data.id; + params.modeName = data.modeName; + } + + Taro.setStorageSync("FR200NursingHistory", params); + // console.log("更新updateFR200NursingHistory"); + + // 基础模式可在这里调用函数更新图标Echarts + // 最新一条数据jsonStatus + // 注意事项:只拿working状态 + // todo + } else { + this.setFR200NursingHistory(jsonStatus); + } + }; + // 改变模式时,清空dataArray,防止数据无限叠加 + changeItemUpdateFR200NursingHistory() { + this.FR200NursingHistory = Taro.getStorageSync("FR200NursingHistory"); + if (this.FR200NursingHistory) { + this.FR200NursingHistory.dataArray = []; + Taro.setStorageSync("FR200NursingHistory", this.FR200NursingHistory); + } + } + /** + * @name 删除WL200护理历史 + * @description 参数1 护理历史 参数2 强制删除 + * 如果传入护理历史ID与现有ID相等,则删除。 + * 如果参数二为真,则强制删除 + */ + rmFR200NursingHistory = (FR200NursingHistory, hard = false) => { + const nowFR200NursingHistory = Taro.getStorageSync("FR200NursingHistory"); + if (nowFR200NursingHistory) { + Taro.setStorageSync("FR200Echart", nowFR200NursingHistory); // 临时保存用于观看和调试 + } + + Taro.removeStorageSync("FR200NursingHistory"); + }; + + // 脸部one + todoPromise = async () => { + const nowFR200NursingHistory = Taro.getStorageSync("FR200NursingHistory"); + console.log(nowFR200NursingHistory, "nowFR200NursingHistory"); + + // 护理脸部 + if ( + [ + "face", + "eyes", + "nasolabialFold", + "mandibularLine", + "headLiftingPro", + ].includes(nowFR200NursingHistory.jsonStatus.workMode) + ) { + // 把working=工作中的状态数据筛选出来 + let filtered = nowFR200NursingHistory.dataArray.filter( + (item) => item.workStatus === "working" + ); + // 能量发数 + filtered = filtered.slice(0, 360); + // 脸部能量 + let faceEnergy = 0; + filtered.forEach((item) => { + faceEnergy += item.joulePerSecond; + }); + // 计算平均数 + // let sum = filtered.reduce((accumulator, currentValue) => accumulator + currentValue.impedance, 0); + // let average = sum / filtered.length; + + // 最大 + let max: any = Math.max(...filtered.map((item) => item.impedance)); + max = this.determineTier(max); + // 最小 + let min: any = Math.min(...filtered.map((item) => item.impedance)); + min = this.determineTier(min); + // 平均数最大等级处于2 + let average: any = max / 2; + average = this.determineTier(average); + + // 能量图里面的图谱每10秒为一个数组 + // 创建一个空数组用于存储分组后的结果 + // 创建一个空数组用于存储分组后的结果 + let groupedAa: any[] = []; + // 使用循环遍历数组 aa + for (let i = 0; i < filtered.length; i += 10) { + // 提取每组的三个对象 + let group = filtered.slice(i, i + 10); + + // 找到组中最大的 name 值 + let maxName = Math.max(...group.map((obj) => obj.impedance)); + + // 计算并存储每组的平均数 + let average: any = this.determineTier(maxName / 2); + // let average = maxName / 2; + if (average >= 1) { + average = average - 1; + average = average * 10; + } + // 将包含该组对象和平均数的对象添加到 groupedAa 数组中 + groupedAa.push(average); + } + + let nursingData = { + // nursingTime:result, + nursingData: JSON.stringify({ + faceEnergy, + max, + min, + average, + groupedAa, + filtered: filtered.length, + workMode: nowFR200NursingHistory.workMode, + }), + }; + + return nursingData; + } + return { + nursingData: JSON.stringify({ + workMode: nowFR200NursingHistory.workMode, + }), + showFace: true, + }; + }; + + // 计算挡位 + determineTier = (sun) => { + // 定义每档的范围 + const tiers = [0, 200, 280, 360, 440, 520, 600, 680, 760, 840]; + + // 遍历每一档的范围,找到 sun 所属的档 + for (let i = 0; i < tiers.length; i++) { + if (sun < tiers[i + 1]) { + return i + 1; + } + } + + // 如果 sun 不在任何一档范围内,返回默认档或处理错误 + return "10"; + }; + /** 提交护理记录:完成护理后自动调用,会跳转页面 */ + PostNursingLogClock = async (data: any = null, isJump = true) => { + // todo 建议写一个Promise异步函数,用 await 执行,在提交前处理好数据 + + // return; + let { currentDevice, ActiveModeItem } = this.state; + let params = {}; + if (data) { + params = data; + } else { + params = { + instrumentId: currentDevice.id, + instrumentName: currentDevice.name, + modeId: ActiveModeItem.id, + modeName: ActiveModeItem.modeName, + nursingTime: s_to_hms(this.elapsedTime), + }; + } + let res1: any = await this.todoPromise(); + if (!res1?.showFace) { + params = { ...params, ...res1 }; + } + console.log(res1, "查看返回数据"); + + let res2: any = await InstrumentInfo.apiNursingLog.addLog(params); + console.log("PostNursingLogClock", res2); + if (res2.data.code === 200) { + let params = { + instrumentId: currentDevice.id, + }; + // 上传护理完成的仪器ID + let res = await InstrumentInfo.apiClock.addClockInstrument(params); + console.log(res, "护理完成"); + + this.rmFR200NursingHistory(this.FR200NursingHistory); // 护理完成,删除记录 + if (isJump) { + this.setState({ + isShowNursingSuccess: true, + }); + setTimeout(() => { + this.setState({ + isShowNursingSuccess: false, + }); + + this.goFaceReport( + res1, + ActiveModeItem.id, + res2.data.data, + currentDevice.id + ); // 跳转 + }, 2000); + } + } + }; + /*** 护理记录 END ***/ + + //蓝牙断开连接处理 + bluetoothDisconnectProcessing() { + clearInterval(timer); + Taro.offBLEConnectionStateChange(this.listener); // 需传入与监听时同一个的函数对象 + Taro.offBLECharacteristicValueChange((res) => { + console.log("offBLECharacteristicValueChange", res); + }); + if (!this.state.isToOTA) { + Taro.closeBluetoothAdapter(); + } + } + + /** 会自动关闭的护理错误提示 */ + showTips(ctx) { + if (!ctx) return; + if (showTipsTimer) clearTimeout(showTipsTimer); + this.setState({ + errorTips: ctx, + }); + showTipsTimer = setTimeout(() => { + this.setState({ + errorTips: "", + }); + }, 2000); + } + /** 不自动关闭的护理错误提示 */ + openTips(ctx) { + if (!ctx) return; + this.setState({ + errorTips: ctx, + }); + } + /** 关闭护理错误提示 */ + closeTips() { + this.setState({ + errorTips: "", + }); + } + + /** 结束护理弹窗 */ + onEndPlan = async () => { + this.setState({ + isEndCarePlan: true, + }); + }; + confirmEndBtn = () => { + console.log("confirmEndBtn", this.isCheckNurseTime()); + if (this.isCheckNurseTime()) { + this.endNurseFun(); + this.cancelEndBtn(); + } else { + this.handleWorkStatus(false, "end"); + this.setState({ + isEndCarePlan: false, + isNotEnoughTime: true, + }); + } + }; + cancelEndBtn = () => { + this.setState({ + isEndCarePlan: false, + }); + }; + /** 弹窗 END*/ + + // 打开通用错误弹窗 + openErrorTipsText = (str) => { + this.setState({ + isShowErrorTipsText: true, + errorTipsText: str, + }); + }; + // 关闭通用错误弹窗 + closeErrorTipsText = () => { + this.setState({ + isShowErrorTipsText: false, + }); + }; + + closeNotEnoughTime = () => { + this.setState({ + isNotEnoughTime: false, + }); + Taro.reLaunch({ + url: "/pages/index/index", + }); + }; + + /** 完成护理提交:跳转护理报告页 */ + goFaceReport = async (data, id, deviceid, currentDevice) => { + let nursingData = JSON.parse(data.nursingData); + // 跳转前置空定时器,防止重复提交 + if (currentTimeTimer) clearInterval(currentTimeTimer); + if ( + [ + "face", + "eyes", + "nasolabialFold", + "mandibularLine", + "headLiftingPro", + ].includes(nursingData.workMode) + ) { + let ids = Number(deviceid); + // 获取echarts数据 这个是获取接口更新echarts页面 + let res2 = await InstrumentInfo.apiNursingLog.getStatiCDE(ids); + + let nursingDatas = JSON.parse(res2.data.data.nursingData); + let obj = { + modeName: res2.data.data.modeName, + data: nursingDatas, + }; + + let report = true; + go( + "/recoding/pages/face_report_one/face_report_one?id=" + + ids + + "&report=" + + report + + "&obj=" + + JSON.stringify(obj) + ); + } else if ("moistureTest" === nursingData.workMode) { + console.log("水分测试"); + } else { + console.log("跳转11111111"); + + let report = true; + + go( + "/recoding/pages/face_report/face_report?id=" + + deviceid + + "&recordId=" + + currentDevice + + "&report=" + + report + ); + } + }; + + // 完成配对 + pairingChange = () => { + this.setState({ + isConnectShow: false, + isShowNurse: true, + }); + setTimeout(() => { + this.onNursingTap("switch"); + }); + }; + connectionClose = () => { + this.setState({ + isConnectShow: false, + }); + Taro.switchTab({ url: "/pages/index/index" }); + }; + + // 手动护理模式切换:提示是否保存护理 + /**仅关闭*/ + closeTipsSave = () => { + this.setState({ + isShowTipsSave: false, + }); + }; + /**关闭+切换*/ + cancelTipsSave = () => { + this.setState({ + isShowTipsSave: false, + }); + this.modeCurrentFun(this.tempModeCurrent, true); // 不提交护理记录,也不进行校验 + }; + /**关闭+提交+切换*/ + confirmTipsSave = async () => { + this.setState({ + isShowTipsSave: false, + }); + this.PostNursingLogClock(null, false); // 先提交护理记录 + this.modeCurrentFun(this.tempModeCurrent, true); // 不进行校验 + }; + + /** 初次护理信息弹窗 */ + firstNurseInfo = async () => { + let { currentDevice } = this.state; + let res = await InstrumentInfo.firstNurseInfo({ + instrumentId: currentDevice.id, + }); + console.log(res, "接口"); + + if (res.data.code === 200) { + let isFirstTipShow = getStorageSync( + "first_instrument_" + currentDevice.id + ); + console.log(isFirstTipShow, "查看返回值"); + + if (!isFirstTipShow) { + if (res.data.data.length !== 0) { + // 首次进入页面:自动打开打卡介绍弹窗 + this.setState({ nurseInfo: res.data.data, isFirstTipShow: true }); + } + + setStorageSync("first_instrument_" + currentDevice.id, true); + } else { + this.setState({ nurseInfo: res.data.data }); + } + } + }; + onTipShowOpen = async () => { + this.setState({ isFirstTipShow: true }); + }; + onTipShowClose = async () => { + setStorageSync("first_instrument_" + this.state.currentDevice.id, true); + this.setState({ isFirstTipShow: false }); + }; + /** 初次护理信息弹窗 END */ + + customBack = () => { + Taro.switchTab({ url: "/pages/index/index" }); + }; + + onModeLockOpen = async () => { + this.setState({ isModeLock: true }); + }; + onModeLockClose = async () => { + this.setState({ isModeLock: false }); + }; + + // 获取并设置视频时间 + GetVideosTime = (event) => { + console.log("获取并设置视频时间GetVideosTime", event?.detail?.duration); + if (event?.detail?.duration) { + let duration = Math.floor(event?.detail?.duration); + let currentTime = s_to_ms(duration); + this.setState({ currentTime, currentVideoTime: currentTime }); + } + }; + + VideoSrcLoad = (video: string = "") => { + this.setState({ + currentVideoSrc: "", + }); + setTimeout(() => { + this.setState({ + currentVideoSrc: video, + }); + }, 10); + }; + switchVideoPlay = () => { + setTimeout(() => { + let videoRef = Taro.createVideoContext("myVideo", this); + videoRef.play(); + }, 100); + }; + switchVideoPause = () => { + setTimeout(() => { + let videoRef = Taro.createVideoContext("myVideo", this); + videoRef.pause(); + }, 100); + }; + + render() { + let { + title, + isConnectShow, + GearData, + waterStepList, + waterStepIndex, + isShowStepTips, + isShowNurse, + isStopNurse, + ModeList, + ModeType, + ModeStepIndex, + ActiveModeItem, + currentWorkModeType, + isSwitchActiveMode, + ModeID, + activeModeID, + isShowCountdown, + countdown, + Electricity, + errorTips, + isEndCarePlan, + currentTime, + DeviceConnectStatus, + isShowErrorTipsText, + errorTipsText, + isNotEnoughTime, + isShowNursingSuccess, + currentDevice, + isConnectionBlutoot, + isShowTipsSave, + isFirstTipShow, + nurseInfo, + isRuningTest, + isShowHistoryMsg, + isModeLock, + currentVideoSrc, + currentGear, + showEcharts, + echartsData, + isShowReReadRecordConnect, + currentServiceData, + series, + isFullScreen, + } = this.state; + + return ( + + + {!isFullScreen && ( + + + + + + + + + + } + textAlgin="center" + cancelButtonText="取消" + confirmButtonText="确定" + close={this.cancelModeSwitchBtn} + confirm={this.confirmModeSwitchBtn} + /> + + + 正在同步护理记录... + + + + + + + + + + + + + 倒计时:{currentTime} + + + + + 档位:{currentGear}档 + + + + + + + {ModeList.length > 0 && ( + + )} + + {/* */} + + + + + + + + {(ActiveModeItem.modeType === "maskPenetration" || + ActiveModeItem.modeType === "essence") && ( + + )} + + {ActiveModeItem.modeType === "moistureTest" && ( + + )} + + +