单应用项目,可以创建很多独立工具类页面 ,不用登录 初始化的页面
zhangchen
2025-09-16 a99886eb9c73018235e2c373f3d82a2b0b2311d5
Merge branch 'ID1766-添加推送登录功能' into test
15个文件已修改
12个文件已添加
1987 ■■■■■ 已修改文件
postcss.config.js 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/commom.type.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/dict/index.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/dict/types/index.type.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventory/itemDict.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventory/types/itemDict.type.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/patient_hemo_med_start/index.ts 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/patient_hemo_med_start/types/index.type.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/user.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/user/index.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/css/iconfont.css 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/font/iconfont.ttf 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/font/iconfont.woff 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/font/iconfont.woff2 补丁 | 查看 | 原始文档 | blame | 历史
src/main.ts 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/bedsideAuxiliaryScreen.ts 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/type/bedsideAuxiliaryScreen.type.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/type/user.type.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/axios.ts 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/httpApi.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/EndDialysis/index.vue 360 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/Login/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/Login/userInfo.vue 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/components/StartDialysis/index.vue 865 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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,
    }
  }
src/api/commom.type.ts
New file
@@ -0,0 +1,6 @@
export interface ListParams {
    page: number;
    size: number;
    wherecondition: string;
    ordercondition: string;
}
src/api/dict/index.ts
New file
@@ -0,0 +1,14 @@
import request from "@/utils/axios";
import type { DictTypeParams } from './types/index.type';
export function getDictTypeApi(params: DictTypeParams) {
  return request({
      url: '/dict/base/info/listDictValuesByType',
      method: 'post',
      headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
      },
      params,
  })
}
src/api/dict/types/index.type.ts
New file
@@ -0,0 +1,9 @@
export enum DictType {
    穿刺方式 = 'in_med_cc_method',
    穿刺针类型 = 'in_med_cc_zhen_type',
    穿刺方向 = 'in_med_cc_ccfx'
}
export interface DictTypeParams {
    dictType: DictType;
}
src/api/inventory/itemDict.ts
New file
@@ -0,0 +1,15 @@
import request from "@/utils/axios";
import type { InventoryItemListParams } from './types/itemDict.type';
/**
 * 获取库存字典api
 * @param params
 * @returns
 */
export function getInventoryItemListApi(params: InventoryItemListParams) {
    return request({
      url: '/inventory/item/detail/info/list',
      method: 'post',
      params
    })
}
src/api/inventory/types/itemDict.type.ts
New file
@@ -0,0 +1,6 @@
import { ListParams } from '@/api/commom.type';
export interface InventoryItemListParams extends ListParams {
    isPure?: number;
}
src/api/patient_hemo_med_start/index.ts
New file
@@ -0,0 +1,67 @@
import request from "@/utils/axios";
import type {
  StartRecord,
  RecordData,
} from "../patient_hemo_med_start/types/index.type";
/**
 * 获取开始透析数据api
 * @param params `recordCode=${recordCode}`
 * @returns
 */
export function addDefaultRowApi(
  params: string
): Promise<{ data: StartRecord }> {
  return request({
    url: "/patient/hemo/med/start/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,
  });
}
/**
 * 获取结束透析数据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 updateEndRowApi(params: RecordData) {
  return request({
    url: "/patient/hemo/med/end/update",
    method: "post",
    data: params,
  });
}
src/api/patient_hemo_med_start/types/index.type.ts
New file
@@ -0,0 +1,70 @@
export interface StartRecord {
  code: string;
  createTime: string; // "2025-09-12 17:06:42"
  createUser: string | null;
  deletedTime: string | null;
  doneByg: string;
  doneBygName: string;
  doneBygSpecName: string;
  doneGl: string;
  doneGlName: string;
  doneGlSpecName: string;
  id: number | null;
  isDeleted: number | null;
  monitorDataCode: string;
  recordCode: string;
  remark: string;
  schemeName: string;
  /** 穿刺方向 */
  startCcASideDirection: string;
  startCcNurse: string;
  startCcNurseName: string;
  /** 穿刺方式 */
  startCcType: string;
  startCcZhenA: string;
  startCcZhenAName: string;
  startCcZhenASpecName: string;
  startCcZhenType: string;
  startCcZhenV: string;
  startCcZhenVName: string;
  startCcZhenVSpecName: string;
  startEntranceType: string;
  startIsCcOrHy: number | null;
  startIsSave: number | null;
  startMedNurse: string;
  startMedNurseName: string;
  startTime: string | null;
  startTxqNo: string;
  startUpNurse: string;
  startUpNurseName: string;
  startUseHlb: string;
  startUseHlbName: string;
  startUseHlbSpecName: string;
  startUseNsy: string;
  startUseNsyName: string;
  startUseNsySpecName: string;
  startYcNurse: string;
  startYcNurseName: string;
  startYingxue: string;
  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;
}
src/api/user.ts
New file
@@ -0,0 +1,16 @@
import request from "@/utils/axios";
/**
 * 副屏退出登录Api
 * @returns
 */
export function logOutApi(params: string) {
  return request({
    url: "/user/info/logoutForSubScreen",
    method: "post",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    data: params,
  });
}
src/api/user/index.ts
New file
@@ -0,0 +1,17 @@
import request from "@/utils/axios";
/**
 * 根据角色获取用户列表api
 * @param params
 * @returns
 */
