From 2a8070b96f4aec8059ea96935b9166aec75b188b Mon Sep 17 00:00:00 2001
From: zhangchen <1652267879@qq.com>
Date: 星期二, 16 九月 2025 13:27:27 +0800
Subject: [PATCH] ID1766-结束透析完成

---
 src/api/patient_hemo_med_start/types/index.type.ts                       |   21 ++
 src/views/mobile/bedsideAuxiliaryScreen/components/EndDialysis/index.vue |  360 ++++++++++++++++++++++++++++++++++++++++
 src/api/patient_hemo_med_start/index.ts                                  |   56 +++++-
 src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue         |   16 +
 postcss.config.js                                                        |   49 +++-
 src/main.ts                                                              |    4 
 6 files changed, 474 insertions(+), 32 deletions(-)

diff --git a/postcss.config.js b/postcss.config.js
index b493b9a..d4d24ba 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -1,24 +1,41 @@
+// module.exports = {
+//   plugins: {
+//     'postcss-pxtorem': {
+//       rootValue: 37.5,
+//       propList: ['*'],
+//       include: file => {
+//         if (!file) return false;
+//         const normalized = file.replace(/\\/g, '/');
+//         // 只转换 mobile 目录里的样式 OR element-plus 目录里的样式
+//         // 但 element-plus 样式只在 mobile 目录被引用时才生效,需保证引用范围
+//         return normalized.includes('/src/views/mobile/') || normalized.includes('/node_modules/element-plus/');
+//       },
+//       exclude: file => {
+//         if (!file) return false;
+//         const normalized = file.replace(/\\/g, '/');
+//         // 排除除 element-plus 外的 node_modules,防止无关样式被转
+//         if (/node_modules/.test(normalized) && !normalized.includes('/node_modules/element-plus/')) {
+//           return true;
+//         }
+//         return false;
+//       },
+//       minPixelValue: 2,
+//     }
+//   }
+// }
+
 module.exports = {
   plugins: {
     'postcss-pxtorem': {
       rootValue: 37.5,
       propList: ['*'],
-      include: file => {
-        if (!file) return false;
-        const normalized = file.replace(/\\/g, '/');
-        // 只转换 mobile 目录里的样式 OR element-plus 目录里的样式
-        // 但 element-plus 样式只在 mobile 目录被引用时才生效,需保证引用范围
-        return normalized.includes('/src/views/mobile/') || normalized.includes('/node_modules/element-plus/');
-      },
-      exclude: file => {
-        if (!file) return false;
-        const normalized = file.replace(/\\/g, '/');
-        // 排除除 element-plus 外的 node_modules,防止无关样式被转
-        if (/node_modules/.test(normalized) && !normalized.includes('/node_modules/element-plus/')) {
-          return true;
-        }
-        return false;
-      },
+      // ✅ 用正则匹配路径,不依赖字符串 includes
+      include: [
+        /src[\/\\]views[\/\\]mobile/, // 只转 mobile 目录
+        /node_modules[\/\\]element-plus/ // 转 element-plus 样式
+      ],
+      // ✅ 排除其他 node_modules
+      exclude: (file) => /node_modules/.test(file) && !/node_modules[\/\\]element-plus/.test(file),
       minPixelValue: 2,
     }
   }
diff --git a/src/api/patient_hemo_med_start/index.ts b/src/api/patient_hemo_med_start/index.ts
index d0fa1f9..05f7b70 100644
--- a/src/api/patient_hemo_med_start/index.ts
+++ b/src/api/patient_hemo_med_start/index.ts
@@ -1,12 +1,17 @@
 import request from "@/utils/axios";
-import type { StartRecord } from "../patient_hemo_med_start/types/index.type";
+import type {
+  StartRecord,
+  RecordData,
+} from "../patient_hemo_med_start/types/index.type";
 
 /**
  * 获取开始透析数据api
  * @param params `recordCode=${recordCode}`
- * @returns 
+ * @returns
  */
-export function addDefaultRowApi(params: string) :Promise<{ data: StartRecord }> {
+export function addDefaultRowApi(
+  params: string
+): Promise<{ data: StartRecord }> {
   return request({
     url: "/patient/hemo/med/start/addDefaultRow",
     method: "post",
@@ -19,13 +24,44 @@
 
 /**
  * 修改开始透析数据api
+ * @param params
+ * @returns
+ */
+export function updateMedstartDataApi(params: StartRecord) {
+  return request({
+    url: "/patient/hemo/med/start/update",
+    method: "post",
+    data: params,
+  });
+}
+
+/**
+ * 获取结束透析数据api
+ * @param params `recordCode=${recordCode}`
+ * @returns
+ */
+export function addDefaultEndRowApi(
+  params: string
+): Promise<{ data: RecordData }> {
+  return request({
+    url: "/patient/hemo/med/end/addDefaultRow",
+    method: "post",
+    headers: {
+      "Content-Type": "application/x-www-form-urlencoded",
+    },
+    data: params,
+  });
+}
+
+/**
+ * 修改结束透析数据api
  * @param params 
  * @returns 
  */
-export function updateMedstartDataApi(params: StartRecord) {
-    return request({
-        url: '/patient/hemo/med/start/update',
-        method: 'post',
-        data: params,
-    });
-}
\ No newline at end of file
+export function updateEndRowApi(params: RecordData) {
+  return request({
+    url: "/patient/hemo/med/end/update",
+    method: "post",
+    data: params,
+  });
+}
diff --git a/src/api/patient_hemo_med_start/types/index.type.ts b/src/api/patient_hemo_med_start/types/index.type.ts
index dd5b623..013e1ce 100644
--- a/src/api/patient_hemo_med_start/types/index.type.ts
+++ b/src/api/patient_hemo_med_start/types/index.type.ts
@@ -46,6 +46,25 @@
   startYcNurse: string;
   startYcNurseName: string;
   startYingxue: string;
-  updateTime: string; // "2025-09-12 17:06:42"
+  updateTime: string;
+  updateUser: string | null;
+}
+
+export interface RecordData {
+  code: string;
+  createTime: string;
+  createUser: string | null;
+  deletedTime: string | null;
+  endDownNurse: string;
+  endDownNurseName: string;
+  endHuixue: string;
+  endTime: string;
+  id: number | null;
+  isDeleted: number | null;
+  lastTimeMonitorDataTime: string;
+  monitorDataCode: string;
+  recordCode: string;
+  remark: string;
+  updateTime: string;
   updateUser: string | null;
 }
diff --git a/src/main.ts b/src/main.ts
index 267b11f..b3d124c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -10,6 +10,8 @@
 import VConsole from 'vconsole'
 import { createPinia } from 'pinia'
 import '@/assets/css/iconfont.css'
+import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
+
 if (import.meta.env.VITE_ENV === 'development') {
 // 如果需要在手机平板上打开控制台,安装一个这个
     const vConsole = new VConsole()
@@ -18,4 +20,4 @@
 
 const app = createApp(App)
 
-app.use(router).use(pinia).use(ElementPlus).use(Vant).mount('#app')
+app.use(router).use(pinia).use(ElementPlus, { locale: zhCn }).use(Vant).mount('#app')
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/components/EndDialysis/index.vue b/src/views/mobile/bedsideAuxiliaryScreen/components/EndDialysis/index.vue
new file mode 100644
index 0000000..a292564
--- /dev/null
+++ b/src/views/mobile/bedsideAuxiliaryScreen/components/EndDialysis/index.vue
@@ -0,0 +1,360 @@
+<template>
+  <div class="end-dialysis-container">
+    <el-dialog
+      v-model="show"
+      center
+      title="结束透析"
+      width="80%"
+      :show-close="false"
+      class="end-dialysis-dialog"
+      :destroy-on-close="true"
+      :close-on-click-modal="false"
+    >
+      <template #header>
+        <div class="end-dialysis-header">
+          <span class="header-title">结束透析</span>
+          <img
+            :src="closeImg"
+            class="header-close"
+            @click="handleCancel"
+            alt=""
+          />
+        </div>
+      </template>
+      <div class="end-dialysis-content" v-loading="loading">
+        <el-form :model="formData" size="large">
+          <el-row :gutter="10">
+            <el-col :span="24">
+              <el-form-item label="结束时间">
+                <el-row>
+                  <el-col :span="8">
+                    <el-date-picker
+                      v-model="formData.endData"
+                      :clearable="false"
+                      type="date"
+                      placeholder="选择日期"
+                      format="YYYY/MM/DD"
+                      value-format="YYYY-MM-DD"
+                      style="width: 100%"
+                    />
+                  </el-col>
+                  <el-col :span="8">
+                    <el-time-select
+                      :clearable="false"
+                      v-model="formData.endTime"
+                      start="00:00"
+                      step="00:01"
+                      end="23:59"
+                      placeholder="选择时间"
+                      style="width: 100%"
+                    />
+                  </el-col>
+                  <el-col :span="3">
+                    <el-button type="primary" @click="addTime(1)"
+                      >时间+1</el-button
+                    >
+                  </el-col>
+                  <el-col :span="3">
+                    <el-button type="primary" @click="addTime(0.5)"
+                      >时间+0.5</el-button
+                    >
+                  </el-col>
+                </el-row>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="10">
+            <el-col :span="8">
+              <el-form-item label="下机护士">
+                <el-select
+                  style="width: 100%"
+                  v-model="formData.endDownNurse"
+                  placeholder="请选择"
+                >
+                  <el-option
+                    v-for="item in nurseOptions"
+                    :key="item.code"
+                    :label="item.userName"
+                    :disabled="item.isValid === 0"
+                    :value="item.code"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="回血(ml/min)">
+                <el-input style="width: 100%" v-model="formData.endHuixue" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+      </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">
+import { computed, reactive, toRefs } from "vue";
+import closeImg from "@/img/close.png";
+import {
+  addDefaultEndRowApi,
+  updateEndRowApi,
+} from "@/api/patient_hemo_med_start/index";
+import type { RecordData } from "@/api/patient_hemo_med_start/types/index.type";
+import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
+import dayjs from "dayjs";
+import { getUsersByRoleCodeApi } from "@/api/user/index";
+import { ElMessage } from "element-plus";
+
+interface FormData {
+  id: number;
+  recordCode: string;
+  endTime: string;
+  endDownNurse: string;
+  endDownNurseName: string;
+  lastTimeMonitorDataTime: string;
+  endHuixue: string;
+  endData: string;
+}
+
+interface State {
+  show: boolean;
+  loading: boolean;
+  formData: FormData;
+  nurseOptions: any[];
+  defaultEndTime: string;
+}
+export default {
+  name: "EndDialysis",
+  setup() {
+    const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
+
+    const state = reactive<State>({
+      show: false,
+      loading: false,
+      formData: {
+        id: 0,
+        recordCode: "",
+        endTime: "",
+        endDownNurse: "",
+        endDownNurseName: "",
+        lastTimeMonitorDataTime: "",
+        endHuixue: "",
+        endData: "",
+      },
+      nurseOptions: [],
+      defaultEndTime: "", // 默认的日期,原始数据,用于计算
+    });
+
+    const recordCode = computed(() => {
+      return bedsideAuxiliaryScreenStore.deviceData.underTreatment.透析单编号;
+    });
+
+    const clientCode = computed(() => {
+      return bedsideAuxiliaryScreenStore.deviceData.客户编号;
+    });
+
+    const userCode = computed(() => {
+      return bedsideAuxiliaryScreenStore.userInfo.userCode;
+    });
+
+    const openDialog = async () => {
+      state.loading = true;
+      state.show = true;
+      try {
+        const { data } = await addDefaultEndRowApi(
+          `recordCode=${recordCode.value}`
+        );
+
+        const dataCopy = JSON.parse(JSON.stringify(data));
+        const notLs = [null, ""];
+
+        if (notLs.includes(data.endDownNurse)) {
+          dataCopy.endDownNurse = userCode.value;
+        }
+
+        if (notLs.includes(data.endTime)) {
+          dataCopy.endTime = dayjs().format("HH:mm");
+          // @ts-ignore
+          dataCopy.endData = dayjs().format("YYYY-MM-DD");
+          state.defaultEndTime = dayjs().format("YYYY-MM-DD HH:mm");
+        } else {
+          dataCopy.endTime = dayjs(data.endTime).format("HH:mm");
+          // @ts-ignore
+          dataCopy.endData = dayjs(data.endTime).format("YYYY-MM-DD");
+          state.defaultEndTime = dayjs(data.endTime).format("YYYY-MM-DD HH:mm");
+        }
+        state.formData = dataCopy as unknown as FormData;
+        await getNurses();
+      } catch (error) {
+        setTimeout(() => {
+          state.show = false;
+        }, 1000);
+      } finally {
+        state.loading = false;
+      }
+    };
+
+    const handleCancel = () => {
+      state.show = false;
+    };
+
+    const handleConfirm = async () => {
+      if (state.loading) return false;
+      state.loading = true;
+      try {
+        const paramsData = Object.assign({}, state.formData, {
+          endTime: dayjs(
+            `${state.formData.endData} ${state.formData.endTime}`
+          ).format("YYYY-MM-DD HH:mm:ss"),
+        });
+        await updateEndRowApi(paramsData as unknown as RecordData);
+        ElMessage.success("操作成功!");
+        state.show = false;
+      } finally {
+        state.loading = false;
+      }
+    };
+
+    const addTime = (hourNum: number) => {
+      if (["", null].includes(state.formData.lastTimeMonitorDataTime))
+        return false;
+      const addDate = addHours(
+        state.formData.lastTimeMonitorDataTime + "",
+        hourNum
+      );
+      state.formData.endData = dayjs(addDate).format("YYYY-MM-DD");
+      state.formData.endTime = dayjs(addDate).format("HH:mm");
+    };
+
+    const addHours = (dateStr: string, hours: number): string => {
+      return dayjs(dateStr).add(hours, "hour").format("YYYY-MM-DD HH:mm:ss");
+    };
+
+    /**
+     * 获取护士数据
+     */
+    const getNurses = async () => {
+      const { data } = await getUsersByRoleCodeApi(
+        `clientCode=${clientCode.value}&roleClass=nurse`
+      );
+      state.nurseOptions = data;
+    };
+
+    return {
+      ...toRefs(state),
+      closeImg,
+      handleCancel,
+      handleConfirm,
+      openDialog,
+      addTime,
+    };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.end-dialysis-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;
+  }
+  .end-dialysis-header {
+    position: relative;
+    height: 16px;
+    background: #769aff;
+    .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);
+      }
+    }
+  }
+  .end-dialysis-content {
+    padding: 0 12px 0px 12px;
+    margin-bottom: 4px;
+    border-bottom: 1px solid #d8d8d8;
+  }
+  .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>
\ No newline at end of file
diff --git a/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue b/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
index 31457c2..a553767 100644
--- a/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
+++ b/src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
@@ -330,6 +330,8 @@
     </div>
     <!-- 定时任务 -->
     <ScheduledTaskDialog ref="scheduledTaskDialogRef" />
+    <!-- 结束透析组件 -->
+    <EndDialysis ref="endDialysisRef" />
   </div>
 </template>
 <script lang="ts" setup name="UnderTreatment">
@@ -354,6 +356,7 @@
 const ScheduledTaskDialog = defineAsyncComponent(
   () => import("../components/ScheduledTask.vue")
 );
+const EndDialysis = defineAsyncComponent(() => import("../components/EndDialysis/index.vue"));
 import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
 import {
   formatSubstituteMode,
@@ -389,6 +392,7 @@
 const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
 
 const scheduledTaskDialogRef = ref<any>(null);
+const endDialysisRef = ref<any>(null);
 
 // ktv趋势图的
 const ktvListEchartRef = ref<HTMLElement | null>(null);
@@ -784,10 +788,14 @@
 
 /** 结束透析 */
 const onEndClick = () => {
-  ElMessage({
-    message: "功能开发中,敬请期待!",
-    type: "warning",
-  });
+  if (
+    !bedsideAuxiliaryScreenStore.deviceCode ||
+    !bedsideAuxiliaryScreenStore.deviceData.deviceCode
+  )
+    return ElMessage.warning("未初始化或正在进行初始化操作中");
+  if (!bedsideAuxiliaryScreenStore.userInfo?.token)
+    return ElMessage.warning("请登录");
+  endDialysisRef.value?.openDialog();
 };
 
 onMounted(() => {

--
Gitblit v1.8.0