#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <EEPROM.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266WebServer.h>
#include <WiFiClient.h>
#include <ArduinoJson.h> // 必须安装
// ---------- 引脚定义 ----------
#define RELAY_PIN 5 // D1
#define LED_PIN 2 // D4, 低电平亮
// ---------- 网络参数 ----------
#define AP_SSID "SmartPlug"
#define AP_IP IPAddress(192, 168, 4, 1)
#define AP_GATEWAY IPAddress(192, 168, 4, 1)
#define AP_SUBNET IPAddress(255, 255, 255, 0)
#define TCP_PORT 8080
#define UDP_PORT 8080
#define HTTP_PORT 8266
// ---------- EEPROM 地址 ----------
#define EEPROM_SIZE 512
#define WIFI_FLAG_ADDR 0
#define SSID_ADDR 1
#define PASS_ADDR 33
#define TIMER_BASE_ADDR 100
#define WEEKDAYS 7
#define MAX_SLOTS_PER_DAY 4
// ---------- 全局对象 ----------
WiFiServer tcpServer(TCP_PORT);
WiFiUDP udp;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 8 * 3600, 60000);
ESP8266WebServer httpServer(HTTP_PORT);
ESP8266HTTPUpdateServer httpUpdater; // 只保留本地 OTA 上传
// ---------- 定时结构 ----------
struct TimeSlot {
bool enabled;
char timeOn[6];
char timeOff[6];
};
struct DaySchedule {
int slotCount;
TimeSlot slots[MAX_SLOTS_PER_DAY];
};
DaySchedule weekSchedule[WEEKDAYS];
// ---------- 其他全局变量 ----------
bool relayState = false;
unsigned long lastPrintTime = 0;
unsigned long lastTimeCheck = 0;
// ---------- 函数声明 ----------
void setupMode();
bool connectWiFi(const char* ssid, const char* pass);
void saveWiFiCredentials(const char* ssid, const char* pass);
void readWiFiCredentials(char* ssid, char* pass);
void clearWiFiCredentials();
void handleTCPCommand(const String& cmd, WiFiClient &client);
void handleUDPPacket();
void checkTimers();
void setRelay(bool state);
void setupHTTPRoutes();
void loadTimersFromEEPROM();
void saveTimersToEEPROM();
void setup() {
Serial.begin(115200);
Serial.println(F("\n\n--- Smart Plug Start ---"));
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, HIGH);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
EEPROM.begin(EEPROM_SIZE);
loadTimersFromEEPROM();
setupHTTPRoutes();
uint8_t wifiFlag = EEPROM.read(WIFI_FLAG_ADDR);
if (wifiFlag == 0xAA) {
char ssid[33] = {0};
char pass[65] = {0};
readWiFiCredentials(ssid, pass);
if (connectWiFi(ssid, pass)) {
setupMode();
return;
} else {
clearWiFiCredentials();
}
}
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(AP_IP, AP_GATEWAY, AP_SUBNET);
WiFi.softAP(AP_SSID);
setupMode();
}
void loop() {
// TCP 客户端处理(保留但简化)
WiFiClient client = tcpServer.available();
if (client) {
String cmd = "";
bool gotCommand = false;
while (client.connected() && !gotCommand) {
if (client.available()) {
char c = client.read();
cmd += c;
if (c == '\n') {
cmd.trim();
handleTCPCommand(cmd, client);
gotCommand = true;
}
}
yield();
}
client.stop();
}
handleUDPPacket();
httpServer.handleClient();
if (WiFi.status() == WL_CONNECTED) {
checkTimers();
}
static unsigned long lastBlink = 0;
if (millis() - lastBlink >= 500) {
lastBlink = millis();
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
if (millis() - lastPrintTime >= 1000) {
lastPrintTime = millis();
Serial.printf("[%s] Relay=%s", WiFi.getMode() == WIFI_AP ? "AP" : "STA", relayState ? "ON" : "OFF");
if (WiFi.status() == WL_CONNECTED) Serial.printf(" IP:%s", WiFi.localIP().toString().c_str());
Serial.println();
}
delay(1);
}
// ---------- EEPROM 读写 ----------
void loadTimersFromEEPROM() {
uint8_t flag = EEPROM.read(TIMER_BASE_ADDR);
if (flag != 0xA5) {
for (int d = 0; d < WEEKDAYS; d++) {
weekSchedule[d].slotCount = 0;
for (int s = 0; s < MAX_SLOTS_PER_DAY; s++) {
weekSchedule[d].slots[s].enabled = false;
strcpy(weekSchedule[d].slots[s].timeOn, "08:00");
strcpy(weekSchedule[d].slots[s].timeOff, "22:00");
}
}
saveTimersToEEPROM();
return;
}
int addr = TIMER_BASE_ADDR + 1;
for (int d = 0; d < WEEKDAYS; d++) {
weekSchedule[d].slotCount = EEPROM.read(addr++);
if (weekSchedule[d].slotCount > MAX_SLOTS_PER_DAY) weekSchedule[d].slotCount = MAX_SLOTS_PER_DAY;
for (int s = 0; s < MAX_SLOTS_PER_DAY; s++) {
weekSchedule[d].slots[s].enabled = EEPROM.read(addr++);
for (int j = 0; j < 5; j++) {
weekSchedule[d].slots[s].timeOn[j] = EEPROM.read(addr++);
}
weekSchedule[d].slots[s].timeOn[5] = '\0';
for (int j = 0; j < 5; j++) {
weekSchedule[d].slots[s].timeOff[j] = EEPROM.read(addr++);
}
weekSchedule[d].slots[s].timeOff[5] = '\0';
}
}
}
void saveTimersToEEPROM() {
EEPROM.write(TIMER_BASE_ADDR, 0xA5);
int addr = TIMER_BASE_ADDR + 1;
for (int d = 0; d < WEEKDAYS; d++) {
EEPROM.write(addr++, weekSchedule[d].slotCount);
for (int s = 0; s < MAX_SLOTS_PER_DAY; s++) {
EEPROM.write(addr++, weekSchedule[d].slots[s].enabled);
for (int j = 0; j < 5; j++) {
EEPROM.write(addr++, weekSchedule[d].slots[s].timeOn[j]);
}
for (int j = 0; j < 5; j++) {
EEPROM.write(addr++, weekSchedule[d].slots[s].timeOff[j]);
}
}
}
EEPROM.commit();
}
// ---------- 定时检查 ----------
void checkTimers() {
if (millis() - lastTimeCheck < 10000) return;
lastTimeCheck = millis();
if (!timeClient.update()) return;
String now = timeClient.getFormattedTime().substring(0, 5);
int day = timeClient.getDay();
if (day < 0 || day > 6) return;
DaySchedule &today = weekSchedule[day];
static String lastActionTime = "";
if (now == lastActionTime) return;
for (int s = 0; s < today.slotCount; s++) {
TimeSlot &slot = today.slots[s];
if (!slot.enabled) continue;
if (now == String(slot.timeOn) && !relayState) {
setRelay(true);
lastActionTime = now;
Serial.printf("Timer ON (day %d, slot %d)\n", day, s);
break;
}
if (now == String(slot.timeOff) && relayState) {
setRelay(false);
lastActionTime = now;
Serial.printf("Timer OFF (day %d, slot %d)\n", day, s);
break;
}
}
}
// ---------- HTTP 路由 ----------
void setupHTTPRoutes() {
httpServer.on("/status", []() {
httpServer.send(200, "text/plain", "DEVICE_OK");
});
httpServer.on("/state", []() {
String json = "{\"relay\":\"" + String(relayState ? "on" : "off") + "\"}";
httpServer.send(200, "application/json", json);
});
httpServer.on("/on", []() {
setRelay(true);
httpServer.send(200, "application/json", "{\"result\":\"ok\",\"state\":\"on\"}");
});
httpServer.on("/off", []() {
setRelay(false);
httpServer.send(200, "application/json", "{\"result\":\"ok\",\"state\":\"off\"}");
});
// 配网
httpServer.on("/config", HTTP_POST, []() {
if (httpServer.hasArg("plain")) {
String body = httpServer.arg("plain");
int ssidStart = body.indexOf("\"ssid\":\"") + 8;
int ssidEnd = body.indexOf("\"", ssidStart);
int pwdStart = body.indexOf("\"pwd\":\"") + 7;
int pwdEnd = body.indexOf("\"", pwdStart);
if (ssidStart >= 8 && pwdStart >= 7) {
String ssid = body.substring(ssidStart, ssidEnd);
String pwd = body.substring(pwdStart, pwdEnd);
saveWiFiCredentials(ssid.c_str(), pwd.c_str());
httpServer.send(200, "application/json", "{\"result\":\"ok\"}");
delay(1000);
ESP.restart();
return;
}
}
httpServer.send(400, "application/json", "{\"result\":\"error\"}");
});
// 获取定时
httpServer.on("/timers", HTTP_GET, []() {
const char* dayNames[] = {"周日","周一","周二","周三","周四","周五","周六"};
String json = "[";
for (int d = 0; d < WEEKDAYS; d++) {
if (d > 0) json += ",";
json += "{";
json += "\"day\":" + String(d);
json += ",\"label\":\"" + String(dayNames[d]) + "\"";
json += ",\"slots\":[";
for (int s = 0; s < weekSchedule[d].slotCount; s++) {
if (s > 0) json += ",";
json += "{";
json += "\"enabled\":" + String(weekSchedule[d].slots[s].enabled ? "true" : "false");
json += ",\"on\":\"" + String(weekSchedule[d].slots[s].timeOn) + "\"";
json += ",\"off\":\"" + String(weekSchedule[d].slots[s].timeOff) + "\"";
json += "}";
}
json += "]";
json += "}";
}
json += "]";
httpServer.send(200, "application/json", json);
});
// 保存定时
httpServer.on("/timers", HTTP_POST, []() {
if (httpServer.hasArg("plain")) {
String body = httpServer.arg("plain");
Serial.printf("Timers POST body: %s\n", body.c_str());
DynamicJsonDocument doc(4096);
DeserializationError error = deserializeJson(doc, body);
if (error) {
Serial.printf("JSON error: %s\n", error.c_str());
httpServer.send(400, "application/json", "{\"result\":\"error\"}");
return;
}
JsonArray arr;
if (doc.is<JsonArray>()) {
arr = doc.as<JsonArray>();
} else if (doc.containsKey("timers")) {
arr = doc["timers"].as<JsonArray>();
} else {
httpServer.send(400, "application/json", "{\"result\":\"error\"}");
return;
}
for (int d = 0; d < WEEKDAYS; d++) {
weekSchedule[d].slotCount = 0;
for (int s = 0; s < MAX_SLOTS_PER_DAY; s++) {
weekSchedule[d].slots[s].enabled = false;
strcpy(weekSchedule[d].slots[s].timeOn, "08:00");
strcpy(weekSchedule[d].slots[s].timeOff, "22:00");
}
}
for (JsonObject dayObj : arr) {
int day = dayObj["day"] | -1;
if (day < 0 || day >= WEEKDAYS) continue;
JsonArray slotsArr = dayObj["slots"];
if (!slotsArr) continue;
int s = 0;
for (JsonObject slotObj : slotsArr) {
if (s >= MAX_SLOTS_PER_DAY) break;
weekSchedule[day].slots[s].enabled = slotObj["enabled"] | false;
String on = slotObj["on"] | "08:00";
String off = slotObj["off"] | "22:00";
if (on.length() > 5) on = on.substring(0, 5);
if (off.length() > 5) off = off.substring(0, 5);
strcpy(weekSchedule[day].slots[s].timeOn, on.c_str());
strcpy(weekSchedule[day].slots[s].timeOff, off.c_str());
s++;
}
weekSchedule[day].slotCount = s;
}
saveTimersToEEPROM();
httpServer.send(200, "application/json", "{\"result\":\"ok\"}");
Serial.println(F("Timers saved (week multi-slot)"));
} else {
httpServer.send(400, "application/json", "{\"result\":\"error\"}");
}
});
}
// ---------- 其他函数 ----------
void setupMode() {
tcpServer.begin();
udp.begin(UDP_PORT);
// OTA 升级页面(用于手机上传 .bin 文件)
httpUpdater.setup(&httpServer, "/update");
if (WiFi.getMode() == WIFI_STA) {
timeClient.begin();
timeClient.update();
}
httpServer.begin();
Serial.printf("HTTP server started on port %d\n", HTTP_PORT);
}
bool connectWiFi(const char* ssid, const char* pass) {
WiFi.begin(ssid, pass);
int tries = 0;
while (WiFi.status() != WL_CONNECTED && tries < 40) {
delay(500);
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
tries++;
}
digitalWrite(LED_PIN, HIGH);
return WiFi.status() == WL_CONNECTED;
}
void saveWiFiCredentials(const char* ssid, const char* pass) {
EEPROM.write(WIFI_FLAG_ADDR, 0xAA);
for (int i = 0; i < 32; i++) EEPROM.write(SSID_ADDR + i, i < strlen(ssid) ? ssid[i] : 0);
for (int i = 0; i < 64; i++) EEPROM.write(PASS_ADDR + i, i < strlen(pass) ? pass[i] : 0);
EEPROM.commit();
}
void readWiFiCredentials(char* ssid, char* pass) {
for (int i = 0; i < 32; i++) ssid[i] = EEPROM.read(SSID_ADDR + i);
ssid[32] = 0;
for (int i = 0; i < 64; i++) pass[i] = EEPROM.read(PASS_ADDR + i);
pass[64] = 0;
}
void clearWiFiCredentials() {
EEPROM.write(WIFI_FLAG_ADDR, 0x00);
EEPROM.commit();
}
void handleTCPCommand(const String& cmd, WiFiClient &client) {
// 不再使用 TCP 控制,简单响应即可
client.println("use HTTP");
}
void handleUDPPacket() {
int packetSize = udp.parsePacket();
if (packetSize) {
char incoming[255];
int len = udp.read(incoming, 255);
if (len > 0) {
incoming[len] = 0;
String msg(incoming);
msg.trim();
if (msg == "SCAN") {
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.write("DEVICE_OK");
udp.endPacket();
}
}
}
}
void setRelay(bool state) {
relayState = state;
digitalWrite(RELAY_PIN, state ? LOW : HIGH);
}
拖入的目录结构:
================================
[文件夹] C:\Users\Administrator\WeChatProjects\miniprogram-1
Folder PATH listing
Volume serial number is 000000B8 94EB:D5C5
C:\USERS\ADMINISTRATOR\WECHATPROJECTS\MINIPROGRAM-1
©¦ .gitignore
©¦ app.js
©¦ app.json
©¦ app.miniapp.json
©¦ app.wxss
©¦ project.config.json
©¦ project.miniapp.json
©¦ project.private.config.json
©¦ sitemap.json
©¦
©À©¤©¤©¤i18n
©¦ base.json
©¦
©À©¤©¤©¤miniapp
©¦ ©À©¤©¤©¤android
©¦ ©¦ ©¦ i18nInfo.json
©¦ ©¦ ©¦
©¦ ©¦ ©¸©¤©¤©¤nativeResources
©¦ ©¦ ©À©¤©¤©¤app
©¦ ©¦ ©À©¤©¤©¤assets
©¦ ©¦ ©¸©¤©¤©¤res
©¦ ©¦ ©¸©¤©¤©¤raw
©¦ ©¸©¤©¤©¤ios
©¦ i18nInfo.json
©¦
©À©¤©¤©¤pages
©¦ ©À©¤©¤©¤config
©¦ ©¦ config.js
©¦ ©¦ config.json
©¦ ©¦ config.wxml
©¦ ©¦ config.wxss
©¦ ©¦
©¦ ©À©¤©¤©¤index
©¦ ©¦ index.js
©¦ ©¦ index.json
©¦ ©¦ index.wxml
©¦ ©¦ index.wxss
©¦ ©¦
©¦ ©À©¤©¤©¤logs
©¦ ©¦ logs.js
©¦ ©¦ logs.json
©¦ ©¦ logs.wxml
©¦ ©¦ logs.wxss
©¦ ©¦
©¦ ©À©¤©¤©¤ota
©¦ ©¦ ota.js
©¦ ©¦ ota.json
©¦ ©¦ ota.wxml
©¦ ©¦ ota.wxss
©¦ ©¦
©¦ ©¸©¤©¤©¤timer
©¦ timer.js
©¦ timer.json
©¦ timer.wxml
©¦ timer.wxss
©¦
©¸©¤©¤©¤utils
util.js
================================
================================
时间:周五 2026/05/15 20:40:18.71
================================
================================
【文件夹】C:\Users\Administrator\WeChatProjects\miniprogram-1
================================
===== \Users\Administrator\WeChatProjects\miniprogram-1\.gitignore =====
# Windows
[Dd]esktop.ini
Thumbs.db
$RECYCLE.BIN/
# macOS
.DS_Store
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
# Node.js
node_modules/
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\app.js =====
App({
onLaunch() {
console.log('智能插座小程序启动')
},
globalData: {
deviceIP: "",
deviceState: "off",
isConnected: false
}
})
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\app.json =====
{
"pages": [
"pages/index/index",
"pages/config/config",
"pages/timer/timer",
"pages/ota/ota"
],
"window": {
"navigationBarTitleText": "智能插座",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
},
"sitemapLocation": "sitemap.json"
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\app.miniapp.json =====
{
"adapteByMiniprogram": {
"userName": "gh_f6160cf4db7c"
}
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\app.wxss =====
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\project.config.json =====
{
"compileType": "miniprogram",
"libVersion": "trial",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"coverView": true,
"es6": true,
"postcss": true,
"minified": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"compileWorklet": false,
"uglifyFileName": false,
"uploadWithSourceMap": true,
"packNpmManually": false,
"minifyWXSS": true,
"minifyWXML": true,
"localPlugins": false,
"condition": true,
"swc": false,
"disableSWC": true,
"disableUseStrict": false,
"useCompilerPlugins": false
},
"condition": {},
"editorSetting": {
"tabIndent": "auto",
"tabSize": 2
},
"appid": "wx89c4a5fd311848ec",
"simulatorPluginLibVersion": {
"wxext14566970e7e9f62": "2.27.3"
},
"projectArchitecture": "multiPlatform"
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\project.miniapp.json =====
{
"miniVersion": "v2",
"name": "%name%",
"version": "0.0.1",
"versionCode": 100,
"i18nFilePath": "i18n",
"mini-ohos": {
"sdkVersion": "0.5.1"
},
"mini-android": {
"resourcePath": "miniapp/android/nativeResources",
"sdkVersion": "1.6.24",
"toolkitVersion": "0.11.0",
"useExtendedSdk": {
"media": false,
"bluetooth": false,
"network": false,
"scanner": false,
"xweb": false
},
"icons": {
"hdpi": "",
"xhdpi": "",
"xxhdpi": "",
"xxxhdpi": ""
},
"splashscreen": {
"hdpi": "",
"xhdpi": "",
"xxhdpi": ""
},
"enableVConsole": "open",
"privacy": {
"enable": true
}
},
"mini-ios": {
"sdkVersion": "1.7.0",
"toolkitVersion": "0.0.9",
"useExtendedSdk": {
"WeAppOpenFuns": true,
"WeAppNetwork": false,
"WeAppBluetooth": false,
"WeAppMedia": false,
"WeAppLBS": false,
"WeAppOthers": false
},
"enableVConsole": "open",
"icons": {
"mainIcon120": "",
"mainIcon180": "",
"spotlightIcon80": "",
"spotlightIcon120": "",
"settingsIcon58": "",
"settingsIcon87": "",
"notificationIcon40": "",
"notificationIcon60": "",
"appStore1024": ""
},
"splashScreen": {
"customImage": ""
},
"privacy": {
"enable": false
},
"enableOpenUrlNavigate": true
}
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\project.private.config.json =====
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "SmartPlug",
"setting": {
"compileHotReLoad": true,
"urlCheck": false,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"useApiHook": true,
"showShadowRootInWxmlPanel": true,
"useStaticServer": false,
"useLanDebug": false,
"showES6CompileOption": false,
"bigPackageSizeSupport": false,
"checkInvalidKey": true,
"ignoreDevUnusedFiles": true
},
"libVersion": "3.16.0"
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\sitemap.json =====
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\config =====
[core]
bare = false
repositoryformatversion = 0
filemode = false
symlinks = false
ignorecase = true
logallrefupdates = true
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\description =====
Unnamed repository; edit this file 'description' to name the repository.
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\HEAD =====
ref: refs/heads/master
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\index =====
DIRC j[ j[ ¤ Ÿúp¾ȥ†HI˜C¤&ŠªZ½
.gitignore jj jթ ¤ Œπ̭>>⺐鼐¦£|C± app.js jj jY· ¤ *¿
û\ GüHy py݁" app.json jj jj ¤ †Ƽœ㛽责aG2큠app.wxss jҡ j! ¤ Š▿°Ÿ'1RA_q`眄`F犣 pages/config/config.js jӨ j¼ ¤ .D³ㆁ[
¯饟WT¬_µ pages/config/config.json jҹ jթ ¤ Љμ¿":;‰Τ¾C¡@ pages/config/config.wxml jҒ jթ ¤ P°œӭV֣пZx¥Aª4 pages/config/config.wxss jj jXǠ ¤
-¬̤ϒ"ӌ•ĮA8™jﻬ£² pages/index/index.js jj j ¤ .ʴ-k$„õ٦ÿ{56a¯´ը pages/index/index.json jj j⃠ ¤ Աnں ¹s¢[멉ü峉ٴ pages/index/index.wxml jj jթ ¤ †¡Nꠛ욋A䱂ݝ䟇h6 pages/index/index.wxss jj jj ¤ 1
öªū۲¢|ߵī!&±[ pages/logs/logs.js jj jj ¤ µ[Z%AuH›Žõ¶öå¦ pages/logs/logs.json jj jj ¤ ŏþyǘĢ,"ü
ö
ϩW] pages/logs/logs.wxml jj jj ¤ ÷3ù١ܠŸj먳㜤8vΝ$ pages/logs/logs.wxss jց jւ ¤ À̟ꎹÿrùǏኮܯ¹ˆ% pages/ota/ota.js jց jפ ¤ 0™£
:¾Ϥv"´¼B
?= pages/ota/ota.json jց jւ ¤ +õy1©9†m"C %ú;)跑 pages/ota/ota.wxml jց jւ ¤
%¬f,ݛ¦ÿS›Ҵpm pages/ota/ota.wxss jց jւ ¤ šõq„1šŽ>>l?-뷌£ pages/timer/timer.js jց jי ¤ 0úޅg9+rYDXk%¢i pages/timer/timer.json jց jւ ¤ Pܕ1ʇjªú˜6"Zùӭ> pages/timer/timer.wxml jց jւ ¤ Kݱ鍽2%6弸hûŦ| pages/timer/timer.wxss jj jj ¤ }:u üɷ底sԸ퇄ül<" project.config.json jj jj ¤ ‰ۤT퐋컚øƕ¶¡'楞 project.private.config.json jj jթ ¤ ç²²m7›è븻™փ¬-7N0 sitemap.json jj jj ¤ ̶KŽ&<<›U¢>>ψJ„߽
utils/util.js I\ƻ [¶·˜`Xzö 3ý
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\hooks\README.sample =====
#!/bin/sh
#
# Place appropriately named executable hook scripts into this directory
# to intercept various actions that git takes. See `git help hooks` for
# more information.
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\info\exclude =====
# File patterns to ignore; see `git help ignore` for more information.
# Lines that start with '#' are comments.
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\logs\HEAD =====
0000000000000000000000000000000000000000 01b7670ed2b0d10a8554e11069295d8bdfce01b1 unknown <unknown@example.com> 1778735894 +0800 commit (initial): Initial Commit
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\logs\refs\heads\master =====
0000000000000000000000000000000000000000 01b7670ed2b0d10a8554e11069295d8bdfce01b1 unknown <unknown@example.com> 1778735894 +0800 commit (initial): Initial Commit
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\01\b7670ed2b0d10a8554e11069295d8bdfce01b1 =====
x•Á
!E[ûo„š"O"hէ<õIҨ18ԧ'я´>>8熖J•̤0b8i層b$+)F㌪﹉---"BO‚¶~o+lõQ۫¼~Sy.|œG֢=N茬%J)wÿŭ枩뷰ҭ9ú
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\06\c6fc9ce35b7d02e031b1e8f4636147321dedc1 =====
x́ƒ Ю=Ŭº!A±馸Q§A €Q۴"ÿ~~F[¯¡=oc*„v?Rb¬kڑ>>¬ȡ„o° ͋--ЋqJ6"‚U§„ɢQ¡^n(☉; £·۪j£,͎SƵF---1V~o)Ӵ„Ԉ\cޑ¯aPƐ›%<„ˆ᠑gڟ<ѧr?ȋ
͏š?žՁL
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\10\fa70bec8e58648499843a426118aaa5a7f04bd =====
xͱ°`瀾C¡®ƶppն(HS)yڨ"W'T¾>>ᎍ¨'|µž¥ɉ8ŸÀ٥З¯ˆ£0ΰִ"UAhřüPnϛ])6U½䌳4±ݭ/9
leDĻ€7¸4YŽ˜g
ؑ}翕Ko¢??ƒxRp„֢ž??ø2>
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\19\99a30d3abecfa47693b4bc420e0d90edb9ba3d =====
xKʉOR0±`¨楒PPʋ,ˌO,ɌϳJ,
ɬɉ
I(Q²RPz:{דݛžö¶?ߵ\‰---<< $.0
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\25\ac662cdd9ba6ff539bd2340501f4073dc8706d =====
xeъƒ †w.hCš=¦k‚©¸ŢwŸfÁƮηýÿƉh轔õזXÀs)µܪৎP͈¸уeЫ‹*t°--E
‚¥, '/ýV®4A)ytd6
ԱKԄƒ͐‚'۹---£iD8D72 ۢ,=0i‰Om}9Fùlϭ?I›öγ¥??¾~ nR¹
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\27\b2b26d379bc328ebf87b99d643ac2d374e0730 =====
xKʉOR0´4e¨撐PJI-NV²RPzںùɮ¾gsּ›־d÷¶糚ž͞'û>k\ÿ~Oϋõ۟ö7½hh*x¶p±BFIIA±•¾~JjYjN~AjQ±^yjfEfž^a¡^r~®~nf^fAQ~zQb.H'~'Zž_"_œY'š›X ---Q'›£¤rEQiNj1ЙѠ7ù‰ɥ™ùy w%椤---ƒ
ӓA‚ZJ@eµ±\µ ҀR
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\33\616dd5346895721e6c3696823938bfc0296567 =====
x+)JMU06e040031Q(-Ɍы*f(mõ쐅2>>٦ž簪'¸ÿ /X
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\33\f9d9e1dca09f6aeb68b3e39c243876ce5d240f =====
x5ŽÁNİD9÷+|„CPY!!e¿ƭ܄'G‰)‹оû6N--Ƴof]ಾüL ‰8&GºžBք¿=lB·.??Z5{XU>÷|[<<Š`%¬=¤ՍġЯ@4:6ڇ}ǚ9;ӢᲗ2ڌn搸ö&ʆu4ý½`3·&--0Cܝ¢fº{ |<7܈õQŽs#ûû½tɓ¯
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\37\9661355a30f70ad4697fdb4944e85bf3aff335 =====
x+)JMU045`040031QȌKIЋ*fXsF弥)---y¦Y稱3뽯œśД䧱œ*эViùú@rٿjS³ăՀQU^'›ð1¦rŋ¢_krþyú™UŅmý^-¸ýFŠ۱‰¡ӝ٧÷ٳ̠荂
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\3a\7500fcc9f7e5fa95738dd478edc7c4fc6c3c94 =====
xmSˮۀ칟a蜋š´pz
P €Q(z V´D„Z.öQŵüdGR{!‡œYnɒ®ַ7<<U¦¤5ĸ?û¼ʚҤ¬Ԗڬ}♊g´ŽD'ڛꥫIZ±šj-6 ýü•º¢‡j€"rꅝzOº~듲?!oŽͨ>́#Ϋ---NªҎ{ªŽ¢n@<<4xRꚩžDüƒ.^[ށFž---%o¦}B†dpC·‚‹§J伡΅#V'ƒ'qǡ¦>©³J䤸ü|ճ¬¬(r㻔b_Ӵ=°;璪¦ýዼ·Gh"řiX *ȷ¹<<p&VLBݭA`>,ڻ4ŏ<Ÿw³(Ӎ"芒€‹®SdL)/#þݡOOý"ƒûᾭbƒ˩Mw8ׇq)±˷˼"ºB†d¼??"9ýI1¸Ȃ1".랗wꖾ뫵횕!^Œ‹m¦ߨxº:ýEú
Ѡ
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\44\b3e34601f39b86855b0aafe925df5754ac5fb5 =====
xKʉOR01c¨楒PPʋ,ˌO,ɌϳJ,
ɬɉ
I(Q²RP
ϴ˼ٚû|^®Z ކý
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\45\18d8c621b8cfae8a70add49482bbef09f118ba =====
x+)JMU065`040031QЋϬɌϋ/JeøU°6ϙԄ>>VEճ셪J,(Ћ*f蹿̛杲vMxi3a٢g¡(*·þŽY྇£rAÁ'¹>>*ﲊNARS^Q\̀v쏜ǑµL7¾ø'œ认ûö ‰($¦§3쬸aۮ'°0ܨ¦eýʭ7w™ϭ‡šQP"Ÿ•š\¢---œŸ-----™tЊ<<R†?'¿?ý5µ¸÷Jśㇾ䘀¬„)/(ʬK,IEіy[%䭠÷›ߑ?Ž]ݶIýٓyP[Š3KRsA~¯¾iS®ù샚¯Tϼ漆לݠ⚒'̜bㄜ<<&S‹䲌¦5YZ쿠™š z)•$
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\50\1adc1531ca876a03aafa9d9836945af9d32d3e =====
xKʉOR05¶`°)ˌ-WHΉ,.¶UJΏ+ǏK-R²㥒P@'+ɬɉU²{ºnֳ雞ںùź}Ϸ®³чր¡)³$5bд'Ԋ; Χsvx9}˻=³l@™ə©E
¹ù)©¶J%™¹©J
I™y)ə‰y逑┒ÿ¼0ض°d烴낌T²<<®·<<Er Ў}ˆ*ˆaN'ơ›)rxZ .‡(&ީI¥%%ùy*I, †TbY*Ⱥ"%
'ʂ`Еe榖U*Áb:©$Oɮɾ¹O€Ĩ>Č`¸@Ë ŸS>>
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\76\4bc2ce26ab9b55a21cbb069dcf084a8418dffd =====
xu'1kð
;ûWܐˆM'@鐨>>"'%Yҥ„€嚍\¬SOcҔK'[wtª;SÃ㼮ӠO𥬖??OЈ⏳Œ@lJګC½¹®ûҞܔڠɚŸqӴrH¯œ'8---F¬øاmݢ3¬C!•D²
/a<<ș„j2JĥϛöB˓W°qºV¶ߦż:›`‡‡cGˆ,!ý<<ʮY--f:ü]-ÿ
2[²-¯¸[졅•K¨擑㴭ש¡Χc©g³Ÿ¿‡〟ů˜栍
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\85\cf1bfe79c718c4222c94fc090af60dcf29575d =====
xMAƒ E>>öS¶
šš®ҽꔐ‡҈©‹´·oþ¼?ӑ렭o'q朜諥µĹª„劉Ŀ艻/ُ陵ƒÿxƒ°½Q22>0UˆŽ\?Á²ޟn--,Ɲšۉ„›dÁX|m߅吷m6¸±²☙q 0£y
¸
®)Ր̵ ¦$š}¥Է¥?ߟP>>N5
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\85\f6aac5ab16db728fa27cdf75c4ab2126b1105b =====
xMOÁŽ ݳ_1·B²¡&f/MܓG&õf<кvkZP˜FöߝHW%dfx÷xT<<`>ûùʳ蜓??ـ0Pہ<ž‡̴֣ιG4¤ʷ3%Ě4(oȔ'HNlw|¿¹8>>rf/Մӟt@Z²$iߚy¹ꆩ$癸¼ٚf1W¦ౠG¥{s'ŒÀⷲ‹b4xû¤<X¤_胳½¡Mۣ´x~£
R1ۿ"¦K2ý©ˆM£JCj£ˆ/W¨
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\86\a14eeaa0dbec1a0b41e43142dd1de4df076836 =====
xmQݮƒ Oq']Ө¬n꓀ '!4xl͚¾{8˜ởߡڲ¨ʢ픛ƒT\(犈ι>>¬Ÿ0ڿIgÉoµu-¼Ux᳒†ŒBɑ[(٣;¡B-"ݠɉ¬¾EBg·m‰Y̓Š ՊšzaP¸HOT~R3‡я‡²,£---™0>>FÁ---ۖn£Bv˜u\8⨗ˬ֛´=l俉z6D;mº©Œ'œŽÁˡˆݭ±|A›ik྇ʿúþv ||]ם¯˜ 𦉠†^&ܪ+³¨v{‡b>c/';2<<7
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\89\db2454ed500becfb5af8c6d5b6a10227e6e59e =====
xuRÁnӀ圯°rFNR½•¦¨•ˆbQΫ{jo³ޱ¤)ª¨I¢P¥
---„^@(_Cl§'~1I¥&ˆƒ¥ռ緯͌-жݸ²`"G
%£py2̺ƒ쳳??ֱösvG?¾ÿzú<?¼G罬{´[ɠ˜œµǧ?1B
۠hӁ¹Žs;BiL˜YYý*鿍߈gڢ{¾&/¿Œ.ޥ#"¤ýOɑٵ.D="™Ρzח:Œ--ŠE꠰™
ໜš;;Թ(\rr⩖䤼ӈ"*N퍝ù:
›yþi]² "®ˆ؛ hͥGH>,¢SXC]
d.!ZŐK+±⃓›:Xµɡ1[l¯)H¢"˜>
Ԫd¶Ƚl1]‰Fµ¦Ҫ ÿLj
ƒ¹÷˜S-3ͦTX¬q9v¹Ž怸‚启!ιŽ|lX>s±QEԫ*L‚˜MA¿[ši(濢L--Á¦Υ"ùU<<´2™烫¼ܦ^
"1,¾V†¨??{]֙ஃhκ㞄e¨?'dӽO‹˓狣}?¿ඦѴüo›·J梡aÿ?Π
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\8c\cfc0cced03bb1ee2ba90e93c90a6a37c4312b1 =====
xKʉOR043`p,(Ш撐ȏKΐД q'sRõrŸ͜õ¢yﳾIOw-º¡ÿùŠúŸNXÿ´k
º&PyHš™˜㒘'h5#%µ,39ճÀJAI ¤BA"\'X'
̏KƒŠg;秥¥&---¤¦X)¤%攧‚L媕 ד;™
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\93\77a3c7317b48fb8d3d1f2dfc089202126db990 =====
x+)JMU045`040031Q(ɌM-ҋ*f˜%ސxV߮œ·t_›û,FS"Ÿǰ끜kº¥vQ¤D¶꧉:E™(ªʫrs¤žjϢ^õk)Q?/뚡©*.f¸[øҗֈլ©͏Œߟ> ¬2=3
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\9a\14f571841f31139a8ebb6cede3102deb374ca3 =====
x}'Mkۀ†{6ø?L|Zƒj ԕ¤¦---öИ⻙H#e‰²+¤±|n顴úqI.
䘣ŸљФ¢ٙwžygöK·ϟŸ¤ζºª`
¤?ýng¤T§ݎ@¦I'°úpvlŽ0^<L⸷Ž湈onJX¢s>"wƒ´>>º¤°ߪЁi">x͒괭ŠQ†¤MMu9A˜3†ױ*¾ȣ2-Ń:+=E՚Ǥ 6¨`Ot)TL25)¾" fǑsণ§bj2TŠ˯¿oo~.?_ܞýYœ荀°¥|`Ş肜ª'&µü³DJſ.'֨ ǻ£=---"ɬ䂟G,k1•ھ ³¬ƦId|ÿ£¬3+WSø‡wŸȻ>>
°5[/ۡ_z"1ߛ½ݷQCµ±
ɏü~¥eiXPŒ;r')ϯeù±µ-µ\¾噭6<8
ŕ·嗫¿7?---gˏ_Ÿ~ݵžwMP܄"¶zj
¶ú•NU OŸű¨6¯ۙ÷ÿ•Ǭ¢
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\ac\cc24cfd294d30c95c4ae4138996aeffb6ca3b2 =====
x•V[kGŴ_¼¢b%F!#>>`z±ÁNú(£լ¼d5#fGqƒ#p ±c㤉)M/Рм$m§±sqúg¼k驿¡猬®ֲ]ƒ¤e挙ׂQ#cʣ¹‚‡ŠЖ‹\"
¦&[->>02G̞!¤N|‚gvݷٌ\
XVQ¯øaUpΜŪ①df݄Ϋªϳ‰öDk‡߽ŽWnAŽNq¾ÿBк]H.°{뱳'Q÷i´½'¢ýЉ™š‚²t}˜uP p€Œछ䦍¬dBŽ=t"·‡‡rҫPC粀õeY3ԩd U|W{~ˆ¶Va=dTº‹Sš|ÀÀ¤
䀴uߣ¶ƞ®·
†xr܀„̮\D Yú։Œ‚ ¡²---‰•{+_ûŸù{wMŽ㘅° õ>oÀB½-©\."Nš8½J‚;\ɀSs^·§ANͧ eRS¶*ø---,і¶da\ú4›DŒ>ƒ??*‰®$©MˆŝɚB±֫°'DzỶDö⇯£;/ّϿE[ÿ¾„°[€Ԥׅk[ƻO㛛л秾꺴ûüퟴ?ۨýM¿>>A¬©髳թof?7ʂ'@L东
ڵڹ˜Bjf䦄·̌¿eػ!*‚l³„SŒE‡k܈=_Aœ >\ϥ<<褃͌gŒ;ńL¤'|¢yL,¶]d°´e0þeҧ.5띝Uvi2ퟵ?À˜칶>ɤ!¤%¤ª‰‚ZÀùꤗimS՚ހɔ[r2¯$t㉑¬.RYu怮Á
›·ƒ H8["---}®&&¥¤7lL'ؓ™9†ƒkւu.º¿𮫰ѭ¨û,ꮇ<<ûaVE³I۬‰Kƒ FݫiEh<<5+ڜ© 鶩õþڍºw㜰--WǠ6֫˜顧Ž‹=--õ8 걗fŽ™ø0`2Ž[̉"›#p\𤕡Äˍ{KҗIwYW啮e I•>¥³g`&}ô"Bڶ"t€uضP#-V´µݻõȂi;ž҇FաsI"ŒŽx˔V1#g클~%>zûG⋙ž½4§y¯Œ.[ÀŒU±·:£E2Dߑc2²0ϫⰒ¡ =f?‹‚ݗ±.}`¯cýŸ!`ؔݝþϏBõ¼󠅷迅‹ÿm˜ûqޜ‚φkʿ?ܞÿYÿ÷¿û¿>,﹍/x‹ӫ~^£©-ƒ
-µ--À¥›üŒ&H`¼`ž¿ɤùҨ-鯡$³"狡-Á';>ҩüOٛ
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\b0\9cd36d56f29cf4c9d6a3d07f115a78a541aa34 =====
xu˪1E>>䟮d퐦Š)Ηأw8--S²鶠ݫ١š??$--R(x [舘§'÷>>£ĀǾ|Ցµҷ0˜thP"´$ƒ9$ ¥㫭U45N•
Yª¶fŽ\Z뗋r,----Qʷ2Dž‡ºöގɣ†¿J瞩%ޢuңC¡Šõt[
>·v/•Ͽ°õp'껆H 筚ž?TOg㠍
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\b1\6edaba2002b91e73a25beb2909fce5f309d9b4 =====
xSMKðö쯨¹줳¨²DvRPtɨ27‚""
~†sxEœ 2anúgl;OþӴݚف½š÷yI4ifzj,³`Yҍ•R脳ahe\'b[1Źl÷v>>Ή¹ݾϤ=¬2><J™ʊtR#A3D좆Á6`°"^w[ζΏ§žI‹¿œݻĈT®̢Cժ¢3˜ۚ¡€œ†ɭp¹‡ާµs|7‚š>]n ®pᐖS§81P췧>>³mŸ>¤£·÷₸„>>hфŽ}‰٥®̫G "±e---ydժ`*ù¨ĘÁ'†pŽ©EP¨Zúú‚ˆHl³eP´PAµ6ûkŒǠœ6ܗ¿®Lڧf˜1OxIʇ}ݳ(¸‹úǫöM'ˆƹ%ʥ%]‡"Əs.xkȍŒ€³FHWV-N<aݖ5þ‹+OVQ?¥8¾Ywj-ۭFݧhɓ¥չ!??½e?뛸 [
֟uuvh
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\b5\5b5a25411975f285b21a489b8ef5b6f6c325a6 =====
xKʉOR06`¨撐P*-ΌKwΏ-ȏKͫ)V²R ײՂ ⟋›
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\b9\80f83d872460a15732d9397fa9b5d9ba379d87 =====
x+)JMU04µ`01
似´̴†¯M>>ʫ˜5l÷¯l'b·䥉0˜Q™---'ZÁ`>-Ѵʠ;ו̺۞./¢?¯ÿl
'ωO/fx´t廓*ݽퟞ?Á¸fA¤ΰ=uÁ ]
û{ˊKõ&@dK2sS‹&---/>nX?ז^÷Ǥ&¡ܝ n‹Aˠ
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\bf\85fb5ca047fc4879a070f21ee8ba79dd011594 =====
xmQMKÀõܟ֫4{‚`n⡓Lšt'Š‹ DГAƴýΦ¶ج٥öͼ÷f֏зZ퍋†㰔ǠَsLAºB†0¨v¶µ(#>>ձ
х喻*€†>>??±d¬O??k€bɻ"憠ls剓€C8›>峫ɴö¡ȟjҕ춏:±Œ†>>˜ ²5›QùM·¼G漁›觔\§ù눚-뻘úVp-- ៨†հς¬ꏋ]ž6eµW͓²"´sHAu
֔7¥,f¡L¡™iPK商¤Xº٣ûw~S|\£Ѭr?½{ž}¾̇_ūµµK†ÿ™>T¢Ǎ쉈˧f1˜_‰ʷӇձ衱³Әþ ÿº㠍
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\ca\742d6b2484f5e019a6ff7b353661c2efb4d528 =====
xKʉOR01c¨撐Pʋ,ˌO,ɌϳJ,
ɬɉ
I(Q²RPz6s潏ú'=ݵ\‰<< i------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\cc\06cdcce9092a85a0c82d20dfbd017675752e90 =====
x+)JMU041b040031QȯIԋ*f8sŸù_忢Ÿ2ǽz>>³~g‡*ŠŠü<ə‹yö_R6y˞'>މowRS^'›ðµҐi¥Œe[՟֚/¶ODQS\̠º&M箬eÿƒg_2aeü®{¢ >>:w
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\cc\df03ea0e79ff72f91cc74fe14aaedcafb98825 =====
xu'ÁJÀ†=úcN)†A°*„¶‡žo½l"5c6l&V(9׃Š žZÁ‚|苘ڇpӤ
º§ݝٿþùf‡¾¶·e1?D A mp(ꁠ7굃8T !އ¦.7`upO@ޖTGh¯K¨6½p-ڷ։ £K5<e#"'屠‹m´¸Z~>,ng˹G2H
¸‚ø̧Ģ8¬§q?;ŵZ¶©W!oqJš㈙gSߩKW
¤O´n!Gl›Ӱl¥þ圙gŒcšZS˯RO¥4@Ҿ(¨duG܅*Kº¹ө]ݬ
øÀ---֍U 愣Or3ù~üz{VUU°)rJ#¶뻒RF숓tS†>>šXE $Y܄M÷œ²劵§•ى旋<<>>rTadY‚審Ŋ ¦iy£Ub=Ι¯°ؤL_--¯³²䦯Ⱥ¿"nü ûГ
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\d0\c9cebc150ebf943a1b3b890418cee4be43a140 =====
xKʉOR01²d°)ˌ-WHΉ,.¶UJΏ+ǏK-R²㥒P@'+ɬɉU²ϴ˼ٚû|}Vl
"재߾bÿ¼g}K_¬۷tIû›v>ЇֺI+؞̼‚ҒSA¡ '195#?'%µȖ dۓ ½ϗoЈötє‚*JʌKk²U*N-IÁd >K*Lԇ¸¿뛞/h„jx@8v³a΅\y~Q
ª°'JKJF•$ ]Y'XT✟-----™®¤PRYj<<TP"™›XT©ü¤'<%>>§{ž.0Ĕ ™ݠ
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\dd\71e94d3d322536e53cf868fbf2a1e04bc5667c =====
xU۪1DûȿK
¢]Ժ-c+4iȿǗRڗat436°
y>½G11Ý'ᵊ麎ýᾟ,ժ
}£†ù܌M¼Š2֨Áa̝ތ^©Js#ÁTµ#Ám°<•̍Òp€>.Eh¹©¨r4"d*‹´¿Q
Rþ=û͝¿u<<嬱+ˢ¼i8µ
y8 b·üӻŽŒ‰ܧ½l½ꤎg
‡鍍=¬ĪzJ8i8ÿŒõ---Bj]
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\e2\963fb09f923152415f7160e7dc046046e70a63 =====
x}S[kAö9пpº٠X' %"Ĕ!>ȋ:ݝ¦K·;ˬ¤҅[ Iբ⋗„汭bAmÿK馲䟰̎.%÷i.g¾8lҩ•̭"¹¾ ⹐‡:ϓK±2©S½µ°ˆ 9h\ø¾m倓'㽗Œ¶ѮH.Ť•OE¥RZթbúLlپ竈¦·ƨ¨ ¶c쒧A!HLQ Za}¹ö?˜¨ùb"ˆ‹ \™>>i¤£D8T 3)% ˆJJªĞ}YVÀޞ,cѴ1@±f•_ a‡栾<û6¼x¶>>ᩧÿöüú,žƫ---¹4Ž"> §¢Á]µ"q ²mĄ˜^Bq•a:̧2U¾ˆÀ凾ˮ9¼þc֝aIJݺX
N„-)¥SøEŒ¬š쇶`"S"hµX®0s›
ٞNÀ„ž""--̵©‰--¨B bYœú>ú^ƈ¯d>>FZé'8 㢇ٔ6¥N¤7㞨™¹[„‡Á寏²4™C
‡յ?†'ס‹Oڔ_²¹c!͵BõN±ö¸PΫ·[2ӀK⊃
´5¾殏IMnªラЂ·G?¯㰕꽳ۯ˜&
¿7s՞¡¬!??鍈•7є¹dcˆ¹=± HBƒ!猫:弟®ᅞkW:c߂"/ó.þвɄ‰š•ӪůŸœø°ýu??=*78ø1sb~꿒ª~ü‚ğ֞nܠ
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\e2\a5a9eec924fd3bc6ddd0fc3c9130abf9c80a9a =====
x+)JMU041c040031QȉO/*fhý¶ꨪ±ۅý‹jY¨¶Q UI~Ö訕GɒO›¤<f÷}ݶ?2dE啹9祿U---8¢¤3叧šᱨŠŠ‹ŒxgÁü¬י›ϑ±(;<< 0Á>½
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\f5\793102a91c39866d9343001025fa3b29e8b791 =====
xKʉOR0²´d°)ˌ-WHΉ,.¶UJΏ+ǏK-R²㥒P@'+ɬɉU²{:{דݛüCŸö¶?ߵ܆¤SuybQž'ݣYޯ藀(}±¿ýùŠ·?ힿlښ瓶"i[---TZR'Ÿ§"™---R'X`<<T\'XT´JI¡¤² Ֆ l(̭I%@žº¼b>ĥ6úSÀN¢‰ˆה Žz:¡}O---´?ٳ녻ª§]+^¶÷>°¬淨;ŽԂc
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\f5\82ba777a03283dbfa984181a07390d0c583094 =====
x+)JMU045a040031QHΏKˌ*fx4;üI†AŽﰤ¸=犆W"ŸǠ²ù±㧙mќ럪ޏY¿UYyEnÅ"綈%m݉"q煎芊‹6̹œöiΗ"ז_¨ŒªX긊 S†@ܠ
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\objects\fa\e05e4567392b7259440f586b25f2187e2aa269 =====
xKʉOR0±`¨楒PPʋ,ˌO,ɌϳJ,
ɬɉ
I(Q²RPzºnֳ雞¬۷|ﺥ^®Z %)f
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\.git\refs\heads\master =====
01b7670ed2b0d10a8554e11069295d8bdfce01b1
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\i18n\base.json =====
{
"ios": {
"name": "SmartPlugSam"
},
"android": {
"name": "SmartPlugSam"
},
"common": {
"name": "SmartPlugSam"
}
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\miniapp\android\i18nInfo.json =====
{"base":{"ios":{"name":"SmartPlugSam"},"android":{"name":"SmartPlugSam"}}}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\miniapp\ios\i18nInfo.json =====
{"base":{"ios":{"name":"SmartPlugSam"},"android":{"name":"SmartPlugSam"}}}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\config\config.js =====
const app = getApp()
Page({
data: {
ssid: "",
pwd: "",
deviceOnline: false, // 替代心跳数字,显示是否在线
configStatus: "正在检测设备..."
},
onLoad() {
this.checkDeviceStatus()
// 每 3 秒检测一次设备在线状态
this.intervalId = setInterval(() => {
this.checkDeviceStatus()
}, 3000)
},
onUnload() {
if (this.intervalId) clearInterval(this.intervalId)
},
// 通过 HTTP 检查设备是否在线
checkDeviceStatus() {
const ip = app.globalData.deviceIP || "192.168.4.1" // AP 模式默认 IP
wx.request({
url: `http://${ip}:8266/status`,
method: "GET",
timeout: 2000,
success: (res) => {
if (res.data === "DEVICE_OK") {
this.setData({ deviceOnline: true, configStatus: "设备已连接" })
} else {
this.setData({ deviceOnline: false, configStatus: "设备响应异常" })
}
},
fail: () => {
this.setData({ deviceOnline: false, configStatus: "设备离线" })
}
})
},
setSSID(e) { this.setData({ ssid: e.detail.value }) },
setPWD(e) { this.setData({ pwd: e.detail.value }) },
// 开始配网(HTTP POST)
startConfig() {
let { ssid, pwd } = this.data
if (!ssid || !pwd) {
wx.showToast({ title: '请输入完整信息', icon: 'none' })
return
}
wx.showToast({ title: '正在配网...', icon: 'loading', duration: 5000 })
// 注意:配网时设备一般在 AP 模式,IP 是 192.168.4.1
wx.request({
url: "http://192.168.4.1:8266/config",
method: "POST",
data: { ssid, pwd },
header: { 'content-type': 'application/json' },
timeout: 5000,
success: (res) => {
wx.hideToast()
if (res.data && res.data.result === "ok") {
wx.showModal({
title: '配网成功',
content: '设备即将重启并连接WiFi,请稍候重新搜索设备。',
showCancel: false
})
} else {
wx.showToast({ title: '配网失败,请重试', icon: 'none' })
}
},
fail: () => {
wx.hideToast()
wx.showToast({ title: '请求失败,请检查设备连接', icon: 'none' })
}
})
}
})
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\config\config.json =====
{
"navigationBarTitleText": "WiFi配网"
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\config\config.wxml =====
<view class="container">
<view class="title">WiFi配网</view>
<view class="tip">请连接设备热点后配置</view>
<!-- 显示设备在线状态 -->
<view class="status-tip">
<text wx:if="{{deviceOnline}}" style="color:green;">● 设备在线</text>
<text wx:else style="color:red;">● 设备离线</text>
<text> {{configStatus}}</text>
</view>
<input placeholder="WiFi名称(SSID)" bindinput="setSSID" class="input"/>
<input placeholder="WiFi密码" bindinput="setPWD" class="input" password/>
<button bindtap="startConfig" type="primary" class="btn">开始配网</button>
</view>
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\config\config.wxss =====
.container { padding: 40rpx; }
.title { font-size: 36rpx; text-align: center; margin: 30rpx 0; }
.tip { text-align: center; color: #666; margin-bottom: 40rpx; }
.input { border: 1rpx solid #ddd; padding: 20rpx; margin: 20rpx 0; border-radius: 8rpx; font-size: 28rpx; }
.btn { margin-top: 40rpx; }
.heartbeat { font-size: 36rpx; text-align: center; color: #07c; margin: 20rpx 0; font-weight: bold; }
.status-tip { text-align: center; color: #666; margin-bottom: 20rpx; }
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\index\index.js =====
const app = getApp()
Page({
data: {
deviceIP: "",
isConnected: false,
deviceState: "off",
inputIP: "",
scanning: false // 防止重复点击
},
onLoad() {
const savedIP = wx.getStorageSync('deviceIP') || ""
this.setData({
deviceIP: app.globalData.deviceIP || savedIP || "",
isConnected: app.globalData.isConnected || false,
deviceState: app.globalData.deviceState || "off",
inputIP: savedIP
})
// 如果有保存的 IP,自动尝试连接一次
if (savedIP) {
this.quickConnect(savedIP)
}
},
// 快速连接(不弹 Toast)
quickConnect(ip) {
wx.request({
url: `http://${ip}:8266/status`,
method: "GET",
timeout: 1500,
success: (res) => {
if (res.data === "DEVICE_OK") {
this.onDeviceFound(ip)
}
},
fail: () => {}
})
},
// 扫描局域网(主搜索)
scanNetwork() {
if (this.data.scanning) return
this.setData({ scanning: true })
wx.showToast({ title: "正在扫描局域网...", icon: "loading", duration: 10000 })
// 获取本机 IP
wx.getLocalIPAddress({
success: (res) => {
const localIP = res.localip // 如 "192.168.10.100"
this.scanSubnet(localIP)
},
fail: () => {
// 如果获取 IP 失败,回退到扫描常见网段
this.scanCommonSubnets()
}
})
},
// 根据本机 IP 扫描同子网
scanSubnet(localIP) {
const parts = localIP.split('.')
if (parts.length !== 4) {
this.scanCommonSubnets()
return
}
const prefix = `${parts[0]}.${parts[1]}.${parts[2]}` // 如 "192.168.10"
this.doScan(prefix)
},
// 扫描多个常见网段(备用)
scanCommonSubnets() {
// 常见家庭网段
const subnets = ['192.168.1', '192.168.0', '192.168.10', '192.168.31']
// 依次扫描,每个 2 秒超时
this.doMultiScan(subnets, 0)
},
// 依次扫描多个子网(递归)
doMultiScan(subnets, index) {
if (index >= subnets.length) {
this.setData({ scanning: false })
wx.hideToast()
wx.showToast({ title: "未找到设备", icon: "none" })
return
}
wx.showToast({ title: `扫描 ${subnets[index]}.x ...`, icon: "loading", duration: 2000 })
this.doScan(subnets[index], () => {
// 当前网段未找到,继续下一个
setTimeout(() => this.doMultiScan(subnets, index + 1), 300)
})
},
// 实际扫描一个子网的所有 IP (1~254)
doScan(prefix, onComplete) {
const total = 254
const concurrency = 20 // 并发数
let found = false
let completed = 0
let canceled = false
const checkIP = (suffix) => {
if (found || canceled) return
const ip = `${prefix}.${suffix}`
wx.request({
url: `http://${ip}:8266/status`,
method: "GET",
timeout: 800, // 快速超时
success: (res) => {
if (res.data === "DEVICE_OK" && !found) {
found = true
canceled = true
this.onDeviceFound(ip)
wx.hideToast()
}
},
fail: () => {},
complete: () => {
completed++
if (completed >= total && !found) {
// 扫描完毕
if (onComplete) {
onComplete()
} else {
this.setData({ scanning: false })
wx.hideToast()
wx.showToast({ title: "未找到设备", icon: "none" })
}
}
}
})
}
// 使用定时器分批并发,避免瞬间请求过多
let current = 1
const batch = () => {
if (found || canceled) return
for (let i = 0; i < concurrency && current <= total; i++) {
checkIP(current++)
}
if (current <= total) {
setTimeout(batch, 50) // 每 50ms 发一批
}
}
batch()
},
// 发现设备后的公共处理
onDeviceFound(ip) {
app.globalData.deviceIP = ip
app.globalData.isConnected = true
wx.setStorageSync('deviceIP', ip)
this.setData({
deviceIP: ip,
isConnected: true,
inputIP: ip,
scanning: false
})
wx.showToast({ title: `设备在线: ${ip}`, icon: "success", duration: 3000 })
},
// ---------- 手动搜索(保留)----------
onInputIP(e) {
this.setData({ inputIP: e.detail.value })
},
searchByIP() {
const ip = this.data.inputIP.trim()
if (!ip) {
wx.showToast({ title: '请输入设备IP', icon: 'none' })
return
}
wx.showToast({ title: "连接中...", icon: "loading", duration: 3000 })
wx.request({
url: `http://${ip}:8266/status`,
method: "GET",
timeout: 2000,
success: (res) => {
if (res.data === "DEVICE_OK") {
this.onDeviceFound(ip)
} else {
wx.showToast({ title: "设备响应异常", icon: "none" })
}
},
fail: () => {
wx.showToast({ title: "连接失败", icon: "none" })
}
})
},
// ---------- 控制命令 ----------
sendHttpCommand(path, successCallback) {
if (!this.data.deviceIP) {
wx.showToast({ title: "请先搜索设备", icon: "none" })
return
}
wx.request({
url: `http://${this.data.deviceIP}:8266${path}`,
method: "GET",
timeout: 2000,
success: (res) => {
if (res.data && res.data.result === "ok") {
if (successCallback) successCallback(res.data.state)
} else {
wx.showToast({ title: "设备响应异常", icon: "none" })
}
},
fail: () => {
wx.showToast({ title: "命令发送失败", icon: "none" })
}
})
},
sendOn() {
this.sendHttpCommand("/on", (state) => {
this.setData({ deviceState: state })
app.globalData.deviceState = state
wx.showToast({ title: "已打开", icon: "success" })
})
},
sendOff() {
this.sendHttpCommand("/off", (state) => {
this.setData({ deviceState: state })
app.globalData.deviceState = state
wx.showToast({ title: "已关闭", icon: "success" })
})
},
goConfig() { wx.navigateTo({ url: '/pages/config/config' }) },
goTimer() { wx.navigateTo({ url: '/pages/timer/timer' }) },
goOTA() { wx.navigateTo({ url: '/pages/ota/ota' }) }
})
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\index\index.json =====
{
"navigationBarTitleText": "智能V2插座"
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\index\index.wxml =====
<view class="container">
<view class="title">智能插座</view>
<view class="status-box">
<view class="status-item">
<text>设备状态:</text>
<text wx:if="{{!isConnected}}" class="status-offline">未连接</text>
<text wx:elif="{{deviceState=='on'}}" class="status-on">已开启</text>
<text wx:else class="status-off">已关闭</text>
</view>
<view class="status-item" wx:if="{{deviceIP}}">
<text>设备 IP:{{deviceIP}}</text>
</view>
</view>
<!-- 一键扫描(自动发现) -->
<button bindtap="scanNetwork" type="primary" class="btn" loading="{{scanning}}">
{{scanning ? '扫描中...' : '一键搜索设备'}}
</button>
<!-- 手动输入备用 -->
<view class="ip-box">
<input
placeholder="手动输入IP (高级)"
value="{{inputIP}}"
bindinput="onInputIP"
class="ip-input"
/>
<button bindtap="searchByIP" size="mini" type="default" class="search-btn">连接</button>
</view>
<button bindtap="goConfig" class="btn">WiFi配网</button>
<view wx:if="{{isConnected}}">
<button bindtap="sendOn" type="success" class="btn">打开插座</button>
<button bindtap="sendOff" type="warn" class="btn">关闭插座</button>
<button bindtap="goTimer" class="btn">定时设置</button>
<button bindtap="goOTA" class="btn">固件升级</button>
</view>
</view>
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\index\index.wxss =====
.container { padding: 40rpx; background-color: #f5f5f5; min-height: 100vh; }
.title { font-size: 40rpx; font-weight: bold; text-align: center; margin: 30rpx 0; }
.status-box { background: white; border-radius: 16rpx; padding: 30rpx; margin-bottom: 30rpx; }
.status-item { font-size: 30rpx; margin: 10rpx 0; text-align: center; }
.status-on { color: #07c; font-weight: bold; }
.status-off { color: #666; }
.status-offline { color: #999; }
.btn { margin: 15rpx 0; border-radius: 8rpx; }
.ip-box {
display: flex;
align-items: center;
margin: 15rpx 0;
background: white;
border-radius: 16rpx;
padding: 20rpx;
}
.ip-input {
flex: 1;
border: 1rpx solid #ddd;
padding: 10rpx;
border-radius: 8rpx;
font-size: 28rpx;
}
.search-btn {
margin-left: 20rpx;
}
.ip-box {
display: flex;
align-items: center;
margin: 15rpx 0;
background: white;
border-radius: 16rpx;
padding: 20rpx;
}
.ip-input {
flex: 1;
border: 1rpx solid #ddd;
padding: 10rpx;
border-radius: 8rpx;
font-size: 26rpx;
}
.search-btn {
margin-left: 20rpx;
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\logs\logs.js =====
// logs.js
const util = require('../../utils/util.js')
Page({
data: {
logs: []
},
onLoad() {
this.setData({
logs: (wx.getStorageSync('logs') || []).map(log => {
return {
date: util.formatTime(new Date(log)),
timeStamp: log
}
})
})
}
})
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\logs\logs.json =====
{
"usingComponents": {
}
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\logs\logs.wxml =====
<!--logs.wxml-->
<scroll-view class="scrollarea" scroll-y type="list">
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
<view class="log-item">{{index + 1}}. {{log.date}}</view>
</block>
</scroll-view>
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\logs\logs.wxss =====
page {
height: 100vh;
display: flex;
flex-direction: column;
}
.scrollarea {
flex: 1;
overflow-y: hidden;
}
.log-item {
margin-top: 20rpx;
text-align: center;
}
.log-item:last-child {
padding-bottom: env(safe-area-inset-bottom);
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\ota\ota.js =====
const app = getApp()
Page({
data: {
deviceIP: "",
isConnected: false,
filePath: "",
fileName: "",
uploading: false,
progress: 0
},
onLoad() {
// 获取全局设备 IP
const ip = app.globalData.deviceIP
this.setData({
deviceIP: ip || "",
isConnected: !!ip
})
},
// 选择固件文件
chooseFile() {
wx.chooseMessageFile({
count: 1,
type: 'file',
extension: ['.bin'], // 只显示 .bin 文件
success: (res) => {
const file = res.tempFiles[0]
this.setData({
filePath: file.path,
fileName: file.name
})
},
fail: (err) => {
console.error('选择文件失败', err)
wx.showToast({ title: '选择文件失败', icon: 'none' })
}
})
},
// 开始升级
startOTA() {
if (!this.data.filePath) {
wx.showToast({ title: '请先选择固件文件', icon: 'none' })
return
}
if (!this.data.deviceIP) {
wx.showToast({ title: '未连接设备', icon: 'none' })
return
}
this.setData({ uploading: true, progress: 0 })
const uploadTask = wx.uploadFile({
url: `http://${this.data.deviceIP}:8266/update`,
filePath: this.data.filePath,
name: 'file', // ESP OTA 服务要求的字段名
header: {
'Content-Type': 'multipart/form-data'
},
success: (res) => {
if (res.statusCode === 200) {
wx.showModal({
title: '升级成功',
content: '设备正在重启,请稍候重新搜索连接。',
showCancel: false
})
} else {
wx.showToast({ title: '升级失败,请重试', icon: 'none' })
}
},
fail: (err) => {
console.error('上传失败', err)
wx.showToast({ title: '上传失败,请检查连接', icon: 'none' })
},
complete: () => {
this.setData({ uploading: false })
}
})
// 监听上传进度
uploadTask.onProgressUpdate((res) => {
this.setData({ progress: res.progress })
})
}
})
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\ota\ota.json =====
{
"navigationBarTitleText": "固件升级"
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\ota\ota.wxml =====
<view class="container">
<view class="title">固件升级</view>
<view class="warn">⚠️ 升级过程请勿断电</view>
<!-- 设备连接状态 -->
<view class="status-box">
<text wx:if="{{isConnected}}" class="status-online">● 设备已连接 ({{deviceIP}})</text>
<text wx:else class="status-offline">● 未连接设备</text>
</view>
<!-- 选择文件 -->
<view class="file-section">
<button bindtap="chooseFile" class="btn">选择固件文件</button>
<view class="file-info" wx:if="{{fileName}}">
<text>已选择:{{fileName}}</text>
</view>
</view>
<!-- 升级按钮 -->
<button
bindtap="startOTA"
type="warn"
class="btn"
disabled="{{uploading || !filePath}}"
loading="{{uploading}}"
>
{{uploading ? '上传中' : '开始升级固件'}}
</button>
<!-- 进度条 -->
<view class="progress-box" wx:if="{{uploading}}">
<progress percent="{{progress}}" show-info stroke-width="12" />
<text>上传进度:{{progress}}%</text>
</view>
<view class="tip">
* 固件文件为 .bin 格式,可从 Arduino IDE 导出
</view>
</view>
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\ota\ota.wxss =====
.container {
padding: 40rpx;
text-align: center;
min-height: 100vh;
background-color: #f5f5f5;
}
.title {
font-size: 36rpx;
margin-bottom: 30rpx;
}
.warn {
color: red;
font-size: 28rpx;
margin-bottom: 40rpx;
}
.status-box {
margin-bottom: 30rpx;
font-size: 28rpx;
}
.status-online {
color: #07c;
}
.status-offline {
color: #999;
}
.file-section {
background: white;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
}
.file-info {
margin-top: 20rpx;
font-size: 26rpx;
color: #666;
}
.btn {
margin-bottom: 30rpx;
border-radius: 8rpx;
}
.progress-box {
background: white;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
}
.tip {
margin-top: 40rpx;
color: #999;
font-size: 24rpx;
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\timer\timer.js =====
const app = getApp()
Page({
data: {
weekTimers: [] // 7 个对象:{ day, label, slots: [{enabled, on, off}, ...] }
},
onLoad() {
this.fetchTimers()
},
fetchTimers() {
if (!app.globalData.deviceIP) {
wx.showToast({ title: "请先连接设备", icon: "none" })
return
}
wx.request({
url: `http://${app.globalData.deviceIP}:8266/timers`,
method: "GET",
timeout: 3000,
success: (res) => {
if (Array.isArray(res.data) && res.data.length === 7) {
this.setData({ weekTimers: res.data })
} else {
this.initDefault()
}
},
fail: () => {
wx.showToast({ title: "获取定时失败", icon: "none" })
this.initDefault()
}
})
},
initDefault() {
const days = ["周日","周一","周二","周三","周四","周五","周六"]
const defaultTimers = days.map((label, day) => ({
day, label,
slots: [] // 开始无时段
}))
this.setData({ weekTimers: defaultTimers })
},
// 在指定天添加一个时段
addSlot(e) {
const day = e.currentTarget.dataset.day
const newTimers = this.data.weekTimers.map(item => {
if (item.day === day) {
if (item.slots.length >= 4) {
wx.showToast({ title: "每天最多4个时段", icon: "none" })
return item // 不添加
}
const slots = [...item.slots, { enabled: true, on: "08:00", off: "22:00" }]
return { ...item, slots }
}
return item
})
this.setData({ weekTimers: newTimers })
},
// 删除指定天的某个时段
deleteSlot(e) {
const { day, index } = e.currentTarget.dataset
const newTimers = this.data.weekTimers.map(item => {
if (item.day === day) {
const slots = item.slots.filter((_, i) => i !== index)
return { ...item, slots }
}
return item
})
this.setData({ weekTimers: newTimers })
},
// 启用/禁用时段
toggleSlotEnabled(e) {
const { day, index } = e.currentTarget.dataset
const newTimers = this.data.weekTimers.map(item => {
if (item.day === day) {
const slots = item.slots.map((s, i) => {
if (i === index) return { ...s, enabled: !s.enabled }
return s
})
return { ...item, slots }
}
return item
})
this.setData({ weekTimers: newTimers })
},
// 设置开机时间
setOnTime(e) {
const { day, index } = e.currentTarget.dataset
const newTimers = this.data.weekTimers.map(item => {
if (item.day === day) {
const slots = item.slots.map((s, i) => {
if (i === index) return { ...s, on: e.detail.value }
return s
})
return { ...item, slots }
}
return item
})
this.setData({ weekTimers: newTimers })
},
// 设置关机时间
setOffTime(e) {
const { day, index } = e.currentTarget.dataset
const newTimers = this.data.weekTimers.map(item => {
if (item.day === day) {
const slots = item.slots.map((s, i) => {
if (i === index) return { ...s, off: e.detail.value }
return s
})
return { ...item, slots }
}
return item
})
this.setData({ weekTimers: newTimers })
},
saveTimers() {
if (!app.globalData.deviceIP) {
wx.showToast({ title: "未连接设备", icon: "none" })
return
}
// 整理发送数据,只保留必要字段
const payload = this.data.weekTimers.map(({ day, slots }) => ({
day,
slots: slots.map(({ enabled, on, off }) => ({ enabled, on, off }))
}))
wx.request({
url: `http://${app.globalData.deviceIP}:8266/timers`,
method: "POST",
data: payload,
header: { 'content-type': 'application/json' },
timeout: 3000,
success: (res) => {
if (res.data && res.data.result === "ok") {
wx.showToast({ title: "保存成功", icon: "success" })
setTimeout(() => wx.navigateBack(), 1500)
} else {
wx.showToast({ title: "保存失败", icon: "none" })
}
},
fail: () => {
wx.showToast({ title: "请求失败", icon: "none" })
}
})
}
})
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\timer\timer.json =====
{
"navigationBarTitleText": "定时设置"
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\timer\timer.wxml =====
<view class="container">
<view class="title">一周定时设置</view>
<view wx:for="{{weekTimers}}" wx:key="day" class="day-card">
<view class="day-header">
<text class="day-label">{{item.label}}</text>
<button size="mini" bindtap="addSlot" data-day="{{item.day}}" class="add-btn"wx:if="{{item.slots.length < 4}}">+ 添加时段</button>
</view>
<!-- 时段列表 -->
<view wx:for="{{item.slots}}" wx:key="index" wx:for-item="slot" wx:for-index="idx" class="slot-box">
<view class="slot-header">
<text>时段 {{idx + 1}}</text>
<view class="slot-actions">
<switch checked="{{slot.enabled}}" bindchange="toggleSlotEnabled" data-day="{{item.day}}" data-index="{{idx}}" />
<button size="mini" bindtap="deleteSlot" data-day="{{item.day}}" data-index="{{idx}}" class="delete-btn">删除</button>
</view>
</view>
<view class="time-row">
<text>开机:</text>
<picker mode="time" value="{{slot.on}}" bindchange="setOnTime" data-day="{{item.day}}" data-index="{{idx}}">
<view class="picker-text">{{slot.on}}</view>
</picker>
</view>
<view class="time-row">
<text>关机:</text>
<picker mode="time" value="{{slot.off}}" bindchange="setOffTime" data-day="{{item.day}}" data-index="{{idx}}">
<view class="picker-text">{{slot.off}}</view>
</picker>
</view>
</view>
<view wx:if="{{item.slots.length === 0}}" class="empty-tip">暂无时段</view>
</view>
<button bindtap="saveTimers" type="primary" class="save-btn">保存一周定时</button>
</view>
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\pages\timer\timer.wxss =====
.container {
padding: 30rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin-bottom: 30rpx;
}
.day-card {
background: white;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.day-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
}
.day-label {
font-size: 30rpx;
font-weight: bold;
}
.add-btn {
font-size: 24rpx;
}
.slot-box {
background: #f9f9f9;
border-radius: 12rpx;
padding: 15rpx;
margin-bottom: 15rpx;
}
.slot-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
font-size: 28rpx;
font-weight: bold;
}
.slot-actions {
display: flex;
align-items: center;
}
.delete-btn {
margin-left: 15rpx;
color: red;
}
.time-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8rpx 0;
font-size: 28rpx;
}
.picker-text {
color: #07c;
}
.empty-tip {
text-align: center;
color: #999;
font-size: 26rpx;
padding: 10rpx 0;
}
.save-btn {
margin-top: 40rpx;
border-radius: 8rpx;
}
------------------------------------------------
===== \Users\Administrator\WeChatProjects\miniprogram-1\utils\util.js =====
const formatTime = date => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
}
const formatNumber = n => {
n = n.toString()
return n[1] ? n : `0${n}`
}
module.exports = { formatTime }
------------------------------------------------