export function getUsersByRoleCodeApi(params: string) {
  return request({
    url: "/user/info/getUsersByRoleCode",
    method: "post",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    data: params,
  });
}
src/assets/css/iconfont.css
@@ -1,8 +1,8 @@
@font-face {
  font-family: "iconfont"; /* Project id 5011061 */
  src: url('//at.alicdn.com/t/c/font_5011061_crebeujq91a.woff2?t=1756705233110') format('woff2'),
       url('//at.alicdn.com/t/c/font_5011061_crebeujq91a.woff?t=1756705233110') format('woff'),
       url('//at.alicdn.com/t/c/font_5011061_crebeujq91a.ttf?t=1756705233110') format('truetype');
  src: url('../font/iconfont.woff2') format('woff2'),
       url('../font/iconfont.woff') format('woff'),
       url('../font/iconfont.ttf') format('truetype');
}
.iconfont {
@@ -20,3 +20,4 @@
.icon-saoma:before {
  content: "\e749";
}
src/assets/font/iconfont.ttf
Binary files differ
src/assets/font/iconfont.woff
Binary files differ
src/assets/font/iconfont.woff2
Binary files differ
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')
src/store/bedsideAuxiliaryScreen.ts
@@ -14,7 +14,8 @@
} from "./type/bedsideAuxiliaryScreen.type";
import { ElMessage } from "element-plus/es";
import { Local } from "@/utils/storage";
import type { UserInfo } from './type/user.type';
import type { DeviceLoginRecord } from './type/user.type';
import { logOutApi } from "@/api/user";
export const useBedsideAuxiliaryScreenStore = defineStore(
  "bedsideAuxiliaryScreen",
@@ -32,10 +33,7 @@
    const taskData = ref<Task[]>([]);
    /** 用户信息 */
    const userInfo = ref<UserInfo | null>(Local.get('userInfo'));
    /** 用户token */
    const token = ref<string>(Local.get('token'));
    const userInfo = ref<DeviceLoginRecord | null>(null);
    /** 设置副屏版本号 */
    const setVersion = (val: string) => {
@@ -78,23 +76,15 @@
    };
    /** 设置用户信息 */
    const setUserInfo = (user: UserInfo) => {
    const setUserInfo = (user: DeviceLoginRecord | null) => {
      userInfo.value = user;
      Local.set('userInfo', user);
    };
    /** 设置token */
    const setToken = (str: string) => {
      token.value = str;
      Local.set('token', str);
    }
    /** 退出登录 */
    const logout = () => {
      Local.remove('token');
      Local.remove('userInfo');
      token.value = '';
      userInfo.value = null;
    const logout = async (deviceCodeStr: string) => {
      await logOutApi(deviceCodeStr);
      setUserInfo(null);
    };
    // SSE 相关状态
@@ -185,6 +175,9 @@
          }
          deviceData.value = formatDeviceData(deviceData.value, dataBody);
          // 当前登录的用户信息
          setUserInfo(dataBody.当前登录状态);
          // 判断本地的版本号与远程的版本号是否一致,如果不一致则执行刷新操作
          if (dataBody.服务端版本号 !== version.value) {
            refreshVersion(dataBody.服务端版本号);
@@ -249,9 +242,7 @@
      setVersion,
      refreshVersion,
      userInfo,
      token,
      setUserInfo,
      setToken,
      logout,
    };
  }
src/store/type/bedsideAuxiliaryScreen.type.ts
@@ -1,6 +1,7 @@
import { tryConvertToInt, deepClone } from "@/utils/utils";
import { Local } from "@/utils/storage";
import dayjs from "dayjs";
import type { DeviceLoginRecord } from './user.type';
export interface IotInfo {
  属性历史列表: any[];
  床号: string;
@@ -24,6 +25,7 @@
  管路: string[];
  透析器: string[];
  透析模式: string[];
  透析单编号: string;
}
enum EPushType {
@@ -212,6 +214,7 @@
  自定义配置项: Customconfiguration;
  服务端版本号: string;
  是否需要立即刷新: 0 | 1;
  当前登录状态: DeviceLoginRecord | null;
}
interface Customconfiguration {
@@ -247,6 +250,7 @@
  sphygmomanometer: Sphygmomanometer;
  customConfiguration: Customconfiguration;
  患者出生日期: string;
  客户编号: string;
}
export const defaultSphygmomanometer = () :Sphygmomanometer => {
@@ -315,6 +319,7 @@
  carePackage: any[];
  punctureNeedle: PunctureNeedle[];
  vascularAccess: VascularAccess[];
  透析单编号: string;
}
export const defaultconsumablesCollection = (): ConsumablesCollection => {
@@ -326,6 +331,7 @@
    管路: [],
    透析器: [],
    透析模式: [],
    透析单编号: '',
  };
};
@@ -339,6 +345,7 @@
    carePackage: [], // 一次性使用透析护理包列表
    punctureNeedle: [], // 穿刺针列表
    vascularAccess: [], // 血管通路列表
    透析单编号: ''
  };
};
@@ -373,6 +380,7 @@
  体重增长_透前_上次透后: number | null; // 体重增长(透前-上次透后)
  体重增持_透前_干体重: number | null; // 体重增长(透前-干体重)
  四点血压图数据: 四点血压图数据[];
  透析单编号: string;
}
export const defaultSignedIn = (): SignedIn => {
@@ -407,6 +415,7 @@
    体重增长_透前_上次透后: null, // 体重增长(透前-上次透后)
    体重增持_透前_干体重: null, // 体重增长(透前-干体重)
    四点血压图数据: [],
    透析单编号: '',
  };
};
@@ -443,6 +452,7 @@
  venousPressure2: number | null; // 静脉压
  transmembranePressure2: number | null; // 跨膜压
  处方脱水量: number | null; // 处方脱水量
  透析单编号: string;
}
export interface MonitoringRecord {
@@ -485,6 +495,7 @@
    venousPressure2: null, // 静脉压
    transmembranePressure2: null, // 跨膜压
    处方脱水量: null,
    透析单编号: ''
  };
};
@@ -510,6 +521,7 @@
    // @ts-ignore
    pageType, // 当前要展示的页面
    treatmentStatus: EMedStatus.NOT_CHECKED_IN, // 透析状态
    客户编号: "",
    consumablesCollection: defaultconsumablesCollection(), // 未排班时需要的数据
    notSignedIn: defalutNotSignedIn(), // 未签到时需要的数据
    signedIn: defaultSignedIn(), // 已签到时需要的数据
@@ -540,6 +552,7 @@
  // 默认床号(设备号)
  result.devicdeNo = seeMsg.IOT信息?.床号;
  result.deviceCode = seeMsg.IOT信息?.设备唯一编号;
  // 自定义配置项
  result.customConfiguration = seeMsg.自定义配置项 ?? defaultCustomconfiguration();
@@ -577,6 +590,9 @@
      result.patFormNumber = seeMsg.透析状态?.患者门诊住院号;
      result.患者出生日期 = seeMsg.透析状态?.患者出生日期 || "";
      result.客户编号 = seeMsg.透析状态?.clientCode;
      // 未签到页面需要显示的
      if (treatmentStatus === EMedStatus.NOT_CHECKED_IN) {
        result.pageType = EPageType.NOT_SIGNED_IN;
@@ -589,6 +605,7 @@
        notSignedIn.punctureNeedle = seeMsg.透析状态?.穿刺针列表 ?? [];
        notSignedIn.vascularAccess = seeMsg.透析状态?.血管通路列表 ?? [];
        notSignedIn.anticoagulant = seeMsg.透析状态?.抗凝剂列表 ?? [];
        notSignedIn.透析单编号 = seeMsg.透析状态?.透析单编号;
        result.notSignedIn = notSignedIn;
      }
@@ -635,6 +652,7 @@
          signedIn.上次透析单所属日期 = dayjs(signedIn.四点血压图数据[0].透析日期).format("YYYY-MM-DD");
          // signedIn.本次透析单所属日期 = dayjs(signedIn.四点血压图数据[0].透析日期).format("YYYY-MM-DD");
        }
        signedIn.透析单编号 = seeMsg.透析状态?.透析单编号;
        result.signedIn = signedIn;
      }
      // 剩下的全使用治疗中的页面
@@ -698,6 +716,7 @@
            ?.realTimeKtvCalcDetailResultInfo ?? [];
        underTreatment.dialysisAge = seeMsg.透析状态?.透析龄 ?? null;
        underTreatment.处方脱水量 = seeMsg.透析状态?.处方脱水量 ?? null;
        underTreatment.透析单编号 = seeMsg.透析状态?.透析单编号;
        result.underTreatment = underTreatment;
      }
