Merge branch 'ID1825-床旁副屏改版' into test
| New file |
| | |
| | | import { ref, watch } from "vue"; |
| | | |
| | | let currentAudio: HTMLAudioElement | null = null; |
| | | |
| | | export function useAudioPlayer() { |
| | | const source = ref<string | null>(null); |
| | | const isPlaying = ref(false); |
| | | |
| | | // 播放音频 |
| | | const play = (src: string) => { |
| | | if (!src) return; |
| | | |
| | | // 如果当前正在播放其他音频,先暂停并释放 |
| | | if (currentAudio) { |
| | | currentAudio.pause(); |
| | | currentAudio = null; |
| | | isPlaying.value = false; |
| | | } |
| | | |
| | | currentAudio = new Audio(src); |
| | | currentAudio.loop = true; // 循环播放 |
| | | currentAudio.volume = 1.0; // 音量最大 |
| | | currentAudio |
| | | .play() |
| | | .then(() => { |
| | | isPlaying.value = true; |
| | | }) |
| | | .catch((err) => { |
| | | console.error("音频播放失败:", err); |
| | | }); |
| | | |
| | | // 监听播放完毕 |
| | | currentAudio.onended = () => { |
| | | isPlaying.value = false; |
| | | }; |
| | | |
| | | source.value = src; |
| | | }; |
| | | |
| | | // 暂停播放 |
| | | const pause = () => { |
| | | if (currentAudio) { |
| | | currentAudio.pause(); |
| | | isPlaying.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 停止播放(并清除资源) |
| | | const stop = () => { |
| | | if (currentAudio) { |
| | | currentAudio.pause(); |
| | | currentAudio.currentTime = 0; |
| | | currentAudio = null; |
| | | isPlaying.value = false; |
| | | source.value = null; |
| | | } |
| | | }; |
| | | |
| | | return { |
| | | source, |
| | | isPlaying, |
| | | play, |
| | | pause, |
| | | stop, |
| | | }; |
| | | } |
| | |
| | | const dataBody = JSON.parse(datax) as SseMsgData; |
| | | console.log("dataBody: ", dataBody); |
| | | // 倒计时提示文本 |
| | | if (dataBody.倒计时?.提醒文本) { |
| | | const taskTime = dayjs(dataBody.倒计时?.当前服务器时间).add(dataBody.倒计时?.设定提醒倒计时, 'minute') |
| | | if (dataBody.倒计时?.提醒文本 && Number(dataBody.倒计时?.设定提醒倒计时 > 0)) { |
| | | const serverTimeRaw = dataBody.倒计时?.当前服务器时间; |
| | | const reminderMinutes = Number(dataBody.倒计时?.设定提醒倒计时 ?? 0); |
| | | const serverTimeFormatted = serverTimeRaw.replace(' ', 'T'); |
| | | |
| | | const taskTime = dayjs(serverTimeFormatted).add(reminderMinutes, 'second'); |
| | | setSyncTask({ |
| | | deviceCode: dataBody.IOT信息.设备唯一编号, |
| | | recordCode: dataBody.透析状态?.透析单编号, |
| | | taskDate: taskTime.format('YYYY-MM-DD HH:mm'), |
| | | taskName: dataBody.倒计时?.提醒文本, |
| | | overdue: false, |
| | | sync: true, |
| | | countdown: dataBody.倒计时?.设定提醒倒计时 |
| | | }) |
| | | } else { |
| | |
| | | 透析结束时间: number | null; |
| | | 透析处方备注: string; |
| | | 最近最大脱水量透析时长: string; |
| | | 透析龄: number | null; |
| | | } |
| | | |
| | | export interface VascularAccess { |
| | |
| | | bloodVolumeMonitoring: number | null; // 血容量监测 |
| | | dialysisFluidFlowRate: number | null; // 透析液流量 |
| | | ktvList: KtvItem[]; // 实时ktv计算结果列表 |
| | | dialysisAge: number | null, // 透析龄 |
| | | } |
| | | |
| | | export interface MonitoringRecord { |
| | |
| | | dialysisFluidFlowRate: null, |
| | | ktvList: [], |
| | | prescriptionDialysisDuration: null, // 透析处方的时长(单位:小时) |
| | | dialysisAge: null, |
| | | }; |
| | | }; |
| | | |
| | |
| | | underTreatment.ktvList = |
| | | seeMsg.透析状态?.实时ktv计算结果列表 |
| | | ?.realTimeKtvCalcDetailResultInfo ?? []; |
| | | underTreatment.dialysisAge = seeMsg.透析状态?.透析龄 ?? null; |
| | | |
| | | result.underTreatment = underTreatment; |
| | | } |
| | | } |
| | |
| | | import alertbaojin from "@/assets/alert.wav"; |
| | | import cxybaojing from "@/assets/cxy.mp3"; |
| | | import gybaojing from "@/assets/gy.mp3"; |
| | | import kclbaojing from "@/assets/kcl.mp3"; |
| | | import tdddbaojing from "@/assets/tzddd.mp3"; |
| | | import tzxllbaojing from "@/assets/tzxll.mp3"; |
| | | import cgbaojing from "@/assets/cg.mp3"; |
| | | |
| | | export interface Task { |
| | | /** 设备code */ |
| | | deviceCode: string; |
| | |
| | | taskName: string; |
| | | /** 是否过期 */ |
| | | overdue: boolean; |
| | | /** 倒计时,如果存在该字段则表明是远程传过来的 */ |
| | | /** 是否远程传过来的, 只有远程传过来的时间到了再报*/ |
| | | sync: boolean; |
| | | /** 倒计时,单位秒 */ |
| | | countdown?: number; |
| | | } |
| | | |
| | | export interface TaskItem { |
| | | label: string; |
| | | value: string; |
| | | backgroundColor: string; |
| | | promptTone: string; |
| | | } |
| | | |
| | | |
| | | export const taskOptions : TaskItem[] = [ |
| | | { |
| | | label: "测血压", |
| | | value: "测血压", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: cxybaojing, |
| | | }, |
| | | { |
| | | label: "开超滤", |
| | | value: "开超滤", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: kclbaojing, |
| | | }, |
| | | { |
| | | label: "给药", |
| | | value: "给药", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: gybaojing, |
| | | }, |
| | | { |
| | | label: "调电导度", |
| | | value: "调电导度", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: tdddbaojing, |
| | | }, |
| | | { |
| | | label: "调血流量", |
| | | value: "调血流量", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: tzxllbaojing, |
| | | }, |
| | | { |
| | | label: "冲管", |
| | | value: "冲管", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: cgbaojing, |
| | | }, |
| | | ] |
| | |
| | | console.error('Error setting timeout alert:', error); |
| | | throw error; |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | /** |
| | | * 停止定时任务 |
| | | * @param deviceCode |
| | | * @returns |
| | | */ |
| | | export const stopTimeoutAlert = async (deviceCode: string) => { |
| | | try { |
| | | const response = await axios.post(`${apiBaseUrl}/patient/hemo/med/record/stopTimeoutAlert`, { deviceCode }, { |
| | | headers: { |
| | | 'Content-Type': 'application/x-www-form-urlencoded' |
| | | } |
| | | }); |
| | | return response.data; |
| | | } catch (error) { |
| | | throw error; |
| | | } |
| | | } |
| | |
| | | <div class="bedside-auxiliary-screen-header"> |
| | | <div class="header-left"> |
| | | <!-- 没有设备编号 --> |
| | | <span v-if="pageType === 0" class="info-text">未绑定设备</span> |
| | | <span v-if="pageType === pageTypeEnum.NOT_INIT" class="info-text" |
| | | >未绑定设备</span |
| | | > |
| | | <template v-else> |
| | | <!-- 设备号 --> |
| | | <span class="info-text">{{ |
| | | bedsideAuxiliaryScreenStore.deviceData.devicdeNo |
| | | }}</span> |
| | | <!-- 加载中 --> |
| | | <span v-if="pageType === 1" class="info-text" |
| | | <span v-if="pageType === pageTypeEnum.LOADING" class="info-text" |
| | | >页面初始化中,请耐心等待!</span |
| | | > |
| | | <!-- 未排班 --> |
| | | <span v-else-if="pageType === 2" class="info-text">当前尚未排班</span> |
| | | <span |
| | | v-else-if="pageType === pageTypeEnum.UNPLANNED_SCHEDULE" |
| | | class="info-text" |
| | | >当前尚未排班</span |
| | | > |
| | | <!-- 有排班 --> |
| | | <template v-else> |
| | | <span class="info-text">{{ patientInfo.patientName }}</span> |
| | | <span class="info-text">{{ patientInfo.age }}岁</span> |
| | | <span class="info-text">{{ patientInfo.gender }}</span> |
| | | <span v-if="patientInfo.patFormNumber" class="info-text"> |
| | | {{ patientInfo.patForm }}:{{ patientInfo.patFormNumber }}</span |
| | | {{ patientInfo.patForm }}:{{ patientInfo.patFormNumber }}</span |
| | | > |
| | | <span |
| | | v-if=" |
| | | pageType === pageTypeEnum.DURING_DIALYSIS && |
| | | patientInfo.dialysisAge |
| | | " |
| | | class="info-text" |
| | | > |
| | | 透析龄: {{ patientInfo.dialysisAge?.years }}年{{ |
| | | patientInfo.dialysisAge?.months |
| | | }}月 |
| | | </span> |
| | | </template> |
| | | {{ taskCountdown }} |
| | | </template> |
| | | </div> |
| | | <div class="header-right"> |
| | | <span |
| | | v-if=" |
| | | bedsideAuxiliaryScreenStore.taskData && |
| | | bedsideAuxiliaryScreenStore.taskData.length > 0 |
| | | " |
| | | class="countdown" |
| | | > |
| | | {{ formattedCountdown }} |
| | | </span> |
| | | <img |
| | | :src="atRegularTimeImg" |
| | | class="btn-img" |
| | |
| | | <SettingDeviceDialog ref="settingDeviceDialogRef" /> |
| | | <!-- 定时任务组件 --> |
| | | <ScheduledTaskDialog ref="scheduledTaskDialogRef" /> |
| | | <!-- 定时任务提醒组件 --> |
| | | <TaskAlert ref="taskAlertRef" @close="taskAlaetClose" /> |
| | | </template> |
| | | |
| | | <script lang="ts" setup name="Header"> |
| | |
| | | const ScheduledTaskDialog = defineAsyncComponent( |
| | | () => import("./ScheduledTask.vue") |
| | | ); |
| | | const TaskAlert = defineAsyncComponent(() => import("./TaskAlart.vue")); |
| | | |
| | | import atRegularTimeImg from "../../../../img/dingshi.png"; |
| | | import setUpImg from "../../../../img/shezhi.png"; |
| | | import userImg from "../../../../img/user.png"; |
| | | |
| | | import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen"; |
| | | import { EPatForm } from "@/store/type/bedsideAuxiliaryScreen.type"; |
| | | import { EPatForm, EPageType } from "@/store/type/bedsideAuxiliaryScreen.type"; |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore(); |
| | | |
| | | let timer: number; |
| | | let timer: ReturnType<typeof setInterval> | null = null; |
| | | |
| | | const pageTypeEnum = ref(EPageType); |
| | | const settingDeviceDialogRef = ref<any>(null); |
| | | const scheduledTaskDialogRef = ref<any>(null); |
| | | const taskCountdown = ref(""); // 定时任务倒计时文本 |
| | | const taskAlertRef = ref<any>(null); |
| | | |
| | | const countdown = ref(null); // 定时任务的倒计时 |
| | | const isTaskAlartIsOpen = ref(false); // 定时任务的提醒弹框是否显示 |
| | | |
| | | const pageType = computed(() => { |
| | | return bedsideAuxiliaryScreenStore.deviceData.pageType; |
| | |
| | | ? "门诊号" |
| | | : "住院号", |
| | | patFormNumber: bedsideAuxiliaryScreenStore.deviceData.patFormNumber, |
| | | dialysisAge: bedsideAuxiliaryScreenStore.deviceData.underTreatment |
| | | .dialysisAge |
| | | ? convertMonths( |
| | | bedsideAuxiliaryScreenStore.deviceData.underTreatment.dialysisAge |
| | | ) |
| | | : null, |
| | | }; |
| | | }); |
| | | |
| | | // watch( |
| | | // () => bedsideAuxiliaryScreenStore.taskData, |
| | | // (newData: Task[]) => { |
| | | // console.log('定时任务更新了') |
| | | // if ( |
| | | // bedsideAuxiliaryScreenStore.deviceData.deviceCode && |
| | | // newData.length > 0 |
| | | // ) { |
| | | // console.log('newData: ', newData) |
| | | // updateCountdown(newData[0].taskDate); |
| | | // } else { |
| | | // taskCountdown.value = ""; |
| | | // } |
| | | // }, |
| | | // { deep: true } |
| | | // ); |
| | | const formattedCountdown = computed(() => { |
| | | if (countdown.value == null || countdown.value <= 0) return "0s"; |
| | | |
| | | const minutes = Math.floor(countdown.value / 60); |
| | | const seconds = countdown.value % 60; |
| | | |
| | | if (minutes > 0) { |
| | | return `${minutes}m ${seconds}s`; |
| | | } else { |
| | | return `${seconds}s`; |
| | | } |
| | | }); |
| | | |
| | | watch( |
| | | () => bedsideAuxiliaryScreenStore.taskData?.[0]?.countdown, |
| | | (val) => { |
| | | if (typeof val === "number") { |
| | | startCountdown(val); |
| | | } else { |
| | | clearTimer(); |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | |
| | | watch(countdown, (newVal) => { |
| | | if (newVal <= 0 && !isTaskAlartIsOpen.value) { |
| | | isTaskAlartIsOpen.value = true; |
| | | |
| | | // 弹窗逻辑,替换为你自己的弹窗组件或 UI 框 |
| | | showTaskAlart(); |
| | | } |
| | | }); |
| | | |
| | | // 清除定时器函数 |
| | | function clearTimer() { |
| | | if (timer) { |
| | | clearInterval(timer); |
| | | timer = null; |
| | | } |
| | | } |
| | | |
| | | // 启动新的倒计时 |
| | | function startCountdown(seconds: number) { |
| | | clearTimer(); |
| | | countdown.value = seconds; |
| | | |
| | | timer = setInterval(() => { |
| | | if (countdown.value > 0) { |
| | | countdown.value -= 1; |
| | | } else { |
| | | clearTimer(); |
| | | } |
| | | }, 1000); |
| | | } |
| | | |
| | | const convertMonths = (months: number): { years: number; months: number } => { |
| | | const years = Math.floor(months / 12); |
| | | const remainingMonths = months % 12; |
| | | return { years, months: remainingMonths }; |
| | | }; |
| | | |
| | | const showTaskAlart = () => { |
| | | clearTimer(); |
| | | taskAlertRef.value.openDialog( |
| | | bedsideAuxiliaryScreenStore.taskData?.[0]?.taskName |
| | | ); |
| | | }; |
| | | |
| | | const taskAlaetClose = () => { |
| | | clearTimer(); |
| | | bedsideAuxiliaryScreenStore.clearTask(); |
| | | isTaskAlartIsOpen.value = false; |
| | | }; |
| | | |
| | | const openSettingDeviceDialog = () => { |
| | | settingDeviceDialogRef.value?.openDialog(); |
| | | }; |
| | | |
| | | const openScheduledTaskDialog = () => { |
| | | if (!bedsideAuxiliaryScreenStore.deviceCode || !bedsideAuxiliaryScreenStore.deviceData.deviceCode) return ElMessage.warning('未初始化或正在进行初始化操作中'); |
| | | if ( |
| | | !bedsideAuxiliaryScreenStore.deviceCode || |
| | | !bedsideAuxiliaryScreenStore.deviceData.deviceCode |
| | | ) |
| | | return ElMessage.warning("未初始化或正在进行初始化操作中"); |
| | | scheduledTaskDialogRef.value?.openDialog(); |
| | | }; |
| | | |
| | |
| | | }); |
| | | }; |
| | | |
| | | const getCountdown = (taskDate: string) => { |
| | | const now = dayjs(); |
| | | const target = dayjs(taskDate).second(0).millisecond(0); |
| | | |
| | | const diff = target.diff(now, "second"); |
| | | |
| | | if (diff <= 0) return ""; |
| | | |
| | | const minutes = Math.floor(diff / 60); |
| | | const seconds = diff % 60; |
| | | |
| | | return `${minutes}m${seconds}s`; |
| | | }; |
| | | |
| | | const updateCountdown = (taskDate: string) => { |
| | | taskCountdown.value = getCountdown(taskDate); |
| | | timer = window.setInterval(updateCountdown, 1000); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | if ( |
| | | bedsideAuxiliaryScreenStore.deviceData.deviceCode && |
| | | bedsideAuxiliaryScreenStore.taskData.length > 0 |
| | | ) { |
| | | getCountdown(bedsideAuxiliaryScreenStore.taskData[0].taskDate); |
| | | } |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | timer && clearInterval(timer); |
| | | clearTimer(); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | .info-text { |
| | | font-family: PingFangSC, PingFang SC; |
| | | font-weight: 600; |
| | | font-size: 11px; |
| | | font-size: 9px; |
| | | color: #ffffff; |
| | | text-align: left; |
| | | font-style: normal; |
| | | &:not(:first-child) { |
| | | margin-left: 6px; |
| | | margin-left: 4px; |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | transition: all 0.2s; |
| | | } |
| | | .countdown { |
| | | font-family: PingFangSC, PingFang SC; |
| | | font-weight: 600; |
| | | font-size: 9px; |
| | | color: #bb3e3e; |
| | | line-height: 15rpx; |
| | | text-align: left; |
| | | font-style: normal; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | > |
| | | <template #header> |
| | | <div class="scheduled-task-header"> |
| | | <span class="header-title">创建定时任务</span> |
| | | <span class="header-title">{{ title }}</span> |
| | | <img |
| | | :src="closeImg" |
| | | class="header-close" |
| | |
| | | <div class="content-left"> |
| | | <div class="content-left-date"> |
| | | <TimePicker v-model="timeValue" /> |
| | | <div class="date-btn"> |
| | | <!-- 创建的情况 --> |
| | | <div class="date-btn" v-if="type === 0"> |
| | | <div |
| | | v-for="(item, index) in dateOptions" |
| | | :key="index" |
| | |
| | | @click="onAddMinutesClick(item)" |
| | | > |
| | | {{ item.label }} |
| | | </div> |
| | | </div> |
| | | <!-- 查看的情况 --> |
| | | <div class="countdown" v-else> |
| | | <div class="countdown-btn"> |
| | | 剩余时间:{{ formattedCountdown }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="content-right"></div> |
| | | <div class="content-right"> |
| | | <div class="stop-btn" v-if="type === 1" @click="stopTask"> |
| | | 紧急停止 |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="my-button cancel" @click="handleCancel">取消</div> |
| | |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { computed, ref } from "vue"; |
| | | import { computed, onUnmounted, ref, watch } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { setTimeoutAlert } from "@/utils/httpApi"; |
| | | import { setTimeoutAlert, stopTimeoutAlert } from "@/utils/httpApi"; |
| | | import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen"; |
| | | import { taskOptions } from "@/store/type/task.type"; |
| | | import type { TaskItem } from "@/store/type/task.type"; |
| | | |
| | | // @ts-ignore |
| | | import TimePicker from "./TimePicker.vue"; |
| | | |
| | | import closeImg from "@/img/close.png"; |
| | | import alertbaojin from "@/assets/alert.wav"; |
| | | import cxybaojing from "@/assets/cxy.mp3"; |
| | | import gybaojing from "@/assets/gy.mp3"; |
| | | import kclbaojing from "@/assets/kcl.mp3"; |
| | | import tdddbaojing from "@/assets/tzddd.mp3"; |
| | | import tzxllbaojing from "@/assets/tzxll.mp3"; |
| | | import cgbaojing from "@/assets/cg.mp3"; |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | interface TaskItem { |
| | | label: string; |
| | | value: string; |
| | | backgroundColor: string; |
| | | promptTone: string; |
| | | } |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | interface DateItem { |
| | | label: string; |
| | |
| | | const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore(); |
| | | |
| | | const isShow = ref(false); |
| | | |
| | | const type = ref<0 | 1>(0); // 0 创建 | 1 查看 |
| | | const taskName = ref(""); // 任务名称 |
| | | const isInpDisabled = ref(false); // 输入框是否禁用 |
| | | |
| | | const timeValue = ref(""); |
| | | |
| | | const detaCheck = ref<number | null>(); // 这个是判断时间按钮的 |
| | | |
| | | const loading = ref(false); |
| | | |
| | | const taskOptions = ref<TaskItem[]>([ |
| | | { |
| | | label: "测血压", |
| | | value: "测血压", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: cxybaojing, |
| | | }, |
| | | { |
| | | label: "开超滤", |
| | | value: "开超滤", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: kclbaojing, |
| | | }, |
| | | { |
| | | label: "给药", |
| | | value: "给药", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: gybaojing, |
| | | }, |
| | | { |
| | | label: "调电导度", |
| | | value: "调电导度", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: tdddbaojing, |
| | | }, |
| | | { |
| | | label: "调血流量", |
| | | value: "调血流量", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: tzxllbaojing, |
| | | }, |
| | | { |
| | | label: "冲管", |
| | | value: "冲管", |
| | | backgroundColor: "#E6A23C", |
| | | promptTone: cgbaojing, |
| | | }, |
| | | ]); |
| | | |
| | | const dateOptions = ref<DateItem[]>([ |
| | | { label: "15分钟", value: 15 }, |
| | |
| | | { label: "60分钟", value: 60 }, |
| | | ]); |
| | | |
| | | const taskItemCheck = computed(() => { |
| | | return taskOptions.value.find((e) => e.value === taskName.value)?.value || ""; |
| | | let timer: ReturnType<typeof setInterval> | null = null; |
| | | const countdown = ref(null); // 定时任务的倒计时 |
| | | |
| | | const formattedCountdown = computed(() => { |
| | | if (countdown.value == null || countdown.value <= 0) return "0s"; |
| | | |
| | | const minutes = Math.floor(countdown.value / 60); |
| | | const seconds = countdown.value % 60; |
| | | |
| | | if (minutes > 0) { |
| | | return `${minutes}m ${seconds}s`; |
| | | } else { |
| | | return `${seconds}s`; |
| | | } |
| | | }); |
| | | |
| | | const title = computed(() => { |
| | | return type.value ? "查看定时任务" : "创建定时任务"; |
| | | }); |
| | | |
| | | const taskItemCheck = computed(() => { |
| | | return taskOptions.find((e) => e.value === taskName.value)?.value || ""; |
| | | }); |
| | | |
| | | watch( |
| | | () => bedsideAuxiliaryScreenStore.taskData?.[0]?.countdown, |
| | | (val) => { |
| | | if (typeof val === "number") { |
| | | startCountdown(val); |
| | | } else { |
| | | clearTimer(); |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | |
| | | watch(countdown, (newVal) => { |
| | | if (newVal <= 0) { |
| | | loading.value = false; |
| | | handleCancel(); |
| | | // ElMessage.warning('当前任务发生变化,请重新设置'); |
| | | } |
| | | }); |
| | | |
| | | // 清除定时器函数 |
| | | function clearTimer() { |
| | | if (timer) { |
| | | clearInterval(timer); |
| | | timer = null; |
| | | } |
| | | } |
| | | |
| | | // 启动新的倒计时 |
| | | function startCountdown(seconds: number) { |
| | | clearTimer(); |
| | | countdown.value = seconds; |
| | | |
| | | timer = setInterval(() => { |
| | | if (countdown.value > 0) { |
| | | countdown.value -= 1; |
| | | } else { |
| | | clearTimer(); |
| | | } |
| | | }, 1000); |
| | | } |
| | | |
| | | const openDialog = () => { |
| | | isShow.value = true; |
| | | const time = dayjs(); |
| | | timeValue.value = time.format("HH:mm"); |
| | | if ( |
| | | !bedsideAuxiliaryScreenStore.taskData || |
| | | bedsideAuxiliaryScreenStore.taskData.length <= 0 |
| | | ) { |
| | | const time = dayjs(); |
| | | timeValue.value = time.format("HH:mm"); |
| | | detaCheck.value = null; |
| | | isInpDisabled.value = false; |
| | | taskName.value = ""; |
| | | type.value = 0; |
| | | } else { |
| | | const taskDataItem = bedsideAuxiliaryScreenStore.taskData[0]; |
| | | timeValue.value = dayjs(taskDataItem.taskDate).format("HH:mm"); |
| | | // 查是否是定型文的内容 |
| | | const writing = taskOptions.find((e) => e.value === taskDataItem.taskName); |
| | | if (writing) { |
| | | taskName.value = writing.value; |
| | | isInpDisabled.value = true; |
| | | } else { |
| | | taskName.value = taskDataItem.taskName; |
| | | } |
| | | type.value = 1; |
| | | } |
| | | }; |
| | | |
| | | const onStereotypedWritingClick = (item: TaskItem) => { |
| | |
| | | }; |
| | | |
| | | const onAddMinutesClick = (item: DateItem) => { |
| | | // if (detaCheck.value === item.value) { |
| | | // detaCheck.value = null; |
| | | // timeValue.value = addMinutes(timeValue.value, -item.value) |
| | | // } else { |
| | | detaCheck.value = item.value; |
| | | timeValue.value = addMinutes(timeValue.value, item.value); |
| | | // console.log('addMinutes(timeValue.value, item.value): ', addMinutes(timeValue.value, item.value)) |
| | | // } |
| | | }; |
| | | |
| | | const addMinutes = (time: string, delta: number): string => { |
| | |
| | | taskDate: dayjs(fullDateTime).format("YYYY-MM-DD HH:mm"), |
| | | taskName: params.alertText, |
| | | overdue: false, |
| | | sync: false, |
| | | countdown: diffMinutes * 60, |
| | | }); |
| | | // 这里得先把loading关了 |
| | | loading.value = false; |
| | | handleCancel(); |
| | | } catch (error) { |
| | | console.log(error); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | /** 停止任务 */ |
| | | const stopTask = () => { |
| | | ElMessageBox.confirm( |
| | | '是否确认停止当前任务?', |
| | | '提示', |
| | | { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: '取消', |
| | | type: 'warning', |
| | | } |
| | | ) |
| | | .then(async () => { |
| | | loading.value = true; |
| | | try { |
| | | await stopTimeoutAlert(bedsideAuxiliaryScreenStore.deviceData.deviceCode); |
| | | bedsideAuxiliaryScreenStore.clearTask(); |
| | | clearTimer(); |
| | | loading.value = false; |
| | | handleCancel(); |
| | | ElMessage.success('已停止') |
| | | } catch(error) { |
| | | ElMessage.error('操作失败:' + error) |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | |
| | | }) |
| | | }; |
| | | |
| | | onUnmounted(() => { |
| | | clearTimer(); |
| | | }); |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | |
| | | } |
| | | } |
| | | .scheduled-task-content { |
| | | padding: 0 12px 0px; |
| | | padding: 0 12px 0px 12px; |
| | | margin-bottom: 4px; |
| | | border-bottom: 1px solid #d8d8d8; |
| | | display: flex; |
| | |
| | | text-align: center; |
| | | font-size: 11px; |
| | | background-color: #769aff; |
| | | } |
| | | } |
| | | .countdown { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-left: 5px; |
| | | .countdown-btn { |
| | | padding: 0 8px; |
| | | background: #769aff; |
| | | box-shadow: 1px 1 0px 0px rgba(130, 126, 126, 0.5); |
| | | border-radius: 2px; |
| | | font-family: PingFangSC, PingFang SC; |
| | | font-weight: 600; |
| | | font-size: 11px; |
| | | line-height: 23px; |
| | | color: #ffffff; |
| | | text-shadow: 1px 1px 0px rgba(130, 126, 126, 0.5); |
| | | text-align: left; |
| | | font-style: normal; |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | .content-right { |
| | | width: 59px; |
| | | padding: 0 3px; |
| | | .stop-btn { |
| | | width: 100%; |
| | | background: #ff7472; |
| | | border-radius: 2px; |
| | | font-family: PingFangSC, PingFang SC; |
| | | font-weight: 600; |
| | | font-size: 6px; |
| | | color: #ffffff; |
| | | line-height: 18px; |
| | | text-align: center; |
| | | font-style: normal; |
| | | } |
| | | } |
| | | } |
| | | .my-button { |
| New file |
| | |
| | | <template> |
| | | <div class="task-alert-container"> |
| | | <el-dialog |
| | | v-model="show" |
| | | title="任务提醒" |
| | | width="60%" |
| | | :show-close="false" |
| | | class="task-alert-dialog" |
| | | :destroy-on-close="true" |
| | | :close-on-click-modal="false" |
| | | center |
| | | > |
| | | <template #header> |
| | | <div class="task-alert-header"> |
| | | <span class="header-title">任务提醒</span> |
| | | <img |
| | | :src="closeImg" |
| | | class="header-close" |
| | | @click="handleCancel" |
| | | alt="" |
| | | /> |
| | | </div> |
| | | </template> |
| | | <div class="task-alert-content"> |
| | | <span>{{ taskName }}</span> |
| | | </div> |
| | | <template #footer> |
| | | <div class="my-button cancel" @click="handleCancel">关闭</div> |
| | | <div class="my-button confirm" @click="handleCancel">确认</div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { ref } from "vue"; |
| | | import closeImg from "@/img/close.png"; |
| | | import { useAudioPlayer } from "@/composables/useAudioPlayer"; |
| | | import { taskOptions } from "@/store/type/task.type"; |
| | | import alertbaojin from "@/assets/alert.wav"; |
| | | |
| | | const { play, stop } = useAudioPlayer(); |
| | | |
| | | const emit = defineEmits<{ |
| | | (e: "close"): void; |
| | | }>(); |
| | | |
| | | const show = ref(false); |
| | | const taskName = ref(""); |
| | | |
| | | const openDialog = (name: string) => { |
| | | // 这里使用传值是为了防止sse任务结束后推送过来的数据没有 |
| | | taskName.value = name; |
| | | show.value = true; |
| | | const item = taskOptions.find((e) => e.value === name); |
| | | if (item) { |
| | | play(item.promptTone); |
| | | } else { |
| | | play(alertbaojin); |
| | | } |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | show.value = false; |
| | | stop(); |
| | | emit("close"); |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="less" scoped> |
| | | * { |
| | | box-sizing: border-box; |
| | | } |
| | | .task-alert-container { |
| | | ::v-deep(.el-dialog) { |
| | | padding: 0; |
| | | border-radius: 6px; |
| | | overflow: hidden; |
| | | } |
| | | ::v-deep(.el-dialog__footer) { |
| | | padding: 4px; |
| | | } |
| | | ::v-deep(.el-upload-dragger) { |
| | | height: 65px; |
| | | padding: 0 !important; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | ::v-deep(.el-upload-dragger .el-icon--upload) { |
| | | display: none; |
| | | } |
| | | ::v-deep(.el-dialog__header) { |
| | | padding-bottom: 6px; |
| | | } |
| | | .task-alert-header { |
| | | position: relative; |
| | | height: 16px; |
| | | background: #ff7472; |
| | | .header-title { |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translateX(-50%) translateY(-50%); |
| | | font-family: AlibabaPuHuiTi, AlibabaPuHuiTi; |
| | | font-weight: 500; |
| | | font-size: 8px; |
| | | color: #ffffff; |
| | | line-height: 11px; |
| | | text-align: center; |
| | | } |
| | | .header-close { |
| | | position: absolute; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | right: 6px; |
| | | width: 15px; |
| | | height: 15px; |
| | | transition: transform 0.2s; |
| | | |
| | | &:active { |
| | | opacity: 0.6; |
| | | transform: translateY(-50%) scale(0.95); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .task-alert-content { |
| | | height: 50px; |
| | | max-height: 80px; |
| | | font-size: 14px; |
| | | color: #333; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | overflow: hidden; |
| | | overflow-y: auto; |
| | | } |
| | | .my-button { |
| | | display: inline-block; |
| | | border-radius: 2px; |
| | | padding: 0px 10px; |
| | | font-family: PingFangSC, PingFang SC; |
| | | font-weight: 500; |
| | | font-size: 7px; |
| | | color: #ffffff; |
| | | line-height: 16px; |
| | | letter-spacing: 1px; |
| | | text-align: center; |
| | | font-style: normal; |
| | | transition: transform 0.1s ease, opacity 0.1s ease; |
| | | cursor: pointer; |
| | | &:active { |
| | | transform: scale(0.95); |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | &:not(:first-child) { |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | &.confirm { |
| | | background: #769aff; |
| | | } |
| | | &.cancel { |
| | | background: #bbc6dd; |
| | | } |
| | | &.refresh { |
| | | background: #e6a23c; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | <style> |
| | | .task-alert-dialog { |
| | | margin: 0 auto; |
| | | top: 50% !important; |
| | | transform: translateY(-50%) !important; |
| | | } |
| | | </style> |
| | |
| | | <span class="item-left" |
| | | >平均脱水量:{{ pageData.averageDehydrationRate }} L</span |
| | | > |
| | | <span class="item-right">(最近3周9次)</span> |
| | | <span class="item-right">(最近9次)</span> |
| | | </div> |
| | | <div class="dehydrated-level-item"> |
| | | <span class="item-left" |
| | |
| | | <span class="item-left" |
| | | >平均脱水量:{{ pageData.averageDehydrationRate }} L</span |
| | | > |
| | | <span class="item-right">(最近3周9次)</span> |
| | | <span class="item-right">(最近9次)</span> |
| | | </div> |
| | | <div class="dehydrated-level-item"> |
| | | <div class="item-left"> |
| | |
| | | <div class="progress-box"> |
| | | <div class="item-num"> |
| | | {{ jgTime4(pageData.dialysisDuration) }}/{{ |
| | | pageData.prescriptionDialysisDuration |
| | | pageData.prescriptionDialysisDurationHour |
| | | }}:{{ pageData.prescriptionDialysisDurationMin }} |
| | | </div> |
| | | <ProgressBar |