2025-07-08 10:38:19 +08:00

1278 lines
34 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="relay-control">
<!-- 设备状态 -->
<!-- <button type="default" @click="consolebutton()">ceshi </button> -->
<view class="card">
<view class="status-titletop">{{title}}</view>
<view style="padding:20rpx;">
<u--form labelPosition="left" labelWidth="100"
:labelStyle="{ marginRight: '16px', lineHeight: '32px', width: '50px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',color: '#000000' }">
<view class="version-wrap">
<u-form-item :label="$tt('status.deviceVersion')">
<u-row>
<u-col span="8">
<u--text :text="'Version' + device.firmwareVersion"></u--text>
</u-col>
</u-row>
</u-form-item>
</view>
</u--form>
</view>
</view>
<!-- 继电器状态显示 -->
<view class="relay-status">
<view class="status-title">继电器状态</view>
<view class="relay-grid">
<view class="relay-item" v-for="(item, index) in relayList" :key="index" :class="{'relay-active': item.status === 1}">
<!-- 可点击的继电器名称 -->
<view class="relay-name" @click="checkOnline(editRelayName, index)">
{{item.remark}}
<u-icon name="edit-pen" size="16" color="#999"></u-icon>
</view>
<view class="relay-state" :class="{'active': item.status === 1}">
<view class="status-indicator" :class="{'active': item.status === 1}"></view>
当前状态:{{item.status === 1 ? '吸合' : '断开'}}
</view>
<u-button
:type="item.status === 1 ? 'error' : 'primary'"
size="normal"
@click="checkOnline(toggleRelay, index)"
:disabled="device.status !== 3"
:class="{'btn-active': item.status === 1}"
>
{{item.status === 1 ? '断开' : '吸合'}}
</u-button>
</view>
</view>
</view>
<!-- 时间方案配置 -->
<!-- <view class="time-schedule">
<view class="schedule-title">时间方案配置</view>
<view class="schedule-list">
<view class="schedule-item" v-for="(item, index) in scheduleList" :key="index">
<view class="schedule-info">
<text>时间:{{item.time}}</text>
<text>动作:{{item.action === 1 ? '开启' : '关闭'}}</text>
<text>继电器:{{item.relayIndexes.map(index => index + 1).join(', ')}}</text>
<text>重复日期:{{item.weekdays.map(index => weekdays[index]).join(', ')}}</text>
</view>
<u-button type="error" size="mini" @click="deleteSchedule(index)">删除</u-button>
</view>
</view>
<view class="add-schedule">
<u-button type="primary" @click="showAddSchedule">添加时间方案</u-button>
</view>
</view> -->
<!-- 时间方案配置 -->
<view class="time-schedule">
<view class="schedule-header">
<view class="schedule-title">时间方案配置</view>
<view style="display: flex; justify-content: flex-end;">
<u-button style="width: 180rpx; height: 40rpx;" type="primary" size="mini"
@click="checkOnline(showAddSchedule)" :disabled="device.status !== 3">
添加
</u-button>
</view>
</view>
<view class="empty-tip" v-if="scheduleList.length === 0">
<u-icon name="list" size="50" color="#c0c4cc"></u-icon>
<text>暂无时间方案,点击添加按钮创建</text>
</view>
<view class="schedule-list">
<view class="schedule-card" v-for="(item, index) in scheduleList" :key="index">
<view class="schedule-time">
<u-icon name="clock" size="18" color="#3c9cff"></u-icon>
<text>{{item.time}}</text>
</view>
<view class="schedule-content">
<!-- <view class="schedule-row">
<text class="label">动作</text>
<u-tag :text="item.action === 1 ? '开启' : '关闭'"
:type="item.action === 1 ? 'success' : 'error'" size="mini"></u-tag>
</view> -->
<view class="schedule-row">
<text class="label">继电器</text>
<view class="relay-tags">
<u-tag
v-for="relayIndex in item.relayIndexes"
:key="relayIndex"
:text="relayList[relayIndex] && relayList[relayIndex].remark ? relayList[relayIndex].remark : '继电器' + (relayIndex + 1)"
type="primary"
size="mini"
/>
</view>
</view>
<view class="schedule-row">
<text class="label">重复日期</text>
<view class="weekday-tags">
<u-tag v-for="dayIndex in item.weekdays" :key="dayIndex" :text="weekdays[dayIndex]"
plain size="mini"></u-tag>
</view>
</view>
</view>
<view class="schedule-actions">
<u-button type="primary" size="mini" plain @click="checkOnline(editSchedule, index)"
:disabled="device.status !== 3">
编辑
</u-button>
<u-button type="error" size="mini" plain @click="checkOnline(deleteSchedule, index)"
:disabled="device.status !== 3">
删除
</u-button>
</view>
</view>
</view>
</view>
<!-- 添加/编辑时间方案弹窗 -->
<u-popup :show="showPopup" mode="center" @close="closeSchedulePopup">
<view class="popup-content">
<view class="popup-title">{{isEditing ? '编辑时间方案' : '添加时间方案'}}</view>
<u--form :model="scheduleForm" ref="uForm">
<u-form-item label="时间">
<u-input v-model="scheduleForm.time" placeholder="请选择时间" @focus="openTimePicker" />
</u-form-item>
<!-- <u-form-item label="动作">
<u-radio-group v-model="scheduleForm.action">
<u-radio :name="1" label="开启"></u-radio>
<u-radio :name="0" label="关闭"></u-radio>
</u-radio-group>
</u-form-item> -->
<u-form-item label="选择继电器">
<view class="checkbox-group">
<view
v-for="(item, index) in relayList"
:key="index"
class="checkbox-item"
:class="{ 'selected': scheduleForm.relayIndexes.includes(index) }"
@click="toggleRelayCheckbox(index)"
>
<view class="checkbox-wrapper">
<view class="checkbox" :class="{ 'checked': scheduleForm.relayIndexes.includes(index) }">
<view v-if="scheduleForm.relayIndexes.includes(index)" class="checkbox-checkmark"></view>
</view>
<view class="relay-info">
<text class="checkbox-label">{{ item.remark || item.name }}</text>
<text class="relay-status-text" :class="{ 'active': scheduleForm.relayIndexes.includes(index) }">
{{ scheduleForm.relayIndexes.includes(index) ? '吸合' : '断开' }}
</text>
</view>
</view>
</view>
</view>
</u-form-item>
<u-form-item label="重复日期">
<view class="checkbox-group repeat-weekdays-group">
<view
v-for="(day, index) in weekdays"
:key="index"
class="checkbox-item"
@click="toggleWeekdayCheckbox(index)"
>
<view class="checkbox-wrapper">
<view class="checkbox" :class="{ 'checked': scheduleForm.weekdays.includes(index) }">
<view v-if="scheduleForm.weekdays.includes(index)" class="checkbox-checkmark"></view>
</view>
<text class="checkbox-label">{{ day }}</text>
</view>
</view>
</view>
</u-form-item>
</u--form>
<view class="popup-buttons">
<u-button @click="closeSchedulePopup">取消</u-button>
<u-button type="primary" @click="isEditing ? updateSchedule() : addSchedule()">{{isEditing ? '更新' : '确定'}}</u-button>
</view>
</view>
</u-popup>
<!-- 时间选择器 -->
<u-datetime-picker :show="showTimePicker" v-model="tempTime" mode="time" @confirm="timeConfirm"
@cancel="showTimePicker = false" />
<!-- 继电器名称编辑弹窗 -->
<u-popup :show="showRelayEditPopup" mode="center" @close="showRelayEditPopup = false">
<view class="popup-content">
<view class="popup-title">修改继电器名称</view>
<u--form :model="relayEditForm" ref="uForm">
<!-- <u-form-item label="名称">
<u-input v-model="relayEditForm.name" placeholder="请输入继电器名称" />
</u-form-item> -->
<u-form-item label="备注">
<u-input v-model="relayEditForm.remark" placeholder="请输入备注信息" />
</u-form-item>
</u--form>
<view class="popup-buttons">
<u-button @click="showRelayEditPopup = false">取消</u-button>
<u-button type="primary" @click="confirmRelayEdit">确定</u-button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import {
getLatestFirmware,
relateChannelList,
getOrderControl
} from '@/apis/modules/device.js';
import {
serviceInvoke
} from '@/apis/modules/runtime.js';
import {
getSipStatusInfo,
getLocationWayInfo
} from '@/helpers/common.js'
export default {
name: 'relay-control',
props: {
device: {
type: Object,
default: null,
required: true
}
},
created() {
// 1秒后执行提取继电器列表
setTimeout(() => {
this.extractRelayList();
this.parseScheduleConfig(); // 解析时间方案 、
// this.consolebutton()
}, 1000);
this.updateDeviceStatus(this.device);
this.mqttCallback();
},
onLoad: function(option) {
console.log(JSON.stringify(this.device))
},
data() {
return {
title: '设备离线',
show: false,
firmware: {},
showTimePicker: false,
showPopup: false,
tempTime: '',
weekdays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
bgStyle: '',
shadowUnEnable: false,
scheduleList: [], // 初始化为空数组,等待解析数据
showRelayEditPopup: false,
relayEditForm: {
index: null,
name: '',
remark: ''
},
relayList: [], // 初始化为空数组,等待提取数据
scheduleList: [],
scheduleForm: {
time: '12:00:00',
action: 1,
relayIndexes: [],
weekdays: []
},
isEditing: false, // 是否处于编辑模式
editingIndex: -1 // 当前编辑的时间方案索引
}
},
methods: {
// 检查设备是否在线
checkOnline(callback, ...args) {
if (this.device.status !== 3) {
uni.showToast({
title: '设备离线,无法操作',
icon: 'none'
});
return false;
}
if (typeof callback === 'function') {
return callback.apply(this, args);
}
return true;
},
// 编辑继电器名称
editRelayName(index) {
if (!this.checkOnline()) return;
this.relayEditForm = {
index,
name: this.relayList[index].name,
remark: this.relayList[index].remark || ''
};
this.showRelayEditPopup = true;
},
// 确认修改继电器信息
async confirmRelayEdit() {
try {
const {
index,
name,
remark
} = this.relayEditForm;
// 1. 更新本地数据
this.relayList[index].name = name;
this.relayList[index].remark = remark;
// 2. 找到对应的备注物模型
const channel = index + 1; // 继电器通道号(1-4)
const remarkModel = this.device.thingsModels.find(
item => item.id === `remark_ch${channel}`
);
if (remarkModel) {
// 3. 更新物模型值
remarkModel.shadow = remark;
// 4. 通过MQTT发布更新
await this.mqttPublish(this.device, remarkModel);
}
this.showRelayEditPopup = false;
uni.showToast({
title: '修改成功',
icon: 'success'
});
} catch (error) {
console.error(' 修改继电器信息失败:', error);
uni.showToast({
title: '修改失败: ' + error.message,
icon: 'none'
});
}
},
// 解析时间方案配置
parseScheduleConfig() {
try {
// 从 thingsModelValue 中找到 cfg_param
const thingsModelValue = JSON.parse(this.device.thingsModelValue);
const cfgParam = thingsModelValue.find(item => item.id === 'cfg_param');
if (cfgParam && cfgParam.value) {
// 提取 JSON 部分(去掉前面的 "JSON="
const jsonStr = cfgParam.value.startsWith('JSON=') ?
cfgParam.value.substring(5) :
cfgParam.value;
// 解析 JSON
const config = JSON.parse(jsonStr);
// 转换格式为 scheduleList 需要的格式
this.scheduleList = config.shedule.map(item => {
// 将 time_s秒数转换为 HH:mm:ss 格式
const hours = Math.floor(item.time_s / 3600);
const minutes = Math.floor((item.time_s % 3600) / 60);
const seconds = item.time_s % 60;
const time =
`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
// 将 week 字符串转换为 weekdays 数组
const weekdays = item.week.split('').map((day, index) => day === '1' ? index : null)
.filter(index => index !== null);
// 获取选中的继电器索引
const relayIndexes = [];
if (item.sw1_s === 1) relayIndexes.push(0);
if (item.sw2_s === 1) relayIndexes.push(1);
if (item.sw3_s === 1) relayIndexes.push(2);
if (item.sw4_s === 1) relayIndexes.push(3);
return {
time,
action: relayIndexes.length > 0 ? 1 : 0, // 如果有选中的继电器则为开启
relayIndexes,
weekdays,
originalData: item // 保留原始数据
};
});
console.log(' 解析出的时间方案:', this.scheduleList);
}
} catch (error) {
console.error(' 解析时间方案配置出错:', error);
}
},
// 提取继电器列表
extractRelayList() {
// 从 device.thingsModels 中筛选出所有开关状态属性
const relayItems = this.device.thingsModels.filter(item =>
item.id.startsWith('sw_ch') && item.datatype.type === 'bool'
);
// 转换为继电器列表格式
this.relayList = relayItems.map((item, index) => ({
name: `继电器${index + 1}`,
status: parseInt(item.shadow) || 0, // 确保是数字类型
remark: this.getRelayRemark(index + 1), // 获取对应通道的备注
model: JSON.parse(JSON.stringify(item)) // 深拷贝物模型对象
}));
console.log(' 继电器列表:', JSON.stringify(this.relayList));
},
// 获取继电器备注(从 remark_chX 属性)
getRelayRemark(channel) {
const remarkItem = this.device.thingsModels.find(item =>
item.id === `remark_ch${channel}`
);
return remarkItem ? remarkItem.shadow : '';
},
// mqtt回调
/* Mqtt回调处理 */
mqttCallback() {
this.$mqttTool.client.on('message', (topic, message, buffer) => {
let topics = topic.split('/');
let productId = topics[1];
let deviceNum = topics[2];
message = JSON.parse(message.toString());
// 判断是否是当前设备的消息
if (this.device.serialNumber !== deviceNum) return;
// 处理状态更新
if (topics[3] == 'status') {
console.log(' 接收到【设备状态-运行】主题:', topic);
console.log(' 接收到【设备状态-运行】内容:', JSON.stringify(message));
this.device.status = message.status;
this.device.isShadow = message.isShadow;
this.device.rssi = message.rssi;
this.updateDeviceStatus(this.device);
}
// 处理物模型属性更新
else if (topics[3] == 'property' || topics[3] == 'function' || topic.endsWith('ws/service')) {
console.log(' 接收到【物模型】主题:', topic);
console.log(' 接收到【物模型】内容:', JSON.stringify(message));
// 更新继电器状态
if (Array.isArray(message.message)) {
message.message.forEach(item => {
// 检查是否是继电器状态更新
if (item.id && item.id.startsWith('sw_ch')) {
const channel = parseInt(item.id.replace('sw_ch', ''));
if (!isNaN(channel) && channel >= 1 && channel <= 4) {
const index = channel - 1;
this.relayList[index].status = parseInt(item.value) || 0;
}
}
// 检查是否是继电器备注更新
else if (item.id && item.id.startsWith('remark_ch')) {
const channel = parseInt(item.id.replace('remark_ch', ''));
if (!isNaN(channel) && channel >= 1 && channel <= 4) {
const index = channel - 1;
this.relayList[index].remark = item.value || '';
}
}
});
}
}
});
},
//发送指令
async mqttPublish(device, model) {
const command = {};
command[model.id] = model.shadow;
const params = {
deviceId: device.deviceId,
modelId: model.modelId
}
//判断是否有权限
const response = await getOrderControl(params);
if (response.code != 200) {
uni.$u.toast(response.msg);
return;
}
const data = {
serialNumber: device.serialNumber,
productId: device.productId,
remoteCommand: command,
identifier: model.id,
modelName: model.name,
isShadow: device.status != 3,
type: model.type,
}
serviceInvoke(data).then(response => {
if (response.code === 200) {
uni.showToast({
icon: 'none',
title: this.$tt('status.service')
});
}
})
},
/** 更新设备状态 */
updateDeviceStatus(device) {
if (device.status === 3) {
this.title = this.$tt('status.online');
this.bgStyle =
'background-color:#5ac725;border-top-left-radius:6px;border-top-right-radius:6px;';
this.shadowUnEnable = false;
} else {
if (device.isShadow === 1) {
this.bgStyle =
'background-color:#3c9cff;border-top-left-radius:6px;border-top-right-radius:6px;';
this.title = this.$tt('status.shadow');
this.shadowUnEnable = false;
} else {
this.bgStyle =
'background-color:#909399;border-top-left-radius:6px;border-top-right-radius:6px;';
this.title = this.$tt('status.deviceOffline');
this.shadowUnEnable = true;
}
}
},
consolebutton() {
console.log("按钮打印", JSON.stringify(this.device))
},
// 切换继电器状态
async toggleRelay(index) {
if (!this.checkOnline()) return;
try {
// 1. 更新本地状态
const newStatus = this.relayList[index].status === 1 ? 0 : 1;
this.relayList[index].status = newStatus;
// 2. 找到对应的物模型
const channel = index + 1; // 继电器通道号(1-4)
const switchModel = this.device.thingsModels.find(
item => item.id === `sw_ch${channel}`
);
if (switchModel) {
// 3. 更新物模型值
switchModel.shadow = newStatus.toString();
// 4. 通过MQTT发布更新
await this.mqttPublish(this.device, switchModel);
}
} catch (error) {
console.error(' 切换继电器状态失败:', error);
// 回滚状态
this.relayList[index].status = this.relayList[index].status === 1 ? 0 : 1;
uni.showToast({
title: '操作失败: ' + error.message,
icon: 'none'
});
}
},
showAddSchedule() {
if (!this.checkOnline()) return;
// 重置编辑状态
this.isEditing = false;
this.editingIndex = -1;
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
this.scheduleForm = {
time: `${hours}:${minutes}:${seconds}`,
action: 1,
relayIndexes: [],
weekdays: []
};
this.showPopup = true;
},
openTimePicker() {
this.showTimePicker = true;
// 以当前表单时间为准若无则默认12:00:00
this.tempTime = this.scheduleForm.time || '12:00:00';
},
timeConfirm(e) {
let timeStr = '';
let value = e.value !== undefined ? e.value : this.tempTime;
// 如果是时间戳
if (typeof value === 'number') {
const date = new Date(value);
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
timeStr = `${hours}:${minutes}:${seconds}`;
} else if (typeof value === 'string') {
// 兼容直接选择字符串
timeStr = value;
}
this.scheduleForm.time = timeStr;
this.showTimePicker = false;
},
async addSchedule() {
try {
// 1. 添加时间方案到本地列表
const newSchedule = {
...this.scheduleForm
};
console.log('【addSchedule】newSchedule:', newSchedule); // 打印表单内容
this.scheduleList.push(newSchedule);
this.showPopup = false;
// 2. 转换为设备需要的格式
const deviceSchedule = this.convertToDeviceSchedule(newSchedule);
console.log('【addSchedule】deviceSchedule:', deviceSchedule); // 打印转换后内容
// 3. 准备要发送的数据
const scheduleConfig = {
shedule: [...this.scheduleList.map(s => s.originalData || this.convertToDeviceSchedule(s))]
};
console.log('【addSchedule】scheduleConfig:', scheduleConfig); // 打印最终发送内容
// 4. 找到 cfg_param 模型
const cfgParamModel = this.device.thingsModels.find(item => item.id === 'cfg_param');
if (!cfgParamModel) {
throw new Error('找不到定时配置模型');
}
// 5. 更新模型值
cfgParamModel.shadow = `JSON=${JSON.stringify(scheduleConfig)}`;
// 6. 通过 MQTT 发布
await this.mqttPublish(this.device, cfgParamModel);
uni.showToast({
title: '时间方案添加成功',
icon: 'success'
});
} catch (error) {
console.error(' 添加时间方案失败:', error);
uni.showToast({
title: '添加失败: ' + error.message,
icon: 'none'
});
}
},
// 将前端格式转换为设备格式
convertToDeviceSchedule(schedule) {
console.log('【convertToDeviceSchedule】入参 schedule:', schedule);
// 保证 time 字符串为 HH:mm:ss 格式
let timeStr = schedule.time;
if (timeStr.length === 5) { // 只有HH:mm
timeStr += ':00';
}
const [hours, minutes, seconds] = timeStr.split(':').map(Number);
console.log('【convertToDeviceSchedule】split结果:', hours, minutes, seconds);
const time_s = hours * 3600 + minutes * 60 + seconds;
console.log('【convertToDeviceSchedule】time_s:', time_s);
// 将星期数组转换为字符串
const weekArr = new Array(7).fill('0');
schedule.weekdays.forEach(day => weekArr[day] = '1');
const week = weekArr.join('');
// 设置继电器状态
const relayStatus = [0, 0, 0, 0]; // 默认全部关闭
schedule.relayIndexes.forEach(index => relayStatus[index] = 1);
return {
time_s,
week,
sw1_s: relayStatus[0],
sw2_s: relayStatus[1],
sw3_s: relayStatus[2],
sw4_s: relayStatus[3]
};
},
async deleteSchedule(index) {
if (!this.checkOnline()) return;
try {
// 1. 从列表中删除
this.scheduleList.splice(index, 1);
// 2. 准备要发送的数据
const scheduleConfig = {
shedule: this.scheduleList.map(s => s.originalData || this.convertToDeviceSchedule(s))
};
// 3. 找到 cfg_param 模型
const cfgParamModel = this.device.thingsModels.find(item => item.id === 'cfg_param');
if (!cfgParamModel) {
throw new Error('找不到定时配置模型');
}
// 4. 更新模型值
cfgParamModel.shadow = `JSON=${JSON.stringify(scheduleConfig)}`;
// 5. 通过 MQTT 发布
await this.mqttPublish(this.device, cfgParamModel);
uni.showToast({
title: '删除成功',
icon: 'success'
});
} catch (error) {
console.error(' 删除时间方案失败:', error);
uni.showToast({
title: '删除失败: ' + error.message,
icon: 'none'
});
}
},
// 编辑时间方案
editSchedule(index) {
if (!this.checkOnline()) return;
// 设置编辑模式
this.isEditing = true;
this.editingIndex = index;
// 获取要编辑的时间方案
const schedule = this.scheduleList[index];
// 填充表单数据
this.scheduleForm = {
time: schedule.time,
action: schedule.action,
relayIndexes: [...schedule.relayIndexes], // 深拷贝数组
weekdays: [...schedule.weekdays] // 深拷贝数组
};
// 显示弹窗
this.showPopup = true;
},
// 更新时间方案
async updateSchedule() {
try {
// 1. 更新本地数据
const updatedSchedule = {
...this.scheduleForm
};
// 保留原始数据(如果存在)
if (this.scheduleList[this.editingIndex].originalData) {
updatedSchedule.originalData = this.scheduleList[this.editingIndex].originalData;
}
this.scheduleList[this.editingIndex] = updatedSchedule;
this.showPopup = false;
// 2. 转换为设备需要的格式
const deviceSchedule = this.convertToDeviceSchedule(updatedSchedule);
console.log('【updateSchedule】deviceSchedule:', deviceSchedule);
// 3. 准备要发送的数据
const scheduleConfig = {
shedule: this.scheduleList.map(s => s.originalData || this.convertToDeviceSchedule(s))
};
console.log('【updateSchedule】scheduleConfig:', scheduleConfig);
// 4. 找到 cfg_param 模型
const cfgParamModel = this.device.thingsModels.find(item => item.id === 'cfg_param');
if (!cfgParamModel) {
throw new Error('找不到定时配置模型');
}
// 5. 更新模型值
cfgParamModel.shadow = `JSON=${JSON.stringify(scheduleConfig)}`;
// 6. 通过 MQTT 发布
await this.mqttPublish(this.device, cfgParamModel);
// 7. 重置编辑状态
this.isEditing = false;
this.editingIndex = -1;
uni.showToast({
title: '时间方案更新成功',
icon: 'success'
});
} catch (error) {
console.error('更新时间方案失败:', error);
uni.showToast({
title: '更新失败: ' + error.message,
icon: 'none'
});
}
},
// 切换继电器checkbox
toggleRelayCheckbox(index) {
const currentIndex = this.scheduleForm.relayIndexes.indexOf(index);
if (currentIndex > -1) {
// 如果已选中,则移除
this.scheduleForm.relayIndexes.splice(currentIndex, 1);
} else {
// 如果未选中,则添加
this.scheduleForm.relayIndexes.push(index);
}
},
// 切换星期checkbox
toggleWeekdayCheckbox(index) {
const currentIndex = this.scheduleForm.weekdays.indexOf(index);
if (currentIndex > -1) {
// 如果已选中,则移除
this.scheduleForm.weekdays.splice(currentIndex, 1);
} else {
// 如果未选中,则添加
this.scheduleForm.weekdays.push(index);
}
},
// 关闭时间方案弹窗
closeSchedulePopup() {
this.showPopup = false;
this.resetScheduleForm();
},
// 重置表单(在关闭弹窗时调用)
resetScheduleForm() {
this.scheduleForm = {
time: '12:00:00',
action: 1,
relayIndexes: [],
weekdays: []
};
this.isEditing = false;
this.editingIndex = -1;
},
}
}
</script>
<style lang="scss">
/* 添加禁用状态的样式 */
.u-button[disabled] {
opacity: 0.6;
cursor: not-allowed;
}
.relay-name[disabled] {
opacity: 0.6;
cursor: not-allowed;
}
/* 继电器名称样式 */
.relay-name {
color: #333;
font-weight: 500;
margin-bottom: 10rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 8rpx;
border-radius: 8rpx;
}
.relay-name:active {
background-color: #f0f0f0;
}
/* 编辑图标样式 */
.u-icon-edit-pen {
opacity: 0.6;
transition: opacity 0.3s;
}
.relay-name:hover .u-icon-edit-pen {
opacity: 1;
}
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx 0;
color: #c0c4cc;
font-size: 28rpx;
gap: 20rpx;
}
.card {
box-shadow: 0 1px 0px 0 rgba(0, 0, 0, 0.1);
border-radius: 20rpx;
background-color: #fff;
&:not(:last-child) {
margin-bottom: 20rpx;
}
.status-title {
font-weight: bold;
font-size: 15px;
color: #333333;
line-height: 42rpx;
text-align: left;
font-style: normal;
text-transform: none;
padding: 15rpx 28rpx;
}
.version-wrap {
background-color: #F7F7F7;
border-radius: 10rpx;
padding: 0 42rpx;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 24rpx;
color: #000000;
line-height: 42rpx;
text-align: left;
font-style: normal;
}
}
.relay-control {
padding: 20rpx;
.relay-status,
.time-schedule {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.schedule-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.schedule-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.schedule-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.schedule-card {
background-color: #f9f9f9;
border-radius: 12rpx;
padding: 20rpx;
display: flex;
flex-direction: column;
gap: 15rpx;
position: relative;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.schedule-time {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.schedule-content {
display: flex;
flex-direction: column;
gap: 12rpx;
padding-left: 28rpx;
}
.schedule-row {
display: flex;
align-items: center;
font-size: 26rpx;
}
.label {
color: #666;
min-width: 120rpx;
}
.relay-tags,
.weekday-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
}
.schedule-actions {
display: flex;
justify-content: flex-end;
margin-top: 10rpx;
gap: 10rpx;
}
.status-title,
.schedule-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.status-titletop {
font-weight: bold;
font-size: 15px;
color: #333333;
line-height: 42rpx;
text-align: left;
font-style: normal;
text-transform: none;
padding: 15rpx 28rpx;
}
.relay-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.relay-item {
background-color: #f5f5f5;
padding: 20rpx;
border-radius: 8rpx;
text-align: center;
transition: all 0.3s ease;
border: 2rpx solid transparent;
&.relay-active {
background-color: #e6f9ea;
border-color: #52c41a;
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.15);
}
.relay-name {
font-size: 28rpx;
margin-bottom: 10rpx;
}
.relay-state {
font-size: 24rpx;
color: #666;
margin-bottom: 10rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
&.active {
color: #52c41a;
}
.status-indicator {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: #ccc;
transition: all 0.3s ease;
&.active {
background-color: #52c41a;
box-shadow: 0 0 8rpx rgba(82, 196, 26, 0.4);
}
}
}
.btn-active {
background-color: #52c41a !important;
border-color: #52c41a !important;
}
}
/* 弹窗专属样式 */
.popup-content {
background-color: #fff;
padding: 30rpx;
border-radius: 12rpx;
width: 80vw;
max-height: 80vh;
overflow-y: auto;
.popup-title {
text-align: center;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 30rpx;
}
/* 原生checkbox样式 */
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15rpx;
padding: 10rpx 0;
}
.checkbox-item {
width: 100%;
padding: 12rpx 8rpx;
border-radius: 6rpx;
background-color: #f8f8f8;
border: 1px solid #eee;
box-sizing: border-box;
transition: all 0.3s ease;
min-height: 60rpx;
display: flex;
align-items: center;
}
.checkbox-item:active {
background-color: #e8f4ff;
border-color: #3c9cff;
}
.checkbox-wrapper {
display: flex;
align-items: center;
gap: 12rpx;
width: 100%;
}
.checkbox {
width: 36rpx;
height: 36rpx;
border: 2rpx solid #ddd;
border-radius: 6rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
transition: all 0.3s ease;
flex-shrink: 0;
}
.checkbox.checked {
background-color: #3c9cff;
border-color: #3c9cff;
}
.checkbox-checkmark {
color: #fff;
font-size: 24rpx;
font-weight: bold;
line-height: 1;
}
.checkbox-label {
font-size: 26rpx;
color: #333;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.relay-info {
display: flex;
flex-direction: column;
gap: 4rpx;
flex: 1;
}
.relay-status-text {
font-size: 22rpx;
color: #999;
transition: all 0.3s ease;
}
.relay-status-text.active {
color: #52c41a;
font-weight: bold;
}
.checkbox-item.selected {
background-color: #e6f9ea;
border-color: #52c41a;
}
/* 表单输入框样式 */
::v-deep .u-input {
padding: 12rpx 20rpx;
background-color: #f8f8f8;
border-radius: 8rpx;
}
/* 按钮区域 */
.popup-buttons {
display: flex;
justify-content: space-around;
margin-top: 30rpx;
gap: 20rpx;
.u-button {
flex: 1;
}
}
}
}
/* 响应式调整 */
@media (min-width: 768px) {
.schedule-card {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.schedule-content {
flex: 1;
flex-direction: row;
flex-wrap: wrap;
gap: 15rpx 30rpx;
}
.schedule-row {
margin-bottom: 0;
}
.schedule-actions {
margin-top: 0;
}
}
/* 小屏幕适配 */
@media (max-width: 375px) {
.checkbox-group {
grid-template-columns: 1fr;
gap: 12rpx;
}
.checkbox-item {
min-height: 56rpx;
padding: 10rpx 6rpx;
}
.checkbox-label {
font-size: 24rpx;
}
}
</style>