src/store/type/user.type.ts
@@ -1,11 +1,18 @@
export interface UserInfo {
    用户头像: string;
    用户昵称: string;
}
export const defaultUserInfo = (): UserInfo => {
    return {
        用户头像: '',
        用户昵称: ''
    }
export interface DeviceLoginRecord {
  clientCode: string;
  code: string;
  createTime: string;      // 格式:YYYY-MM-DD HH:mm:ss
  createUser: string | null;
  deletedTime: string | null;
  deviceCode: string;
  id: number;
  isDeleted: number;       // 0/1 标识
  loginTime: string;       // 格式:YYYY-MM-DD HH:mm:ss
  remark: string | null;
  token: string | null;
  updateTime: string;      // 格式:YYYY-MM-DD HH:mm:ss
  updateUser: string | null;
  userCode: string;
  userName: string;
  userAvatar: string | null;
}
src/utils/axios.ts
New file
@@ -0,0 +1,134 @@
import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import Qs from 'qs'
import { useBedsideAuxiliaryScreenStore } from '@/store/bedsideAuxiliaryScreen'
import { ElMessage } from 'element-plus/es'
// ====== 配置 ======
const BASE_URL = import.meta.env.VITE_API_BASE_URL
const TIME_OUT = 60 * 1 * 1000
// 正在进行中的请求列表,避免重复请求
let reqList: string[] = []
/**
 * 允许某个请求再次发送
 */
const allowRequest = (url?: string) => {
  if (!url) return
  const idx = reqList.indexOf(url)
  if (idx > -1) reqList.splice(idx, 1)
}
/**
 * 创建 axios 实例
 */
const instance = axios.create({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
  paramsSerializer: (params) => Qs.stringify(params, { arrayFormat: 'brackets' }),
})
// ====== 错误处理函数 ======
const errorHandle = (status: number, error: any): void => {
  const msg = error?.msg || error?.message || ''
  switch (status) {
    case 401:
      ElMessage.error(`Error Code: 401, Message: ${msg || '登录失效,请重新登录'}`);
      break
    case 403:
      ElMessage.error(`Error Code: 403, Message: ${msg || '你没有访问权限'}`);
      break
    case 500:
      ElMessage.error(`Error Code: 500, Message: ${msg || '后台错误,请联系管理员'}`);
      break
    case 502:
      alert()
      ElMessage.error(`Error Code: 502, Message: ${msg || '平台环境异常'}`);
      break
    default:
      ElMessage.error(`Error Code: ${status}, Message: ${msg || '未知错误,请刷新重试'}`);
  }
}
// ====== 请求拦截 ======
instance.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const store = useBedsideAuxiliaryScreenStore()
    // 加 token
    if (store.userInfo?.token) {
      config.headers.set('Authorization', `Bearer ${store.userInfo.token}`)
    }
    // 防止同一接口短时间内重复请求
    if (config.url) {
      if (reqList.includes(config.url)) {
        // return Promise.reject(new axios.Cancel(`重复请求:${config.url}`))
      } else {
        reqList.push(config.url)
      }
    }
    return config
  },
  (error) => Promise.reject(error),
)
// ====== 响应拦截 ======
instance.interceptors.response.use(
  (res: AxiosResponse) => {
    // setTimeout(() => allowRequest(res.config.url), 1000)
    if (String(res.status).charAt(0) !== '2') {
      return Promise.reject({
        code: res.status,
        message: res.data?.message || '请求异常,请刷新重试',
      })
    }
    if (res.data?.code === 200) return res.data
    const whiteList: string[] = []
    const requestUrl: string = res?.config?.url || ''
    const isInWhiteList = whiteList.some((item) => requestUrl.includes(item))
    ElMessage.error(res.data.message);
    if (isInWhiteList) return Promise.reject(res.data)
    return Promise.reject(res.data)
  },
  (err: unknown) => {
    const anyErr = err as any
    const configUrl = anyErr?.config?.url
    // 失败后 1s 允许再次请求
    // setTimeout(() => allowRequest(configUrl), 1000)
    // 先判断是否是取消请求
    // if (axios.isCancel(err as any)) {
    //   console.warn('请求被取消:', (err as any)?.message)
    //   return Promise.reject(err)
    // }
    // 判断是否为 axios 的错误类型(有 response)
    if (axios.isAxiosError(err)) {
      const axErr = err // 现在 TypeScript 知道这是 AxiosError
      if (axErr.response) {
        // 使用你现有的错误处理函数
        errorHandle(axErr.response.status, axErr.response.data)
        if (axErr.response.status === 401) {
            ElMessage.error('请登录');
        }
        return Promise.reject(axErr.response.data || axErr.response)
      }
      ElMessage.error('网络请求失败, 请刷新重试');
      return Promise.reject(axErr)
    }
    //(未知错误)
    ElMessage.error('网络请求失败, 请刷新重试');
    return Promise.reject(err)
  },
)
export default instance
src/utils/httpApi.ts
@@ -93,4 +93,22 @@
    } catch (error) {
        throw error;
    }
}
/**
 * 推出登录
 * @param deviceCode
 * @returns
 */
export const logoutForSubScreen = async(deviceCode: string) => {
    try {
        const response = await axios.post(`${apiBaseUrl}/user/info/logoutForSubScreen`, { deviceCode }, {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        });
        return response.data
    } catch (error) {
        throw error;
    }
}
src/views/mobile/bedsideAuxiliaryScreen/components/EndDialysis/index.vue
New file
@@ -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>
src/views/mobile/bedsideAuxiliaryScreen/components/Header.vue
@@ -65,7 +65,8 @@
        alt=""
        @click="openSettingDeviceDialog"
      />
      <img :src="userImg" class="btn-img" alt="" @click="openLoginDialog" />
      <img v-if="userInfo?.code" :src="userInfo?.userAvatar" class="btn-img" alt="" @click="openLoginDialog" />
      <img v-else :src="userImg" class="btn-img" alt="" @click="openLoginDialog" />
    </div>
  </div>
  <!-- 设置设备编号组件 -->
