// pages/surgery/create/index.js const { surgeryApi, doctorApi, deviceApi } = require("../../../utils/api"); const dayjs = require("../../../miniprogram_npm/dayjs/index"); Page({ /** * 页面的初始数据 */ data: { loading: true, // 页面加载状态 submitting: false, // 表单提交状态 isEditMode: false, // 是否为编辑模式 surgeryId: null, // 编辑模式下的手术ID // 表单数据 formData: { surgery_id: "", // 手术编号 surgery_name: "", // 手术名称 patient: "", // 患者姓名 surgery_time: "", // 完整手术时间戳 surgery_time_display: "", // 显示的完整手术时间 }, // 主刀医生相关 doctors: [], // 医生列表 selectedDoctor: null, // 已选择的医生 selectedDoctorValue: "", // 级联选择器选中的医生值 doctorSelectorVisible: false, // 医生选择器弹窗可见性 departmentDoctors: [], // 按科室分组的医生列表 activeTabIndex: 0, // 当前激活的科室标签索引 // 设备相关 devices: [], // 设备列表 deviceTree: [], // 树形结构的设备数据 selectedSubDevices: [], // 已选择的设备 selectedDeviceValues: [], // 已选择的设备值数组 selectedDeviceValue: "", // 当前选中的设备值 deviceSelectorVisible: false, // 设备选择器弹窗可见性 // 新增:设备状态追踪 deviceStatus: {}, // 记录设备状态 {id: {status: 0/1, inUse: bool}} // TreeSelect 组件所需的自定义键名配置 treeSelectKeys: { label: "label", value: "value", children: "children", }, // 级联选择器 cascaderVisible: false, // 级联选择器可见性 doctorCascaderOptions: [], // 医生级联选择器选项 // 日期时间选择器相关 dateTimePickerVisible: false, // 日期时间选择器可见性 currentDate: "", // 当前日期时间用于默认值 minDate: "", // 新增:允许的最小日期时间(当前时间) // 设备时间选择器相关 deviceStartTimePickerVisible: false, // 设备开始时间选择器可见性 deviceEndTimePickerVisible: false, // 设备结束时间选择器可见性 currentDeviceTimeIndex: -1, // 当前正在设置时间的设备索引 currentDeviceStartTime: "", // 当前设备开始时间 currentDeviceEndTime: "", // 当前设备结束时间 }, /** * 生命周期函数--监听页面加载 */ onLoad(options) { // 设置当前时间作为默认值和最小时间限制 this.setCurrentDateTime(); // 手术记录回显 let formData = wx.getStorageSync("surgery_formData") let that = this if (formData) { wx.showModal({ title: "是否显示未保存记录 ", cancelText: "否", confirmText: "是", success (res) { if (res.confirm) { that.setData({ formData: {...formData}, selectedDoctor: formData.doctor }) } else if (res.cancel) { wx.removeStorageSync('surgery_formData') } } }) } // 检查是否为编辑模式 if (options && options.id && options.mode === "edit") { const surgeryId = options.id; this.setData({ isEditMode: true, surgeryId: surgeryId, }); wx.setNavigationBarTitle({ title: "编辑手术记录", }); } else { wx.setNavigationBarTitle({ title: "创建手术记录", }); } // 并行加载医生和设备数据 Promise.all([this.fetchDoctors(), this.fetchDevices()]) .then(() => { // 如果是编辑模式,加载现有手术数据 if (this.data.isEditMode && this.data.surgeryId) { this.fetchSurgeryDetails(this.data.surgeryId); } else { this.setData({ loading: false }); } }) .catch((error) => { this.setData({ loading: false }); }); }, /* * 保存表单数据到本地 */ saveData (doctor) { let formData = {...this.data.formData} if (doctor) formData.doctor = doctor wx.setStorageSync("surgery_formData", formData) }, /** * 获取手术详情 */ async fetchSurgeryDetails(surgeryId) { try { const res = await surgeryApi.getSurgeryById(surgeryId); const surgery = res.data; if (!surgery) { throw new Error("未找到手术记录"); } // 格式化日期显示 - 使用安全的日期解析 const surgeryTime = this.safeParseDate(surgery.surgery_time); const timestamp = surgeryTime.getTime(); const formattedDateTime = dayjs(surgeryTime).format("YYYY年MM月DD日 HH:mm:ss"); // 更新表单数据 this.setData({ formData: { surgery_id: surgery.surgery_id || "", surgery_name: surgery.surgery_name || "", patient: surgery.patient || "", surgery_time: timestamp, surgery_time_display: formattedDateTime, }, }); // 设置选中的医生 if (surgery.doctor) { // 检查选项中是否包含当前医生 const targetDoctorId = surgery.doctor.id; const findDoctorInOptions = (options) => { for (const dept of options) { for (const doctor of dept.children || []) { if (doctor.doctor && doctor.doctor.id === targetDoctorId) { return doctor; } } } return null; }; const foundDoctor = findDoctorInOptions(this.data.doctorCascaderOptions); this.setSelectedDoctor(surgery.doctor); } else { // 手术数据中没有医生信息 } // 设置选中的设备 if (surgery.surgerySubDevices && Array.isArray(surgery.surgerySubDevices)) { const selectedDevices = []; surgery.surgerySubDevices.forEach((surgerySubDevice) => { if (surgerySubDevice.subDevice) { // 为子设备添加设备名称和时间信息 const subDevice = { ...surgerySubDevice.subDevice, device_name: surgerySubDevice.subDevice.device ? surgerySubDevice.subDevice.device.name : "未知设备", // 添加设备时间信息 startTime: surgerySubDevice.start_time || null, endTime: surgerySubDevice.end_time || null, startTimeDisplay: surgerySubDevice.start_time ? dayjs(surgerySubDevice.start_time).format('YYYY-MM-DD HH:mm') : '点击设置', endTimeDisplay: surgerySubDevice.end_time ? dayjs(surgerySubDevice.end_time).format('YYYY-MM-DD HH:mm') : '点击设置', }; selectedDevices.push(subDevice); // 更新设备状态 if (this.data.deviceStatus[subDevice.id]) { const updatedDeviceStatus = { ...this.data.deviceStatus }; updatedDeviceStatus[subDevice.id].inUse = true; this.setData({ deviceStatus: updatedDeviceStatus, }); } } }); this.setData({ selectedSubDevices: selectedDevices, }); } else { // 手术数据中没有设备信息 } this.setData({ loading: false }); } catch (error) { this.showMessage("获取手术详情失败,请返回重试", "error"); this.setData({ loading: false }); } }, /** * 根据医生数据设置选中的医生 */ setSelectedDoctor(doctor) { if (!doctor || !doctor.id) return; // 查找并设置医生级联选择器的值 const doctorValue = `doctor_${doctor.id}`; this.setData({ selectedDoctor: doctor, selectedDoctorValue: doctorValue, }); }, /** * 安全的日期解析函数 */ safeParseDate(dateStr) { if (!dateStr) return new Date(); // 尝试不同的日期格式 const formats = [ dateStr, // 原始格式 dateStr.replace(' ', 'T'), // ISO格式 dateStr.replace(' ', 'T') + '+08:00', // 带时区的ISO格式 new Date(dateStr) // 直接解析 ]; for (let i = 0; i < formats.length; i++) { try { const date = typeof formats[i] === 'string' ? new Date(formats[i]) : formats[i]; if (!isNaN(date.getTime())) { return date; } } catch (e) { // 日期格式解析失败 } } // 如果都失败了,返回当前时间 return new Date(); }, /** * 设置当前日期时间作为默认值和最小时间限制 */ setCurrentDateTime() { const now = new Date(); const formattedDateTime = dayjs(now).format("YYYY-MM-DD HH:mm:ss"); this.setData({ currentDate: formattedDateTime, minDate: formattedDateTime, // 设置最小时间为当前时间 }); }, /** * 获取医生列表 */ async fetchDoctors() { try { // 调用API获取医生列表数据 const res = await doctorApi.getDoctors(); // 后端返回的是分页格式:{list: [...], total: ...} const doctorData = res.data; if (!doctorData || !doctorData.list || !Array.isArray(doctorData.list)) { throw new Error("获取医生数据格式错误"); } // 过滤确保医生数据完整(包含科室信息) const validDoctors = doctorData.list.filter((doctor) => doctor && doctor.id && doctor.name && doctor.department && doctor.department.name); // 按科室分组医生,为级联选择器准备数据 const departmentsMap = new Map(); validDoctors.forEach((doctor) => { const deptId = doctor.department.id; const deptName = doctor.department.name; if (!departmentsMap.has(deptId)) { departmentsMap.set(deptId, { label: deptName, value: `dept_${deptId}`, children: [], }); } // 将医生添加到对应科室的children中 departmentsMap.get(deptId).children.push({ label: doctor.name, value: `doctor_${doctor.id}`, // 保存原始医生对象用于选中后的数据处理 doctor: doctor, }); }); // 将Map转换为数组 const doctorCascaderOptions = Array.from(departmentsMap.values()); this.setData({ loading: false, doctors: validDoctors, doctorCascaderOptions, }); } catch (error) { this.setData({ loading: false }); this.showMessage("获取医生列表失败,请重试", "error"); } }, /** * 获取设备数据并构建树形结构 */ async fetchDevices() { try { // 调用API获取所有设备数据 const res = await deviceApi.getDevices(); // 后端返回的是分页格式:{list: [...], total: ...} const deviceData = res.data; if (!deviceData || !deviceData.list || !Array.isArray(deviceData.list)) { throw new Error("获取设备数据格式错误"); } const devices = deviceData.list; // 获取所有子设备 const subDevicesRes = await deviceApi.getAllSubDevices(); const subDevicesData = subDevicesRes.data; // 后端返回的是分页格式:{list: [...], total: ...} const subDevices = subDevicesData.list || []; // 构建设备状态字典 const deviceStatus = {}; subDevices.forEach((subDevice) => { deviceStatus[subDevice.id] = { status: subDevice.status || 0, // 0表示可用,1表示占用 inUse: false, // 当前表单中是否被选择 }; }); // 构建树形结构数据 const deviceTree = devices.map((device) => { // 找出该设备下的所有子设备 const children = subDevices .filter((sub) => sub.device_id === device.id) .map((sub) => { const subDeviceName = sub.name || `设备${sub.id}`; const statusText = sub.status === 1 ? " (占用中)" : ""; return { label: `${subDeviceName}${statusText}`, // 显示占用状态 value: `subdevice_${sub.id}`, // 移除基于状态的禁用设置,允许选择占用中的设备 // 保存完整的子设备数据 subDevice: { ...sub, name: sub.name || `设备${sub.id}`, // 确保有名称 device_name: device.name, // 添加设备名称,方便显示 }, }; }); return { label: device.name, value: `device_${device.id}`, // 只有当没有子设备时才禁用主设备选择,不再因子设备被占用而禁用 disabled: children.length === 0, children, }; }); this.setData({ devices, deviceTree, deviceStatus, }); } catch (error) { this.showMessage("获取设备数据失败,请重试", "warning"); } }, /** * 处理输入框内容变更 */ onInputChange(e) { const { field } = e.currentTarget.dataset; const { value } = e.detail; this.setData({ [`formData.${field}`]: value, }); this.saveData() }, /** * 显示日期时间选择器 */ showDateTimePicker() { this.setData({ dateTimePickerVisible: true, }); }, /** * 日期时间选择器确认事件 */ onDateTimeConfirm(e) { // 使用 dayjs 格式化日期时间显示 const formattedDateTime = dayjs(e.detail.value).format("YYYY年MM月DD日 HH:mm:ss"); const timestamp = new Date(e.detail.value).getTime(); this.setData({ "formData.surgery_time": timestamp, "formData.surgery_time_display": formattedDateTime, dateTimePickerVisible: false, }); this.saveData() }, /** * 日期时间选择器取消事件 */ onDateTimeCancel() { this.setData({ dateTimePickerVisible: false, }); }, /** * 日期时间选择器变化事件 */ onDateTimeChange(e) { // 日期时间选择器变化事件 }, /** * 日期时间选择器选择事件 */ onDateTimePick(e) { // 日期时间选择器选择事件 }, /** * 显示医生选择器弹窗 - 使用级联选择器选项卡风格 */ showDoctorSelector() { if (this.data.doctorCascaderOptions.length === 0) { this.showMessage("暂无可选医生", "warning"); return; } this.setData({ doctorSelectorVisible: true, }); }, /** * 级联选择器关闭事件 */ onDoctorCascaderClose(e) { this.setData({ doctorSelectorVisible: false, }); }, /** * 级联选择器变更事件 */ onDoctorCascaderChange(e) { const { value, selectedOptions } = e.detail; if (value && value.startsWith("doctor_")) { // 获取医生 ID const doctorId = Number(value.split("_")[1]); // 查找选中的医生数据 const findDoctor = (options, doctorId) => { for (const dept of options) { for (const doctorOption of dept.children || []) { if (doctorOption.doctor && doctorOption.doctor.id === doctorId) { return doctorOption.doctor; } } } return null; }; const selectedDoctor = findDoctor(this.data.doctorCascaderOptions, doctorId); if (selectedDoctor) { this.setData({ selectedDoctor, selectedDoctorValue: value, doctorSelectorVisible: false, // 选择后自动关闭选择器 }); this.showMessage(`已选择医生:${selectedDoctor.name}(${selectedDoctor.department.name})`, "success"); } this.saveData(selectedDoctor) } }, /** * 级联选择器选择事件 */ onDoctorCascaderPick(e) { // 级联选择器选择事件 }, /** * 处理设备选择变更事件 */ onDeviceChange(e) { const { value } = e.detail; // 找出选中的子设备 const selectedSubDevices = []; const findSubDevices = (tree, values) => { if (!tree || !values) return; for (const node of tree) { if (node.children) { for (const child of node.children) { if (values.includes(child.value) && child.subDevice) { selectedSubDevices.push(child.subDevice); } } // 递归检查子节点 findSubDevices(node.children, values); } } }; findSubDevices(this.data.deviceTree, value); this.setData({ selectedDeviceValues: value, selectedSubDevices, }); }, /** * 显示设备选择器 */ showDeviceSelector() { if (this.data.deviceTree.length === 0) { this.showMessage("暂无可选设备", "warning"); return; } this.setData({ deviceSelectorVisible: true, selectedDeviceValue: "", // 重置选择值,确保每次打开时都从顶层开始选择 }); }, /** * 设备选择器关闭事件 */ onDeviceCascaderClose(e) { this.setData({ deviceSelectorVisible: false, }); }, /** * 设备级联选择器变更事件 */ onDeviceCascaderChange(e) { const { value, selectedOptions } = e.detail; if (value && value.startsWith("subdevice_")) { // 获取子设备 ID const subDeviceId = Number(value.split("_")[1]); // 查找选中的子设备数据 const findSubDevice = (options, subDeviceId) => { for (const device of options) { for (const subDeviceOption of device.children || []) { if (subDeviceOption.subDevice && subDeviceOption.subDevice.id === subDeviceId) { return subDeviceOption.subDevice; } } } return null; }; const selectedSubDevice = findSubDevice(this.data.deviceTree, subDeviceId); if (selectedSubDevice) { // 删除设备占用的判断,允许选择任何设备 // 原有代码: // if (selectedSubDevice.status === 1) { // this.showMessage(`设备 ${selectedSubDevice.sub_device_name} 当前已占用,请选择其他设备`, "warning"); // return; // } // 检查是否已经选择过该设备 const existingDeviceIndex = this.data.selectedSubDevices.findIndex((device) => device.id === selectedSubDevice.id); if (existingDeviceIndex === -1) { // 如果是新设备,添加到已选择的设备列表中 const updatedDevices = [...this.data.selectedSubDevices, { ...selectedSubDevice, startTime: this.data.formData.surgery_time || new Date().getTime(), startTimeDisplay: this.data.formData.surgery_time_display || dayjs().format("YYYY-MM-DD HH:mm:ss"), endTime: null, endTimeDisplay: "未设置", }]; // 更新设备状态为已选择 const updatedDeviceStatus = { ...this.data.deviceStatus }; updatedDeviceStatus[selectedSubDevice.id].inUse = true; this.setData({ selectedSubDevices: updatedDevices, selectedDeviceValue: value, deviceSelectorVisible: false, deviceStatus: updatedDeviceStatus, }); // 添加后延迟提示,确保界面已更新 setTimeout(() => { this.showMessage(`已添加设备: ${selectedSubDevice.device_name}-${selectedSubDevice.name}`, "success"); }, 100); } else { this.showMessage("该设备已被选择", "warning"); } } } }, /** * 设备级联选择器选择事件 */ onDeviceCascaderPick(e) { // 设备级联选择器选择事件 }, /** * 显示设备开始时间选择器 */ showDeviceStartTimePicker(e) { const { index } = e.currentTarget.dataset; const device = this.data.selectedSubDevices[index]; this.setData({ currentDeviceTimeIndex: index, currentDeviceStartTime: device.startTime || this.data.formData.surgery_time || this.data.currentDate, deviceStartTimePickerVisible: true, }); }, /** * 显示设备结束时间选择器 */ showDeviceEndTimePicker(e) { const { index } = e.currentTarget.dataset; const device = this.data.selectedSubDevices[index]; this.setData({ currentDeviceTimeIndex: index, currentDeviceEndTime: device.endTime || this.data.currentDate, deviceEndTimePickerVisible: true, }); }, /** * 设备开始时间选择器确认事件 */ onDeviceStartTimeConfirm(e) { const { currentDeviceTimeIndex } = this.data; if (currentDeviceTimeIndex === -1) return; // 格式化时间显示 const formattedDateTime = dayjs(e.detail.value).format("YYYY-MM-DD HH:mm:ss"); const timestamp = new Date(e.detail.value).getTime(); // 更新设备时间信息 const updatedDevices = [...this.data.selectedSubDevices]; updatedDevices[currentDeviceTimeIndex] = { ...updatedDevices[currentDeviceTimeIndex], startTime: timestamp, startTimeDisplay: formattedDateTime, }; this.setData({ selectedSubDevices: updatedDevices, deviceStartTimePickerVisible: false, currentDeviceTimeIndex: -1, }); this.showMessage(`设备开始时间已设置: ${formattedDateTime}`, "success"); }, /** * 设备开始时间选择器取消事件 */ onDeviceStartTimeCancel() { this.setData({ deviceStartTimePickerVisible: false, currentDeviceTimeIndex: -1, }); }, /** * 设备结束时间选择器确认事件 */ onDeviceEndTimeConfirm(e) { const { currentDeviceTimeIndex } = this.data; if (currentDeviceTimeIndex === -1) return; // 格式化时间显示 const formattedDateTime = dayjs(e.detail.value).format("YYYY-MM-DD HH:mm:ss"); const timestamp = new Date(e.detail.value).getTime(); // 更新设备时间信息 const updatedDevices = [...this.data.selectedSubDevices]; updatedDevices[currentDeviceTimeIndex] = { ...updatedDevices[currentDeviceTimeIndex], endTime: timestamp, endTimeDisplay: formattedDateTime, }; this.setData({ selectedSubDevices: updatedDevices, deviceEndTimePickerVisible: false, currentDeviceTimeIndex: -1, }); this.showMessage(`设备结束时间已设置: ${formattedDateTime}`, "success"); }, /** * 设备结束时间选择器取消事件 */ onDeviceEndTimeCancel() { this.setData({ deviceEndTimePickerVisible: false, currentDeviceTimeIndex: -1, }); }, /** * 删除已选择的设备 */ removeDevice(e) { const { index } = e.currentTarget.dataset; const removedDevice = this.data.selectedSubDevices[index]; const newSelectedDevices = [...this.data.selectedSubDevices]; newSelectedDevices.splice(index, 1); // 更新设备状态为未选择 const updatedDeviceStatus = { ...this.data.deviceStatus }; updatedDeviceStatus[removedDevice.id].inUse = false; this.setData({ selectedSubDevices: newSelectedDevices, deviceStatus: updatedDeviceStatus, }); this.showMessage(`已删除设备: ${removedDevice.device_name}-${removedDevice.name}`, "warning"); }, /** * 验证表单数据 */ validateForm() { const { surgery_id, surgery_name, patient, surgery_time } = this.data.formData; const { selectedDoctor } = this.data; console.log(1, surgery_id); // 验证手术编号 if (!surgery_id) { this.showMessage("请填写手术编号", "error"); return false; } if (typeof surgery_id !== "string" || surgery_id.trim().length < 3 || surgery_id.trim().length > 50) { this.showMessage("手术编号格式不正确,长度应在3-50个字符之间", "error"); return false; } // 验证手术名称 if (!surgery_name) { this.showMessage("请填写手术名称", "error"); return false; } // 验证患者姓名 if (!patient) { this.showMessage("请填写患者姓名", "error"); return false; } // 验证手术时间 if (!surgery_time) { this.showMessage("请选择手术时间", "error"); return false; } // 验证医生选择 if (!selectedDoctor) { this.showMessage("请选择主刀医生", "error"); return false; } return true; }, /** * 处理表单提交 */ async handleSubmit() { if (!this.validateForm()) { return; } this.setData({ submitting: true }); try { const { surgery_id, surgery_name, patient, surgery_time } = this.data.formData; const { selectedDoctor, selectedSubDevices } = this.data; // 将时间转换为数据库需要的格式(YYYY-MM-DD HH:mm:ss) const formattedDateTime = dayjs(surgery_time).format('YYYY-MM-DD HH:mm:ss'); // 构建提交数据,确保与网页端数据结构一致 const submitData = { surgery_id, // 手术编号 surgery_name, // 手术名称 surgery_time: formattedDateTime, // 手术时间(本地时间格式) surgery_doctor_id: selectedDoctor.id, // 主刀医生ID patient, // 患者姓名 sub_device_ids: selectedSubDevices.map((device) => device.id), // 使用设备ID列表 // 添加设备时间信息 device_times: selectedSubDevices.map((device) => ({ sub_device_id: device.id, start_time: device.startTime ? dayjs(device.startTime).format('YYYY-MM-DD HH:mm:ss') : null, end_time: device.endTime ? dayjs(device.endTime).format('YYYY-MM-DD HH:mm:ss') : null, })), }; let res; if (this.data.isEditMode) { // 调用更新手术API res = await surgeryApi.updateSurgery(this.data.surgeryId, submitData); this.showMessage("更新手术记录成功", "success"); } else { // 调用创建手术API res = await surgeryApi.createSurgery(submitData); this.showMessage("创建手术记录成功", "success"); } // 延迟返回上一页,让用户看到成功提示 setTimeout(() => { // 通过全局变量通知列表页面数据已变更 const pages = getCurrentPages(); if (pages.length > 1) { const prevPage = pages[pages.length - 2]; // 如果上一个页面是手术列表页,标记数据已变更 if (prevPage.route === 'pages/surgery/index' && prevPage.markDataChanged) { prevPage.markDataChanged(); } } wx.removeStorageSync("surgery_formData") wx.navigateBack(); }, 1500); } catch (error) { // 简化错误处理,使错误信息更简洁 let errorMessage = this.data.isEditMode ? "更新失败,请稍后重试" : "创建失败,请稍后重试"; // 检查error.message直接是否包含错误信息 if (error.message && typeof error.message === "string") { if (error.message.includes("手术编号已存在") || error.message.includes("该手术编号已存在")) { errorMessage = `手术编号已重复,请更换`; } else if (error.message.includes("医生") && error.message.includes("时间")) { errorMessage = `医生时间冲突,请调整`; } else { errorMessage = error.message; } } // 检查error.data.message(原有逻辑) else if (error.data && error.data.message) { if (error.data.message.includes("手术编号已存在") || error.data.message.includes("该手术编号已存在")) { errorMessage = `手术编号已重复,请更换`; } else if (error.data.message.includes("医生") && error.data.message.includes("时间")) { errorMessage = `医生时间冲突,请调整`; } else { errorMessage = error.data.message; } } // 检查error.data.msg(后端返回的标准格式) else if (error.data && error.data.msg) { if (error.data.msg.includes("手术编号已存在") || error.data.msg.includes("该手术编号已存在")) { errorMessage = `手术编号已重复,请更换`; } else if (error.data.msg.includes("医生") && error.data.msg.includes("时间")) { errorMessage = `医生时间冲突,请调整`; } else { errorMessage = error.data.msg; } } this.showMessage(errorMessage, "error"); } finally { this.setData({ submitting: false }); } }, /** * 显示消息提示 */ showMessage(message, type = "info") { // 确保消息显示 // 先尝试使用 t-message 组件 const t = this.selectComponent("#t-message"); if (t && typeof t.show === "function") { wx.nextTick(() => { t.show({ message, type, duration: 3000, }); }); } else { // 如果组件不可用,降级使用原生toast let icon = "none"; if (type === "success") icon = "success"; if (type === "error") icon = "error"; wx.showToast({ title: message, icon: icon, duration: 2000, }); } }, });