From 3b2e941a83ad49d3167df864a5a06a81ef944470 Mon Sep 17 00:00:00 2001
From: zhangchen <1652267879@qq.com>
Date: 星期三, 24 九月 2025 23:55:45 +0800
Subject: [PATCH] Merge branch 'ID2130-未签到页面优化' into test

---
 src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue |  562 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 562 insertions(+), 0 deletions(-)

diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
index e69de29..6421ac5 100644
--- a/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/ScheduledTask.vue
@@ -0,0 +1,562 @@
+<template>
+  <div class="scheduled-task-container">
+    <el-dialog
+      v-model="isShow"
+      center
+      title="定时任务"
+      width="80%"
+      :show-close="false"
+      class="scheduled-task-dialog"
+      top="0"
+      :destroy-on-close="true"
+      :close-on-click-modal="false"
+    >
+      <template #header>
+        <div class="scheduled-task-header">
+          <span class="header-title">{{ title }}</span>
+          <img
+            :src="closeImg"
+            class="header-close"
+            @click="handleCancel"
+            alt=""
+          />
+        </div>
+      </template>
+      <div class="scheduled-task-content" v-loading="loading">
+        <div class="content-left">
+          <div class="content-left-date">
+            <TimePicker v-model="timeValue" :disabled="type === 1" />
+            <!-- 创建的情况 -->
+            <div class="date-btn" v-if="type === 0">
+              <div
+                v-for="(item, index) in dateOptions"
+                :key="index"
+                class="my-button date"
+                @click="onAddMinutesClick(item)"
+              >
+                {{ item.label }}
+              </div>
+            </div>
+            <!-- 查看的情况 -->
+            <div class="countdown" v-else>
+              <div class="countdown-btn">
+                剩余时间:{{ formattedCountdown }}
+              </div>
+            </div>
+          </div>
+          <div class="content-left-stereotyped-writing">
+            <div class="stereotyped-writing">
+              <input
+                v-model.trim="taskName"
+                type="text"
+                :disabled="isInpDisabled || type === 1"
+                class="stereotyped-writing-input"
+                placeholder="请输入自定义内容"
+              />
+            </div>
+            <div class="stereotyped-writing-list">
+              <div
+                v-for="(item, index) in taskOptions"
+                :key="index"
+                class="my-button list-item"
+                @click="onStereotypedWritingClick(item)"
+                :class="taskItemCheck === item.value ? 'check' : ''"
+              >
+                {{ item.label }}
+              </div>
+            </div>
+          </div>
+        </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>
+        <div
+          class="my-button confirm"
+          :class="loading ? 'cancel' : ''"
+          @click="handleConfirm"
+        >
+          确认
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onUnmounted, ref, watch } from "vue";
+import dayjs from "dayjs";
+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 { ElMessage, ElMessageBox } from "element-plus";
+
+interface DateItem {
+  label: string;
+  value: number;
+}
+
+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 dateOptions = ref<DateItem[]>([
+  { label: "15分钟", value: 15 },
+  { label: "30分钟", value: 30 },
+  { label: "45分钟", value: 45 },
+  { label: "60分钟", value: 60 },
+]);
+
+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;
+  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) => {
+  if (type.value === 1) return;
+  if (taskName.value === item.value) {
+    taskName.value = "";
+    isInpDisabled.value = false;
+  } else {
+    taskName.value = item.value;
+    isInpDisabled.value = true;
+  }
+};
+
+const onAddMinutesClick = (item: DateItem) => {
+  if (type.value === 1) return;
+  detaCheck.value = item.value;
+  timeValue.value = addMinutes(timeValue.value, item.value);
+};
+
+const addMinutes = (time: string, delta: number): string => {
+  const [hours, minutes] = time.split(":").map(Number);
+  const totalMinutes = hours * 60 + minutes + delta;
+
+  // 处理负数和超过 1440(一天分钟数)情况
+  const normalized = (totalMinutes + 1440) % 1440;
+
+  const newHours = Math.floor(normalized / 60);
+  const newMinutes = normalized % 60;
+
+  return `${newHours.toString().padStart(2, "0")}:${newMinutes
+    .toString()
+    .padStart(2, "0")}`;
+};
+
+const handleCancel = () => {
+  if (loading.value) return;
+  isShow.value = false;
+};
+
+const handleConfirm = async () => {
+  if (loading.value) return;
+  const today = dayjs().format("YYYY-MM-DD");
+  const fullDateTime = dayjs(`${today} ${timeValue.value}`)
+    .second(0)
+    .millisecond(0); // 秒和毫秒都去掉,要不然间隔短了就是0分钟
+
+  const now = dayjs().second(0).millisecond(0); // 秒和毫秒都去掉
+  if (!fullDateTime.isAfter(now))
+    return ElMessage.warning("任务提醒时间不能早于或等于当前时间");
+  if (!taskName.value) return ElMessage.warning("任务内容不能为空");
+  loading.value = true;
+  try {
+    const diffMinutes = fullDateTime.diff(now, "minute");
+    const params = {
+      deviceCode: bedsideAuxiliaryScreenStore.deviceData.deviceCode,
+      minutes: diffMinutes,
+      alertText: taskName.value,
+    };
+    const recordCode = bedsideAuxiliaryScreenStore.deviceData.recordCode;
+    const { data, message } = await setTimeoutAlert(params);
+    if (data !== "OK") return ElMessage.warning(message);
+    ElMessage.success("操作成功");
+    bedsideAuxiliaryScreenStore.setSyncTask({
+      deviceCode: params.deviceCode,
+      recordCode: recordCode,
+      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,
+});
+</script>
+
+<style lang="less" scoped>
+* {
+  box-sizing: border-box;
+}
+.scheduled-task-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;
+  }
+  .scheduled-task-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);
+      }
+    }
+  }
+  .scheduled-task-content {
+    padding: 0 12px 0px 12px;
+    margin-bottom: 4px;
+    border-bottom: 1px solid #d8d8d8;
+    display: flex;
+    .content-left {
+      flex: 1;
+      border-right: 1px solid #d8d8d8;
+      padding-bottom: 6px;
+      padding-right: 12px;
+      .content-left-date {
+        border-bottom: 1px solid #d8d8d8;
+        display: flex;
+
+        .date-btn {
+          flex: 1;
+          display: flex;
+          flex-wrap: wrap;
+          gap: 9px;
+          margin-left: 5px;
+          justify-content: flex-end;
+          .date {
+            margin-left: 0;
+            padding: 0;
+            width: 44px;
+            height: 18px;
+            line-height: 18px;
+            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-left-stereotyped-writing {
+        .stereotyped-writing {
+          position: relative;
+          height: 16px;
+          padding-top: 6px;
+          margin-bottom: 6px;
+          border-radius: 2px;
+          border: 1px solid #979797;
+          overflow: hidden;
+          .stereotyped-writing-input {
+            position: absolute;
+            left: 0;
+            top: 0;
+            width: 100%;
+            height: 100%;
+            border: none;
+            outline: none;
+            padding: 0 4px;
+            line-height: 16px;
+            font-size: 9px;
+            font-family: PingFangSC, PingFang SC;
+            vertical-align: middle;
+            box-sizing: border-box; // 避免padding撑高
+
+            &::placeholder {
+              font-family: inherit;
+              font-size: inherit;
+              line-height: inherit;
+              color: #aaaaaa;
+              opacity: 1;
+            }
+            &:disabled {
+              background-color: #f5f5f5; // 灰色背景
+              color: #999999; // 灰色文字
+              cursor: not-allowed;
+            }
+          }
+        }
+        .stereotyped-writing-list {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 7px;
+          .list-item {
+            width: 40px;
+            margin-left: 0;
+            padding-left: 0;
+            padding-right: 0;
+            background-color: #e6a23c;
+            &.check {
+              background-color: #bbc6dd;
+            }
+          }
+        }
+      }
+    }
+    .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 {
+    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>
+.scheduled-task-dialog {
+  margin: 0 auto;
+  top: 50% !important;
+  transform: translateY(-50%) !important;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.8.0