@@ -164,6 +165,14 @@
  }
});
const userInfo = computed(() => {
  if (!bedsideAuxiliaryScreenStore.userInfo) return null
  return Object.assign({}, bedsideAuxiliaryScreenStore.userInfo, {
    userAvatar: bedsideAuxiliaryScreenStore.userInfo?.userAvatar ? bedsideAuxiliaryScreenStore.userInfo?.userAvatar : generateCircularAvatar(bedsideAuxiliaryScreenStore.userInfo?.userName)
  });
});
watch(
  () => bedsideAuxiliaryScreenStore.taskData?.[0]?.countdown,
  (val) => {
@@ -246,6 +255,29 @@
    loginDialogRef.value.openDialog();
};
function generateCircularAvatar(name: string, size = 100): string {
  console.log('name: ', name)
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d')!;
  // 绘制圆形背景
  ctx.fillStyle = '#dae5ec';
  ctx.beginPath();
  ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
  ctx.fill();
  // 绘制文字(第一个字)
  ctx.fillStyle = '#70a3dd'; // 字体颜色
  ctx.font = `${size * 0.5}px sans-serif`; // 字体大小为头像大小的一半
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(name.charAt(0), size / 2, size / 2);
  return canvas.toDataURL('image/png'); // 返回 Base64 图片
}
onUnmounted(() => {
  clearTimer();
});
src/views/mobile/bedsideAuxiliaryScreen/components/Login/index.vue
@@ -59,13 +59,24 @@
    });
    const isLoginng = computed(() => {
      return !!bedsideAuxiliaryScreenStore.token;
      return !!bedsideAuxiliaryScreenStore.userInfo?.code;
    });
    const qrCodeData = computed(() => {
      return {
        deviceCode: bedsideAuxiliaryScreenStore.deviceCode,
        deviceNo: bedsideAuxiliaryScreenStore.deviceData.devicdeNo,
      };
    });
    const userInfo = computed(() => {
      const userInfo = bedsideAuxiliaryScreenStore.userInfo;
      if (!userInfo) return null;
      return {
        ...userInfo,
        床号: bedsideAuxiliaryScreenStore.deviceData.devicdeNo,
        设备编号: bedsideAuxiliaryScreenStore.deviceCode
      }
    });
    const openDialog = () => {
@@ -77,17 +88,25 @@
    };
    const onLogout = () => {
      if (state.loading) return ElMessage.warning('正在退出中...')
      ElMessageBox.confirm("是否确认退出当前登录用户?", "提示", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
            bedsideAuxiliaryScreenStore.logout();
          ElMessage({
            type: "success",
            message: "退出成功!",
          });
        .then(async() => {
            state.loading = true
            try {
              await bedsideAuxiliaryScreenStore.logout(`deviceCode=${bedsideAuxiliaryScreenStore.deviceCode}`);
              handleCancel();
              ElMessage.success('退出登录成功')
              handleCancel();
            } catch (error) {
              console.error('error: ', error)
              // ElMessage.error('退出登录失败')
            } finally {
              state.loading = false;
            }
        })
        .catch(() => {});
    };
@@ -100,6 +119,7 @@
      handleCancel,
      openDialog,
      onLogout,
      userInfo,
    };
  },
};
src/views/mobile/bedsideAuxiliaryScreen/components/Login/userInfo.vue
@@ -1,7 +1,7 @@
<template>
  <div class="userinfo-container">
    <img :src="userInfo.用户头像" alt="" srcset="" class="user-avatar">
    <div class="user-text">当前账户:{{ userInfo.用户昵称 }}</div>
    <img :src="userInfo.userAvatar" alt="" srcset="" class="user-avatar" />
    <div class="user-text">当前账户:{{ userInfo.userName }}</div>
    <div class="user-text">床号:{{ userInfo.床号 }}</div>
    <div class="user-text">设备号:{{ userInfo.设备编号 }}</div>
  </div>
