first commit
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
// pages/device_maintenance/create/index.js
|
||||
const { deviceApi, deviceMaintenanceApi, dictionaryApi } = require("../../../utils/api");
|
||||
const dayjs = require("../../../miniprogram_npm/dayjs/index");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: true,
|
||||
submitting: false,
|
||||
isEditMode: false,
|
||||
maintenanceId: null,
|
||||
|
||||
subDevice: {}, // 子设备信息
|
||||
maintenanceTypes: [], // 保养类型
|
||||
typeIndex: -1,
|
||||
|
||||
form: {
|
||||
sub_device_id: "",
|
||||
maintenance_type: "",
|
||||
start_time: "",
|
||||
start_time_display: "",
|
||||
end_time: "",
|
||||
end_time_display: "",
|
||||
notes: "",
|
||||
},
|
||||
|
||||
customType: "", // 自定义类型
|
||||
showCustomInput: false,
|
||||
|
||||
// 选择器可见性
|
||||
typePickerVisible: false,
|
||||
startTimePickerVisible: false,
|
||||
endTimePickerVisible: false,
|
||||
currentDate: dayjs().format("YYYY-MM-DD HH:mm"),
|
||||
},
|
||||
|
||||
onLoad: function (options) {
|
||||
const sub_device_id = options.sub_device_id;
|
||||
const maintenance_id = options.maintenance_id;
|
||||
|
||||
this.setData({
|
||||
isEditMode: !!maintenance_id,
|
||||
maintenanceId: maintenance_id || null,
|
||||
"form.sub_device_id": parseInt(sub_device_id),
|
||||
});
|
||||
|
||||
wx.setNavigationBarTitle({
|
||||
title: this.data.isEditMode ? "编辑保养记录" : "创建保养记录",
|
||||
});
|
||||
|
||||
this.initializePage(sub_device_id, maintenance_id);
|
||||
},
|
||||
|
||||
async initializePage(sub_device_id, maintenance_id) {
|
||||
try {
|
||||
await this.loadMaintenanceTypes();
|
||||
|
||||
if (this.data.isEditMode) {
|
||||
await this.loadMaintenanceData(maintenance_id);
|
||||
} else {
|
||||
await this.loadSubDeviceInfo(sub_device_id);
|
||||
this.setData({
|
||||
"form.start_time": new Date().getTime(),
|
||||
"form.start_time_display": dayjs().format("YYYY-MM-DD HH:mm"),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.showMessage("页面加载失败,请返回重试", "error");
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
async loadMaintenanceTypes() {
|
||||
try {
|
||||
const res = await dictionaryApi.getDictionaryByType("maintenance_type");
|
||||
if (res.code === 200) {
|
||||
const types = res.data.map(item => ({
|
||||
value: item.value,
|
||||
label: item.value,
|
||||
}));
|
||||
types.push({ value: "custom", label: "手动输入" });
|
||||
this.setData({ maintenanceTypes: types });
|
||||
|
||||
if (!this.data.isEditMode && types.length > 1) {
|
||||
this.setData({
|
||||
typeIndex: 0,
|
||||
"form.maintenance_type": types[0].value,
|
||||
});
|
||||
} else if (types.length === 1) {
|
||||
// 只有手动输入选项
|
||||
this.setData({
|
||||
typeIndex: 0,
|
||||
showCustomInput: true,
|
||||
"form.maintenance_type": "",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
maintenanceTypes: [{ value: "custom", label: "手动输入" }],
|
||||
typeIndex: 0,
|
||||
showCustomInput: true,
|
||||
"form.maintenance_type": "",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async loadSubDeviceInfo(sub_device_id) {
|
||||
try {
|
||||
const res = await deviceApi.getSubDeviceById(sub_device_id);
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
this.setData({ subDevice: res.data });
|
||||
} else {
|
||||
throw new Error('获取子设备信息失败');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showMessage("加载设备信息失败", "error");
|
||||
this.setData({ subDevice: { name: '加载失败' } });
|
||||
}
|
||||
},
|
||||
|
||||
async loadMaintenanceData(maintenance_id) {
|
||||
try {
|
||||
const res = await deviceMaintenanceApi.getMaintenance(maintenance_id);
|
||||
if (res.code === 200) {
|
||||
const maintenance = res.data;
|
||||
await this.loadSubDeviceInfo(maintenance.sub_device_id);
|
||||
|
||||
this.setData({
|
||||
form: {
|
||||
sub_device_id: maintenance.sub_device_id,
|
||||
maintenance_type: maintenance.maintenance_type,
|
||||
start_time: new Date(maintenance.start_time).getTime(),
|
||||
start_time_display: dayjs(maintenance.start_time).format("YYYY-MM-DD HH:mm"),
|
||||
end_time: maintenance.end_time ? new Date(maintenance.end_time).getTime() : "",
|
||||
end_time_display: maintenance.end_time ? dayjs(maintenance.end_time).format("YYYY-MM-DD HH:mm") : "",
|
||||
notes: maintenance.notes || "",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
throw new Error('获取保养记录失败');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showMessage("加载保养记录失败", "error");
|
||||
}
|
||||
},
|
||||
|
||||
showTypePicker() {
|
||||
this.setData({ typePickerVisible: true });
|
||||
},
|
||||
|
||||
showStartTimePicker() {
|
||||
this.setData({ startTimePickerVisible: true });
|
||||
},
|
||||
|
||||
showEndTimePicker() {
|
||||
this.setData({ endTimePickerVisible: true });
|
||||
},
|
||||
|
||||
onTypeConfirm(e) {
|
||||
const selectedValue = e.detail.value[0];
|
||||
const selectedIndex = this.data.maintenanceTypes.findIndex(type => type.value === selectedValue);
|
||||
const selectedType = this.data.maintenanceTypes[selectedIndex];
|
||||
|
||||
if (!selectedType) {
|
||||
this.showMessage("选择的保养类型无效", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedType.value === "custom") {
|
||||
this.setData({
|
||||
typeIndex: selectedIndex,
|
||||
showCustomInput: true,
|
||||
"form.maintenance_type": "",
|
||||
typePickerVisible: false,
|
||||
});
|
||||
} else {
|
||||
this.setData({
|
||||
typeIndex: selectedIndex,
|
||||
showCustomInput: false,
|
||||
"form.maintenance_type": selectedType.value,
|
||||
typePickerVisible: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onTypePickerCancel() {
|
||||
this.setData({ typePickerVisible: false });
|
||||
},
|
||||
|
||||
onTypeChange(e) {
|
||||
const selectedValue = e.detail.value[0];
|
||||
const selectedIndex = this.data.maintenanceTypes.findIndex(type => type.value === selectedValue);
|
||||
this.setData({ typeIndex: selectedIndex });
|
||||
},
|
||||
|
||||
onCustomTypeChange(e) {
|
||||
const customType = e.detail.value;
|
||||
this.setData({
|
||||
customType: customType,
|
||||
"form.maintenance_type": customType,
|
||||
});
|
||||
},
|
||||
|
||||
onStartTimeConfirm(e) {
|
||||
const { value } = e.detail;
|
||||
// 将ISO格式转换为iOS兼容格式
|
||||
const formattedValue = value.replace('T', ' ').substring(0, 16);
|
||||
this.setData({
|
||||
"form.start_time": new Date(value).getTime(),
|
||||
"form.start_time_display": dayjs(value).format("YYYY-MM-DD HH:mm"),
|
||||
startTimePickerVisible: false,
|
||||
});
|
||||
},
|
||||
|
||||
onStartTimeCancel() {
|
||||
this.setData({ startTimePickerVisible: false });
|
||||
},
|
||||
|
||||
onEndTimeConfirm(e) {
|
||||
const { value } = e.detail;
|
||||
// 将ISO格式转换为iOS兼容格式
|
||||
const formattedValue = value.replace('T', ' ').substring(0, 16);
|
||||
this.setData({
|
||||
"form.end_time": new Date(value).getTime(),
|
||||
"form.end_time_display": dayjs(value).format("YYYY-MM-DD HH:mm"),
|
||||
endTimePickerVisible: false,
|
||||
});
|
||||
},
|
||||
|
||||
onEndTimeCancel() {
|
||||
this.setData({ endTimePickerVisible: false });
|
||||
},
|
||||
|
||||
onNotesChange(e) {
|
||||
this.setData({ "form.notes": e.detail.value });
|
||||
},
|
||||
|
||||
validateForm() {
|
||||
const { sub_device_id, maintenance_type, start_time } = this.data.form;
|
||||
|
||||
if (!sub_device_id) {
|
||||
this.showMessage("子设备信息加载失败", "error");
|
||||
return false;
|
||||
}
|
||||
if (!maintenance_type) {
|
||||
this.showMessage("请选择或输入保养类型", "error");
|
||||
return false;
|
||||
}
|
||||
if (!start_time) {
|
||||
this.showMessage("请选择开始时间", "error");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
async submitForm() {
|
||||
if (!this.validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ submitting: true });
|
||||
|
||||
try {
|
||||
const formData = {
|
||||
sub_device_id: this.data.form.sub_device_id,
|
||||
maintenance_type: this.data.form.maintenance_type,
|
||||
start_time: dayjs(this.data.form.start_time).format("YYYY-MM-DD HH:mm:ss"),
|
||||
end_time: this.data.form.end_time ? dayjs(this.data.form.end_time).format("YYYY-MM-DD HH:mm:ss") : null,
|
||||
notes: this.data.form.notes || '',
|
||||
};
|
||||
|
||||
let res;
|
||||
if (this.data.isEditMode) {
|
||||
res = await deviceMaintenanceApi.updateMaintenance(this.data.maintenanceId, formData);
|
||||
} else {
|
||||
res = await deviceMaintenanceApi.createMaintenance(formData);
|
||||
}
|
||||
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
this.showMessage(this.data.isEditMode ? "更新成功" : "添加成功", "success");
|
||||
setTimeout(() => {
|
||||
wx.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
throw new Error(res.message || (this.data.isEditMode ? "更新失败" : "添加失败"));
|
||||
}
|
||||
} catch (error) {
|
||||
this.showMessage(error.message || (this.data.isEditMode ? "更新失败" : "添加失败"), "error");
|
||||
} finally {
|
||||
this.setData({ submitting: false });
|
||||
}
|
||||
},
|
||||
|
||||
showMessage(message, type = "info") {
|
||||
wx.showToast({
|
||||
title: message,
|
||||
icon: type === "success" ? "success" : type === "error" ? "error" : "none",
|
||||
duration: 2000,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"navigationBarTitleText": "创建保养记录",
|
||||
"usingComponents": {
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-input": "tdesign-miniprogram/input/input",
|
||||
"t-textarea": "tdesign-miniprogram/textarea/textarea",
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-message": "tdesign-miniprogram/message/message",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-picker": "tdesign-miniprogram/picker/picker",
|
||||
"t-picker-item": "tdesign-miniprogram/picker-item/picker-item",
|
||||
"t-date-time-picker": "tdesign-miniprogram/date-time-picker/date-time-picker"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<view class="container">
|
||||
<t-message id="t-message" />
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<t-loading theme="circular" size="40rpx" loading />
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<block wx:else>
|
||||
<view class="form-group">
|
||||
<!-- 子设备信息显示 -->
|
||||
<view class="form-item">
|
||||
<text class="label">子设备 <text class="required">*</text></text>
|
||||
<t-input
|
||||
value="{{subDevice.name || '加载中...'}}"
|
||||
placeholder="加载中..."
|
||||
disabled
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 保养类型选择 -->
|
||||
<view class="form-item">
|
||||
<text class="label">保养类型 <text class="required">*</text></text>
|
||||
<view class="datetime-picker-wrapper" bindtap="showTypePicker">
|
||||
<t-cell
|
||||
title="{{form.maintenance_type || '请选择保养类型'}}"
|
||||
arrow
|
||||
hover
|
||||
note="{{form.maintenance_type ? '' : '必选'}}"
|
||||
t-class="picker-cell"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手动输入保养类型 -->
|
||||
<view class="form-item" wx:if="{{showCustomInput}}">
|
||||
<text class="label">自定义类型</text>
|
||||
<t-input
|
||||
value="{{customType}}"
|
||||
placeholder="请输入自定义保养类型"
|
||||
bind:change="onCustomTypeChange"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 开始时间选择 -->
|
||||
<view class="form-item">
|
||||
<text class="label">开始时间 <text class="required">*</text></text>
|
||||
<view class="datetime-picker-wrapper" bindtap="showStartTimePicker">
|
||||
<t-cell
|
||||
title="{{form.start_time_display || '请选择开始时间'}}"
|
||||
arrow
|
||||
hover
|
||||
note="{{form.start_time ? '' : '必选'}}"
|
||||
t-class="picker-cell"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 结束时间选择 -->
|
||||
<view class="form-item">
|
||||
<text class="label">结束时间</text>
|
||||
<view class="datetime-picker-wrapper" bindtap="showEndTimePicker">
|
||||
<t-cell
|
||||
title="{{form.end_time_display || '请选择结束时间'}}"
|
||||
arrow
|
||||
hover
|
||||
note="{{form.end_time ? '' : '可选'}}"
|
||||
t-class="picker-cell"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<view class="form-item">
|
||||
<text class="label">备注</text>
|
||||
<t-textarea
|
||||
value="{{form.notes}}"
|
||||
placeholder="请输入保养备注信息(选填)"
|
||||
maxlength="200"
|
||||
indicator
|
||||
bind:change="onNotesChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-container">
|
||||
<t-button
|
||||
theme="primary"
|
||||
size="large"
|
||||
loading="{{submitting}}"
|
||||
disabled="{{submitting}}"
|
||||
bind:tap="submitForm"
|
||||
block
|
||||
>{{isEditMode ? '更新保养记录' : '添加保养记录'}}</t-button>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 保养类型选择器 -->
|
||||
<t-picker
|
||||
title="选择保养类型"
|
||||
visible="{{typePickerVisible}}"
|
||||
value="{{[form.maintenance_type]}}"
|
||||
bind:change="onTypeChange"
|
||||
bind:cancel="onTypePickerCancel"
|
||||
bind:confirm="onTypeConfirm"
|
||||
>
|
||||
<t-picker-item options="{{maintenanceTypes}}" />
|
||||
</t-picker>
|
||||
|
||||
<!-- 开始时间选择器 -->
|
||||
<t-date-time-picker
|
||||
title="选择开始时间"
|
||||
visible="{{startTimePickerVisible}}"
|
||||
mode="minute"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
value="{{form.start_time || currentDate}}"
|
||||
confirm-btn="确认"
|
||||
cancel-btn="取消"
|
||||
bind:confirm="onStartTimeConfirm"
|
||||
bind:cancel="onStartTimeCancel"
|
||||
auto-close
|
||||
show-week
|
||||
/>
|
||||
|
||||
<!-- 结束时间选择器 -->
|
||||
<t-date-time-picker
|
||||
title="选择结束时间"
|
||||
visible="{{endTimePickerVisible}}"
|
||||
mode="minute"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
value="{{form.end_time || currentDate}}"
|
||||
confirm-btn="确认"
|
||||
cancel-btn="取消"
|
||||
bind:confirm="onEndTimeConfirm"
|
||||
bind:cancel="onEndTimeCancel"
|
||||
auto-close
|
||||
show-week
|
||||
/>
|
||||
</view>
|
||||
@@ -0,0 +1,246 @@
|
||||
/* pages/surgery/create/index.wxss */
|
||||
|
||||
.container {
|
||||
padding: 30rpx;
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 16rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #e34d59;
|
||||
}
|
||||
|
||||
.picker-cell {
|
||||
padding: 20rpx !important;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f8f8f8 !important;
|
||||
}
|
||||
|
||||
/* 原生选择器样式 */
|
||||
.picker-view {
|
||||
padding: 24rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.time-picker {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.picker-note {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
|
||||
/* 设备选择相关样式 - 更新 */
|
||||
.device-selection-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.device-selection-header text {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.add-device-btn {
|
||||
color: #06a56c;
|
||||
font-size: 26rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.selected-devices-list {
|
||||
margin-top: 12rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
padding: 12rpx;
|
||||
display: block; /* 确保设备列表容器显示 */
|
||||
}
|
||||
|
||||
.device-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 12rpx;
|
||||
padding: 8rpx;
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
display: block; /* 确保设备项显示 */
|
||||
}
|
||||
|
||||
.device-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.device-item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.device-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #e34d59;
|
||||
color: #fff;
|
||||
border-radius: 4rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
padding: 10rpx;
|
||||
color: #e34d59;
|
||||
}
|
||||
|
||||
.no-devices {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
padding: 30rpx 0;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.submit-container {
|
||||
margin-top: 60rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 年月日时分秒选择器样式 */
|
||||
.datetime-picker-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.datetime-picker-container {
|
||||
position: fixed;
|
||||
bottom: -500rpx;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
z-index: 1001;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.datetime-picker-container.show {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.datetime-picker-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.datetime-picker-header .title {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.datetime-picker-header .cancel-btn,
|
||||
.datetime-picker-header .confirm-btn {
|
||||
font-size: 28rpx;
|
||||
padding: 8rpx 10rpx;
|
||||
}
|
||||
|
||||
.datetime-picker-header .cancel-btn {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.datetime-picker-header .confirm-btn {
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.datetime-picker-body {
|
||||
padding: 20rpx 0;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.picker-item {
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.time-display {
|
||||
padding: 20rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 自定义级联选择器样式 */
|
||||
:host {
|
||||
--td-cascader-active-color: #0052d9;
|
||||
}
|
||||
|
||||
/* 设备选择器的自定义样式 */
|
||||
.custom-picker {
|
||||
--td-picker-confirm-color: #0052d9;
|
||||
}
|
||||
|
||||
.custom-confirm-btn {
|
||||
color: #0052d9 !important;
|
||||
}
|
||||
|
||||
.custom-cancel-btn {
|
||||
color: #999 !important;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
const { deviceMaintenanceApi } = require("../../utils/api");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
sub_device_id: null,
|
||||
subDeviceInfo: null,
|
||||
maintenanceList: [],
|
||||
loading: false,
|
||||
maintenanceTypes: [
|
||||
{ value: '常规保养', label: '常规保养' },
|
||||
{ value: '故障维修', label: '故障维修' },
|
||||
{ value: '定期检查', label: '定期检查' },
|
||||
{ value: '设备升级', label: '设备升级' },
|
||||
],
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
if (options.sub_device_id) {
|
||||
this.setData({ sub_device_id: options.sub_device_id });
|
||||
this.loadSubDeviceInfo();
|
||||
this.fetchMaintenanceList();
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '缺少设备ID',
|
||||
icon: 'error',
|
||||
complete: () => wx.navigateBack(),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (this.data.sub_device_id) {
|
||||
this.fetchMaintenanceList();
|
||||
}
|
||||
},
|
||||
|
||||
// 加载子设备信息
|
||||
loadSubDeviceInfo() {
|
||||
const app = getApp();
|
||||
const subDevice = app.globalData.subDevices?.find(
|
||||
d => d.id === parseInt(this.data.sub_device_id)
|
||||
);
|
||||
if (subDevice) {
|
||||
this.setData({
|
||||
subDeviceInfo: subDevice,
|
||||
deviceStatus: subDevice.status || 'normal',
|
||||
selectedStatus: subDevice.status || 'normal'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 获取保养记录列表
|
||||
fetchMaintenanceList() {
|
||||
this.setData({ loading: true });
|
||||
|
||||
deviceMaintenanceApi.getMaintenanceBySubDeviceId(this.data.sub_device_id)
|
||||
.then(res => {
|
||||
const formattedList = res.data.list.map(item => {
|
||||
item.start_time_formatted = dayjs(item.start_time).format('YYYY-MM-DD HH:mm');
|
||||
item.end_time_formatted = item.end_time ? dayjs(item.end_time).format('YYYY-MM-DD HH:mm') : '';
|
||||
item.maintenance_by = item.operator?.nickname || '-';
|
||||
item.notes = item.notes || '无';
|
||||
item.device_name = item.subDevice?.device?.name || '未知设备';
|
||||
item.sub_device_name = item.subDevice?.name || '未知子设备';
|
||||
return item;
|
||||
});
|
||||
this.setData({
|
||||
maintenanceList: formattedList,
|
||||
loading: false
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
wx.showToast({
|
||||
title: '获取记录失败',
|
||||
icon: 'error',
|
||||
});
|
||||
this.setData({ loading: false });
|
||||
});
|
||||
},
|
||||
|
||||
// 添加保养记录
|
||||
addMaintenance() {
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_maintenance/create/index?sub_device_id=${this.data.sub_device_id}`
|
||||
});
|
||||
},
|
||||
|
||||
// 显示编辑对话框 - 改为跳转到编辑页面
|
||||
showEditDialog(e) {
|
||||
const record = e.currentTarget.dataset.record;
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_maintenance/create/index?sub_device_id=${this.data.sub_device_id}&maintenance_id=${record.id}`
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 删除保养记录
|
||||
deleteMaintenance(e) {
|
||||
const record = e.currentTarget.dataset.record;
|
||||
|
||||
wx.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除此保养记录吗?此操作不可恢复!',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
deviceMaintenanceApi.deleteMaintenance(record.id)
|
||||
.then(() => {
|
||||
wx.showToast({ title: '删除成功', icon: 'success' });
|
||||
this.fetchMaintenanceList();
|
||||
})
|
||||
.catch(err => {
|
||||
wx.showToast({
|
||||
title: err.response?.data?.msg || '删除失败',
|
||||
icon: 'error',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 滑动单元格操作
|
||||
onActionClick(e) {
|
||||
const record = e.currentTarget.dataset.item;
|
||||
const { text } = e.detail;
|
||||
|
||||
// 构造一个与旧函数兼容的事件对象
|
||||
const mockEvent = {
|
||||
currentTarget: {
|
||||
dataset: {
|
||||
record: record
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (text === '编辑') {
|
||||
this.showEditDialog(mockEvent);
|
||||
} else if (text === '删除') {
|
||||
this.deleteMaintenance(mockEvent);
|
||||
}
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
onBack() {
|
||||
wx.navigateBack();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-dialog": "tdesign-miniprogram/dialog/dialog",
|
||||
"t-input": "tdesign-miniprogram/input/input",
|
||||
"t-textarea": "tdesign-miniprogram/textarea/textarea",
|
||||
"t-picker": "tdesign-miniprogram/picker/picker",
|
||||
"t-swipe-cell": "tdesign-miniprogram/swipe-cell/swipe-cell"
|
||||
},
|
||||
"navigationBarTitleText": "保养记录"
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<view class="maintenance-page">
|
||||
<!-- 页面头部 -->
|
||||
<view class="page-header">
|
||||
<view class="page-title">保养记录</view>
|
||||
<view class="header-actions">
|
||||
<t-button
|
||||
size="small"
|
||||
theme="primary"
|
||||
bind:tap="addMaintenance">
|
||||
添加保养记录
|
||||
</t-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保养记录列表 -->
|
||||
<view class="maintenance-list">
|
||||
<view wx:if="{{loading}}" class="loading-container">
|
||||
<t-loading theme="circular" size="80rpx" />
|
||||
<view class="loading-text">加载中...</view>
|
||||
</view>
|
||||
|
||||
<block wx:elif="{{maintenanceList.length > 0}}">
|
||||
<t-swipe-cell
|
||||
wx:for="{{maintenanceList}}"
|
||||
wx:key="id"
|
||||
right="{{[{text: '编辑', className: 't-swipe-cell-demo-btn edit-btn'}, {text: '删除', className: 't-swipe-cell-demo-btn delete-btn'}]}}"
|
||||
bind:click="onActionClick"
|
||||
data-item="{{item}}"
|
||||
>
|
||||
<view class="maintenance-item">
|
||||
<view class="item-body">
|
||||
<view class="field-row">
|
||||
<text class="field-label">所属设备</text>
|
||||
<text class="field-value">{{item.device_name}}</text>
|
||||
</view>
|
||||
|
||||
<view class="field-row">
|
||||
<text class="field-label">子设备名称</text>
|
||||
<text class="field-value">{{item.sub_device_name}}</text>
|
||||
</view>
|
||||
|
||||
<view class="field-row">
|
||||
<text class="field-label">保养类型</text>
|
||||
<view class="field-value">
|
||||
<t-tag
|
||||
variant="light"
|
||||
size="small">
|
||||
{{item.maintenance_type}}
|
||||
</t-tag>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field-row">
|
||||
<text class="field-label">开始时间</text>
|
||||
<text class="field-value">{{item.start_time_formatted}}</text>
|
||||
</view>
|
||||
|
||||
<view class="field-row">
|
||||
<text class="field-label">结束时间</text>
|
||||
<text class="field-value">{{item.end_time_formatted || '进行中...'}}</text>
|
||||
</view>
|
||||
|
||||
<view class="field-row">
|
||||
<text class="field-label">操作者</text>
|
||||
<text class="field-value">{{item.maintenance_by}}</text>
|
||||
</view>
|
||||
|
||||
<view class="field-row">
|
||||
<text class="field-label">备注</text>
|
||||
<text class="field-value notes-value">{{item.notes}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</t-swipe-cell>
|
||||
</block>
|
||||
|
||||
<t-empty wx:else description="暂无保养记录" />
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -0,0 +1,206 @@
|
||||
.maintenance-page {
|
||||
padding: 0;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #e8e8e8;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 保养记录列表 */
|
||||
.maintenance-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.maintenance-item {
|
||||
display: block;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.maintenance-type,
|
||||
.item-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-body {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.field-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notes-value {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.t-swipe-cell-demo-btn {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
padding: 0 40rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #0052d9;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e34d59;
|
||||
}
|
||||
|
||||
/* 弹窗表单 */
|
||||
.dialog-form {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.dialog-form .t-cell {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.dialog-form .t-input,
|
||||
.dialog-form .t-textarea {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 750rpx) {
|
||||
.stats-section {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1 1 50%;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.item-actions .t-button {
|
||||
flex: 1;
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.maintenance-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.t-empty {
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
.t-tag {
|
||||
border-radius: 8rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 按钮组样式优化 */
|
||||
.item-actions .t-button {
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
}
|
||||
|
||||
/* 图标颜色 */
|
||||
.t-icon[name="time"] {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.t-icon[name="user"] {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.t-icon[name="money-circle"] {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.field-value .t-tag {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.t-icon[name="notes"] {
|
||||
color: #909399;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// pages/device_management/index.js
|
||||
const { deviceApi } = require("../../utils/api");
|
||||
const Toast = require("../../miniprogram_npm/tdesign-miniprogram/toast/index");
|
||||
|
||||
Page({
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
devices: [], // 设备列表数据
|
||||
loading: true, // 加载状态
|
||||
loadError: false, // 加载错误状态
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad() {
|
||||
this.fetchDevices();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
// 页面显示时也刷新数据
|
||||
this.fetchDevices();
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取设备列表数据
|
||||
*/
|
||||
async fetchDevices() {
|
||||
this.setData({
|
||||
loading: true,
|
||||
loadError: false,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await deviceApi.getDevices();
|
||||
|
||||
// 处理设备数据,计算子设备数量
|
||||
const deviceList = response.data.list || response.data || [];
|
||||
const formattedDevices = deviceList.map((device) => {
|
||||
// 计算子设备数量
|
||||
const subDeviceCount = device.subdevices ? device.subdevices.length : 0;
|
||||
|
||||
return {
|
||||
...device,
|
||||
subDeviceCount: subDeviceCount,
|
||||
};
|
||||
});
|
||||
|
||||
this.setData({
|
||||
devices: formattedDevices,
|
||||
loading: false,
|
||||
});
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
loadError: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 查看子设备
|
||||
*/
|
||||
viewSubDevices(e) {
|
||||
const device = e.currentTarget.dataset.device;
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_subdevices/index?device_id=${device.id}`
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.fetchDevices();
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: "设备管理",
|
||||
path: "/pages/device_management/index",
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"navigationBarTitleText": "设备管理",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f7f8fa",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"t-toast": "tdesign-miniprogram/toast/toast",
|
||||
"t-dialog": "tdesign-miniprogram/dialog/dialog",
|
||||
"t-skeleton": "tdesign-miniprogram/skeleton/skeleton",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-swipe-cell": "tdesign-miniprogram/swipe-cell/swipe-cell",
|
||||
"t-fab": "tdesign-miniprogram/fab/fab",
|
||||
"t-input": "tdesign-miniprogram/input/input"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<view class="device-container">
|
||||
<block wx:if="{{loading}}">
|
||||
<!-- 加载中骨架屏 -->
|
||||
<view wx:for="{{5}}" wx:key="index" class="skeleton-item">
|
||||
<t-skeleton theme="paragraph" loading></t-skeleton>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{loadError}}">
|
||||
<!-- 加载错误提示 -->
|
||||
<view class="error-container">
|
||||
<t-empty icon="error-circle" description="加载失败,请下拉刷新重试" />
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{devices && devices.length > 0}}">
|
||||
<!-- 设备列表 -->
|
||||
<view class="device-list">
|
||||
<block wx:for="{{devices}}" wx:key="id">
|
||||
<view class="device-card" bindtap="viewSubDevices" data-device="{{item}}">
|
||||
<view class="device-title">{{item.name}}</view>
|
||||
<view class="device-info">
|
||||
<view class="info-item usage">
|
||||
<text>使用次数: {{item.usage_count || 0}}次</text>
|
||||
</view>
|
||||
<!-- 子设备数量显示 -->
|
||||
<view class="info-item subdevice-count">
|
||||
<text class="count">子设备: {{item.subDeviceCount}}个</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="device-id-tag">ID: {{item.id}}</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<!-- 空数据提示 -->
|
||||
<view class="empty-container">
|
||||
<t-empty icon="device" description="暂无设备数据" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- Toast 消息提示 -->
|
||||
<t-toast id="t-toast" />
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/* pages/device_management/index.wxss */
|
||||
page {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.device-container {
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
margin: 16rpx 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 设备列表样式 */
|
||||
.device-list {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
/* 滑动单元格容器 */
|
||||
.swipe-cell-container {
|
||||
margin-bottom: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.device-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
position: relative;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.device-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
padding-right: 100rpx;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 使用次数样式 */
|
||||
.usage {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.usage text {
|
||||
font-size: 28rpx;
|
||||
color: #0052d9;
|
||||
background-color: #eef4ff;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 子设备数量样式 */
|
||||
.subdevice-count {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.subdevice-count .count {
|
||||
font-size: 28rpx;
|
||||
color: #06a56c;
|
||||
background-color: #e8f6f1;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.device-id-tag {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 空状态和错误容器 */
|
||||
.empty-container,
|
||||
.error-container {
|
||||
padding: 100rpx 24rpx;
|
||||
}
|
||||
|
||||
/* 侧滑按钮样式 */
|
||||
.swipe-cell-btn {
|
||||
height: 100% !important;
|
||||
width: 120rpx !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
font-size: 28rpx !important;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #0052d9 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e34d59 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* 修改TDesign原生样式,确保按钮高度与卡片一致 */
|
||||
.device-list .t-swipe-cell__right {
|
||||
height: 100% !important;
|
||||
display: flex !important;
|
||||
align-items: stretch !important;
|
||||
}
|
||||
|
||||
/* 对话框内容样式 */
|
||||
.dialog-content {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.t-input {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
/* 悬浮按钮样式 */
|
||||
.t-fab {
|
||||
position: fixed !important;
|
||||
bottom: 120rpx !important;
|
||||
right: 32rpx !important;
|
||||
}
|
||||
|
||||
/* 对话框内容样式 */
|
||||
.dialog-content {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.t-input {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
const { deviceApi } = require("../../utils/api");
|
||||
const Toast = require("../../miniprogram_npm/tdesign-miniprogram/toast/index");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
device: null,
|
||||
device_id: null,
|
||||
loading: true,
|
||||
loadError: false,
|
||||
|
||||
// 设备状态相关
|
||||
deviceStatus: 'normal',
|
||||
deviceStatusMap: {
|
||||
normal: '正常',
|
||||
maintenance: '保养中',
|
||||
repair: '维修中',
|
||||
fault: '故障',
|
||||
},
|
||||
statusDialogVisible: false,
|
||||
selectedStatus: 'normal',
|
||||
|
||||
// 设备编辑相关
|
||||
editDialogVisible: false,
|
||||
editForm: {
|
||||
name: '',
|
||||
description: '',
|
||||
},
|
||||
|
||||
// 子设备相关
|
||||
subDevices: [],
|
||||
subDeviceLoading: false,
|
||||
|
||||
// 保养记录相关
|
||||
maintenanceStats: {
|
||||
total: 0,
|
||||
completed: 0,
|
||||
inProgress: 0,
|
||||
},
|
||||
|
||||
// 操作选项
|
||||
actions: [
|
||||
{
|
||||
icon: 'setting',
|
||||
text: '更改状态',
|
||||
color: '#0052d9',
|
||||
action: 'changeStatus'
|
||||
},
|
||||
{
|
||||
icon: 'add-circle',
|
||||
text: '添加保养记录',
|
||||
color: '#06a56c',
|
||||
action: 'addMaintenance'
|
||||
},
|
||||
{
|
||||
icon: 'time',
|
||||
text: '保养记录',
|
||||
color: '#fa8c16',
|
||||
action: 'viewMaintenance'
|
||||
},
|
||||
{
|
||||
icon: 'edit',
|
||||
text: '编辑设备',
|
||||
color: '#722ed1',
|
||||
action: 'editDevice'
|
||||
},
|
||||
{
|
||||
icon: 'view-list',
|
||||
text: '管理子设备',
|
||||
color: '#0052d9',
|
||||
action: 'manageSubDevices'
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
text: '删除设备',
|
||||
color: '#e34d59',
|
||||
action: 'deleteDevice'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
if (options.device_id) {
|
||||
this.setData({ device_id: options.device_id });
|
||||
this.loadDeviceInfo();
|
||||
this.loadSubDevices();
|
||||
this.loadMaintenanceStats();
|
||||
} else {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '缺少设备ID',
|
||||
theme: 'error',
|
||||
});
|
||||
wx.navigateBack();
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 页面显示时刷新数据
|
||||
if (this.data.device_id) {
|
||||
this.loadDeviceInfo();
|
||||
this.loadSubDevices();
|
||||
this.loadMaintenanceStats();
|
||||
}
|
||||
},
|
||||
|
||||
// 加载设备信息
|
||||
async loadDeviceInfo() {
|
||||
this.setData({ loading: true, loadError: false });
|
||||
|
||||
try {
|
||||
const response = await deviceApi.getDevices();
|
||||
const deviceList = response.data.list || response.data || [];
|
||||
const device = deviceList.find(d => d.id === parseInt(this.data.device_id));
|
||||
|
||||
if (device) {
|
||||
// 计算子设备数量
|
||||
const subDeviceCount = device.sub_devices ? device.sub_devices.length : 0;
|
||||
|
||||
this.setData({
|
||||
device: { ...device, subDeviceCount },
|
||||
deviceStatus: device.status || 'normal',
|
||||
selectedStatus: device.status || 'normal',
|
||||
editForm: {
|
||||
name: device.name,
|
||||
description: device.description || '',
|
||||
},
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setData({
|
||||
loadError: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
loadError: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 加载子设备
|
||||
async loadSubDevices() {
|
||||
this.setData({ subDeviceLoading: true });
|
||||
|
||||
try {
|
||||
const response = await deviceApi.getSubDevicesByDeviceId(this.data.device_id);
|
||||
const subDevices = response.data.list || response.data || [];
|
||||
this.setData({
|
||||
subDevices: subDevices,
|
||||
subDeviceLoading: false,
|
||||
});
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
subDevices: [],
|
||||
subDeviceLoading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 加载保养记录统计
|
||||
async loadMaintenanceStats() {
|
||||
try {
|
||||
// 这里可以调用保养记录统计API
|
||||
// 目前使用模拟数据
|
||||
this.setData({
|
||||
maintenanceStats: {
|
||||
total: 0,
|
||||
completed: 0,
|
||||
inProgress: 0,
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// 加载保养统计失败,使用默认值
|
||||
}
|
||||
},
|
||||
|
||||
// 处理操作按钮点击
|
||||
handleAction(e) {
|
||||
const action = e.currentTarget.dataset.action;
|
||||
|
||||
switch (action) {
|
||||
case 'changeStatus':
|
||||
this.showStatusDialog();
|
||||
break;
|
||||
case 'addMaintenance':
|
||||
this.addMaintenance();
|
||||
break;
|
||||
case 'viewMaintenance':
|
||||
this.viewMaintenance();
|
||||
break;
|
||||
case 'editDevice':
|
||||
this.showEditDialog();
|
||||
break;
|
||||
case 'manageSubDevices':
|
||||
this.manageSubDevices();
|
||||
break;
|
||||
case 'deleteDevice':
|
||||
this.deleteDevice();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// 显示状态更改弹窗
|
||||
showStatusDialog() {
|
||||
this.setData({
|
||||
statusDialogVisible: true,
|
||||
selectedStatus: this.data.deviceStatus,
|
||||
});
|
||||
},
|
||||
|
||||
// 关闭状态更改弹窗
|
||||
closeStatusDialog() {
|
||||
this.setData({
|
||||
statusDialogVisible: false,
|
||||
});
|
||||
},
|
||||
|
||||
// 选择状态
|
||||
selectStatus(e) {
|
||||
const status = e.currentTarget.dataset.status;
|
||||
this.setData({
|
||||
selectedStatus: status,
|
||||
});
|
||||
},
|
||||
|
||||
// 确认状态更改
|
||||
async confirmStatusChange() {
|
||||
const { selectedStatus, device } = this.data;
|
||||
|
||||
try {
|
||||
await deviceApi.updateDevice(device.id, { status: selectedStatus });
|
||||
|
||||
this.setData({
|
||||
deviceStatus: selectedStatus,
|
||||
statusDialogVisible: false,
|
||||
});
|
||||
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '状态更新成功',
|
||||
theme: 'success',
|
||||
});
|
||||
|
||||
// 刷新设备信息
|
||||
this.loadDeviceInfo();
|
||||
} catch (error) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '状态更新失败',
|
||||
theme: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 显示编辑弹窗
|
||||
showEditDialog() {
|
||||
this.setData({
|
||||
editDialogVisible: true,
|
||||
});
|
||||
},
|
||||
|
||||
// 关闭编辑弹窗
|
||||
closeEditDialog() {
|
||||
this.setData({
|
||||
editDialogVisible: false,
|
||||
});
|
||||
},
|
||||
|
||||
// 确认编辑
|
||||
async confirmEdit() {
|
||||
const { editForm, device } = this.data;
|
||||
|
||||
if (!editForm.name.trim()) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '请输入设备名称',
|
||||
theme: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await deviceApi.updateDevice(device.id, {
|
||||
name: editForm.name.trim(),
|
||||
description: editForm.description.trim(),
|
||||
});
|
||||
|
||||
this.setData({
|
||||
editDialogVisible: false,
|
||||
});
|
||||
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '设备信息更新成功',
|
||||
theme: 'success',
|
||||
});
|
||||
|
||||
// 刷新设备信息
|
||||
this.loadDeviceInfo();
|
||||
} catch (error) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '更新失败',
|
||||
theme: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 添加保养记录
|
||||
addMaintenance() {
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_maintenance/create/index?device_id=${this.data.device_id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 查看保养记录
|
||||
viewMaintenance() {
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_maintenance/index?device_id=${this.data.device_id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 管理子设备
|
||||
manageSubDevices() {
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_subdevices/index?device_id=${this.data.device_id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 查看子设备详情
|
||||
viewSubDeviceDetail(e) {
|
||||
const subDevice = e.currentTarget.dataset.subdevice;
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_subdevices/index?device_id=${this.data.device_id}&sub_device_id=${subDevice.id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 删除设备
|
||||
deleteDevice() {
|
||||
wx.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除此设备吗?此操作不可撤销。',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.confirmDelete();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 确认删除
|
||||
async confirmDelete() {
|
||||
const { device } = this.data;
|
||||
|
||||
try {
|
||||
await deviceApi.deleteDevice(device.id);
|
||||
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '删除成功',
|
||||
theme: 'success',
|
||||
});
|
||||
|
||||
// 返回设备管理页面
|
||||
wx.navigateBack();
|
||||
} catch (error) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '删除失败',
|
||||
theme: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
onBack() {
|
||||
wx.navigateBack();
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
Promise.all([
|
||||
this.loadDeviceInfo(),
|
||||
this.loadSubDevices(),
|
||||
this.loadMaintenanceStats(),
|
||||
]).then(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
}).catch(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"navigationBarTitleText": "设备管理详情",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f7f8fa",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"t-toast": "tdesign-miniprogram/toast/toast",
|
||||
"t-dialog": "tdesign-miniprogram/dialog/dialog",
|
||||
"t-skeleton": "tdesign-miniprogram/skeleton/skeleton",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag",
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-input": "tdesign-miniprogram/input/input",
|
||||
"t-textarea": "tdesign-miniprogram/textarea/textarea"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<view class="device-detail-page">
|
||||
<!-- 页面头部 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left">
|
||||
<view class="back-btn" bind:tap="onBack">
|
||||
<t-icon name="chevron-left" size="48rpx" />
|
||||
</view>
|
||||
<view class="device-info">
|
||||
<view class="device-name">{{device.name}}</view>
|
||||
<view class="device-meta">
|
||||
<text class="device-id">ID: {{device.id}}</text>
|
||||
<text class="subdevice-count">子设备: {{device.subDeviceCount || 0}}个</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<block wx:if="{{loading}}">
|
||||
<view class="loading-container">
|
||||
<t-skeleton theme="paragraph" loading />
|
||||
<t-skeleton theme="paragraph" loading />
|
||||
<t-skeleton theme="paragraph" loading />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<block wx:elif="{{loadError}}">
|
||||
<view class="error-container">
|
||||
<t-empty icon="error-circle" description="加载失败,请下拉刷新重试" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<block wx:elif="{{device}}">
|
||||
<!-- 子设备列表 -->
|
||||
<view class="subdevices-section">
|
||||
<view class="section-title">
|
||||
子设备列表
|
||||
<text class="subdevice-count">({{subDevices.length}})</text>
|
||||
</view>
|
||||
<view wx:if="{{subDeviceLoading}}" class="loading-container">
|
||||
<t-loading theme="circular" size="40rpx" />
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
<block wx:elif="{{subDevices.length > 0}}">
|
||||
<view
|
||||
wx:for="{{subDevices}}"
|
||||
wx:key="id"
|
||||
class="subdevice-item"
|
||||
bind:tap="viewSubDeviceDetail"
|
||||
data-subdevice="{{item}}">
|
||||
<view class="subdevice-info">
|
||||
<view class="subdevice-name">{{item.name}}</view>
|
||||
<view class="subdevice-meta">
|
||||
<text class="subdevice-id">ID: {{item.id}}</text>
|
||||
<text class="subdevice-usage">使用: {{item.usage_count || 0}}次</text>
|
||||
<text class="subdevice-status">
|
||||
状态: <text class="status-badge status-{{item.status || 'normal'}}">{{deviceStatusMap[item.status || 'normal']}}</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<t-icon name="chevron-right" size="32rpx" color="#999" />
|
||||
</view>
|
||||
</block>
|
||||
<t-empty wx:else description="暂无子设备" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- Toast 消息提示 -->
|
||||
<t-toast id="t-toast" />
|
||||
</view>
|
||||
@@ -0,0 +1,331 @@
|
||||
.device-detail-page {
|
||||
padding: 0;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.device-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.device-id {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 24rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.status-normal {
|
||||
color: #06a56c;
|
||||
background-color: #e8f6f1;
|
||||
}
|
||||
|
||||
.status-maintenance {
|
||||
color: #fa8c16;
|
||||
background-color: #fff7e6;
|
||||
}
|
||||
|
||||
.status-repair {
|
||||
color: #722ed1;
|
||||
background-color: #f9f0ff;
|
||||
}
|
||||
|
||||
.status-fault {
|
||||
color: #e34d59;
|
||||
background-color: #fff1f0;
|
||||
}
|
||||
|
||||
/* 加载和错误状态 */
|
||||
.loading-container, .error-container {
|
||||
padding: 100rpx 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 内容区块 */
|
||||
.info-section, .stats-section, .actions-section, .subdevices-section {
|
||||
margin: 20rpx 0;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.subdevice-count {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
/* 信息卡片 */
|
||||
.info-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 统计信息 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-number.in-progress {
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.stat-number.completed {
|
||||
color: #06a56c;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-item:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 子设备列表 */
|
||||
.subdevice-item {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
margin-bottom: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.subdevice-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.subdevice-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.subdevice-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.subdevice-id {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.subdevice-usage {
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.subdevice-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 24rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 状态选择器 */
|
||||
.status-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.status-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f7f8fa;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.status-option.selected {
|
||||
background-color: #eef4ff;
|
||||
border-color: #0052d9;
|
||||
}
|
||||
|
||||
.status-option:active {
|
||||
background-color: #e7e9eb;
|
||||
}
|
||||
|
||||
.status-option text {
|
||||
margin-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 编辑表单 */
|
||||
.edit-form {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.t-input, .t-textarea {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// pages/device_status/index.js
|
||||
const { deviceApi } = require("../../utils/api");
|
||||
|
||||
Page({
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
devices: [],
|
||||
statusMap: {
|
||||
0: "空闲",
|
||||
1: "使用中",
|
||||
2: "维修中",
|
||||
},
|
||||
loading: true, // 添加加载状态
|
||||
activeDevices: [], // 当前展开的设备面板,空数组表示全部收起
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
// 检查用户是否已登录
|
||||
const app = getApp();
|
||||
if (app.globalData.token) {
|
||||
// 已登录用户,跳转到设备管理页面
|
||||
wx.redirectTo({
|
||||
url: '/pages/device_management/index',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchDeviceStatus();
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取设备状态数据
|
||||
*/
|
||||
fetchDeviceStatus() {
|
||||
const that = this;
|
||||
|
||||
// 保存当前展开状态
|
||||
const currentActiveDevices = this.data.activeDevices || [];
|
||||
|
||||
// 显示加载中
|
||||
that.setData({ loading: true });
|
||||
|
||||
deviceApi
|
||||
.getDeviceStatus()
|
||||
.then((response) => {
|
||||
// 处理API响应格式,获取设备数组
|
||||
const devices = response.data || [];
|
||||
|
||||
// 处理设备状态的显示文本
|
||||
devices.forEach((device) => {
|
||||
if (device.sub_devices && device.sub_devices.length) {
|
||||
device.sub_devices.forEach((subDevice) => {
|
||||
subDevice.statusText = that.data.statusMap[subDevice.status] || subDevice.status;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 保持之前的展开状态
|
||||
that.setData({
|
||||
devices,
|
||||
activeDevices: currentActiveDevices, // 保持之前的展开状态
|
||||
loading: false,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
wx.showToast({
|
||||
title: "获取设备状态失败",
|
||||
icon: "error",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
// 停止下拉刷新动画
|
||||
wx.stopPullDownRefresh();
|
||||
that.setData({ loading: false });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理折叠面板展开/收起事件
|
||||
*/
|
||||
handleCollapseChange(e) {
|
||||
this.setData({
|
||||
activeDevices: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 跳转到保养记录页面
|
||||
*/
|
||||
goToMaintenancePage(e) {
|
||||
const subDeviceId = e.currentTarget.dataset.subDeviceId;
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_maintenance/index?sub_device_id=${subDeviceId}`,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.fetchDeviceStatus();
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: "设备状态监控",
|
||||
path: "/pages/device_status/index",
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"navigationBarTitleText": "设备状态监控",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f6f6f6",
|
||||
"usingComponents": {
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
|
||||
"t-navbar": "tdesign-miniprogram/navbar/navbar",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-collapse": "tdesign-miniprogram/collapse/collapse",
|
||||
"t-collapse-panel": "tdesign-miniprogram/collapse-panel/collapse-panel"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<view class="device-page">
|
||||
<view class="device-container">
|
||||
<!-- 设备列表 -->
|
||||
<block wx:if="{{devices.length > 0}}">
|
||||
<t-collapse value="{{activeDevices}}" expandMutex="{{false}}" bind:change="handleCollapseChange">
|
||||
<t-collapse-panel
|
||||
wx:for="{{devices}}"
|
||||
wx:key="device_id"
|
||||
wx:for-item="device"
|
||||
value="{{index}}"
|
||||
header="{{device.device_name}}"
|
||||
header-right-content="{{device.sub_devices.length || 0}}个子设备"
|
||||
t-class="device-panel">
|
||||
|
||||
<!-- 设备图标 -->
|
||||
<view slot="header-icon" class="device-icon">
|
||||
<t-icon name="device" size="48rpx" />
|
||||
</view>
|
||||
|
||||
<!-- 子设备列表 -->
|
||||
<block wx:if="{{device.sub_devices && device.sub_devices.length > 0}}">
|
||||
<t-cell
|
||||
wx:for="{{device.sub_devices}}"
|
||||
wx:key="id"
|
||||
wx:for-item="subDevice"
|
||||
title="{{device.device_name}}-{{subDevice.name}}"
|
||||
description="子设备"
|
||||
hover
|
||||
t-class="sub-device-cell"
|
||||
bordered>
|
||||
<!-- 状态标签和保养记录按钮 -->
|
||||
<view slot="note" class="note-container">
|
||||
<t-tag
|
||||
theme="{{subDevice.status === 0 ? 'success' : (subDevice.status === 1 ? 'primary' : 'warning')}}"
|
||||
variant="light"
|
||||
size="small">
|
||||
{{subDevice.statusText}}
|
||||
</t-tag>
|
||||
</view>
|
||||
</t-cell>
|
||||
</block>
|
||||
|
||||
<!-- 无子设备时的提示 -->
|
||||
<t-empty wx:else description="暂无子设备" />
|
||||
</t-collapse-panel>
|
||||
</t-collapse>
|
||||
</block>
|
||||
|
||||
<!-- 无设备时的提示 -->
|
||||
<t-empty wx:else icon="device" description="暂无设备数据" />
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,48 @@
|
||||
/* pages/device_status/index.wxss */
|
||||
|
||||
.device-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.device-container {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.device-panel {
|
||||
margin-bottom: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.device-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 16rpx;
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.sub-device-cell {
|
||||
padding: 20rpx 32rpx;
|
||||
}
|
||||
|
||||
.status-tag-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 适配不同状态的标签样式 */
|
||||
.t-tag--success {
|
||||
color: #07c160 !important;
|
||||
}
|
||||
|
||||
.t-tag--primary {
|
||||
color: #0052d9 !important;
|
||||
}
|
||||
|
||||
.t-tag--warning {
|
||||
color: #ff9702 !important;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
const { deviceApi } = require("../../utils/api");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
device_id: null,
|
||||
device: null,
|
||||
subDevices: [],
|
||||
loading: true,
|
||||
loadError: false,
|
||||
|
||||
// 子设备状态相关
|
||||
statusDialogVisible: false,
|
||||
selectedStatus: 0,
|
||||
currentSubDeviceForStatus: null,
|
||||
|
||||
deviceStatusMap: {
|
||||
0: "空闲",
|
||||
1: "使用中",
|
||||
2: "维护中",
|
||||
},
|
||||
|
||||
// 状态选择选项
|
||||
statusOptions: [
|
||||
{ value: 0, label: "空闲", color: "#06a56c" },
|
||||
{ value: 1, label: "使用中", color: "#1890ff" },
|
||||
{ value: 2, label: "维护中", color: "#fa8c16" },
|
||||
],
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
if (options.device_id) {
|
||||
this.setData({ device_id: options.device_id });
|
||||
this.loadDeviceInfo();
|
||||
this.loadSubDevices();
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: "缺少设备ID",
|
||||
icon: "error",
|
||||
});
|
||||
wx.navigateBack();
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 页面显示时刷新数据
|
||||
if (this.data.device_id) {
|
||||
this.loadSubDevices();
|
||||
}
|
||||
},
|
||||
|
||||
// 加载设备信息
|
||||
async loadDeviceInfo() {
|
||||
try {
|
||||
const response = await deviceApi.getDevices();
|
||||
const deviceList = response.data.list || response.data || [];
|
||||
const device = deviceList.find((d) => d.id === parseInt(this.data.device_id));
|
||||
|
||||
if (device) {
|
||||
this.setData({ device });
|
||||
}
|
||||
} catch (error) {
|
||||
// 加载设备信息失败,继续执行
|
||||
}
|
||||
},
|
||||
|
||||
// 加载子设备列表
|
||||
async loadSubDevices() {
|
||||
this.setData({ loading: true, loadError: false });
|
||||
|
||||
try {
|
||||
const response = await deviceApi.getSubDevicesByDeviceId(this.data.device_id);
|
||||
const subDevices = response.data.list || response.data || [];
|
||||
|
||||
// 为每个子设备添加状态信息
|
||||
const formattedSubDevices = subDevices.map((subDevice) => ({
|
||||
...subDevice,
|
||||
status: subDevice.status !== undefined ? subDevice.status : 0,
|
||||
statusText: this.data.deviceStatusMap[subDevice.status !== undefined ? subDevice.status : 0],
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
subDevices: formattedSubDevices,
|
||||
loading: false,
|
||||
});
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
loadError: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 显示状态更改对话框
|
||||
showStatusDialog(e) {
|
||||
const subDevice = e.currentTarget.dataset.subdevice;
|
||||
this.setData({
|
||||
statusDialogVisible: true,
|
||||
selectedStatus: subDevice.status !== undefined ? subDevice.status : 0,
|
||||
currentSubDeviceForStatus: subDevice,
|
||||
});
|
||||
},
|
||||
|
||||
// 关闭状态对话框
|
||||
closeStatusDialog() {
|
||||
this.setData({
|
||||
statusDialogVisible: false,
|
||||
selectedStatus: 0,
|
||||
currentSubDeviceForStatus: null,
|
||||
});
|
||||
},
|
||||
|
||||
// 选择状态
|
||||
selectStatus(e) {
|
||||
const status = parseInt(e.currentTarget.dataset.status);
|
||||
this.setData({
|
||||
selectedStatus: status,
|
||||
});
|
||||
},
|
||||
|
||||
// 确认状态更改
|
||||
async confirmStatusChange() {
|
||||
const { selectedStatus, currentSubDeviceForStatus } = this.data;
|
||||
|
||||
try {
|
||||
await deviceApi.updateSubDevice(currentSubDeviceForStatus.id, {
|
||||
status: selectedStatus,
|
||||
name: currentSubDeviceForStatus.name,
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: "状态更新成功",
|
||||
icon: "success",
|
||||
});
|
||||
|
||||
this.closeStatusDialog();
|
||||
this.loadSubDevices();
|
||||
} catch (error) {
|
||||
wx.showToast({
|
||||
title: "状态更新失败",
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 查看子设备保养记录
|
||||
viewMaintenance(e) {
|
||||
const subDevice = e.currentTarget.dataset.subdevice;
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_maintenance/index?sub_device_id=${subDevice.id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 添加保养记录
|
||||
addMaintenance(e) {
|
||||
const subDevice = e.currentTarget.dataset.subdevice;
|
||||
wx.navigateTo({
|
||||
url: `/pages/device_maintenance/create/index?sub_device_id=${subDevice.id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
onBack() {
|
||||
wx.navigateBack();
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadSubDevices()
|
||||
.then(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
})
|
||||
.catch(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"navigationBarTitleText": "子设备管理",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f7f8fa",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"t-toast": "tdesign-miniprogram/toast/toast",
|
||||
"t-dialog": "tdesign-miniprogram/dialog/dialog",
|
||||
"t-skeleton": "tdesign-miniprogram/skeleton/skeleton",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<view class="subdevices-page">
|
||||
<!-- 页面头部 -->
|
||||
<view class="page-header">
|
||||
<view class="device-info">
|
||||
<view class="device-name">{{device.name}}</view>
|
||||
<view class="device-meta">
|
||||
<text class="subdevice-count">子设备: {{subDevices.length}}个</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<block wx:if="{{loading}}">
|
||||
<view class="loading-container">
|
||||
<t-skeleton theme="paragraph" loading />
|
||||
<t-skeleton theme="paragraph" loading />
|
||||
<t-skeleton theme="paragraph" loading />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<block wx:elif="{{loadError}}">
|
||||
<view class="error-container">
|
||||
<t-empty icon="error-circle" description="加载失败,请下拉刷新重试" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 子设备列表 -->
|
||||
<block wx:elif="{{subDevices.length > 0}}">
|
||||
<view class="subdevices-list">
|
||||
<view
|
||||
wx:for="{{subDevices}}"
|
||||
wx:key="id"
|
||||
class="subdevice-card">
|
||||
<view class="card-header">
|
||||
<view class="subdevice-info">
|
||||
<view class="subdevice-name">{{item.name}}</view>
|
||||
<view class="subdevice-meta">
|
||||
<text class="subdevice-status">
|
||||
状态: <text class="status-badge status-{{item.status}}">{{item.statusText}}</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="usage-info">
|
||||
<text class="usage-count">{{item.usage_count || 0}}次</text>
|
||||
<text class="usage-label">使用</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{item.description}}" class="subdevice-desc">
|
||||
<text>{{item.description}}</text>
|
||||
</view>
|
||||
|
||||
<view class="card-actions">
|
||||
<t-button
|
||||
size="small"
|
||||
variant="outline"
|
||||
bind:tap="viewMaintenance"
|
||||
data-subdevice="{{item}}">
|
||||
保养记录
|
||||
</t-button>
|
||||
<t-button
|
||||
size="small"
|
||||
variant="outline"
|
||||
bind:tap="addMaintenance"
|
||||
data-subdevice="{{item}}">
|
||||
添加保养
|
||||
</t-button>
|
||||
<t-button
|
||||
size="small"
|
||||
variant="outline"
|
||||
bind:tap="showStatusDialog"
|
||||
data-subdevice="{{item}}">
|
||||
更改状态
|
||||
</t-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<block wx:else>
|
||||
<view class="empty-container">
|
||||
<t-empty icon="device" description="暂无子设备" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
|
||||
<!-- 状态更改对话框 -->
|
||||
<view class="dialog-mask" wx:if="{{statusDialogVisible}}" bindtap="closeStatusDialog">
|
||||
<view class="dialog-container" catchtap="">
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">更改子设备状态</text>
|
||||
<text class="dialog-close" bindtap="closeStatusDialog">×</text>
|
||||
</view>
|
||||
|
||||
<view class="dialog-body">
|
||||
<view class="status-current">
|
||||
<text>当前状态:{{currentSubDeviceForStatus ? deviceStatusMap[currentSubDeviceForStatus.status] : '未知'}}</text>
|
||||
</view>
|
||||
|
||||
<view class="status-options">
|
||||
<view
|
||||
wx:for="{{statusOptions}}"
|
||||
wx:key="value"
|
||||
class="status-option {{selectedStatus === item.value ? 'selected' : ''}}"
|
||||
catchtap="selectStatus"
|
||||
data-status="{{item.value}}">
|
||||
<text class="status-dot" style="background-color: {{item.color}}"></text>
|
||||
<text class="status-label">{{item.label}}</text>
|
||||
<text class="status-check" wx:if="{{selectedStatus === item.value}}">✓</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="dialog-footer">
|
||||
<button class="dialog-btn cancel-btn" bindtap="closeStatusDialog">取消</button>
|
||||
<button class="dialog-btn confirm-btn" bindtap="confirmStatusChange">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,340 @@
|
||||
.subdevices-page {
|
||||
padding: 0;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.device-meta {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.subdevice-count {
|
||||
color: #0052d9;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 加载和错误状态 */
|
||||
.loading-container,
|
||||
.error-container,
|
||||
.empty-container {
|
||||
padding: 100rpx 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 子设备列表 */
|
||||
.subdevices-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.subdevice-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.subdevice-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.subdevice-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.subdevice-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.subdevice-id {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.subdevice-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 24rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.status-0 {
|
||||
color: #06a56c;
|
||||
background-color: #e8f6f1;
|
||||
}
|
||||
|
||||
.status-1 {
|
||||
color: #1890ff;
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
|
||||
.status-2 {
|
||||
color: #fa8c16;
|
||||
background-color: #fff7e6;
|
||||
}
|
||||
|
||||
.usage-info {
|
||||
text-align: center;
|
||||
padding: 12rpx;
|
||||
background: #f0f2f5;
|
||||
border-radius: 8rpx;
|
||||
min-width: 100rpx;
|
||||
}
|
||||
|
||||
.usage-count {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.usage-label {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.subdevice-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 12rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.card-actions .t-button {
|
||||
flex: 1;
|
||||
min-width: 120rpx;
|
||||
font-size: 24rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
}
|
||||
|
||||
/* 状态选择器 */
|
||||
.status-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.status-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f7f8fa;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.status-option.selected {
|
||||
background-color: #eef4ff;
|
||||
border-color: #0052d9;
|
||||
}
|
||||
|
||||
.status-option:active {
|
||||
background-color: #e7e9eb;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
margin-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-check {
|
||||
position: absolute;
|
||||
right: 32rpx;
|
||||
color: #0052d9;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 自定义弹窗样式 */
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
width: 600rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
animation: slideUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100rpx);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dialog-close:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 30rpx;
|
||||
max-height: 600rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.dialog-btn {
|
||||
flex: 1;
|
||||
height: 100rpx;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-size: 28rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.dialog-btn:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
color: #666;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
color: #0052d9;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 弹窗内容区域 */
|
||||
.dialog-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.status-current {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 16rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6rpx;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
// pages/edit-profile/index.js
|
||||
const { userApi } = require("../../utils/api");
|
||||
const AuthUtil = require("../../utils/auth");
|
||||
|
||||
Page({
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
username: '',
|
||||
nickname: '',
|
||||
originalUsername: '',
|
||||
originalNickname: '',
|
||||
loading: false,
|
||||
// 密码相关
|
||||
hasPassword: false, // 用户是否已设置密码
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadUserInfo();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
// 每次显示时刷新用户信息
|
||||
this.loadUserInfo();
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载用户信息
|
||||
*/
|
||||
loadUserInfo() {
|
||||
const userInfo = AuthUtil.getUserInfo();
|
||||
|
||||
// 对于编辑页面,为了确保has_password字段的准确性,总是从服务器获取最新信息
|
||||
// 但先使用本地信息快速显示,避免页面闪烁
|
||||
if (userInfo && userInfo.username) {
|
||||
this.setData({
|
||||
username: userInfo.username || '',
|
||||
nickname: userInfo.nickname || '',
|
||||
originalUsername: userInfo.username || '',
|
||||
originalNickname: userInfo.nickname || '',
|
||||
hasPassword: !!(userInfo.has_password), // 使用本地缓存的has_password
|
||||
});
|
||||
}
|
||||
|
||||
// 总是从服务器获取最新信息,确保has_password字段准确
|
||||
this.fetchUserInfo();
|
||||
},
|
||||
|
||||
/**
|
||||
* 从服务器获取用户信息
|
||||
*/
|
||||
fetchUserInfo() {
|
||||
wx.showLoading({ title: '加载中...' });
|
||||
|
||||
userApi.getCurrentUser()
|
||||
.then(res => {
|
||||
if ((res.code === 1 || res.code === 200) && res.data && res.data.user) {
|
||||
const user = res.data.user;
|
||||
this.setData({
|
||||
username: user.username || '',
|
||||
nickname: user.nickname || '',
|
||||
originalUsername: user.username || '',
|
||||
originalNickname: user.nickname || '',
|
||||
hasPassword: !!(user.has_password || user.password), // 判断用户是否已设置密码
|
||||
});
|
||||
|
||||
// 更新本地存储
|
||||
AuthUtil.setUserInfo(user);
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '获取用户信息失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
wx.showToast({
|
||||
title: '获取用户信息失败',
|
||||
icon: 'none'
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
wx.hideLoading();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户名输入变化
|
||||
*/
|
||||
onUsernameChange(e) {
|
||||
this.setData({
|
||||
username: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 昵称输入变化
|
||||
*/
|
||||
onNicknameChange(e) {
|
||||
this.setData({
|
||||
nickname: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 当前密码输入变化
|
||||
*/
|
||||
onOldPasswordChange(e) {
|
||||
this.setData({
|
||||
oldPassword: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 新密码输入变化
|
||||
*/
|
||||
onNewPasswordChange(e) {
|
||||
this.setData({
|
||||
newPassword: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 确认密码输入变化
|
||||
*/
|
||||
onConfirmPasswordChange(e) {
|
||||
this.setData({
|
||||
confirmPassword: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 验证密码输入
|
||||
*/
|
||||
validatePassword() {
|
||||
const { hasPassword, oldPassword, newPassword, confirmPassword } = this.data;
|
||||
|
||||
// 如果没有输入新密码,说明不想修改密码
|
||||
if (!newPassword.trim() && !confirmPassword.trim()) {
|
||||
return { valid: true, changePassword: false };
|
||||
}
|
||||
|
||||
// 如果用户已有密码,必须输入当前密码
|
||||
if (hasPassword && !oldPassword.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入当前密码',
|
||||
icon: 'none'
|
||||
});
|
||||
return { valid: false, changePassword: false };
|
||||
}
|
||||
|
||||
// 新密码长度检查
|
||||
if (newPassword.length < 6 || newPassword.length > 50) {
|
||||
wx.showToast({
|
||||
title: '新密码长度必须在6-50字符之间',
|
||||
icon: 'none'
|
||||
});
|
||||
return { valid: false, changePassword: false };
|
||||
}
|
||||
|
||||
// 确认密码检查
|
||||
if (newPassword !== confirmPassword) {
|
||||
wx.showToast({
|
||||
title: '确认密码与新密码不一致',
|
||||
icon: 'none'
|
||||
});
|
||||
return { valid: false, changePassword: false };
|
||||
}
|
||||
|
||||
return { valid: true, changePassword: true };
|
||||
},
|
||||
|
||||
/**
|
||||
* 验证输入
|
||||
*/
|
||||
validateInput() {
|
||||
const { username, nickname } = this.data;
|
||||
|
||||
if (!username.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入用户名',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (username.length < 3 || username.length > 50) {
|
||||
wx.showToast({
|
||||
title: '用户名长度必须在3-50字符之间',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查用户名格式(只能包含字母和数字)
|
||||
const usernameRegex = /^[a-zA-Z0-9]+$/;
|
||||
if (!usernameRegex.test(username)) {
|
||||
wx.showToast({
|
||||
title: '用户名只能包含字母和数字',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!nickname.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入昵称',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nickname.length < 2 || nickname.length > 50) {
|
||||
wx.showToast({
|
||||
title: '昵称长度必须在2-50字符之间',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查是否有修改
|
||||
*/
|
||||
hasChanges() {
|
||||
const { username, nickname, originalUsername, originalNickname, newPassword } = this.data;
|
||||
return username !== originalUsername ||
|
||||
nickname !== originalNickname ||
|
||||
newPassword.trim() !== '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存用户信息修改
|
||||
*/
|
||||
saveProfileChanges() {
|
||||
const { username, nickname } = this.data;
|
||||
|
||||
return userApi.updateProfile({
|
||||
username: username.trim(),
|
||||
nickname: nickname.trim()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存密码修改
|
||||
*/
|
||||
savePasswordChanges() {
|
||||
const { hasPassword, oldPassword, newPassword, confirmPassword } = this.data;
|
||||
|
||||
return userApi.changePassword(
|
||||
hasPassword ? oldPassword : '',
|
||||
newPassword,
|
||||
confirmPassword
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存修改
|
||||
*/
|
||||
handleSave() {
|
||||
if (!this.validateInput()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordValidation = this.validatePassword();
|
||||
if (!passwordValidation.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.hasChanges()) {
|
||||
wx.showToast({
|
||||
title: '没有修改内容',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ loading: true });
|
||||
|
||||
// 检查是否需要修改个人信息
|
||||
const { username, nickname, originalUsername, originalNickname } = this.data;
|
||||
const profileChanged = username !== originalUsername || nickname !== originalNickname;
|
||||
|
||||
// 需要执行的保存操作
|
||||
const saveOperations = [];
|
||||
|
||||
if (profileChanged) {
|
||||
saveOperations.push(this.saveProfileChanges());
|
||||
}
|
||||
|
||||
if (passwordValidation.changePassword) {
|
||||
saveOperations.push(this.savePasswordChanges());
|
||||
}
|
||||
|
||||
// 执行所有保存操作
|
||||
Promise.all(saveOperations)
|
||||
.then(results => {
|
||||
// 检查所有操作是否都成功(支持code: 1和code: 200)
|
||||
const allSuccess = results.every(res => res.code === 1 || res.code === 200);
|
||||
|
||||
if (allSuccess) {
|
||||
wx.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 如果有个人信息更新,更新本地用户信息
|
||||
const profileResult = results.find(res => res.data && res.data.user);
|
||||
if (profileResult) {
|
||||
AuthUtil.setUserInfo(profileResult.data.user);
|
||||
}
|
||||
|
||||
// 延时返回上一页
|
||||
setTimeout(() => {
|
||||
wx.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
// 找出失败的操作
|
||||
const failedResults = results.filter(res => res.code !== 1);
|
||||
const errorMsg = failedResults.map(res => res.msg).join(';');
|
||||
wx.showToast({
|
||||
title: errorMsg || '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
wx.showToast({
|
||||
title: err.message || '保存失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.setData({ loading: false });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消修改
|
||||
*/
|
||||
handleCancel() {
|
||||
if (this.hasChanges()) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '确定要放弃修改吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.navigateBack();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
wx.navigateBack();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面卸载时的处理
|
||||
*/
|
||||
onUnload() {
|
||||
// 如果有未保存的修改,可以在这里提示用户
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "编辑个人信息",
|
||||
"navigationBarBackgroundColor": "#0052d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<!-- 编辑个人信息页面 -->
|
||||
<view class="edit-profile-container">
|
||||
|
||||
<view class="form-section">
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text class="label-text">用户名</text>
|
||||
</view>
|
||||
<input
|
||||
class="form-input"
|
||||
placeholder="请输入用户名"
|
||||
value="{{username}}"
|
||||
bindinput="onUsernameChange"
|
||||
maxlength="50"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text class="label-text">昵称</text>
|
||||
</view>
|
||||
<input
|
||||
class="form-input"
|
||||
placeholder="请输入昵称"
|
||||
value="{{nickname}}"
|
||||
bindinput="onNicknameChange"
|
||||
maxlength="50"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="tips">
|
||||
<text class="tips-text">用户名只能包含字母和数字,长度3-50字符</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 密码修改区域 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">
|
||||
<text>修改密码</text>
|
||||
<text class="section-subtitle">{{hasPassword ? '如不修改请留空' : '微信登录用户可设置密码'}}</text>
|
||||
</view>
|
||||
|
||||
<view class="form-item" wx:if="{{hasPassword}}">
|
||||
<view class="form-label">
|
||||
<text class="label-text">当前密码</text>
|
||||
</view>
|
||||
<input
|
||||
class="form-input"
|
||||
type="password"
|
||||
placeholder="请输入当前密码"
|
||||
value="{{oldPassword}}"
|
||||
bindinput="onOldPasswordChange"
|
||||
maxlength="50"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text class="label-text">新密码</text>
|
||||
</view>
|
||||
<input
|
||||
class="form-input"
|
||||
type="password"
|
||||
placeholder="{{hasPassword ? '请输入新密码' : '请设置密码'}}"
|
||||
value="{{newPassword}}"
|
||||
bindinput="onNewPasswordChange"
|
||||
maxlength="50"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text class="label-text">确认密码</text>
|
||||
</view>
|
||||
<input
|
||||
class="form-input"
|
||||
type="password"
|
||||
placeholder="请再次输入密码确认"
|
||||
value="{{confirmPassword}}"
|
||||
bindinput="onConfirmPasswordChange"
|
||||
maxlength="50"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="tips">
|
||||
<text class="tips-text">密码长度6-50字符,建议包含字母和数字</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="button-section">
|
||||
<button
|
||||
class="save-btn {{loading ? 'loading' : ''}}"
|
||||
bindtap="handleSave"
|
||||
disabled="{{loading}}"
|
||||
>
|
||||
{{loading ? '保存中...' : '保存修改'}}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="cancel-btn"
|
||||
bindtap="handleCancel"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,128 @@
|
||||
/* pages/edit-profile/index.wxss */
|
||||
.edit-profile-container {
|
||||
padding: 30rpx;
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.form-section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #0052d9;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 30rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
border-left: 6rpx solid #0052d9;
|
||||
}
|
||||
|
||||
.tips-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 30rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.section-title text:first-child {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.button-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background-color: #0052d9;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.save-btn.loading {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.save-btn:not(.loading):active {
|
||||
background-color: #003ba3;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cancel-btn:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// pages/home/index.js
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<!--pages/home/index.wxml-->
|
||||
<text>pages/home/index.wxml</text>
|
||||
@@ -0,0 +1 @@
|
||||
/* pages/home/index.wxss */
|
||||
@@ -0,0 +1,356 @@
|
||||
// pages/login/index.js
|
||||
const { userApi } = require("../../utils/api");
|
||||
const AuthUtil = require("../../utils/auth");
|
||||
|
||||
Page({
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
username: "",
|
||||
password: "",
|
||||
loading: false,
|
||||
wechatLoading: false, // 微信登录加载状态
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
// 检查是否已登录
|
||||
this.checkLoginStatus();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
// 每次页面显示时也检查登录状态
|
||||
this.checkLoginStatus();
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
// 获取全局应用实例
|
||||
const app = getApp();
|
||||
|
||||
// 如果全局状态显示已登录,则自动跳转到首页
|
||||
if (app.globalData.isLoggedIn && app.globalData.userInfo) {
|
||||
wx.switchTab({
|
||||
url: "/pages/surgery/index"
|
||||
});
|
||||
} else {
|
||||
// 检查是否有token
|
||||
if (AuthUtil.hasToken()) {
|
||||
this.autoLogin();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 尝试自动登录
|
||||
async autoLogin() {
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
const isValid = await AuthUtil.validateUser();
|
||||
|
||||
if (isValid) {
|
||||
const userInfo = AuthUtil.getUserInfo();
|
||||
|
||||
// 立即更新全局状态
|
||||
const app = getApp();
|
||||
app.globalData.isLoggedIn = true;
|
||||
app.globalData.userInfo = userInfo;
|
||||
|
||||
// 显示提示
|
||||
wx.showToast({
|
||||
title: "自动登录成功",
|
||||
icon: "success",
|
||||
duration: 1000
|
||||
});
|
||||
|
||||
// 启动token自动刷新
|
||||
AuthUtil.startTokenRefresh();
|
||||
|
||||
// 立即跳转到首页
|
||||
// 先尝试隐藏 toast,避免影响跳转
|
||||
wx.hideToast();
|
||||
|
||||
wx.switchTab({
|
||||
url: "/pages/surgery/index",
|
||||
success: () => {
|
||||
},
|
||||
fail: (err) => {
|
||||
// 如果 switchTab 失败,尝试使用 reLaunch
|
||||
wx.reLaunch({
|
||||
url: "/pages/surgery/index",
|
||||
success: () => {
|
||||
},
|
||||
fail: (reLaunchErr) => {
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 自动登录失败,已清除登录状态
|
||||
}
|
||||
} catch (err) {
|
||||
// 自动登录异常
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
// 用户名输入变化
|
||||
onUsernameChange(e) {
|
||||
this.setData({
|
||||
username: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
// 密码输入变化
|
||||
onPasswordChange(e) {
|
||||
this.setData({
|
||||
password: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到设备状态页面
|
||||
goToDeviceStatus() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/device_status/index',
|
||||
});
|
||||
},
|
||||
|
||||
// 微信一键登录
|
||||
handleWechatLogin() {
|
||||
this.setData({ wechatLoading: true });
|
||||
|
||||
// 调用微信登录
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
if (res.code) {
|
||||
// 获取用户信息(可选)
|
||||
wx.getUserProfile({
|
||||
desc: '用于完善用户资料',
|
||||
success: (userRes) => {
|
||||
// 调用后端API进行微信登录
|
||||
this.performWechatLogin(res.code, userRes.userInfo.nickName);
|
||||
},
|
||||
fail: () => {
|
||||
// 用户拒绝授权,使用code进行登录(不带昵称)
|
||||
this.performWechatLogin(res.code);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '获取微信授权失败',
|
||||
icon: 'none'
|
||||
});
|
||||
this.setData({ wechatLoading: false });
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.showToast({
|
||||
title: '微信登录调用失败',
|
||||
icon: 'none'
|
||||
});
|
||||
this.setData({ wechatLoading: false });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 执行微信登录
|
||||
performWechatLogin(code, nickname = null) {
|
||||
userApi.miniLogin(code, nickname)
|
||||
.then((res) => {
|
||||
if (res.code === 1 || res.code === 200) {
|
||||
// 登录成功,保存token和用户信息
|
||||
const { token, user } = res.data;
|
||||
|
||||
AuthUtil.setToken(token);
|
||||
AuthUtil.setUserInfo(user);
|
||||
|
||||
// 启动token自动刷新
|
||||
AuthUtil.startTokenRefresh();
|
||||
|
||||
// 立即更新全局状态
|
||||
const app = getApp();
|
||||
app.globalData.isLoggedIn = true;
|
||||
app.globalData.userInfo = user;
|
||||
|
||||
wx.showToast({
|
||||
title: "微信登录成功",
|
||||
icon: "success",
|
||||
duration: 1000
|
||||
});
|
||||
|
||||
// 立即跳转到首页
|
||||
|
||||
// 先尝试隐藏 toast,避免影响跳转
|
||||
wx.hideToast();
|
||||
|
||||
wx.switchTab({
|
||||
url: "/pages/surgery/index",
|
||||
success: () => {
|
||||
},
|
||||
fail: (err) => {
|
||||
// 如果 switchTab 失败,尝试使用 reLaunch
|
||||
wx.reLaunch({
|
||||
url: "/pages/surgery/index",
|
||||
success: () => {
|
||||
},
|
||||
fail: (reLaunchErr) => {
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 登录失败
|
||||
wx.showToast({
|
||||
title: res.msg || "微信登录失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
wx.showToast({
|
||||
title: err.message || "微信登录失败,请稍后重试",
|
||||
icon: "none",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.setData({ wechatLoading: false });
|
||||
});
|
||||
},
|
||||
|
||||
// 处理登录
|
||||
handleLogin() {
|
||||
const { username, password } = this.data;
|
||||
|
||||
// 基本表单验证
|
||||
if (!username.trim()) {
|
||||
wx.showToast({
|
||||
title: "请输入用户名",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password.trim()) {
|
||||
wx.showToast({
|
||||
title: "请输入密码",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
this.setData({ loading: true });
|
||||
|
||||
|
||||
// 调用登录API
|
||||
userApi
|
||||
.login(username, password)
|
||||
.then((res) => {
|
||||
// 检查登录是否成功(支持 code: 1 或 code: 200)
|
||||
if (res.code !== 1 && res.code !== 200) {
|
||||
wx.showToast({
|
||||
title: res.msg || "用户名或密码错误",
|
||||
icon: "none",
|
||||
});
|
||||
this.setData({ loading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户状态(小程序允许状态>=1的用户登录)
|
||||
if (res.data.user.user_status < 1) {
|
||||
wx.showToast({
|
||||
title: "您的账户已被禁用或需要等待管理员审核",
|
||||
icon: "none",
|
||||
});
|
||||
this.setData({ loading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
// 登录成功,保存token
|
||||
const { token, user } = res.data;
|
||||
|
||||
AuthUtil.setToken(token);
|
||||
AuthUtil.setUserInfo(user);
|
||||
|
||||
// 启动token自动刷新
|
||||
AuthUtil.startTokenRefresh();
|
||||
|
||||
// 立即更新全局状态
|
||||
const app = getApp();
|
||||
app.globalData.isLoggedIn = true;
|
||||
app.globalData.userInfo = user;
|
||||
|
||||
wx.showToast({
|
||||
title: "登录成功",
|
||||
icon: "success",
|
||||
duration: 1000
|
||||
});
|
||||
|
||||
// 登录成功后立即导航到首页
|
||||
// 先尝试隐藏 toast,避免影响跳转
|
||||
wx.hideToast();
|
||||
|
||||
wx.switchTab({
|
||||
url: "/pages/surgery/index",
|
||||
success: () => {
|
||||
},
|
||||
fail: (err) => {
|
||||
// 如果 switchTab 失败,尝试使用 reLaunch
|
||||
wx.reLaunch({
|
||||
url: "/pages/surgery/index",
|
||||
success: () => {
|
||||
},
|
||||
fail: (reLaunchErr) => {
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
wx.showToast({
|
||||
title: err.message || "登录失败,请稍后重试",
|
||||
icon: "none",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.setData({ loading: false });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {},
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "登录",
|
||||
"usingComponents": {
|
||||
"t-input": "tdesign-miniprogram/input/input",
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<!-- 登录页面 -->
|
||||
<view class="login-container">
|
||||
<view class="login-header">
|
||||
<image class="logo" mode="aspectFit"></image>
|
||||
<view class="title">医疗系统登录</view>
|
||||
</view>
|
||||
|
||||
<view class="login-form">
|
||||
<!-- 微信一键登录区域 -->
|
||||
<view class="wechat-login-section">
|
||||
<t-button theme="primary" size="large" block bindtap="handleWechatLogin" loading="{{wechatLoading}}" icon="user">
|
||||
微信一键登录
|
||||
</t-button>
|
||||
|
||||
<view class="divider">
|
||||
<view class="divider-line"></view>
|
||||
<view class="divider-text">或</view>
|
||||
<view class="divider-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 传统登录表单 -->
|
||||
<t-cell-group>
|
||||
<t-input label="账号" placeholder="请输入账号" value="{{username}}" bindchange="onUsernameChange" clearable>
|
||||
<t-icon slot="left-icon" name="user" size="48rpx" />
|
||||
</t-input>
|
||||
|
||||
<t-input label="密码" placeholder="请输入密码" value="{{password}}" bindchange="onPasswordChange" type="password" clearable>
|
||||
<t-icon slot="left-icon" name="lock-on" size="48rpx" />
|
||||
</t-input>
|
||||
</t-cell-group>
|
||||
|
||||
<view class="btn-wrapper">
|
||||
<t-button theme="default" size="large" block bindtap="handleLogin" loading="{{loading}}">账号密码登录</t-button>
|
||||
</view>
|
||||
|
||||
<!-- 新增设备状态按钮 -->
|
||||
<view class="device-status-btn">
|
||||
<t-button theme="light" size="small" icon="device" variant="outline" bindtap="goToDeviceStatus">设备状态</t-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,97 @@
|
||||
/* pages/login/index.wxss */
|
||||
page {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
padding: 30rpx;
|
||||
background-color: #f6f6f6;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 8vh; /* 从12vh减小到8vh,使整体内容上移 */
|
||||
margin-bottom: 50rpx; /* 稍微减小下边距 */
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 200rpx; /* 进一步增大logo尺寸 */
|
||||
height: 200rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 56rpx; /* 将标题字体从48rpx增大到56rpx */
|
||||
font-weight: bold;
|
||||
color: #0052d9; /* 使用TDesign的主题蓝色,与登录按钮颜色一致 */
|
||||
letter-spacing: 4rpx; /* 增加字间距 */
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1); /* 添加轻微阴影,增强层次感 */
|
||||
}
|
||||
|
||||
.login-form {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx 20rpx; /* 增加内边距 */
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 30rpx;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* 微信登录区域 */
|
||||
.wechat-login-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 分割线样式 */
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 30rpx 0;
|
||||
}
|
||||
|
||||
.divider-line {
|
||||
flex: 1;
|
||||
height: 1rpx;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.divider-text {
|
||||
padding: 0 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
margin-top: 60rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
/* 设备状态按钮样式 */
|
||||
.device-status-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.device-status-btn button {
|
||||
font-size: 24rpx !important;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
position: absolute;
|
||||
bottom: 30rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// pages/mine/index.js
|
||||
const app = getApp();
|
||||
const AuthUtil = require("../../utils/auth");
|
||||
|
||||
Page({
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
userInfo: {},
|
||||
isLoggedIn: false,
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
// 每次页面显示时更新用户信息
|
||||
this.updateUserInfo();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {},
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
updateUserInfo() {
|
||||
// 从认证工具获取用户信息
|
||||
const userInfo = AuthUtil.getUserInfo();
|
||||
const isLoggedIn = AuthUtil.hasToken() && app.globalData.isLoggedIn;
|
||||
|
||||
this.setData({
|
||||
userInfo: userInfo || {},
|
||||
isLoggedIn: isLoggedIn,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 跳转到编辑个人信息页面
|
||||
*/
|
||||
goToEditProfile() {
|
||||
if (!this.data.isLoggedIn) {
|
||||
wx.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
wx.navigateTo({
|
||||
url: '/pages/edit-profile/index'
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理注销登录
|
||||
*/
|
||||
handleLogout() {
|
||||
wx.showModal({
|
||||
title: "确认注销",
|
||||
content: "您确定要退出登录吗?",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 使用认证工具清除登录状态
|
||||
AuthUtil.clearAuth();
|
||||
|
||||
// 提示用户
|
||||
wx.showToast({
|
||||
title: "已注销登录",
|
||||
icon: "success",
|
||||
});
|
||||
|
||||
// 跳转到登录页
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"navigationBarBackgroundColor": "#0052d9",
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "我的",
|
||||
"usingComponents": {
|
||||
"t-avatar": "tdesign-miniprogram/avatar/avatar",
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
|
||||
"t-message": "tdesign-miniprogram/message/message"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!-- pages/mine/index.wxml -->
|
||||
<view class="user-container">
|
||||
<!-- 用户信息部分 -->
|
||||
<view class="user-info" bindtap="goToEditProfile">
|
||||
<t-avatar class="avatar" size="large" icon="user" image="{{userInfo.avatar || ''}}"></t-avatar>
|
||||
<view class="user-detail">
|
||||
<view class="username">{{userInfo.nickname || '未登录'}}</view>
|
||||
<view class="user-id">@{{userInfo.username || ''}}</view>
|
||||
<view class="user-role">{{userInfo.user_status === 1 ? '用户' : (userInfo.user_status === 2 ? '管理员' : '待审核')}}</view>
|
||||
</view>
|
||||
<view class="edit-icon" wx:if="{{isLoggedIn}}">
|
||||
<t-icon name="edit" size="36rpx" color="#0052d9" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能列表 -->
|
||||
<view class="function-list">
|
||||
<t-cell-group>
|
||||
<t-cell title="个人信息" hover left-icon="user" bindtap="goToEditProfile" arrow />
|
||||
<t-cell title="系统设置" hover left-icon="setting" url="" arrow />
|
||||
<t-cell title="关于我们" hover left-icon="info-circle" url="" arrow />
|
||||
</t-cell-group>
|
||||
</view>
|
||||
|
||||
<!-- 注销按钮 -->
|
||||
<view class="logout-container" wx:if="{{isLoggedIn}}">
|
||||
<t-button theme="danger" block size="large" bind:tap="handleLogout">注销登录</t-button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,60 @@
|
||||
/* pages/mine/index.wxss */
|
||||
.user-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40rpx 30rpx;
|
||||
background-color: #0052d9;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 30rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.7;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-role {
|
||||
font-size: 28rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
padding: 10rpx;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.function-list {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.logout-container {
|
||||
margin: 60rpx 30rpx;
|
||||
}
|
||||
@@ -0,0 +1,970 @@
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"navigationBarTitleText": "创建手术记录",
|
||||
"usingComponents": {
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-input": "tdesign-miniprogram/input/input",
|
||||
"t-textarea": "tdesign-miniprogram/textarea/textarea",
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-message": "tdesign-miniprogram/message/message",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-cascader": "tdesign-miniprogram/cascader/cascader",
|
||||
"t-calendar": "tdesign-miniprogram/calendar/calendar",
|
||||
"t-date-time-picker": "tdesign-miniprogram/date-time-picker/date-time-picker",
|
||||
"t-tree-select": "tdesign-miniprogram/tree-select/tree-select"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<view class="container">
|
||||
<t-message id="t-message" />
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<t-loading theme="circular" size="40rpx" loading />
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<block wx:else>
|
||||
<view class="form-group">
|
||||
<view class="form-item">
|
||||
<text class="label">手术编号 <text class="required">*</text></text>
|
||||
<t-input
|
||||
value="{{formData.surgery_id}}"
|
||||
placeholder="请输入手术编号"
|
||||
bindchange="onInputChange"
|
||||
data-field="surgery_id"
|
||||
maxlength="50"
|
||||
disabled="{{isEditMode}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">手术名称 <text class="required">*</text></text>
|
||||
<t-input
|
||||
value="{{formData.surgery_name}}"
|
||||
placeholder="请输入手术名称"
|
||||
bindchange="onInputChange"
|
||||
data-field="surgery_name"
|
||||
maxlength="100"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">患者姓名 <text class="required">*</text></text>
|
||||
<t-input
|
||||
value="{{formData.patient}}"
|
||||
placeholder="请输入患者姓名"
|
||||
bindchange="onInputChange"
|
||||
data-field="patient"
|
||||
maxlength="50"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 手术时间选择 -->
|
||||
<view class="form-item">
|
||||
<text class="label">手术时间 <text class="required">*</text></text>
|
||||
<view class="datetime-picker-wrapper" bindtap="showDateTimePicker">
|
||||
<t-cell
|
||||
title="{{formData.surgery_time_display || '请选择手术时间'}}"
|
||||
arrow
|
||||
hover
|
||||
data-mode="formData.surgery_time"
|
||||
note="{{formData.surgery_time ? '' : '必选'}}"
|
||||
t-class="picker-cell"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">主刀医生 <text class="required">*</text></text>
|
||||
<t-cell
|
||||
title="{{selectedDoctor ? selectedDoctor.name : '请选择主刀医生'}}"
|
||||
arrow
|
||||
hover
|
||||
note="{{selectedDoctor ? selectedDoctor.department.name : '必选'}}"
|
||||
bind:click="showDoctorSelector"
|
||||
t-class="picker-cell"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 设备选择部分 - 使用与手术详情页一致的样式 -->
|
||||
<view class="form-item">
|
||||
<view class="device-selection-header">
|
||||
<text>设备列表</text>
|
||||
<view class="add-device-btn" bind:tap="showDeviceSelector">添加设备</view>
|
||||
</view>
|
||||
|
||||
<!-- 已选择的设备列表 - 新样式 -->
|
||||
<view class="selected-devices-list" wx:if="{{selectedSubDevices.length > 0}}">
|
||||
<block wx:for="{{selectedSubDevices}}" wx:key="id">
|
||||
<view class="device-item">
|
||||
<view class="device-item-content">
|
||||
<view class="device-info">
|
||||
<view class="device-name">{{item.device_name}}-{{item.name}}</view>
|
||||
</view>
|
||||
<view class="device-action">
|
||||
<view class="delete-btn" bind:tap="removeDevice" data-index="{{index}}">删除</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 设备时间选择 -->
|
||||
<view class="device-time-section">
|
||||
<view class="time-picker-group">
|
||||
<view class="time-picker-item">
|
||||
<text class="time-label">开始时间</text>
|
||||
<view class="time-picker" bindtap="showDeviceStartTimePicker" data-index="{{index}}">
|
||||
<text class="time-value">{{item.startTimeDisplay || '点击设置'}}</text>
|
||||
<text class="picker-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="time-picker-item">
|
||||
<text class="time-label">结束时间</text>
|
||||
<view class="time-picker" bindtap="showDeviceEndTimePicker" data-index="{{index}}">
|
||||
<text class="time-value">{{item.endTimeDisplay || '点击设置'}}</text>
|
||||
<text class="picker-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 无设备提示 -->
|
||||
<view class="no-devices" wx:else>
|
||||
<text>暂无选择设备,请点击"添加设备"按钮添加</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-container">
|
||||
<t-button
|
||||
theme="primary"
|
||||
size="large"
|
||||
loading="{{submitting}}"
|
||||
disabled="{{submitting}}"
|
||||
bind:tap="handleSubmit"
|
||||
block>{{isEditMode ? '保存修改' : '创建手术记录'}}</t-button>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 日期时间选择器 -->
|
||||
<t-date-time-picker
|
||||
title="选择手术时间"
|
||||
visible="{{dateTimePickerVisible}}"
|
||||
mode="second"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value="{{formData.surgery_time || currentDate}}"
|
||||
confirm-btn="确认"
|
||||
cancel-btn="取消"
|
||||
bind:confirm="onDateTimeConfirm"
|
||||
bind:cancel="onDateTimeCancel"
|
||||
bind:pick="onDateTimePick"
|
||||
bind:change="onDateTimeChange"
|
||||
auto-close
|
||||
show-week
|
||||
|
||||
/>
|
||||
|
||||
<!-- 医生选择器 - 使用级联选择器选项卡风格 -->
|
||||
<t-cascader
|
||||
visible="{{doctorSelectorVisible}}"
|
||||
options="{{doctorCascaderOptions}}"
|
||||
title="选择主刀医生"
|
||||
theme="tab"
|
||||
value="{{selectedDoctorValue}}"
|
||||
bind:change="onDoctorCascaderChange"
|
||||
bind:pick="onDoctorCascaderPick"
|
||||
bind:close="onDoctorCascaderClose"
|
||||
/>
|
||||
|
||||
<!-- 设备选择器 - 使用级联选择器选项卡风格 -->
|
||||
<t-cascader
|
||||
visible="{{deviceSelectorVisible}}"
|
||||
options="{{deviceTree}}"
|
||||
title="选择使用设备"
|
||||
theme="tab"
|
||||
value="{{selectedDeviceValue}}"
|
||||
bind:change="onDeviceCascaderChange"
|
||||
bind:pick="onDeviceCascaderPick"
|
||||
bind:close="onDeviceCascaderClose"
|
||||
/>
|
||||
|
||||
<!-- 设备开始时间选择器 -->
|
||||
<t-date-time-picker
|
||||
title="选择设备开始使用时间"
|
||||
visible="{{deviceStartTimePickerVisible}}"
|
||||
mode="second"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value="{{currentDeviceStartTime || currentDate}}"
|
||||
confirm-btn="确认"
|
||||
cancel-btn="取消"
|
||||
bind:confirm="onDeviceStartTimeConfirm"
|
||||
bind:cancel="onDeviceStartTimeCancel"
|
||||
auto-close
|
||||
show-week
|
||||
/>
|
||||
|
||||
<!-- 设备结束时间选择器 -->
|
||||
<t-date-time-picker
|
||||
title="选择设备结束使用时间"
|
||||
visible="{{deviceEndTimePickerVisible}}"
|
||||
mode="second"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value="{{currentDeviceEndTime || currentDate}}"
|
||||
confirm-btn="确认"
|
||||
cancel-btn="取消"
|
||||
bind:confirm="onDeviceEndTimeConfirm"
|
||||
bind:cancel="onDeviceEndTimeCancel"
|
||||
auto-close
|
||||
show-week
|
||||
/>
|
||||
</view>
|
||||
@@ -0,0 +1,305 @@
|
||||
/* pages/surgery/create/index.wxss */
|
||||
|
||||
.container {
|
||||
padding: 30rpx;
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 16rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #e34d59;
|
||||
}
|
||||
|
||||
.picker-cell {
|
||||
padding: 20rpx !important;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f8f8f8 !important;
|
||||
}
|
||||
|
||||
/* 原生选择器样式 */
|
||||
.picker-view {
|
||||
padding: 24rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.time-picker {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.picker-note {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
|
||||
/* 设备选择相关样式 - 更新 */
|
||||
.device-selection-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.device-selection-header text {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.add-device-btn {
|
||||
color: #06a56c;
|
||||
font-size: 26rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.selected-devices-list {
|
||||
margin-top: 12rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
padding: 12rpx;
|
||||
display: block; /* 确保设备列表容器显示 */
|
||||
}
|
||||
|
||||
.device-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 12rpx;
|
||||
padding: 8rpx;
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
display: block; /* 确保设备项显示 */
|
||||
}
|
||||
|
||||
.device-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.device-item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.device-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #e34d59;
|
||||
color: #fff;
|
||||
border-radius: 4rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
padding: 10rpx;
|
||||
color: #e34d59;
|
||||
}
|
||||
|
||||
.no-devices {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
padding: 30rpx 0;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.submit-container {
|
||||
margin-top: 60rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 年月日时分秒选择器样式 */
|
||||
.datetime-picker-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.datetime-picker-container {
|
||||
position: fixed;
|
||||
bottom: -500rpx;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
z-index: 1001;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.datetime-picker-container.show {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.datetime-picker-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.datetime-picker-header .title {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.datetime-picker-header .cancel-btn,
|
||||
.datetime-picker-header .confirm-btn {
|
||||
font-size: 28rpx;
|
||||
padding: 8rpx 10rpx;
|
||||
}
|
||||
|
||||
.datetime-picker-header .cancel-btn {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.datetime-picker-header .confirm-btn {
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.datetime-picker-body {
|
||||
padding: 20rpx 0;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.picker-item {
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.time-display {
|
||||
padding: 20rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 自定义级联选择器样式 */
|
||||
:host {
|
||||
--td-cascader-active-color: #0052d9;
|
||||
}
|
||||
|
||||
/* 设备选择器的自定义样式 */
|
||||
.custom-picker {
|
||||
--td-picker-confirm-color: #0052d9;
|
||||
}
|
||||
|
||||
.custom-confirm-btn {
|
||||
color: #0052d9 !important;
|
||||
}
|
||||
|
||||
.custom-cancel-btn {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
/* 设备时间选择部分样式 */
|
||||
.device-time-section {
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.time-picker-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.time-picker-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12rpx 16rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 6rpx;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.time-picker-item .time-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 8rpx 12rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4rpx;
|
||||
border: 1px solid #dee2e6;
|
||||
min-width: 200rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.time-picker .time-value {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.time-picker .picker-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.time-picker:active .picker-arrow {
|
||||
transform: translateX(4rpx);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// pages/surgery/detail/index.js
|
||||
const { surgeryApi } = require("../../../utils/api");
|
||||
const dayjs = require("../../../miniprogram_npm/dayjs/index");
|
||||
|
||||
Page({
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
surgeryId: null, // 手术ID
|
||||
surgery: null, // 手术数据
|
||||
loading: true, // 加载状态
|
||||
loadError: false, // 加载错误状态
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
if (options && options.id) {
|
||||
const surgeryId = Number(options.id);
|
||||
this.setData({ surgeryId });
|
||||
this.fetchSurgeryDetail(surgeryId);
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'error'
|
||||
});
|
||||
// 返回上一页
|
||||
setTimeout(() => {
|
||||
wx.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取手术详情数据
|
||||
*/
|
||||
async fetchSurgeryDetail(surgeryId) {
|
||||
this.setData({
|
||||
loading: true,
|
||||
loadError: false
|
||||
});
|
||||
|
||||
try {
|
||||
// 修正API调用方法名 - 从getSurgeryDetail改为getSurgeryById
|
||||
const res = await surgeryApi.getSurgeryById(surgeryId);
|
||||
|
||||
// 处理手术数据,格式化日期和设备数据
|
||||
const surgery = res.data;
|
||||
|
||||
// 处理医生和科室信息
|
||||
const hasDoctor = !!surgery.doctor;
|
||||
const hasDepartment = !!surgery.doctor?.department;
|
||||
const doctorName = hasDoctor ? surgery.doctor.name : '未知医生';
|
||||
const departmentName = hasDepartment ? surgery.doctor.department.name : '未知科室';
|
||||
|
||||
// 格式化设备数据,将设备名称和子设备名称结合显示
|
||||
let devices = [];
|
||||
if (surgery.surgerySubDevices && surgery.surgerySubDevices.length > 0) {
|
||||
devices = surgery.surgerySubDevices.map(item => {
|
||||
// 获取主设备名称和子设备名称(通常是编号)
|
||||
const deviceName = item.subDevice.device.name || '未知设备';
|
||||
const subDeviceName = item.subDevice.name || '';
|
||||
|
||||
// 格式化使用时间
|
||||
let startTimeDisplay = '未设置';
|
||||
let endTimeDisplay = '未设置';
|
||||
if (item.start_time) {
|
||||
startTimeDisplay = dayjs(item.start_time).format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
if (item.end_time) {
|
||||
endTimeDisplay = dayjs(item.end_time).format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
|
||||
return {
|
||||
id: item.sub_device_id,
|
||||
surgerySubDeviceId: item.id,
|
||||
// 组合成"设备名称-子设备号"的格式
|
||||
displayName: `${deviceName}-${subDeviceName}`,
|
||||
startTime: item.start_time,
|
||||
endTime: item.end_time,
|
||||
startTimeDisplay,
|
||||
endTimeDisplay
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const formattedSurgery = {
|
||||
...surgery,
|
||||
formattedTime: dayjs(surgery.surgery_time).format('YYYY年MM月DD日 HH:mm'),
|
||||
doctorName,
|
||||
departmentName,
|
||||
devices
|
||||
};
|
||||
|
||||
this.setData({
|
||||
surgery: formattedSurgery,
|
||||
loading: false
|
||||
});
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
loading: false,
|
||||
loadError: true
|
||||
});
|
||||
wx.showToast({
|
||||
title: '获取手术详情失败',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回上一级页面
|
||||
*/
|
||||
goBack() {
|
||||
wx.navigateBack();
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"t-navbar": "tdesign-miniprogram/navbar/navbar",
|
||||
"t-skeleton": "tdesign-miniprogram/skeleton/skeleton",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag",
|
||||
"t-message": "tdesign-miniprogram/message/message"
|
||||
},
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<view class="detail-container">
|
||||
<t-navbar title="手术详情" left-arrow bind:go-back="goBack" />
|
||||
|
||||
<block wx:if="{{loading}}">
|
||||
<!-- 加载中骨架屏 -->
|
||||
<view class="skeleton-container">
|
||||
<t-skeleton theme="paragraph" loading></t-skeleton>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{loadError}}">
|
||||
<!-- 加载错误提示 -->
|
||||
<view class="error-container">
|
||||
<t-empty icon="error-circle" description="加载失败,请返回重试" />
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{surgery}}">
|
||||
<view class="detail-card">
|
||||
<!-- 手术基本信息 -->
|
||||
<view class="info-section">
|
||||
<view class="info-header">手术信息</view>
|
||||
<view class="info-content">
|
||||
<view class="info-item">
|
||||
<text class="label">手术编号</text>
|
||||
<text class="value">{{surgery.surgery_id}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">手术名称</text>
|
||||
<text class="value">{{surgery.surgery_name}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">患者姓名</text>
|
||||
<text class="value">{{surgery.patient}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">手术时间</text>
|
||||
<text class="value">{{surgery.formattedTime}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">主刀医生</text>
|
||||
<text class="value doctor">{{surgery.doctorName}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">所属科室</text>
|
||||
<text class="value department">{{surgery.departmentName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备信息部分,修改为更简洁的列表 -->
|
||||
<view class="device-section">
|
||||
<view class="info-header">使用设备 ({{surgery.devices.length}})</view>
|
||||
<view class="device-list">
|
||||
<block wx:if="{{surgery.devices && surgery.devices.length > 0}}">
|
||||
<view class="device-card" wx:for="{{surgery.devices}}" wx:key="id">
|
||||
<view class="device-header">
|
||||
<text class="device-name">{{item.displayName}}</text>
|
||||
</view>
|
||||
<view class="device-time-info">
|
||||
<view class="time-item">
|
||||
<text class="time-label">开始时间:</text>
|
||||
<text class="time-value">{{item.startTimeDisplay}}</text>
|
||||
</view>
|
||||
<view class="time-item">
|
||||
<text class="time-label">结束时间:</text>
|
||||
<text class="time-value">{{item.endTimeDisplay}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view class="no-device">
|
||||
<t-empty icon="info-circle" description="无使用设备记录" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<!-- 数据为空的提示 -->
|
||||
<view class="empty-container">
|
||||
<t-empty icon="info-circle-filled" description="无法获取手术详情" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
@@ -0,0 +1,132 @@
|
||||
/* pages/surgery/detail/index.wxss */
|
||||
page {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.detail-container {
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.skeleton-container {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
margin: 24rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 信息区块样式 */
|
||||
.info-section {
|
||||
padding: 24rpx;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.device-section {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-header {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 160rpx;
|
||||
font-size: 28rpx;
|
||||
color: #888;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 医生和科室标签样式 */
|
||||
.doctor {
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.department {
|
||||
color: #06a56c;
|
||||
}
|
||||
|
||||
/* 新的设备列表样式 */
|
||||
.device-list {
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.device-card {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
margin: 12rpx 0;
|
||||
}
|
||||
|
||||
.device-header {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.device-time-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.time-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.time-label {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
width: 120rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.no-device {
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
/* 错误和空状态容器 */
|
||||
.error-container,
|
||||
.empty-container {
|
||||
padding: 100rpx 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
// pages/surgery/index.js
|
||||
const { surgeryApi } = require("../../utils/api");
|
||||
const dayjs = require("../../miniprogram_npm/dayjs/index");
|
||||
const Toast = require("../../miniprogram_npm/tdesign-miniprogram/toast/index");
|
||||
|
||||
Page({
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
surgeries: [], // 手术列表数据
|
||||
surgeriesList: [],
|
||||
// 分页参数
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// 状态控制
|
||||
loading: true, // 初始加载状态
|
||||
refreshing: false, // 下拉刷新状态
|
||||
loadingMore: false, // 加载更多状态
|
||||
loadError: false, // 加载错误状态
|
||||
noMoreData: false, // 没有更多数据状态
|
||||
isFinish: false, // 是否选中未完成
|
||||
|
||||
confirmDialogVisible: false, // 确认删除对话框可见性
|
||||
currentSurgeryId: null, // 当前操作的手术ID
|
||||
|
||||
// 数据变更状态管理
|
||||
dataHasChanged: false, // 标记数据是否已变更(从编辑/创建页面返回时)
|
||||
lastShowTime: 0, // 上次显示页面的时间戳
|
||||
showTimeout: null, // 防抖定时器
|
||||
|
||||
// TDesign 组件配置
|
||||
loadingProps: {
|
||||
theme: 'circular',
|
||||
size: '40rpx',
|
||||
layout: 'horizontal'
|
||||
},
|
||||
|
||||
loadingTexts: [
|
||||
'下拉刷新',
|
||||
'松手刷新',
|
||||
'正在刷新...',
|
||||
'刷新完成'
|
||||
]
|
||||
|
||||
// 不再在这里全局定义swipeButtons,而是为每个手术项动态生成
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad() {
|
||||
this.loadSurgeries();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
const currentTime = Date.now();
|
||||
|
||||
// 清除之前的定时器(防抖)
|
||||
if (this.data.showTimeout) {
|
||||
clearTimeout(this.data.showTimeout);
|
||||
}
|
||||
|
||||
// 检查是否需要刷新数据
|
||||
const shouldRefresh = this.shouldRefreshData(currentTime);
|
||||
|
||||
if (shouldRefresh) {
|
||||
// 设置新的定时器,延迟500ms执行刷新,避免快速切换页面导致的重复请求
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.performSmartRefresh();
|
||||
}, 500);
|
||||
|
||||
this.setData({
|
||||
showTimeout: timeoutId,
|
||||
lastShowTime: currentTime,
|
||||
dataHasChanged: false
|
||||
});
|
||||
} else {
|
||||
// 更新最后显示时间
|
||||
this.setData({ lastShowTime: currentTime });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 点击未完成
|
||||
*/
|
||||
clickunfinished ({detail}) {
|
||||
this.setData({isFinish: detail.checked})
|
||||
this.filterSurgeriesList()
|
||||
},
|
||||
/**
|
||||
* 过滤手术未完成
|
||||
*/
|
||||
filterSurgeriesList(arr = []) {
|
||||
if (arr.length > 0) this.data.surgeriesList.push(...arr)
|
||||
let list = []
|
||||
if (this.data.isFinish) {
|
||||
list = this.data.surgeries.filter((item) => {
|
||||
if (item.surgerySubDevices && item.surgerySubDevices.length > 0){
|
||||
if (item.surgerySubDevices.some(subDevice => !subDevice.end_time)) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
})
|
||||
this.setData({
|
||||
surgeries: list
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
surgeries: this.data.surgeriesList
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 判断是否需要刷新数据
|
||||
*/
|
||||
shouldRefreshData(currentTime) {
|
||||
// 如果正在加载或刷新,则不执行
|
||||
if (this.data.loading || this.data.refreshing || this.data.loadingMore) {
|
||||
return false;
|
||||
}
|
||||
// 如果数据已变更(从编辑页面返回的标记),则需要刷新
|
||||
if (this.data.dataHasChanged) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果还没有加载过数据,则需要加载
|
||||
if (this.data.surgeries.length === 0 && !this.data.loadError) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果距离上次显示超过5分钟,考虑刷新(防止数据过期)
|
||||
const timeSinceLastShow = currentTime - this.data.lastShowTime;
|
||||
if (timeSinceLastShow > 5 * 60 * 1000) { // 5分钟
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行智能刷新策略
|
||||
*/
|
||||
async performSmartRefresh() {
|
||||
try {
|
||||
// 1. 首先尝试增量更新(只刷新第一页,检查是否有新数据)
|
||||
const freshData = await this.fetchSurgeriesData(1, this.data.pageSize);
|
||||
const freshFirstPage = freshData.formattedSurgeries;
|
||||
|
||||
// 2. 比较数据是否有变化
|
||||
const hasChanges = this.detectChanges(this.data.surgeries, freshFirstPage);
|
||||
|
||||
if (hasChanges) {
|
||||
console.log('📋 检测到数据变化,执行增量更新');
|
||||
await this.performIncrementalUpdate(freshData);
|
||||
} else {
|
||||
console.log('📋 数据无变化,保持当前状态');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('📋 智能刷新失败,回退到完整刷新:', error);
|
||||
// 如果智能刷新失败,回退到完整的刷新
|
||||
this.loadSurgeries();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 检测数据是否有变化
|
||||
*/
|
||||
detectChanges(currentData, freshData) {
|
||||
// 如果数据数量不同,肯定有变化
|
||||
if (currentData.length !== freshData.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果当前没有数据,而新数据有,则有变化
|
||||
if (currentData.length === 0 && freshData.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 比较前几个项目的ID和时间戳
|
||||
const compareCount = Math.min(currentData.length, freshData.length, 3);
|
||||
for (let i = 0; i < compareCount; i++) {
|
||||
const currentItem = currentData[i];
|
||||
const freshItem = freshData[i];
|
||||
|
||||
// 比较ID
|
||||
if (currentItem.id !== freshItem.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 比较更新时间(如果有的话)
|
||||
if (currentItem.updated_at !== freshItem.updated_at) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 比较手术时间
|
||||
if (currentItem.surgery_time !== freshItem.surgery_time) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行增量更新
|
||||
*/
|
||||
async performIncrementalUpdate(freshFirstPage) {
|
||||
// 只在第1页时自动更新,其他页面不进行任何操作
|
||||
if (this.data.page === 1) {
|
||||
this.setData({
|
||||
surgeries: freshFirstPage.formattedSurgeries,
|
||||
total: freshFirstPage.total,
|
||||
noMoreData: freshFirstPage.formattedSurgeries.length < this.data.pageSize
|
||||
});
|
||||
|
||||
// 简单的数据更新提示
|
||||
wx.showToast({
|
||||
title: '数据已更新',
|
||||
icon: 'success',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
// 如果用户在第2页+,不做任何操作,让用户自己选择是否下拉刷新
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置数据变更标记(从其他页面调用)
|
||||
*/
|
||||
markDataChanged() {
|
||||
this.setData({ dataHasChanged: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始加载手术数据
|
||||
*/
|
||||
async loadSurgeries() {
|
||||
this.setData({
|
||||
loading: true,
|
||||
loadError: false,
|
||||
page: 1,
|
||||
noMoreData: false
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await this.fetchSurgeriesData(1, this.data.pageSize);
|
||||
|
||||
this.setData({
|
||||
surgeries: res.formattedSurgeries,
|
||||
surgeriesList: res.formattedSurgeries,
|
||||
total: res.total,
|
||||
page: 1,
|
||||
noMoreData: res.formattedSurgeries.length < this.data.pageSize,
|
||||
loading: false
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
loading: false,
|
||||
loadError: true,
|
||||
});
|
||||
wx.showToast({
|
||||
title: "获取手术列表失败",
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
async onRefresh() {
|
||||
if (this.data.refreshing || this.data.loadingMore) return;
|
||||
this.setData({
|
||||
refreshing: true,
|
||||
page: 1,
|
||||
noMoreData: false
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await this.fetchSurgeriesData(1, this.data.pageSize);
|
||||
|
||||
this.setData({
|
||||
surgeries: res.formattedSurgeries,
|
||||
total: res.total,
|
||||
page: 1,
|
||||
noMoreData: res.formattedSurgeries.length < this.data.pageSize
|
||||
});
|
||||
wx.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
} catch (error) {
|
||||
wx.showToast({
|
||||
title: '刷新失败',
|
||||
icon: 'error'
|
||||
});
|
||||
} finally {
|
||||
this.setData({ refreshing: false });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 滑动底部加载更多
|
||||
*/
|
||||
async onScrollToLower() {
|
||||
// 如果正在加载或没有更多数据,则返回
|
||||
if (this.data.loadingMore || this.data.noMoreData || this.data.refreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({
|
||||
loadingMore: true,
|
||||
page: this.data.page + 1
|
||||
});
|
||||
try {
|
||||
const res = await this.fetchSurgeriesData(this.data.page, this.data.pageSize);
|
||||
this.filterSurgeriesList(res.formattedSurgeries)
|
||||
// 合并数据
|
||||
this.setData({
|
||||
// surgeries: this.data.surgeries.concat(res.formattedSurgeries),
|
||||
total: res.total,
|
||||
noMoreData: res.formattedSurgeries.length < this.data.pageSize
|
||||
});
|
||||
} catch (error) {
|
||||
// 恢复页码
|
||||
this.setData({
|
||||
page: this.data.page - 1
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '加载更多失败',
|
||||
icon: 'error'
|
||||
});
|
||||
} finally {
|
||||
this.setData({ loadingMore: false });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取手术数据的核心方法
|
||||
*/
|
||||
async fetchSurgeriesData(page, pageSize) {
|
||||
const res = await surgeryApi.getSurgeries({
|
||||
page: page,
|
||||
pageSize: pageSize
|
||||
});
|
||||
|
||||
// 处理手术数据,格式化日期并确保医生和科室数据存在
|
||||
// 后端返回的是分页格式:{list: [...], total: ...}
|
||||
const surgeryList = res.data.list || res.data || [];
|
||||
const formattedSurgeries = surgeryList.map((surgery) => {
|
||||
// 诊断医生信息缺失问题
|
||||
const hasDoctor = !!surgery.doctor;
|
||||
const hasDepartment = !!surgery.doctor?.department;
|
||||
|
||||
// 确保医生和科室信息存在并设置默认值
|
||||
const doctorName = hasDoctor ? surgery.doctor.name : "未知医生";
|
||||
const departmentName = hasDepartment ? surgery.doctor.department.name : "未知科室";
|
||||
|
||||
// 计算使用的设备数量
|
||||
const deviceCount = surgery.surgerySubDevices ? surgery.surgerySubDevices.length : 0;
|
||||
|
||||
// 检查是否有未设置结束时间的设备
|
||||
let hasUnfinishedDevice = false;
|
||||
if (surgery.surgerySubDevices && surgery.surgerySubDevices.length > 0) {
|
||||
hasUnfinishedDevice = surgery.surgerySubDevices.some(subDevice => !subDevice.end_time);
|
||||
}
|
||||
|
||||
// 为每个手术记录添加专属的侧滑按钮配置
|
||||
const surgerySwipeButtons = [
|
||||
{
|
||||
text: "编辑",
|
||||
style: "background-color: #0052d9; color: white; width: 120rpx; height: 100%; display: flex; align-items: center; justify-content: center;",
|
||||
data: { id: surgery.id, type: "edit" },
|
||||
},
|
||||
{
|
||||
text: "删除",
|
||||
style: "background-color: #e34d59; color: white; width: 120rpx; height: 100%; display: flex; align-items: center; justify-content: center;",
|
||||
data: { id: surgery.id, type: "delete" },
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
...surgery,
|
||||
formattedTime: dayjs(surgery.surgery_time).format("YYYY年MM月DD日 HH:mm"),
|
||||
// 添加独立的属性,确保即使嵌套对象不存在也能显示
|
||||
doctorName: doctorName,
|
||||
departmentName: departmentName,
|
||||
// 添加设备数量
|
||||
deviceCount: deviceCount,
|
||||
// 添加样式状态标记
|
||||
hasUnfinishedDevice: hasUnfinishedDevice,
|
||||
// 添加侧滑按钮配置
|
||||
swipeButtons: surgerySwipeButtons,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
formattedSurgeries,
|
||||
total: res.data.total || surgeryList.length
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看手术详情
|
||||
*/
|
||||
viewSurgeryDetail(e) {
|
||||
const surgeryId = e.currentTarget.dataset.id;
|
||||
// 跳转到手术详情页
|
||||
wx.navigateTo({
|
||||
url: `/pages/surgery/detail/index?id=${surgeryId}`,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 侧滑单元格点击事件处理
|
||||
*/
|
||||
onSwipeCellClick(e) {
|
||||
const { index } = e.detail;
|
||||
const btnData = e.detail.data;
|
||||
|
||||
if (!btnData || !btnData.id) {
|
||||
Toast({
|
||||
message: "操作失败,请重试",
|
||||
theme: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const surgeryId = btnData.id;
|
||||
const actionType = btnData.type;
|
||||
|
||||
|
||||
// 查找对应的手术记录数据
|
||||
const surgery = this.data.surgeries.find((item) => item.id === surgeryId);
|
||||
if (!surgery) {
|
||||
Toast({
|
||||
message: "找不到对应的手术记录",
|
||||
theme: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据操作类型执行相应的动作
|
||||
if (actionType === "edit" || index === 0) {
|
||||
// 编辑操作
|
||||
this.handleEdit(surgery);
|
||||
} else if (actionType === "delete" || index === 1) {
|
||||
// 删除操作
|
||||
this.handleDelete(surgery);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理编辑操作
|
||||
*/
|
||||
handleEdit(surgery) {
|
||||
// 跳转到编辑页面,实际上是复用创建页面
|
||||
wx.navigateTo({
|
||||
url: `/pages/surgery/create/index?id=${surgery.id}&mode=edit`,
|
||||
success: () => {
|
||||
},
|
||||
fail: (err) => {
|
||||
Toast({
|
||||
message: "页面跳转失败",
|
||||
theme: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理删除操作
|
||||
*/
|
||||
handleDelete(surgery) {
|
||||
// 显示确认对话框
|
||||
this.setData({
|
||||
currentSurgeryId: surgery.id,
|
||||
confirmDialogVisible: true,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 确认删除
|
||||
*/
|
||||
async confirmDelete() {
|
||||
const surgeryId = this.data.currentSurgeryId;
|
||||
|
||||
try {
|
||||
await surgeryApi.deleteSurgery(surgeryId);
|
||||
// 删除成功,刷新列表
|
||||
wx.showToast({
|
||||
title: "删除成功",
|
||||
icon: "success",
|
||||
});
|
||||
|
||||
// 重新加载数据
|
||||
this.loadSurgeries();
|
||||
} catch (error) {
|
||||
wx.showToast({
|
||||
title: "删除失败",
|
||||
icon: "error",
|
||||
});
|
||||
} finally {
|
||||
// 关闭对话框
|
||||
this.setData({
|
||||
confirmDialogVisible: false,
|
||||
currentSurgeryId: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消删除
|
||||
*/
|
||||
cancelDelete() {
|
||||
this.setData({
|
||||
confirmDialogVisible: false,
|
||||
currentSurgeryId: null,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 浮动按钮点击事件处理函数
|
||||
*/
|
||||
handleClick() {
|
||||
// 跳转到手术创建页面
|
||||
wx.navigateTo({
|
||||
url: "/pages/surgery/create/index",
|
||||
success: () => {
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.showToast({
|
||||
title: "页面跳转失败",
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面卸载时清理资源
|
||||
*/
|
||||
onUnload() {
|
||||
// 清除定时器,避免内存泄漏
|
||||
if (this.data.showTimeout) {
|
||||
clearTimeout(this.data.showTimeout);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
|
||||
"t-skeleton": "tdesign-miniprogram/skeleton/skeleton",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-divider": "tdesign-miniprogram/divider/divider",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag",
|
||||
"t-image": "tdesign-miniprogram/image/image",
|
||||
"t-toast": "tdesign-miniprogram/toast/toast",
|
||||
"t-message": "tdesign-miniprogram/message/message",
|
||||
"t-fab": "tdesign-miniprogram/fab/fab",
|
||||
"t-swipe-cell": "tdesign-miniprogram/swipe-cell/swipe-cell",
|
||||
"t-dialog": "tdesign-miniprogram/dialog/dialog",
|
||||
"t-pull-down-refresh": "tdesign-miniprogram/pull-down-refresh/pull-down-refresh",
|
||||
"t-search": "tdesign-miniprogram/search/search",
|
||||
"t-radio": "tdesign-miniprogram/radio/radio"
|
||||
},
|
||||
"navigationBarTitleText": "手术",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f7f8fa"
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<t-pull-down-refresh
|
||||
value="{{refreshing}}"
|
||||
loadingProps="{{loadingProps}}"
|
||||
loadingTexts="{{loadingTexts}}"
|
||||
bind:refresh="onRefresh"
|
||||
bind:scrolltolower="onScrollToLower"
|
||||
maxBarHeight="200"
|
||||
loadingBarHeight="80"
|
||||
lowerThreshold="100"
|
||||
enable-back-to-top="{{true}}"
|
||||
>
|
||||
<view class="surgery-searchbar">
|
||||
<view class="surgery-radio">
|
||||
<t-radio default-checked="{{isFinish}}" allow-uncheck icon="line" label="未完成" bind:change="clickunfinished"/>
|
||||
</view>
|
||||
<view class="surgery-search"><t-search placeholder="请输入患者ID" /></view>
|
||||
</view>
|
||||
<view class="surgery-container">
|
||||
<!-- 初始加载骨架屏 -->
|
||||
<block wx:if="{{loading && surgeries.length === 0}}">
|
||||
<view wx:for="{{5}}" wx:key="index" class="skeleton-item">
|
||||
<t-skeleton theme="paragraph" loading></t-skeleton>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 加载错误提示 -->
|
||||
<block wx:elif="{{loadError}}">
|
||||
<view class="error-container">
|
||||
<t-empty icon="error-circle" description="加载失败,请下拉刷新重试" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 手术列表 -->
|
||||
<block wx:elif="{{surgeries && surgeries.length > 0}}">
|
||||
<view class="surgery-list">
|
||||
<block wx:for="{{surgeries}}" wx:key="id">
|
||||
<t-swipe-cell class="swipe-cell-container" right="{{item.swipeButtons}}" bind:click="onSwipeCellClick">
|
||||
<view class="surgery-card {{item.hasUnfinishedDevice ? 'surgery-card--unfinished' : ''}}" bindtap="viewSurgeryDetail" data-id="{{item.id}}">
|
||||
<view class="surgery-title">
|
||||
{{item.surgery_name}}
|
||||
<view wx:if="{{item.hasUnfinishedDevice}}" class="unfinished-indicator">
|
||||
<text class="unfinished-text">未设置结束时间</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="surgery-info">
|
||||
<view class="info-item patient">
|
||||
<text>患者: {{item.patient}}</text>
|
||||
</view>
|
||||
<view class="info-item time">
|
||||
<text>时间: {{item.formattedTime}}</text>
|
||||
</view>
|
||||
<view class="info-item doctor-dept">
|
||||
<text class="doctor">{{item.doctorName}}</text>
|
||||
<text class="dept">{{item.departmentName}}</text>
|
||||
</view>
|
||||
<!-- 添加设备数量显示 -->
|
||||
<view class="info-item device-count">
|
||||
<text class="count">使用设备: {{item.deviceCount}}个</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="surgery-id-tag">{{item.surgery_id}}</view>
|
||||
</view>
|
||||
</t-swipe-cell>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多状态 -->
|
||||
<view wx:if="{{loadingMore}}" class="loading-more">
|
||||
<t-loading text="加载更多..." size="40rpx" theme="circular" />
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view wx:if="{{noMoreData && surgeries.length > 0}}" class="no-more">
|
||||
<view class="no-more-line"></view>
|
||||
<text class="no-more-text">没有更多手术记录了</text>
|
||||
<view class="no-more-line"></view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 空数据提示 -->
|
||||
<block wx:else>
|
||||
<view class="empty-container">
|
||||
<t-empty icon="info-circle-filled" description="暂无手术记录" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<t-fab icon="add" bind:click="handleClick" aria-label="增加"></t-fab>
|
||||
</view>
|
||||
</t-pull-down-refresh>
|
||||
|
||||
<!-- Toast 消息提示 -->
|
||||
<t-toast id="t-toast" />
|
||||
<!-- 确认删除的对话框 -->
|
||||
<t-dialog
|
||||
visible="{{confirmDialogVisible}}"
|
||||
title="确认删除"
|
||||
content="确定要删除此手术记录吗?此操作不可撤销。"
|
||||
confirm-btn="删除"
|
||||
cancel-btn="取消"
|
||||
bind:confirm="confirmDelete"
|
||||
bind:cancel="cancelDelete"
|
||||
/>
|
||||
@@ -0,0 +1,240 @@
|
||||
/* pages/surgery/index.wxss */
|
||||
page {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
.surgery-searchbar {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
background: white;
|
||||
padding: 0 10px;
|
||||
gap: 0 10px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
top: 0;
|
||||
}
|
||||
.surgery-radio {
|
||||
border-radius: 10px;
|
||||
}
|
||||
.surgery-search {
|
||||
flex: 1;
|
||||
}
|
||||
.surgery-container {
|
||||
padding: 24rpx 0;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
margin: 16rpx 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 手术列表样式 */
|
||||
.surgery-list {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
/* 滑动单元格容器 */
|
||||
.swipe-cell-container {
|
||||
margin-bottom: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.surgery-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
position: relative;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 有未完成设备的卡片样式 - 淡黄色背景 */
|
||||
.surgery-card--unfinished {
|
||||
background-color: #fff8e6;
|
||||
border-left: 6rpx solid #faad14;
|
||||
box-shadow: 0 2rpx 12rpx rgba(250, 173, 20, 0.15);
|
||||
}
|
||||
|
||||
/* 淡黄色卡片内的文本颜色调整 */
|
||||
.surgery-card--unfinished .surgery-title {
|
||||
color: #8b6914;
|
||||
}
|
||||
|
||||
.surgery-card--unfinished .info-item {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.surgery-card--unfinished .doctor {
|
||||
background-color: rgba(250, 173, 20, 0.15);
|
||||
color: #8b6914;
|
||||
}
|
||||
|
||||
.surgery-card--unfinished .dept {
|
||||
background-color: rgba(250, 173, 20, 0.1);
|
||||
color: #8b6914;
|
||||
}
|
||||
|
||||
.surgery-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
padding-right: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 未完成设备状态指示器 */
|
||||
.unfinished-indicator {
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.unfinished-text {
|
||||
font-size: 22rpx;
|
||||
color: #faad14;
|
||||
background-color: rgba(250, 173, 20, 0.1);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid rgba(250, 173, 20, 0.3);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.surgery-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.doctor-dept {
|
||||
margin-top: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.doctor {
|
||||
font-size: 28rpx;
|
||||
color: #0052d9;
|
||||
background-color: #eef4ff;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dept {
|
||||
font-size: 28rpx;
|
||||
color: #06a56c;
|
||||
background-color: #e8f6f1;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 设备数量样式 */
|
||||
.device-count {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.device-count .count {
|
||||
font-size: 28rpx;
|
||||
color: #e54d42;
|
||||
background-color: #feecea;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.surgery-id-tag {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 空状态和错误容器 */
|
||||
.empty-container,
|
||||
.error-container {
|
||||
padding: 100rpx 24rpx;
|
||||
}
|
||||
|
||||
/* 侧滑按钮样式 - 修改部分 */
|
||||
.swipe-cell-btn {
|
||||
height: 100% !important;
|
||||
width: 120rpx !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
font-size: 28rpx !important;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #0052d9 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e34d59 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* 加载更多样式 */
|
||||
.loading-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
/* 没有更多数据样式 */
|
||||
.no-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx 24rpx;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.no-more-line {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.no-more-text {
|
||||
margin: 0 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* TDesign pull-down-refresh 容器调整 */
|
||||
.surgery-container {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
/* 修改TDesign原生样式,确保按钮高度与卡片一致 */
|
||||
.surgery-list .t-swipe-cell__right {
|
||||
height: 100% !important;
|
||||
display: flex !important;
|
||||
align-items: stretch !important;
|
||||
}
|
||||
Reference in New Issue
Block a user