|
|
|
@ -1,15 +1,23 @@
|
|
|
|
import { M9zService, ManualData, LoopInfo } from '../../utils/M9zService';
|
|
|
|
import { M9zService, ManualData, LoopInfo, SunRiseTimeData } from '../../utils/M9zService';
|
|
|
|
|
|
|
|
|
|
|
|
@Component
|
|
|
|
@Component
|
|
|
|
export struct ManualControl {
|
|
|
|
export struct ManualControl {
|
|
|
|
@Prop deviceId: string;
|
|
|
|
@Prop deviceId: string;
|
|
|
|
@State manualData: ManualData = new ManualData(); // 初始化 Class
|
|
|
|
@Prop @Watch('onTabChange') currentTabIndex: number = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@State manualData: ManualData = new ManualData();
|
|
|
|
@State deviceTime: string = '--:--:--';
|
|
|
|
@State deviceTime: string = '--:--:--';
|
|
|
|
@State isConnected: boolean = false;
|
|
|
|
@State isConnected: boolean = false;
|
|
|
|
@State isLoading: boolean = false;
|
|
|
|
@State isLoading: boolean = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@State sunriseTime: string = '--:--:--';
|
|
|
|
|
|
|
|
@State sunsetTime: string = '--:--:--';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Brightness Control
|
|
|
|
@State selectedLoopIndex: number = -1;
|
|
|
|
@State selectedLoopIndex: number = -1;
|
|
|
|
@State currentBrightness: number = 50;
|
|
|
|
@State currentBrightness: number = 50;
|
|
|
|
@State showBrightnessDialog: boolean = false;
|
|
|
|
@State showBrightnessDialog: boolean = false;
|
|
|
|
|
|
|
|
|
|
|
|
@State toastMessage: string = '';
|
|
|
|
@State toastMessage: string = '';
|
|
|
|
@State showToast: boolean = false;
|
|
|
|
@State showToast: boolean = false;
|
|
|
|
|
|
|
|
|
|
|
|
@ -24,6 +32,12 @@ export struct ManualControl {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onTabChange() {
|
|
|
|
|
|
|
|
if (this.currentTabIndex === 0 && this.deviceId) {
|
|
|
|
|
|
|
|
this.refreshData();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
aboutToDisappear() {
|
|
|
|
aboutToDisappear() {
|
|
|
|
if (this.timer > 0) {
|
|
|
|
if (this.timer > 0) {
|
|
|
|
clearInterval(this.timer);
|
|
|
|
clearInterval(this.timer);
|
|
|
|
@ -32,14 +46,12 @@ export struct ManualControl {
|
|
|
|
|
|
|
|
|
|
|
|
startTimer() {
|
|
|
|
startTimer() {
|
|
|
|
this.timer = setInterval((): void => {
|
|
|
|
this.timer = setInterval((): void => {
|
|
|
|
this.m9zService.getDeviceTime({
|
|
|
|
// Local time update for UI clock if needed,
|
|
|
|
onSuccess: (date: Date): void => {
|
|
|
|
// but here we fetch device time periodically or just tick internally?
|
|
|
|
this.deviceTime = this.formatDateTime(date);
|
|
|
|
// The Vue app updates local time every second and valid device time + 1s.
|
|
|
|
},
|
|
|
|
// For now, let's keep fetching device time periodically or simplify.
|
|
|
|
onError: (err: string): void => {
|
|
|
|
// Let's just fetch device time every few seconds to keep it simple and accurate to device.
|
|
|
|
// console.error('获取时间失败', err);
|
|
|
|
// Or better, fetch once and tick locally to reduce traffic.
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}, 1000);
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -54,29 +66,62 @@ export struct ManualControl {
|
|
|
|
loops.push(loop);
|
|
|
|
loops.push(loop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const data = new ManualData();
|
|
|
|
const data = new ManualData();
|
|
|
|
data.disable = false;
|
|
|
|
data.disable = false; // Default to Auto
|
|
|
|
data.loops = loops;
|
|
|
|
data.loops = loops;
|
|
|
|
this.manualData = data;
|
|
|
|
this.manualData = data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
refreshData() {
|
|
|
|
refreshData() {
|
|
|
|
this.isLoading = true;
|
|
|
|
this.isLoading = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Get Manual Info
|
|
|
|
this.m9zService.getManualControlInfo({
|
|
|
|
this.m9zService.getManualControlInfo({
|
|
|
|
onSuccess: (data: ManualData): void => {
|
|
|
|
onSuccess: (data: ManualData): void => {
|
|
|
|
this.manualData = data;
|
|
|
|
this.manualData = data;
|
|
|
|
this.isLoading = false;
|
|
|
|
this.fetchDeviceTime();
|
|
|
|
},
|
|
|
|
},
|
|
|
|
onError: (err: string): void => {
|
|
|
|
onError: (err: string): void => {
|
|
|
|
this.showToastMessage('获取数据失败: ' + err);
|
|
|
|
this.showToastMessage('获取数据失败: ' + err);
|
|
|
|
this.isLoading = false;
|
|
|
|
this.fetchDeviceTime();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fetchDeviceTime() {
|
|
|
|
|
|
|
|
// 2. Get Device Time
|
|
|
|
|
|
|
|
this.m9zService.getDeviceTime({
|
|
|
|
|
|
|
|
onSuccess: (date: Date): void => {
|
|
|
|
|
|
|
|
this.deviceTime = this.formatDateTime(date);
|
|
|
|
|
|
|
|
this.fetchSunRiseTime();
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
onError: (err: string): void => {
|
|
|
|
|
|
|
|
this.fetchSunRiseTime();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fetchSunRiseTime() {
|
|
|
|
|
|
|
|
// 3. Get SunRise/Set Time
|
|
|
|
|
|
|
|
this.m9zService.getSunRiseTime({
|
|
|
|
|
|
|
|
onSuccess: (data: SunRiseTimeData): void => {
|
|
|
|
|
|
|
|
if (data.sunrise) this.sunriseTime = this.formatTimeOnly(data.sunrise);
|
|
|
|
|
|
|
|
if (data.sunset) this.sunsetTime = this.formatTimeOnly(data.sunset);
|
|
|
|
|
|
|
|
this.checkLoadingComplete();
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
onError: (err: string): void => {
|
|
|
|
|
|
|
|
this.checkLoadingComplete();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checkLoadingComplete() {
|
|
|
|
|
|
|
|
this.isLoading = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
toggleMode() {
|
|
|
|
toggleMode() {
|
|
|
|
// Deep copy manualData
|
|
|
|
|
|
|
|
const newData = new ManualData();
|
|
|
|
const newData = new ManualData();
|
|
|
|
newData.disable = !this.manualData.disable;
|
|
|
|
newData.disable = !this.manualData.disable; // Toggle
|
|
|
|
|
|
|
|
// Copy existing loops
|
|
|
|
this.manualData.loops.forEach((l: LoopInfo): void => {
|
|
|
|
this.manualData.loops.forEach((l: LoopInfo): void => {
|
|
|
|
const loop = new LoopInfo();
|
|
|
|
const loop = new LoopInfo();
|
|
|
|
loop.index = l.index;
|
|
|
|
loop.index = l.index;
|
|
|
|
@ -89,7 +134,7 @@ export struct ManualControl {
|
|
|
|
this.m9zService.setManualControl(newData, {
|
|
|
|
this.m9zService.setManualControl(newData, {
|
|
|
|
onSuccess: (success: boolean): void => {
|
|
|
|
onSuccess: (success: boolean): void => {
|
|
|
|
if (success) {
|
|
|
|
if (success) {
|
|
|
|
this.manualData = newData; // Re-assign triggers UI update
|
|
|
|
this.manualData = newData;
|
|
|
|
this.showToastMessage('模式切换成功');
|
|
|
|
this.showToastMessage('模式切换成功');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@ -100,23 +145,12 @@ export struct ManualControl {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
toggleLoop(index: number) {
|
|
|
|
toggleLoop(index: number) {
|
|
|
|
if (!this.manualData.disable) { // disable=true 代表手动模式 (0x01)
|
|
|
|
if (!this.manualData.disable) {
|
|
|
|
this.showToastMessage('请先切换到手动模式');
|
|
|
|
this.showToastMessage('请先切换到手动模式');
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const newData = new ManualData();
|
|
|
|
const newData = this.cloneManualData(this.manualData);
|
|
|
|
newData.disable = this.manualData.disable;
|
|
|
|
|
|
|
|
this.manualData.loops.forEach((l: LoopInfo): void => {
|
|
|
|
|
|
|
|
const loop = new LoopInfo();
|
|
|
|
|
|
|
|
loop.index = l.index;
|
|
|
|
|
|
|
|
loop.name = l.name;
|
|
|
|
|
|
|
|
loop.isOpen = l.isOpen;
|
|
|
|
|
|
|
|
loop.pwm = l.pwm;
|
|
|
|
|
|
|
|
newData.loops.push(loop);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Toggle
|
|
|
|
|
|
|
|
newData.loops[index].isOpen = !newData.loops[index].isOpen;
|
|
|
|
newData.loops[index].isOpen = !newData.loops[index].isOpen;
|
|
|
|
|
|
|
|
|
|
|
|
this.m9zService.setManualControl(newData, {
|
|
|
|
this.m9zService.setManualControl(newData, {
|
|
|
|
@ -146,21 +180,12 @@ export struct ManualControl {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
confirmBrightness() {
|
|
|
|
confirmBrightness() {
|
|
|
|
const newData = new ManualData();
|
|
|
|
const newData = this.cloneManualData(this.manualData);
|
|
|
|
newData.disable = this.manualData.disable;
|
|
|
|
|
|
|
|
this.manualData.loops.forEach((l: LoopInfo): void => {
|
|
|
|
|
|
|
|
const loop = new LoopInfo();
|
|
|
|
|
|
|
|
loop.index = l.index;
|
|
|
|
|
|
|
|
loop.name = l.name;
|
|
|
|
|
|
|
|
loop.isOpen = l.isOpen;
|
|
|
|
|
|
|
|
loop.pwm = l.pwm;
|
|
|
|
|
|
|
|
newData.loops.push(loop);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.selectedLoopIndex >= 0) {
|
|
|
|
if (this.selectedLoopIndex >= 0) {
|
|
|
|
newData.loops[this.selectedLoopIndex].pwm = this.currentBrightness;
|
|
|
|
newData.loops[this.selectedLoopIndex].pwm = this.currentBrightness;
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
// 全局调光
|
|
|
|
// Global
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
newData.loops[i].pwm = this.currentBrightness;
|
|
|
|
newData.loops[i].pwm = this.currentBrightness;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -171,7 +196,6 @@ export struct ManualControl {
|
|
|
|
if (success) {
|
|
|
|
if (success) {
|
|
|
|
this.manualData = newData;
|
|
|
|
this.manualData = newData;
|
|
|
|
this.showBrightnessDialog = false;
|
|
|
|
this.showBrightnessDialog = false;
|
|
|
|
this.showToastMessage('调光成功');
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
onError: (err: string): void => {
|
|
|
|
onError: (err: string): void => {
|
|
|
|
@ -185,16 +209,8 @@ export struct ManualControl {
|
|
|
|
this.showToastMessage('请先切换到手动模式');
|
|
|
|
this.showToastMessage('请先切换到手动模式');
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const newData = new ManualData();
|
|
|
|
const newData = this.cloneManualData(this.manualData);
|
|
|
|
newData.disable = this.manualData.disable;
|
|
|
|
newData.loops.forEach(l => l.isOpen = true);
|
|
|
|
this.manualData.loops.forEach((l: LoopInfo): void => {
|
|
|
|
|
|
|
|
const loop = new LoopInfo();
|
|
|
|
|
|
|
|
loop.index = l.index;
|
|
|
|
|
|
|
|
loop.name = l.name;
|
|
|
|
|
|
|
|
loop.isOpen = true; // ON
|
|
|
|
|
|
|
|
loop.pwm = l.pwm;
|
|
|
|
|
|
|
|
newData.loops.push(loop);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
this.sendControl(newData, '全开成功');
|
|
|
|
this.sendControl(newData, '全开成功');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -203,17 +219,23 @@ export struct ManualControl {
|
|
|
|
this.showToastMessage('请先切换到手动模式');
|
|
|
|
this.showToastMessage('请先切换到手动模式');
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const newData = this.cloneManualData(this.manualData);
|
|
|
|
|
|
|
|
newData.loops.forEach(l => l.isOpen = false);
|
|
|
|
|
|
|
|
this.sendControl(newData, '全关成功');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cloneManualData(src: ManualData): ManualData {
|
|
|
|
const newData = new ManualData();
|
|
|
|
const newData = new ManualData();
|
|
|
|
newData.disable = this.manualData.disable;
|
|
|
|
newData.disable = src.disable;
|
|
|
|
this.manualData.loops.forEach((l: LoopInfo): void => {
|
|
|
|
src.loops.forEach((l: LoopInfo): void => {
|
|
|
|
const loop = new LoopInfo();
|
|
|
|
const loop = new LoopInfo();
|
|
|
|
loop.index = l.index;
|
|
|
|
loop.index = l.index;
|
|
|
|
loop.name = l.name;
|
|
|
|
loop.name = l.name;
|
|
|
|
loop.isOpen = false; // OFF
|
|
|
|
loop.isOpen = l.isOpen;
|
|
|
|
loop.pwm = l.pwm;
|
|
|
|
loop.pwm = l.pwm;
|
|
|
|
newData.loops.push(loop);
|
|
|
|
newData.loops.push(loop);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
this.sendControl(newData, '全关成功');
|
|
|
|
return newData;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sendControl(newData: ManualData, successMsg: string): void {
|
|
|
|
sendControl(newData: ManualData, successMsg: string): void {
|
|
|
|
@ -232,7 +254,11 @@ export struct ManualControl {
|
|
|
|
|
|
|
|
|
|
|
|
syncDeviceTime(): void {
|
|
|
|
syncDeviceTime(): void {
|
|
|
|
this.m9zService.setDeviceTime(new Date(), {
|
|
|
|
this.m9zService.setDeviceTime(new Date(), {
|
|
|
|
onSuccess: (): void => this.showToastMessage('时间同步成功'),
|
|
|
|
onSuccess: (success: boolean): void => {
|
|
|
|
|
|
|
|
this.showToastMessage('时间同步成功');
|
|
|
|
|
|
|
|
// Refresh display
|
|
|
|
|
|
|
|
this.deviceTime = this.formatDateTime(new Date());
|
|
|
|
|
|
|
|
},
|
|
|
|
onError: (err: string): void => this.showToastMessage('同步失败:' + err)
|
|
|
|
onError: (err: string): void => this.showToastMessage('同步失败:' + err)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -255,213 +281,288 @@ export struct ManualControl {
|
|
|
|
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
|
|
|
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
formatTimeOnly(date: Date): string {
|
|
|
|
|
|
|
|
const h = String(date.getHours()).padStart(2, '0');
|
|
|
|
|
|
|
|
const min = String(date.getMinutes()).padStart(2, '0');
|
|
|
|
|
|
|
|
const s = String(date.getSeconds()).padStart(2, '0');
|
|
|
|
|
|
|
|
return `${h}:${min}:${s}`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
build() {
|
|
|
|
build() {
|
|
|
|
Column() {
|
|
|
|
Stack() {
|
|
|
|
// 这里的UI结构参考 ManualControl.vue 和 BluetoothControlPage.ets 融合
|
|
|
|
Scroll() {
|
|
|
|
// 设备信息
|
|
|
|
Column() {
|
|
|
|
Column() {
|
|
|
|
// --- Header Status ---
|
|
|
|
Row() {
|
|
|
|
Column() {
|
|
|
|
Text(`设备时间:${this.deviceTime}`)
|
|
|
|
Row() {
|
|
|
|
.fontSize(14)
|
|
|
|
Text('设备号:N/A').fontSize(12).fontColor('#b2ebf2')
|
|
|
|
.fontColor('#eaf6fc')
|
|
|
|
}.width('100%').margin({bottom: 5})
|
|
|
|
Blank()
|
|
|
|
Row() {
|
|
|
|
Button('刷新')
|
|
|
|
Text('设备名称:智能照明控制器').fontSize(12).fontColor('#b2ebf2')
|
|
|
|
.fontSize(12)
|
|
|
|
}.width('100%').margin({bottom: 10})
|
|
|
|
.height(24)
|
|
|
|
Row() {
|
|
|
|
.onClick(() => this.refreshData())
|
|
|
|
Text(`设备时间:${this.deviceTime}`)
|
|
|
|
}.width('100%').margin({bottom: 10})
|
|
|
|
.fontSize(14).fontColor('#eaf6fc')
|
|
|
|
|
|
|
|
}.width('100%')
|
|
|
|
// 模式切换
|
|
|
|
}
|
|
|
|
Row() {
|
|
|
|
.width('100%')
|
|
|
|
Text('控制模式')
|
|
|
|
.padding(15)
|
|
|
|
.fontSize(14)
|
|
|
|
.backgroundColor('rgba(15, 30, 60, 0.4)')
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
.borderRadius(8)
|
|
|
|
Blank()
|
|
|
|
.margin({bottom: 20})
|
|
|
|
Button(this.manualData.disable ? '手动模式' : '自动模式')
|
|
|
|
|
|
|
|
.fontSize(12)
|
|
|
|
// --- Control Mode ---
|
|
|
|
.fontColor(this.manualData.disable ? '#22fdc8' : '#b2ebf2')
|
|
|
|
Column() {
|
|
|
|
.backgroundColor(this.manualData.disable ? 'rgba(34, 253, 200, 0.1)' : 'rgba(20, 38, 81, 0.95)')
|
|
|
|
Text('控制模式')
|
|
|
|
.border({ width: 1, color: this.manualData.disable ? '#22fdc8' : '#213b67' })
|
|
|
|
.fontSize(14)
|
|
|
|
.onClick((): void => {
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
this.toggleMode();
|
|
|
|
.width('100%')
|
|
|
|
})
|
|
|
|
.margin({ bottom: 10 })
|
|
|
|
}.width('100%').margin({bottom: 15})
|
|
|
|
.fontWeight(FontWeight.Medium)
|
|
|
|
|
|
|
|
|
|
|
|
// 回路控制 Grid
|
|
|
|
Button(this.manualData.disable ? '手动模式' : '自动模式')
|
|
|
|
Text('回路控制')
|
|
|
|
.width('100%')
|
|
|
|
.fontSize(14)
|
|
|
|
.backgroundColor(this.manualData.disable ? 'rgba(34, 253, 200, 0.1)' : 'rgba(20, 38, 81, 0.95)')
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
.fontColor(this.manualData.disable ? '#22fdc8' : '#b2ebf2')
|
|
|
|
.width('100%')
|
|
|
|
.border({ width: 1, color: this.manualData.disable ? '#22fdc8' : '#213b67' })
|
|
|
|
.margin({ bottom: 10 })
|
|
|
|
.height(44)
|
|
|
|
|
|
|
|
.onClick(() => this.toggleMode())
|
|
|
|
Grid() {
|
|
|
|
}
|
|
|
|
ForEach(this.manualData.loops, (loop: LoopInfo): void => {
|
|
|
|
.width('100%')
|
|
|
|
GridItem() {
|
|
|
|
.margin({bottom: 20})
|
|
|
|
Column() {
|
|
|
|
|
|
|
|
Text('💡')
|
|
|
|
// --- Circuit Control ---
|
|
|
|
.fontSize(20)
|
|
|
|
Column() {
|
|
|
|
.fontColor(loop.isOpen ? '#22fdc8' : '#666')
|
|
|
|
Text('回路控制')
|
|
|
|
.backgroundColor(loop.isOpen ? 'rgba(34, 253, 200, 0.2)' : '#2d3a51')
|
|
|
|
.fontSize(14)
|
|
|
|
.width(40).height(40)
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
.borderRadius(20)
|
|
|
|
.width('100%')
|
|
|
|
.textAlign(TextAlign.Center)
|
|
|
|
.margin({ bottom: 10 })
|
|
|
|
.lineHeight(40)
|
|
|
|
.fontWeight(FontWeight.Medium)
|
|
|
|
.border({ width: 1, color: loop.isOpen ? '#22fdc8' : '#3e7ba1' })
|
|
|
|
|
|
|
|
|
|
|
|
Grid() {
|
|
|
|
Text(loop.name)
|
|
|
|
ForEach(this.manualData.loops, (loop: LoopInfo): void => {
|
|
|
|
.fontSize(12)
|
|
|
|
GridItem() {
|
|
|
|
.fontColor('#eaf6fc')
|
|
|
|
Column() {
|
|
|
|
.margin({top: 5})
|
|
|
|
// Icon Circle
|
|
|
|
}
|
|
|
|
Text('💡')
|
|
|
|
.onClick((): void => {
|
|
|
|
.fontSize(24)
|
|
|
|
this.toggleLoop(loop.index);
|
|
|
|
.fontColor(loop.isOpen ? '#22fdc8' : '#666')
|
|
|
|
|
|
|
|
.width(50).height(50)
|
|
|
|
|
|
|
|
.textAlign(TextAlign.Center)
|
|
|
|
|
|
|
|
.borderRadius(25)
|
|
|
|
|
|
|
|
.backgroundColor(loop.isOpen ? 'rgba(34, 253, 200, 0.2)' : '#2d3a51')
|
|
|
|
|
|
|
|
.border({ width: 1, color: loop.isOpen ? '#22fdc8' : '#3e7ba1' })
|
|
|
|
|
|
|
|
.margin({bottom: 8})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Text(`回路${loop.index + 1}`)
|
|
|
|
|
|
|
|
.fontSize(12).fontColor('#eaf6fc')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Text(loop.isOpen ? '开启' : '关闭')
|
|
|
|
|
|
|
|
.fontSize(10).fontColor(loop.isOpen ? '#22fdc8' : '#999')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.onClick(() => this.toggleLoop(loop.index))
|
|
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
|
|
|
|
}
|
|
|
|
.rowsGap(15)
|
|
|
|
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
|
|
|
|
.height(160)
|
|
|
|
.rowsGap(15)
|
|
|
|
}
|
|
|
|
.height(140)
|
|
|
|
.width('100%')
|
|
|
|
|
|
|
|
.margin({bottom: 20})
|
|
|
|
// 调光控制 Grid
|
|
|
|
|
|
|
|
Text('调光控制')
|
|
|
|
// --- Dimming Control ---
|
|
|
|
.fontSize(14)
|
|
|
|
Column() {
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
Text('调光控制')
|
|
|
|
.width('100%')
|
|
|
|
.fontSize(14)
|
|
|
|
.margin({ top: 15, bottom: 10 })
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
|
|
|
|
.width('100%')
|
|
|
|
Grid() {
|
|
|
|
.margin({ bottom: 10 })
|
|
|
|
ForEach(this.manualData.loops, (loop: LoopInfo): void => {
|
|
|
|
.fontWeight(FontWeight.Medium)
|
|
|
|
GridItem() {
|
|
|
|
|
|
|
|
Column() {
|
|
|
|
Grid() {
|
|
|
|
Text(`${loop.pwm}%`)
|
|
|
|
ForEach(this.manualData.loops, (loop: LoopInfo): void => {
|
|
|
|
.fontSize(12)
|
|
|
|
GridItem() {
|
|
|
|
.fontColor('#eaf6fc')
|
|
|
|
Column() {
|
|
|
|
.backgroundColor(loop.pwm > 0 ? 'rgba(34, 253, 200, 0.2)' : '#2d3a51')
|
|
|
|
// Value Circle
|
|
|
|
.width(40).height(40)
|
|
|
|
Stack() {
|
|
|
|
.borderRadius(20)
|
|
|
|
Circle({ width: 50, height: 50 })
|
|
|
|
.textAlign(TextAlign.Center)
|
|
|
|
.fill(loop.pwm > 0 ? 'rgba(34, 253, 200, 0.2)' : '#2d3a51')
|
|
|
|
.lineHeight(40)
|
|
|
|
.stroke(loop.pwm > 0 ? '#22fdc8' : '#3e7ba1')
|
|
|
|
.border({ width: 1, color: loop.pwm > 0 ? '#22fdc8' : '#3e7ba1' })
|
|
|
|
.strokeWidth(1)
|
|
|
|
|
|
|
|
Text(`${loop.pwm}%`)
|
|
|
|
Text(`通道${loop.index + 1}`)
|
|
|
|
.fontSize(12).fontColor('#eaf6fc')
|
|
|
|
.fontSize(10)
|
|
|
|
}
|
|
|
|
.fontColor('#b2ebf2')
|
|
|
|
.margin({bottom: 8})
|
|
|
|
.margin({top: 5})
|
|
|
|
|
|
|
|
}
|
|
|
|
Text(`通道${loop.index + 1}`)
|
|
|
|
.onClick((): void => {
|
|
|
|
.fontSize(12).fontColor('#b2ebf2')
|
|
|
|
this.openBrightnessDialog(loop.index);
|
|
|
|
}
|
|
|
|
|
|
|
|
.onClick(() => this.openBrightnessDialog(loop.index))
|
|
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
|
|
|
|
}
|
|
|
|
.rowsGap(15)
|
|
|
|
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
|
|
|
|
.height(160)
|
|
|
|
.rowsGap(15)
|
|
|
|
}
|
|
|
|
.height(140)
|
|
|
|
.width('100%')
|
|
|
|
|
|
|
|
.margin({bottom: 20})
|
|
|
|
// 快捷操作
|
|
|
|
|
|
|
|
Row() {
|
|
|
|
// --- Quick Actions ---
|
|
|
|
Button('全开')
|
|
|
|
Row() {
|
|
|
|
.fontSize(12)
|
|
|
|
Button('全开')
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
.backgroundColor('#102a45') // Darker
|
|
|
|
.backgroundColor('rgba(34, 253, 200, 0.1)')
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
.border({ width: 1, color: '#22fdc8' })
|
|
|
|
.border({width: 1, color: '#22fdc8'})
|
|
|
|
.layoutWeight(1)
|
|
|
|
.layoutWeight(1)
|
|
|
|
.onClick((): void => {
|
|
|
|
.height(40)
|
|
|
|
this.turnOnAll();
|
|
|
|
.onClick(() => this.turnOnAll())
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
Button('全关')
|
|
|
|
Button('全关')
|
|
|
|
.backgroundColor('#1a1520')
|
|
|
|
.fontSize(12)
|
|
|
|
.fontColor('#fa5050')
|
|
|
|
.fontColor('#fa5050')
|
|
|
|
.border({width: 1, color: '#fa5050'})
|
|
|
|
.backgroundColor('rgba(250, 80, 80, 0.1)')
|
|
|
|
.layoutWeight(1)
|
|
|
|
.border({ width: 1, color: '#fa5050' })
|
|
|
|
.height(40)
|
|
|
|
.layoutWeight(1)
|
|
|
|
.margin({left: 10})
|
|
|
|
.margin({ left: 8 })
|
|
|
|
.onClick(() => this.turnOffAll())
|
|
|
|
.onClick((): void => {
|
|
|
|
|
|
|
|
this.turnOffAll();
|
|
|
|
Button('一键调光')
|
|
|
|
})
|
|
|
|
.backgroundColor('#2a2515')
|
|
|
|
|
|
|
|
.fontColor('#ffcb4d')
|
|
|
|
Button('一键调光')
|
|
|
|
.border({width: 1, color: '#ffcb4d'})
|
|
|
|
.fontSize(12)
|
|
|
|
.layoutWeight(1)
|
|
|
|
.fontColor('#ffcb4d')
|
|
|
|
.height(40)
|
|
|
|
.backgroundColor('rgba(255, 203, 77, 0.1)')
|
|
|
|
.margin({left: 10})
|
|
|
|
.border({ width: 1, color: '#ffcb4d' })
|
|
|
|
.onClick(() => this.openBrightnessDialog(-1))
|
|
|
|
.layoutWeight(1)
|
|
|
|
}.width('100%').margin({bottom: 15})
|
|
|
|
.margin({ left: 8 })
|
|
|
|
|
|
|
|
.onClick((): void => {
|
|
|
|
Row() {
|
|
|
|
this.openBrightnessDialog(-1);
|
|
|
|
Button('同步时间')
|
|
|
|
})
|
|
|
|
.backgroundColor('#102035')
|
|
|
|
|
|
|
|
.fontColor('#40a7fa')
|
|
|
|
|
|
|
|
.border({width: 1, color: '#40a7fa'})
|
|
|
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
|
|
|
.height(40)
|
|
|
|
|
|
|
|
.onClick(() => this.syncDeviceTime())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Button('刷新数据')
|
|
|
|
|
|
|
|
.backgroundColor('rgba(34, 253, 200, 0.1)')
|
|
|
|
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
|
|
|
|
.border({width: 1, color: '#22fdc8'})
|
|
|
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
|
|
|
.height(40)
|
|
|
|
|
|
|
|
.margin({left: 10})
|
|
|
|
|
|
|
|
.onClick(() => this.refreshData())
|
|
|
|
|
|
|
|
}.width('100%').margin({bottom: 20})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Bottom Info ---
|
|
|
|
|
|
|
|
Column() {
|
|
|
|
|
|
|
|
Row() {
|
|
|
|
|
|
|
|
Text(`日出时间:${this.sunriseTime}`).fontSize(12).fontColor('#b2ebf2').layoutWeight(1)
|
|
|
|
|
|
|
|
Text(`日落时间:${this.sunsetTime}`).fontSize(12).fontColor('#b2ebf2').layoutWeight(1)
|
|
|
|
|
|
|
|
}.width('100%')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
|
|
.padding(15)
|
|
|
|
|
|
|
|
.backgroundColor('rgba(15, 30, 60, 0.4)')
|
|
|
|
|
|
|
|
.borderRadius(8)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.width('100%')
|
|
|
|
.width('100%')
|
|
|
|
.margin({ top: 15 })
|
|
|
|
.padding(15)
|
|
|
|
|
|
|
|
|
|
|
|
Row() {
|
|
|
|
|
|
|
|
Button('同步时间')
|
|
|
|
|
|
|
|
.fontSize(12)
|
|
|
|
|
|
|
|
.fontColor('#40a7fa')
|
|
|
|
|
|
|
|
.backgroundColor('rgba(64, 167, 250, 0.1)')
|
|
|
|
|
|
|
|
.border({ width: 1, color: '#40a7fa' })
|
|
|
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
|
|
|
.onClick((): void => {
|
|
|
|
|
|
|
|
this.syncDeviceTime();
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}.width('100%').margin({top: 10})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.padding(15)
|
|
|
|
|
|
|
|
.width('100%')
|
|
|
|
.width('100%')
|
|
|
|
|
|
|
|
.height('100%')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Loading ---
|
|
|
|
|
|
|
|
if (this.isLoading) {
|
|
|
|
|
|
|
|
Stack() {
|
|
|
|
|
|
|
|
Rect().width('100%').height('100%').fill('rgba(0,0,0,0.5)')
|
|
|
|
|
|
|
|
Column() {
|
|
|
|
|
|
|
|
LoadingProgress().width(50).height(50).color('#22fdc8')
|
|
|
|
|
|
|
|
Text('数据加载中...').fontColor('white').margin({top: 10})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.width('100%').height('100%')
|
|
|
|
|
|
|
|
.zIndex(1000)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Toast & Dialogs ---
|
|
|
|
if (this.showToast) {
|
|
|
|
if (this.showToast) {
|
|
|
|
Text(this.toastMessage)
|
|
|
|
Text(this.toastMessage)
|
|
|
|
.fontSize(12)
|
|
|
|
.fontSize(12)
|
|
|
|
.fontColor('white')
|
|
|
|
.fontColor('white')
|
|
|
|
.backgroundColor('rgba(0,0,0,0.7)')
|
|
|
|
.backgroundColor('rgba(0,0,0,0.7)')
|
|
|
|
.padding(10)
|
|
|
|
.padding(10)
|
|
|
|
.borderRadius(5)
|
|
|
|
.borderRadius(5)
|
|
|
|
.position({x: '35%', y: '80%'})
|
|
|
|
.position({x: '35%', y: '80%'})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.showBrightnessDialog) {
|
|
|
|
if (this.showBrightnessDialog) {
|
|
|
|
// 简易弹窗
|
|
|
|
Stack() {
|
|
|
|
Stack() {
|
|
|
|
// Mask
|
|
|
|
Rect().width('100%').height('100%').fill('rgba(0,0,0,0.5)').onClick((): void => {
|
|
|
|
Rect().width('100%').height('100%').fill('rgba(0,0,0,0.6)').onClick(() => {
|
|
|
|
this.showBrightnessDialog = false;
|
|
|
|
this.showBrightnessDialog = false;
|
|
|
|
})
|
|
|
|
|
|
|
|
Column() {
|
|
|
|
|
|
|
|
Text(this.selectedLoopIndex >= 0 ? `通道${this.selectedLoopIndex + 1} 亮度` : '全部通道 亮度')
|
|
|
|
|
|
|
|
.fontColor('#eaf6fc').margin({bottom: 20})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Text(`${this.currentBrightness}%`).fontColor('#22fdc8').fontSize(24).margin({bottom: 20})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Slider({
|
|
|
|
|
|
|
|
value: this.currentBrightness,
|
|
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
|
|
max: 100,
|
|
|
|
|
|
|
|
style: SliderStyle.OutSet
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.onChange((value: number): void => {
|
|
|
|
// Content
|
|
|
|
this.currentBrightness = value;
|
|
|
|
Column() {
|
|
|
|
})
|
|
|
|
Text(this.selectedLoopIndex >= 0 ? `通道${this.selectedLoopIndex + 1} 亮度` : '全部通道 亮度')
|
|
|
|
.width('80%')
|
|
|
|
.fontColor('#eaf6fc').margin({bottom: 20}).fontSize(16)
|
|
|
|
|
|
|
|
|
|
|
|
Row() {
|
|
|
|
Text(`${this.currentBrightness}%`)
|
|
|
|
Button('取消').onClick((): void => {this.showBrightnessDialog = false;}).backgroundColor('transparent').fontColor('#999')
|
|
|
|
.fontColor('#22fdc8').fontSize(36).fontWeight(FontWeight.Bold).margin({bottom: 30})
|
|
|
|
Button('确定').onClick((): void => {this.confirmBrightness();}).backgroundColor('#22fdc8').fontColor('#000')
|
|
|
|
|
|
|
|
}.margin({top: 20}).width('100%').justifyContent(FlexAlign.SpaceAround)
|
|
|
|
Slider({
|
|
|
|
}
|
|
|
|
value: this.currentBrightness,
|
|
|
|
.width('80%')
|
|
|
|
min: 0,
|
|
|
|
.backgroundColor('#1d3553')
|
|
|
|
max: 100,
|
|
|
|
.padding(20)
|
|
|
|
style: SliderStyle.OutSet
|
|
|
|
.borderRadius(10)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
.trackColor('#2d3a51')
|
|
|
|
.position({x: 0, y: 0})
|
|
|
|
.selectedColor('#22fdc8')
|
|
|
|
.zIndex(100)
|
|
|
|
.blockColor('#22fdc8')
|
|
|
|
.width('100%')
|
|
|
|
.onChange((value: number) => {
|
|
|
|
.height('100%')
|
|
|
|
this.currentBrightness = value;
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.width('90%')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Row() {
|
|
|
|
|
|
|
|
Button('取消')
|
|
|
|
|
|
|
|
.onClick(() => {this.showBrightnessDialog = false;})
|
|
|
|
|
|
|
|
.backgroundColor('transparent')
|
|
|
|
|
|
|
|
.fontColor('#999')
|
|
|
|
|
|
|
|
.border({width: 1, color: '#666'})
|
|
|
|
|
|
|
|
.width(100)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Button('确定')
|
|
|
|
|
|
|
|
.onClick(() => {this.confirmBrightness();})
|
|
|
|
|
|
|
|
.backgroundColor('#22fdc8')
|
|
|
|
|
|
|
|
.fontColor('#0b1830')
|
|
|
|
|
|
|
|
.width(100)
|
|
|
|
|
|
|
|
}.margin({top: 40}).width('100%').justifyContent(FlexAlign.SpaceAround)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.width('85%')
|
|
|
|
|
|
|
|
.backgroundColor('#1d3553')
|
|
|
|
|
|
|
|
.padding(30)
|
|
|
|
|
|
|
|
.borderRadius(16)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.position({x: 0, y: 0})
|
|
|
|
|
|
|
|
.zIndex(999)
|
|
|
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
|
|
.height('100%')
|
|
|
|
|
|
|
|
.alignContent(Alignment.Center)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.width('100%')
|
|
|
|
}
|
|
|
|
.height('100%')
|
|
|
|
|
|
|
|
|
|
|
|
@Builder
|
|
|
|
|
|
|
|
ParamRow(label: string, value: string) {
|
|
|
|
|
|
|
|
Row() {
|
|
|
|
|
|
|
|
Text(label).fontSize(10).fontColor('#b2ebf2')
|
|
|
|
|
|
|
|
Blank()
|
|
|
|
|
|
|
|
Text(value).fontSize(10).fontColor('#eaf6fc')
|
|
|
|
|
|
|
|
}.width('100%').margin({bottom: 5})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|