@@ -10,7 +10,6 @@
<script lang="ts">
import { computed } from "vue";
import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
import { defaultUserInfo } from '@/store/type/user.type';
export default {
  name: "UserInfo",
@@ -18,16 +17,41 @@
    const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
    const userInfo = computed(() => {
      const userInfo = bedsideAuxiliaryScreenStore.userInfo || defaultUserInfo();
      const userInfo = bedsideAuxiliaryScreenStore.userInfo;
      if (!userInfo) return null;
      return {
        ...userInfo,
        床号: bedsideAuxiliaryScreenStore.deviceData.devicdeNo,
        设备编号: bedsideAuxiliaryScreenStore.deviceCode
      }
        设备编号: bedsideAuxiliaryScreenStore.deviceCode,
        userAvatar: userInfo?.userAvatar ? userInfo?.userAvatar : generateCircularAvatar(userInfo?.userName)
      };
    });
    function generateCircularAvatar(name: string, size = 100): string {
      console.log("name: ", name);
      const canvas = document.createElement("canvas");
      canvas.width = size;
      canvas.height = size;
      const ctx = canvas.getContext("2d")!;
      // 绘制圆形背景
      ctx.fillStyle = "#769aff";
      ctx.beginPath();
      ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
      ctx.fill();
      // 绘制文字(第一个字)
      ctx.fillStyle = "#FFFFFF"; // 字体颜色
      ctx.font = `${size * 0.5}px sans-serif`; // 字体大小为头像大小的一半
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillText(name.charAt(0), size / 2, size / 2);
      return canvas.toDataURL("image/png"); // 返回 Base64 图片
    }
    return {
        userInfo
      userInfo,
    };
  },
};
@@ -35,21 +59,21 @@
<style lang="less" scoped>
.userinfo-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    .user-avatar {
        width: 15px;
        height: 15px;
        border-radius: 50%;
        overflow: hidden;
        margin-bottom: 5px;
    }
    .user-text {
        font-size: 5px;
        color: #000;
        margin-bottom: 4px;
    }
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  .user-avatar {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    overflow: hidden;
    margin-bottom: 5px;
  }
  .user-text {
    font-size: 5px;
    color: #000;
    margin-bottom: 4px;
  }
}
</style>
src/views/mobile/bedsideAuxiliaryScreen/components/StartDialysis/index.vue
New file
@@ -0,0 +1,865 @@
<template>
  <div class="start-dialysis-container">
    <el-dialog
      v-model="show"
      center
      title="开始透析"
      width="80%"
      :show-close="false"
      class="start-dialysis-dialog"
      :destroy-on-close="true"
      :close-on-click-modal="false"
    >
      <template #header>
        <div class="start-dialysis-header">
          <span class="header-title">开始透析</span>
          <img
            :src="closeImg"
            class="header-close"
            @click="handleCancel"
            alt=""
          />
        </div>
      </template>
      <div class="start-dialysis-content" v-loading="loading">
        <el-form :model="formData" size="large">
          <el-row :gutter="10">
            <el-col :span="6">
              <el-form-item label="开始时间">
                <el-time-select
                  v-model="formData.startTime"
                  start="00:00"
                  step="00:01"
                  end="23:59"
                  placeholder="请选择"
                  :clearable="false"
                  style="width: 100%"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="治疗护士">
                <el-select
                  @change="onStartMedNurseChange"
                  style="width: 100%"
                  v-model="formData.startMedNurse"
                  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="6">
              <el-form-item label="上机护士">
                <el-select
                  style="width: 100%"
                  v-model="formData.startUpNurse"
                  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="6">
              <el-form-item label="预充管路">
                <el-select
                  style="width: 100%"
                  v-model="formData.startYcNurse"
                  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="6">
              <el-form-item label="穿刺/换药">
                <el-select
                  style="width: 100%"
                  v-model="formData.startIsCcOrHy"
                  placeholder="请选择"
                >
                  <el-option
                    v-for="(item, index) in startIsCcOrHyOptions"
                    :key="index"
                    :label="item.label"
                    :value="item.value"
                  ></el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <template v-if="formData.startIsCcOrHy === 1">
              <el-col :span="6">
                <el-form-item label="换药护士">
                  <el-select
                    style="width: 100%"
                    v-model="formData.startCcNurse"
                    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>
            </template>
            <template v-else-if="formData.startIsCcOrHy === 0">
              <el-col :span="6">
                <el-form-item label="穿刺护士">
                  <el-select
                    style="width: 100%"
                    v-model="formData.startCcNurse"
                    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>
            </template>
            <template v-else-if="formData.startIsCcOrHy === 2">
              <el-col :span="6">
                <el-form-item label="换药护士">
                  <el-select
                    style="width: 100%"
                    v-model="formData.startCcNurse"
                    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>
            </template>
            <template
              v-if="
                formData.startIsCcOrHy === 2 || formData.startIsCcOrHy === 0
              "
            >
              <el-col :span="6">
                <el-form-item label="穿刺方式" prop="doneCcType">
                  <el-select
                    style="width: 100%"
                    multiple
                    v-model="formData.startCcType"
                    placeholder="请选择"
                  >
                    <el-option
                      v-for="item in punctureMethodOptions"
                      :key="item.code"
                      :label="item.dictText"
                      :value="item.dictText"
                    >
                    </el-option>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="6">
                <el-form-item label="穿刺针A">
                  <el-select
                    clearable
                    style="width: 100%"
                    v-model="formData.startCcZhenA"
                    placeholder="请选择"
                  >
                    <el-option
                      v-for="item in punctureNeedleOptions"
                      :key="item.code"
                      :label="item.itemName + ' ' + item.itemSpec"
                      :value="item.code"
                      :disabled="item.isShow !== 1"
                    >
                    </el-option>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="6">
                <el-form-item clearable label="穿刺针V">
                  <el-select
                    style="width: 100%"
                    v-model="formData.startCcZhenV"
                    placeholder="请选择"
                  >
                    <el-option
                      v-for="item in punctureNeedleOptions"
                      :key="item.code"
                      :label="item.itemName + ' ' + item.itemSpec"
                      :value="item.code"
                      :disabled="item.isShow !== 1"
                    >
                    </el-option>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="6">
                <el-form-item label="穿刺方向">
                  <el-select
                    style="width: 100%"
                    multiple
                    v-model="formData.startCcASideDirection"
                    placeholder="请选择"
                  >
                    <el-option
                      v-for="item in punctureDirectionOptions"
                      :key="item.code"
                      :label="item.dictText"
                      :value="item.dictText"
                    >
                    </el-option>
                  </el-select>
                </el-form-item>
              </el-col>
            </template>
            <el-col :span="6">
              <el-form-item label="引血(ml/min)">
                <el-input style="width: 100%" v-model="formData.startYingxue" />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="透析器编号">
                <el-input style="width: 100%" v-model="formData.startTxqNo" />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="入科方式">
                <el-select
                  style="width: 100%"
                  v-model="formData.startEntranceType"
                  placeholder="请选择"
                >
                  <el-option
                    v-for="(item, index) in startEntranceTypeOptions"
                    :key="index"
                    :label="item.label"
                    :value="item.value"
                  ></el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="一次性使用管路">
                <el-select v-model="formData.doneGl" placeholder="请选择">
                  <el-option
                    v-for="(item, index) in disposablePipelineOptions"
                    :key="index"
                    :label="item.itemName + ' ' + item.itemSpec"
                    :value="item.code"
                    :disabled="item.isShow !== 1"
                  ></el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="血滤管">
                <el-select
                  v-model="formData.doneByg"
                  :disabled="formData.schemeName !== 'HDF'"
                  placeholder="请选择"
                >
                  <el-option
                    v-for="(item, index) in hemofiltrationTubeOptions"
                    :key="index"
                    :label="item.itemName + ' ' + item.itemSpec"
                    :value="item.code"
                  ></el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="透析浓缩液">
                <el-select
                  v-model="formData.startUseNsy"
                  multiple
                  filterable
                  clearable
                  placeholder="请选择"
                >
                  <el-option
                    v-for="(item, index) in doneUseNsyOptions"
                    :key="index"
                    :label="item.itemName + ' ' + item.itemSpec"
                    :value="item.code"
                  ></el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="透析护理包">
                <el-select
                  v-model="formData.startUseHlb"
                  filterable
                  clearable
                  placeholder="请选择"
                >
                  <el-option
                    v-for="(item, index) in doneUseHlbOptions"
                    :key="index"
                    :label="item.itemName + ' ' + item.itemSpec"
                    :value="item.code"
                  ></el-option>
                </el-select>
              </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 {
  addDefaultRowApi,
  updateMedstartDataApi,
} from "@/api/patient_hemo_med_start/index";
import type { StartRecord } from "@/api/patient_hemo_med_start/types/index.type";
import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
import { getInventoryItemListApi } from "@/api/inventory/itemDict";
import { getUsersByRoleCodeApi } from "@/api/user/index";
import { getDictTypeApi } from "@/api/dict/index";
import { DictType } from "@/api/dict/types/index.type";
import dayjs from "dayjs";
import { ElMessage } from "element-plus/es";
interface FormData
  extends Omit<
    StartRecord,
    "startCcType" | "startCcASideDirection" | "startUseNsy"
  > {
  /** 穿刺方式 */
  startCcType: string[];
  /** 穿刺方向 */
  startCcASideDirection: string[];
  startUseNsy: string[];
}
interface State {
  loading: boolean;
  show: boolean;
  formData: FormData;
  doneUseNsyOptions: any[];
  doneUseHlbOptions: any[];
  disposablePipelineOptions: any[];
  hemofiltrationTubeOptions: any[];
  nurseOptions: any[];
  punctureMethodOptions: any[];
  typePunctureNeedleOptions: any[];
  punctureDirectionOptions: any[];
  punctureNeedleOptions: any[];
  startIsCcOrHyOptions: any[];
  startEntranceTypeOptions: any[];
  startDate: string;
}
export default {
  name: "StartDialysis",
  setup() {
    const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
    const state = reactive<State>({
      loading: false,
      show: false,
      formData: {
        code: "",
        createTime: "",
        createUser: null,
        deletedTime: null,
        doneByg: "",
        doneBygName: "",
        doneBygSpecName: "",
        doneGl: "",
        doneGlName: "",
        doneGlSpecName: "",
        id: null,
        isDeleted: null,
        monitorDataCode: "",
        recordCode: "",
        remark: "",
        schemeName: "",
        /** 穿刺方向 */
        startCcASideDirection: [],
        startCcNurse: "",
        startCcNurseName: "",
        /** 穿刺方式 */
        startCcType: [],
        startCcZhenA: "",
        startCcZhenAName: "",
        startCcZhenASpecName: "",
        startCcZhenType: "",
        startCcZhenV: "",
        startCcZhenVName: "",
        startCcZhenVSpecName: "",
        startEntranceType: "",
        startIsCcOrHy: null,
        startIsSave: null,
        startMedNurse: "",
        startMedNurseName: "",
        startTime: null,
        startTxqNo: "",
        startUpNurse: "",
        startUpNurseName: "",
        startUseHlb: "",
        startUseHlbName: "",
        startUseHlbSpecName: "",
        startUseNsy: [],
        startUseNsyName: "",
        startUseNsySpecName: "",
        startYcNurse: "",
        startYcNurseName: "",
        startYingxue: "",
        updateTime: "",
        updateUser: null,
      },
      doneUseNsyOptions: [], // 透析浓缩液列表
      doneUseHlbOptions: [], // 透析护理包列表
      disposablePipelineOptions: [], // 一次性使用管路列表
      hemofiltrationTubeOptions: [], // 血滤管列表
      nurseOptions: [], // 护士列表
      punctureMethodOptions: [], // 穿刺方式列表
      typePunctureNeedleOptions: [], // 穿刺针类型列表
      punctureDirectionOptions: [], // 穿刺方向列表
      punctureNeedleOptions: [], // 穿刺针列表
      startIsCcOrHyOptions: [
        { label: "穿刺", value: 0 },
        { label: "换药", value: 1 },
        { label: "穿刺+换药", value: 2 },
      ],
      startEntranceTypeOptions: [
        { label: "步行", value: "步行" },
        { label: "扶行", value: "扶行" },
        { label: "轮椅", value: "轮椅" },
        { label: "平床", value: "平床" },
        { label: "床旁", value: "床旁" },
        { label: "助行器", value: "助行器" },
      ],
      startDate: "", // 日期部分
    });
    const recordCode = computed(() => {
      return bedsideAuxiliaryScreenStore.deviceData.signedIn.透析单编号;
    });
    const clientCode = computed(() => {
      return bedsideAuxiliaryScreenStore.deviceData.客户编号;
    });
    const userCode = computed(() => {
      return bedsideAuxiliaryScreenStore.userInfo.userCode;
    });
    const openDialog = async () => {
      state.show = true;
      state.loading = true;
      try {
        const promiseFuns = [
          getDoneUseNsys(),
          getdoneUseHlbs(),
          getDisposablePipelines(),
          getHemofiltrationTubes(),
          getPunctureNeedles(),
          getNurses(),
          getPunctureMethods(),
          getTypePunctureNeedles(),
          getPunctureDirectionOptions(),
        ];
        // if (state.doneUseNsyOptions.length <= 0) {
        //   promiseFuns.push(getDoneUseNsys());
        // }
        // if (state.doneUseHlbOptions.length <= 0) {
        //   promiseFuns.push(getdoneUseHlbs());
        // }
        // if (state.disposablePipelineOptions.length <= 0) {
        //   promiseFuns.push(getDisposablePipelines());
        // }
        // if (state.hemofiltrationTubeOptions.length <= 0) {
        //   promiseFuns.push(getHemofiltrationTubes());
        // }
        // if (state.punctureNeedleOptions.length <= 0) {
        //   promiseFuns.push(getPunctureNeedles());
        // }
        // if (state.nurseOptions.length <= 0) {
        //   promiseFuns.push(getNurses());
        // }
        // if (state.punctureMethodOptions.length <= 0) {
        //   promiseFuns.push(getPunctureMethods());
        // }
        // if (state.typePunctureNeedleOptions.length <= 0) {
        //   promiseFuns.push(getTypePunctureNeedles());
        // }
        // if (state.punctureDirectionOptions.length <= 0) {
        //   promiseFuns.push(getPunctureDirectionOptions());
        // }
        await Promise.all(promiseFuns);
        await getStartData();
      } catch (error) {
        console.error("开始透析初始化失败", error);
        state.show = false;
      } finally {
        state.loading = false;
      }
    };
    /**
     * 获取开始透析默认数据
     */
    const getStartData = async () => {
      const { data } = await addDefaultRowApi(`recordCode=${recordCode.value}`);
      const dataCopy = JSON.parse(JSON.stringify(data));
      const nots = ["", null];
      if (!nots.includes(data.startCcType)) {
        // @ts-ignore
        dataCopy.startCcType = data.startCcType.split(",");
      } else {
        // @ts-ignore
        dataCopy.startCcType = [];
      }
      if (!nots.includes(data.startCcASideDirection)) {
        // @ts-ignore
        dataCopy.startCcASideDirection = data.startCcASideDirection.split(",");
      } else {
        // @ts-ignore
        dataCopy.startCcASideDirection = [];
      }
      if (nots.includes(data.startTime)) {
        dataCopy.startTime = dayjs().format("HH:mm");
        state.startDate = dayjs().format("YYYY-MM-DD");
      } else {
        dataCopy.startTime = dayjs(data.startTime).format("HH:mm");
        state.startDate = dayjs(data.startTime).format("YYYY-MM-DD");
      }
      if (nots.includes(data.startMedNurse)) {
        dataCopy.startMedNurse = userCode.value;
      }
      if (nots.includes(data.startCcNurse)) {
        dataCopy.startCcNurse = userCode.value;
      }
      if (nots.includes(data.startUpNurse)) {
        dataCopy.startUpNurse = userCode.value;
      }
      if (nots.includes(data.startYcNurse)) {
        dataCopy.startYcNurse = userCode.value;
      }
      if (!nots.includes(data.startUseNsy)) {
        // @ts-ignore
        dataCopy.startUseNsy = data.startUseNsy.split(",");
      } else {
        // @ts-ignore
        dataCopy.startUseNsy = [];
      }
      if (data.schemeName !== "HDF") {
        // 如果透析模式不算HDF的话血滤管置空
        dataCopy.doneByg = "";
        dataCopy.doneBygName = "";
      } else {
        const defaultXlg = state.hemofiltrationTubeOptions.find(
          (e) => e.itemName === "义鑫血液净化补液管路(HDIT-1)"
        );
        if (defaultXlg) {
          dataCopy.doneByg = defaultXlg.code;
          dataCopy.doneBygName = defaultXlg.itemName;
        }
      }
      state.formData = dataCopy as unknown as FormData;
    };
    /**
     * 获取透析浓缩液字典数据
     */
    const getDoneUseNsys = async () => {
      const params = {
        page: 0,
        size: 0,
        wherecondition: `inventory_type_code='IT2112291619002680' and is_show = 1 and client_code='${clientCode.value}'`,
        ordercondition: "item_is_use desc, item_sort_order asc",
        isPure: 0,
      };
      const { data } = await getInventoryItemListApi(params);
      state.doneUseNsyOptions = data.list;
    };
    /**
     * 获取透析护理包字典数据
     */
    const getdoneUseHlbs = async () => {
      const params = {
        page: 0,
        size: 0,
        wherecondition: `inventory_type_code='IT2112291619003161' and is_show = 1 and client_code='${clientCode.value}'`,
        ordercondition: "item_is_use desc, item_sort_order asc",
        isPure: 0,
      };
      const { data } = await getInventoryItemListApi(params);
      state.doneUseHlbOptions = data.list;
    };
    /**
     * 获取一次性使用管路字典数据
     */
    const getDisposablePipelines = async () => {
      const params = {
        page: 0,
        size: 0,
        wherecondition: `inventory_type_code='IT2112291619006054' and is_show = 1 and client_code='${clientCode.value}'`,
        ordercondition: "item_is_use desc, item_sort_order asc",
        isPure: 0,
      };
      const { data } = await getInventoryItemListApi(params);
      state.disposablePipelineOptions = data.list;
    };
    /**
     * 获取血滤管字典数据
     */
    const getHemofiltrationTubes = async () => {
      const params = {
        page: 1,
        size: 10,
        wherecondition: `inventory_type_code='IT9024215920713quQV' and is_show = 1 and client_code='${clientCode.value}'`,
        ordercondition: "item_is_use desc, item_sort_order asc",
        isPure: 0,
      };
      const { data } = await getInventoryItemListApi(params);
      state.hemofiltrationTubeOptions = data.list;
    };
    /**
     * 获取穿刺针字典数据
     */
    const getPunctureNeedles = async () => {
      const params = {
        page: 1,
        size: 10,
        wherecondition: `(inventory_type_code='IT3561011130526051' OR inventory_type_code='IT2112291619000061') and client_code='${clientCode.value}'`,
        ordercondition: "item_is_use desc, item_sort_order asc",
      };
      const { data } = await getInventoryItemListApi(params);
      state.punctureNeedleOptions = data.list;
    };
    /**
     * 获取护士数据
     */
    const getNurses = async () => {
      const { data } = await getUsersByRoleCodeApi(
        `clientCode=${clientCode.value}&roleClass=nurse`
      );
      state.nurseOptions = data;
    };
    /**
     * 获取穿刺方式字典数据
     */
    const getPunctureMethods = async () => {
      const { data } = await getDictTypeApi({ dictType: DictType.穿刺方式 });
      state.punctureMethodOptions = data;
    };
    /**
     * 获取穿刺针类型字典数据
     */
    const getTypePunctureNeedles = async () => {
      const { data } = await getDictTypeApi({ dictType: DictType.穿刺针类型 });
      state.typePunctureNeedleOptions = data;
    };
    /**
     * 获取穿刺针方向字典数据
     */
    const getPunctureDirectionOptions = async () => {
      const { data } = await getDictTypeApi({ dictType: DictType.穿刺方向 });
      state.punctureDirectionOptions = data;
    };
    const handleCancel = () => {
      state.show = false;
      state.loading = false;
    };
    const handleConfirm = async () => {
      state.loading = true;
      try {
        const paramsData = Object.assign({}, state.formData, {
          startCcASideDirection:
            state.formData.startCcASideDirection.toString(),
          startCcType: state.formData.startCcType.toString(),
          startUseNsy: state.formData.startUseNsy.join(","),
          startTime: `${state.startDate} ${state.formData.startTime}:00`,
        });
        await updateMedstartDataApi(paramsData);
        ElMessage.success("操作成功!");
        state.show = false;
      } finally {
        state.loading = false;
      }
    };
    /**
     * 治疗护士的改变
     * @param val
     */
    const onStartMedNurseChange = (val: string) => {
      state.formData.startCcNurse = val;
      state.formData.startUpNurse = val;
      state.formData.startYcNurse = val;
    };
    return {
      ...toRefs(state),
      closeImg,
      handleCancel,
      openDialog,
      handleConfirm,
      onStartMedNurseChange,
    };
  },
};
</script>
<style lang="less" scoped>
.start-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;
  }
  .start-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);
      }
    }
  }
  .start-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>
