From b7b79d84b269641a4a8134249f6b5ec33ae6218f Mon Sep 17 00:00:00 2001
From: zhangchen <1652267879@qq.com>
Date: 星期六, 26 七月 2025 14:15:03 +0800
Subject: [PATCH] Merge branch 'ID1825-床旁副屏改版' into test

---
 src/views/mobile/bedsideAuxiliaryScreen/components/TaskAlart.vue     |  184 +++++++++++++
 src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue        |  194 +++++++++----
 src/store/type/bedsideAuxiliaryScreen.type.ts                        |    5 
 src/store/type/task.type.ts                                          |   59 ++++
 src/utils/httpApi.ts                                                 |   20 +
 src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue |  257 +++++++++++++-----
 src/store/bedsideAuxiliaryScreen.ts                                  |    9 
 src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue           |    2 
 src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue     |    4 
 src/composables/useAudioPlayer.ts                                    |   66 ++++
 10 files changed, 663 insertions(+), 137 deletions(-)

diff --git a/src/composables/useAudioPlayer.ts b/src/composables/useAudioPlayer.ts
new file mode 100644
index 0000000..341dfbc
--- /dev/null
+++ b/src/composables/useAudioPlayer.ts
@@ -0,0 +1,66 @@
+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,
+  };
+}
diff --git a/src/store/bedsideAuxiliaryScreen.ts b/src/store/bedsideAuxiliaryScreen.ts
index b93d6cc..1424463 100644
--- a/src/store/bedsideAuxiliaryScreen.ts
+++ b/src/store/bedsideAuxiliaryScreen.ts
@@ -134,14 +134,19 @@
           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 {
diff --git a/src/store/type/bedsideAuxiliaryScreen.type.ts b/src/store/type/bedsideAuxiliaryScreen.type.ts
index 20b3abf..cb59066 100644
--- a/src/store/type/bedsideAuxiliaryScreen.type.ts
+++ b/src/store/type/bedsideAuxiliaryScreen.type.ts
@@ -121,6 +121,7 @@
   透析结束时间: number | null;
   透析处方备注: string;
   最近最大脱水量透析时长: string;
+  透析龄: number | null;
 }
 
 export interface VascularAccess {
@@ -355,6 +356,7 @@
   bloodVolumeMonitoring: number | null; // 血容量监测
   dialysisFluidFlowRate: number | null; // 透析液流量
   ktvList: KtvItem[]; // 实时ktv计算结果列表
+  dialysisAge: number | null, // 透析龄
 }
 
 export interface MonitoringRecord {
@@ -393,6 +395,7 @@
     dialysisFluidFlowRate: null,
     ktvList: [],
     prescriptionDialysisDuration: null, // 透析处方的时长(单位:小时)
+    dialysisAge: null,
   };
 };
 
@@ -553,6 +556,8 @@
         underTreatment.ktvList =
           seeMsg.透析状态?.实时ktv计算结果列表
             ?.realTimeKtvCalcDetailResultInfo ?? [];
+        underTreatment.dialysisAge = seeMsg.透析状态?.透析龄 ?? null;
+
         result.underTreatment = underTreatment;
       }
     }
diff --git a/src/store/type/task.type.ts b/src/store/type/task.type.ts
index fe6fc3c..29b1b04 100644
--- a/src/store/type/task.type.ts
+++ b/src/store/type/task.type.ts
@@ -1,3 +1,11 @@
+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;
@@ -9,6 +17,55 @@
   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,
+  },
+]
\ No newline at end of file
diff --git a/src/utils/httpApi.ts b/src/utils/httpApi.ts
index 8e9e878..4cbc6d7 100644
--- a/src/utils/httpApi.ts
+++ b/src/utils/httpApi.ts
@@ -58,4 +58,22 @@
         console.error('Error setting timeout alert:', error);
         throw error;
     }
-};
\ No newline at end of file
+};
+
+/**
+ * 停止定时任务
+ * @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;
+    }
+}
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue
index 25c014e..bb22b08 100644
--- a/src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue
@@ -2,31 +2,56 @@
   <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"
@@ -46,6 +71,8 @@
   <SettingDeviceDialog ref="settingDeviceDialogRef" />
   <!-- 定时任务组件 -->
   <ScheduledTaskDialog ref="scheduledTaskDialogRef" />
+  <!-- 定时任务提醒组件 -->
+  <TaskAlert ref="taskAlertRef" @close="taskAlaetClose" />
 </template>
 
 <script lang="ts" setup name="Header">
@@ -65,21 +92,27 @@
 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;
@@ -97,32 +130,100 @@
         ? "门诊号"
         : "住院号",
     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();
 };
 
@@ -133,36 +234,8 @@
   });
 };
 
-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>
 
@@ -181,12 +254,12 @@
     .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;
       }
     }
   }
@@ -207,6 +280,15 @@
 
       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>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
index 8111fe6..d931077 100644
--- a/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
@@ -13,7 +13,7 @@
     >
       <template #header>
         <div class="scheduled-task-header">
-          <span class="header-title">创建定时任务</span>
+          <span class="header-title">{{ title }}</span>
           <img
             :src="closeImg"
             class="header-close"
@@ -26,7 +26,8 @@
         <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"
@@ -34,6 +35,12 @@
                 @click="onAddMinutesClick(item)"
               >
                 {{ item.label }}
+              </div>
+            </div>
+            <!-- 查看的情况 -->
+            <div class="countdown" v-else>
+              <div class="countdown-btn">
+                剩余时间:{{ formattedCountdown }}
               </div>
             </div>
           </div>
@@ -60,7 +67,11 @@
             </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>
@@ -77,30 +88,18 @@
 </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;
@@ -110,54 +109,12 @@
 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 },
@@ -166,14 +123,97 @@
   { 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) => {
@@ -187,14 +227,8 @@
 };
 
 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 => {
@@ -246,12 +280,53 @@
       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,
@@ -316,7 +391,7 @@
     }
   }
   .scheduled-task-content {
-    padding: 0 12px 0px;
+    padding: 0 12px 0px 12px;
     margin-bottom: 4px;
     border-bottom: 1px solid #d8d8d8;
     display: flex;
@@ -345,6 +420,27 @@
             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;
           }
         }
       }
@@ -405,6 +501,19 @@
     }
     .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 {
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/TaskAlart.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/TaskAlart.vue
new file mode 100644
index 0000000..cff6899
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/TaskAlart.vue
@@ -0,0 +1,184 @@
+<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>
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue b/src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue
index 9e6984d..13d4fd7 100644
--- a/src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue
+++ b/src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue
@@ -90,7 +90,7 @@
               <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"
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue b/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
index f9c8129..765447a 100644
--- a/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
+++ b/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
@@ -208,7 +208,7 @@
                 <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">
@@ -243,7 +243,7 @@
             <div class="progress-box">
               <div class="item-num">
                 {{ jgTime4(pageData.dialysisDuration) }}/{{
-                  pageData.prescriptionDialysisDuration
+                  pageData.prescriptionDialysisDurationHour
                 }}:{{ pageData.prescriptionDialysisDurationMin }}
               </div>
               <ProgressBar

--
Gitblit v1.8.0