src/views/mobile/bedsideAuxiliaryScreen/pages/SignedIn.vue
@@ -251,7 +251,8 @@
              <tr>
                <td>体重增长</td>
                <td class="flex-td">
                  <span class="sub-text">(透前-干体重)</span><span class="text-right">{{ weightGain2 }}kg</span>
                  <span class="sub-text">(透前-干体重)</span
                  ><span class="text-right">{{ weightGain2 }}kg</span>
                </td>
              </tr>
              <!-- <tr>
@@ -294,14 +295,15 @@
        @click="() => onScheduledTasksClick()"
        class="btn"
      />
      <BlockBotttom
      <!-- <BlockBotttom
        :icon="jiaoHaoImg"
        text="叫号"
        backgroundColor="#20C6B6"
        @click="() => onCallBumberClick()"
        class="btn"
      />
      /> -->
      <BlockBotttom
        v-if="isShowStartBtn"
        :icon="kaiShiImg"
        text="开始"
        backgroundColor="#409EFF"
@@ -311,6 +313,8 @@
    </div>
    <!-- 定时任务 -->
    <ScheduledTaskDialog ref="scheduledTaskDialogRef" />
    <!-- 开始透析组件 -->
    <StartDialysis ref="startDialysisRef" />
  </div>
</template>
<script lang="ts" setup name="SignedIn">
@@ -345,9 +349,11 @@
// @ts-ignore
import BlockBotttom from "../components/BlockBotttom.vue";
import { ElMessage } from "element-plus";
import { EMedStatus } from '@/store/type/bedsideAuxiliaryScreen.type';
const ScheduledTaskDialog = defineAsyncComponent(
  () => import("../components/ScheduledTask.vue")
);
const StartDialysis = defineAsyncComponent(() => import('../components/StartDialysis/index.vue'));
interface Props {
  height: number;
@@ -358,7 +364,7 @@
const bloodPressureRectangularChart = ref<HTMLElement | null>(null);
const scheduledTaskDialogRef = ref<any>(null);
const startDialysisRef = ref<any>(null);
const pageData = computed(() => {
  return Object.assign(bedsideAuxiliaryScreenStore.deviceData.signedIn, {
@@ -416,12 +422,19 @@
  return formattedValue;
});
/**
 * 是否显示开始透析按钮
 */
const isShowStartBtn = computed(() => {
  return +bedsideAuxiliaryScreenStore.deviceData.treatmentStatus === EMedStatus.SIGNED_IN;
})
watch(
  () => pageData.value.四点血压图数据,
  (newVal) => {
    genderBloodPressureRectangularChart(newVal);
});
  }
);
/** 点击定时任务 */
const onScheduledTasksClick = () => {
@@ -441,20 +454,27 @@
};
const onStartClick = () => {
  ElMessage({
    message: "功能开发中,敬请期待!",
    type: "warning",
  });
  if (
    !bedsideAuxiliaryScreenStore.deviceCode ||
    !bedsideAuxiliaryScreenStore.deviceData.deviceCode
  )
    return ElMessage.warning("未初始化或正在进行初始化操作中");
  if (!bedsideAuxiliaryScreenStore.userInfo?.token)
    return ElMessage.warning("请登录");
  startDialysisRef.value?.openDialog();
};
const genderBloodPressureRectangularChart = (datas: 四点血压图数据[] | null) => {
const genderBloodPressureRectangularChart = (
  datas: 四点血压图数据[] | null
) => {
  if (!bloodPressureRectangularChart.value) return;
  const benchmarkData = { width: 386, height: 280 };
  // 获取容器宽高(90% 缩放)
  const containerWidth = bloodPressureRectangularChart.value.offsetWidth * 0.9;
  const containerHeight = bloodPressureRectangularChart.value.offsetHeight * 0.9;
  const containerHeight =
    bloodPressureRectangularChart.value.offsetHeight * 0.9;
  // 获取设备像素比
  const dpr = window.devicePixelRatio || 1;
@@ -515,10 +535,22 @@
  // ========== 血压数据 ==========
  const measurements = [
    { systolic: datas?.[0]?.血压1_透前收缩压 ?? 0, diastolic: datas?.[0]?.血压1_透前舒张压 ?? 0 },
    { systolic: datas?.[0]?.血压2_前半程最低收缩压 ?? 0, diastolic: datas?.[0]?.血压2_前半程最低舒张压 ?? 0 },
    { systolic: datas?.[0]?.血压3_后半程最低收缩压 ?? 0, diastolic: datas?.[0]?.血压3_后半程最低舒张压 ?? 0 },
    { systolic: datas?.[0]?.血压4_透后收缩压 ?? 0, diastolic: datas?.[0]?.血压4_透后舒张压 ?? 0 },
    {
      systolic: datas?.[0]?.血压1_透前收缩压 ?? 0,
      diastolic: datas?.[0]?.血压1_透前舒张压 ?? 0,
    },
    {
      systolic: datas?.[0]?.血压2_前半程最低收缩压 ?? 0,
      diastolic: datas?.[0]?.血压2_前半程最低舒张压 ?? 0,
    },
    {
      systolic: datas?.[0]?.血压3_后半程最低收缩压 ?? 0,
      diastolic: datas?.[0]?.血压3_后半程最低舒张压 ?? 0,
    },
    {
      systolic: datas?.[0]?.血压4_透后收缩压 ?? 0,
      diastolic: datas?.[0]?.血压4_透后舒张压 ?? 0,
    },
  ];
  const measurementWidth = chartWidth / (measurements.length + 1);
@@ -580,10 +612,7 @@
  const cylinderHeight = 计算脱水量刻度 * (datas?.[0]?.超滤总量 || 0);
  const 体重增长_透前减干体重 = weightGain2.value;
  const 透前减干体重减超滤总量差值 =
  Math.round(
    (体重增长_透前减干体重 - (datas?.[0]?.超滤总量 || 0)) * 10
  ) / 10;
    Math.round((体重增长_透前减干体重 - (datas?.[0]?.超滤总量 || 0)) * 10) / 10;
  drawCylinder(
    ctx,
@@ -597,13 +626,11 @@
    datas?.[0]?.脱水百分比 ?? 0,
    datas?.[0]?.透后体重减干体重的差值 ?? 0,
    体重增长_透前减干体重,
    透前减干体重减超滤总量差值,
    透前减干体重减超滤总量差值
  );
  ctx.restore(); // 恢复
};
const drawCylinder = (
  ctx: CanvasRenderingContext2D,
@@ -617,7 +644,7 @@
  脱水百分比: number, // 脱水百分比
  透后体重减干体重的差值: number, // 透后体重减干体重的差值
  体重增长_透前减干体重: number, // 体重增长_透前减干体重
  透前减干体重减超滤总量差值: number, // 透前减干体重减超滤总量差值
  透前减干体重减超滤总量差值: number // 透前减干体重减超滤总量差值
) => {
  ctx.beginPath();
  ctx.arc(x, y + height, radius, 0, Math.PI * 2);
@@ -652,24 +679,26 @@
  // 如果超滤总量 与 体重增长_透前减干体重 相等,则显示 "/超滤总量"
  if (Number(超滤总量) && 体重增长_透前减干体重 === Number(超滤总量)) {
      ctx.font = `${baseFontSize * scale}px Arial`;
      ctx.textAlign = "center";
      ctx.fillStyle = "#07c160";
      ctx.fillText('/' + 超滤总量, textX, canvasHeight - height + height * 0.3 + 10);
    ctx.font = `${baseFontSize * scale}px Arial`;
    ctx.textAlign = "center";
    ctx.fillStyle = "#07c160";
    ctx.fillText(
      "/" + 超滤总量,
      textX,
      canvasHeight - height + height * 0.3 + 10
    );
  } else {
    // 透析前-干体重
    ctx.font = `${baseFontSize * scale}px Arial`;
    ctx.textAlign = "center";
    ctx.fillStyle = "#409EFF";
    ctx.fillText(体重增长_透前减干体重 + '', textX, canvasHeight - height + 10);
    ctx.fillText(体重增长_透前减干体重 + "", textX, canvasHeight - height + 10);
    // 超滤总量
    ctx.font = `${baseFontSize * scale}px Arial`;
    ctx.textAlign = "center";
    ctx.fillStyle = "#07c160";
    ctx.fillText(超滤总量, textX, canvasHeight - height + height * 0.3 + 10);
  }
  if (透前减干体重减超滤总量差值 > 0) {
    ctx.font = `${baseFontSize * scale}px Arial`;
@@ -1095,7 +1124,7 @@
        font-size: 3px;
        color: #666;
        margin-right: 1.2px;
         white-space: nowrap;
        white-space: nowrap;
      }
      // .text-right {
      //   position: absolute;
src/views/mobile/bedsideAuxiliaryScreen/pages/UnderTreatment.vue
@@ -304,20 +304,20 @@
            @click="() => onScheduledTasksClick()"
            class="btn"
          />
          <BlockBotttom
          <!-- <BlockBotttom
            :icon="jiaoHaoImg"
            text="叫号"
            backgroundColor="#20C6B6"
            @click="() => onCallBumberClick()"
            class="btn"
          />
          <BlockBotttom
          /> -->
          <!-- <BlockBotttom
            :icon="addImg"
            text="添加记录"
            backgroundColor="#409EFF"
            @click="() => onAddRecordClick()"
            class="btn"
          />
          /> -->
          <BlockBotttom
            v-if="!whetherDialysisHasBeenEnded"
            :icon="kaiShiImg"
@@ -331,6 +331,8 @@
    </div>
    <!-- 定时任务 -->
    <ScheduledTaskDialog ref="scheduledTaskDialogRef" />
    <!-- 结束透析组件 -->
    <EndDialysis ref="endDialysisRef" />
  </div>
</template>
<script lang="ts" setup name="UnderTreatment">
@@ -355,6 +357,7 @@
const ScheduledTaskDialog = defineAsyncComponent(
  () => import("../components/ScheduledTask.vue")
);
const EndDialysis = defineAsyncComponent(() => import("../components/EndDialysis/index.vue"));
import { useBedsideAuxiliaryScreenStore } from "@/store/bedsideAuxiliaryScreen";
import {
  formatSubstituteMode,
@@ -390,6 +393,7 @@
const bedsideAuxiliaryScreenStore = useBedsideAuxiliaryScreenStore();
const scheduledTaskDialogRef = ref<any>(null);
const endDialysisRef = ref<any>(null);
// ktv趋势图的
const ktvListEchartRef = ref<HTMLElement | null>(null);
@@ -419,6 +423,8 @@
    +bedsideAuxiliaryScreenStore.deviceData.treatmentStatus >= EMedStatus.END
  );
});
watch(
  () => pageData.value.ktvList,
@@ -785,10 +791,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(() => {