1
0
mirror of https://github.com/hanxi/xiaomusic.git synced 2026-03-26 09:59:45 +08:00

增加手动获取设备列表以及高级配置改为Tab结构 (#753)

* Update setting.css

高级设置改为tab结构,减少滚动

* Update setting.html

高级设置改为tab结构,减少滚动

* Update setting.js

高级设置改为tab结构,减少滚动;
扫码登录改为jQuery

* 修改获取所有设备接口

* 增加手动获取设备

* 获取设备列表

* fix:缺少导入

* fix

* 修改二维码图标

* 二维码登录tab兼容手机版页面

* fix:生成二维码在手机端溢出显示

* 增加返回二维码超时时间

* 页面增加二维码超时倒计时
This commit is contained in:
fragrans
2026-02-13 11:14:14 +08:00
committed by GitHub
parent 1dd2ebcbb0
commit 42bc75df97
5 changed files with 874 additions and 824 deletions

View File

@@ -24,8 +24,7 @@ router = APIRouter(dependencies=[Depends(verification)])
@router.get("/device_list")
async def device_list():
"""获取设备列表"""
await xiaomusic.auth_manager.init_all_data()
devices = await xiaomusic.auth_manager.try_update_device_id()
devices = await xiaomusic.getalldevices()
return {"devices": devices}
@router.get("/getvolume")

View File

@@ -1,5 +1,5 @@
"""系统管理路由"""
import asyncio
import json
import os
import io
@@ -44,7 +44,7 @@ from xiaomusic.utils.system_utils import (
restart_xiaomusic,
update_version,
)
from xiaomusic.qrcode_login import MiJiaAPI
router = APIRouter(dependencies=[Depends(verification)])
auth_data_path = config.conf_path if config.conf_path else None
mi_jia_api = MiJiaAPI(auth_data_path=auth_data_path)
@@ -92,6 +92,7 @@ async def get_qrcode():
"success": True,
"qrcode_url": qrcode_url,
"status_url": qrcode_data.get("lp", ""),
"expire_seconds": config.qrcode_timeout,
}
except Exception as e:
log.exception("get_qrcode failed: %s", e)

View File

@@ -21,6 +21,7 @@
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
visibility: hidden;
}
@@ -209,19 +210,19 @@ body {
}
}
/* 表单元素样式 */
.setting-card label {
/* 表单元素样式.setting-panel 统一基础设置与高级 Tab 内表单样式) */
.setting-panel label {
margin-bottom: 6px;
font-size: 14px;
color: #555;
font-weight: 500;
}
.setting-card input[type="text"],
.setting-card input[type="password"],
.setting-card input[type="number"],
.setting-card select,
.setting-card textarea {
.setting-panel input[type="text"],
.setting-panel input[type="password"],
.setting-panel input[type="number"],
.setting-panel select,
.setting-panel textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
@@ -234,24 +235,24 @@ body {
box-shadow 0.2s ease;
}
.setting-card input[type="text"]:focus,
.setting-card input[type="password"]:focus,
.setting-card input[type="number"]:focus,
.setting-card select:focus,
.setting-card textarea:focus {
.setting-panel input[type="text"]:focus,
.setting-panel input[type="password"]:focus,
.setting-panel input[type="number"]:focus,
.setting-panel select:focus,
.setting-panel textarea:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}
.setting-card textarea {
.setting-panel textarea {
min-height: 120px;
resize: vertical;
font-family: monospace;
}
/* 按钮样式 */
.setting-card button,
/* 按钮样式(面板内按钮与 header/footer 统一) */
.setting-panel button,
.header-buttons button,
.setting-footer button {
background-color: #007bff;
@@ -271,20 +272,20 @@ body {
gap: 6px;
}
.setting-card button:hover,
.setting-panel button:hover,
.header-buttons button:hover,
.setting-footer button:hover {
background-color: #0056b3;
}
.setting-card button:active,
.setting-panel button:active,
.header-buttons button:active,
.setting-footer button:active {
transform: translateY(1px);
}
/* 小按钮样式 */
.setting-card button.mini-button,
.setting-panel button.mini-button,
button.mini-button {
padding: 4px 10px !important;
font-size: 12px !important;
@@ -298,14 +299,14 @@ button.mini-button {
}
@media (max-width: 640px) {
.setting-card button,
.setting-panel button,
.header-buttons button,
.setting-footer button {
padding: 12px 16px;
min-height: 44px;
}
.setting-card button.mini-button,
.setting-panel button.mini-button,
button.mini-button {
padding: 8px 12px !important;
font-size: 13px !important;
@@ -318,6 +319,11 @@ button.mini-button {
}
/* 设备选择区域 */
#refresh-device-list {
margin-left: 10px;
margin-bottom: 8px;
}
.device-selection {
display: flex;
flex-wrap: wrap;
@@ -480,6 +486,11 @@ button.mini-button {
/* 添加图标支持 */
.auth-tab-button .material-icons {
font-size: 18px !important;
width: 20px;
min-width: 20px;
text-align: center;
flex-shrink: 0;
overflow: hidden;
}
.auth-tab-panels {
@@ -495,6 +506,31 @@ button.mini-button {
display: block;
}
/* 二维码登录:未点击获取前不显示图片 */
#qrcode-container .qrcode-image-hidden {
display: none !important;
}
#qrcode-container {
width: 100%;
max-width: 320px;
}
#qrcode-image {
display: block;
width: 100%;
max-width: 100%;
height: auto;
margin: 8px 0 10px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
#qrcode-status {
overflow-wrap: anywhere;
}
@keyframes fadeInUp {
from {
opacity: 0;
@@ -509,18 +545,110 @@ button.mini-button {
/* 移动端适配 */
@media (max-width: 640px) {
.auth-tabs {
gap: 8px;
padding: 4px;
flex-direction: column;
gap: 6px;
padding: 6px;
}
.auth-tab-button {
padding: 12px 12px !important;
width: 100%;
display: grid !important;
grid-template-columns: 18px minmax(0, 1fr);
align-items: center;
justify-content: initial;
padding: 12px 14px !important;
font-size: 13px !important;
gap: 6px;
column-gap: 8px;
text-align: left;
}
.auth-tab-button .material-icons {
font-size: 18px !important;
font-size: 17px !important;
width: 18px;
min-width: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.auth-tab-button > span:last-child {
min-width: 0;
text-align: left;
}
#qrcode-container {
max-width: 260px;
}
}
/* 高级配置 Tab 切换 */
.config-tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
padding: 4px;
background-color: #f8f9fa;
border-radius: 12px;
}
.config-tab-button {
flex: 0 1 auto;
min-width: 0;
background: transparent !important;
border: none !important;
padding: 10px 14px !important;
font-size: 13px !important;
font-weight: 500 !important;
color: #6c757d !important;
cursor: pointer;
position: relative;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
border-radius: 8px !important;
z-index: 1;
transform: none !important;
}
.config-tab-button:hover {
color: #495057 !important;
background: transparent !important;
}
.config-tab-button.active {
color: #007bff !important;
font-weight: 600 !important;
background: #fff !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.config-tab-panels {
position: relative;
}
.config-tab-content {
display: none !important;
animation: fadeInUp 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.config-tab-content.active {
display: block !important;
}
.config-tab-content .card-content {
margin-top: 0;
padding: 0 20px 20px 20px;
}
@media (max-width: 640px) {
.config-tabs {
gap: 6px;
padding: 4px;
}
.config-tab-button {
padding: 8px 10px !important;
font-size: 12px !important;
}
}
@@ -882,12 +1010,14 @@ footer a:hover {
}
}
/* 链接样式 */
/* 链接样式(面板内与卡片内统一) */
.setting-panel a,
.setting-card a {
color: #007bff;
text-decoration: none;
}
.setting-panel a:hover,
.setting-card a:hover {
text-decoration: underline;
}
@@ -1077,6 +1207,7 @@ hr {
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: "liga";
font-feature-settings: "liga";
-webkit-font-smoothing: antialiased;
}
@@ -1093,9 +1224,6 @@ hr {
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: "liga";
font-feature-settings: "liga";
-webkit-font-smoothing: antialiased;
}
/* 二维码登录:未点击获取前不显示图片 */
#qrcode-container .qrcode-image-hidden {
display: none !important;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,75 @@
// 获取二维码的函数(点击「获取二维码」后再请求并显示)
let qrcodeCountdownTimer = null;
const DEFAULT_QRCODE_EXPIRE_SECONDS = 120;
function stopQRCodeCountdown() {
if (qrcodeCountdownTimer) {
clearInterval(qrcodeCountdownTimer);
qrcodeCountdownTimer = null;
}
}
function startQRCodeCountdown($qrcodeStatus, $qrcodeImage, expireSeconds) {
stopQRCodeCountdown();
let remainSeconds = Number(expireSeconds);
if (!Number.isFinite(remainSeconds) || remainSeconds <= 0) {
remainSeconds = DEFAULT_QRCODE_EXPIRE_SECONDS;
}
remainSeconds = Math.floor(remainSeconds);
const updateCountdownText = function () {
if (remainSeconds <= 0) {
stopQRCodeCountdown();
$qrcodeImage.addClass("qrcode-image-hidden");
$qrcodeStatus.text("二维码已过期,请点击“刷新二维码”重新获取");
return;
}
$qrcodeStatus.text(
"请使用米家App扫码登录二维码将在 " + remainSeconds + " 秒后过期"
);
remainSeconds -= 1;
};
updateCountdownText();
qrcodeCountdownTimer = setInterval(updateCountdownText, 1000);
}
function fetchQRCode() {
var $qrcodeImage = $("#qrcode-image");
var $qrcodeStatus = $("#qrcode-status");
var $refreshBtn = $("#refresh-qrcode");
if (!$qrcodeImage.length || !$qrcodeStatus.length) return;
stopQRCodeCountdown();
$qrcodeImage.attr("src", "");
$qrcodeStatus.text("正在生成二维码...");
$refreshBtn.text("刷新二维码");
$.get("/api/get_qrcode")
.done(function (data) {
if (data.success) {
if (data.already_logged_in) {
$qrcodeStatus.text(data.message || "已登录,无需更新");
$qrcodeImage.addClass("qrcode-image-hidden");
} else {
$qrcodeImage.attr("src", data.qrcode_url || "").removeClass("qrcode-image-hidden");
startQRCodeCountdown($qrcodeStatus, $qrcodeImage, data.expire_seconds);
}
} else {
$qrcodeStatus.text(data.message || "二维码生成失败,请稍后重试");
}
})
.fail(function (xhr) {
console.error("获取二维码失败:", xhr);
$qrcodeStatus.text("网络错误,请检查连接");
});
}
// ============ 字体加载检测 ============
// 检测字体加载完成,避免图标文字闪烁
(function() {
(function () {
// 使用 Promise.race 实现超时保护
const fontLoadTimeout = new Promise(resolve => {
setTimeout(() => {
@@ -24,6 +93,8 @@
})();
$(function () {
$("#refresh-qrcode").on("click", fetchQRCode);
// 拉取版本
$.get("/getversion", function (data, status) {
console.log(data, status, data["version"]);
@@ -94,13 +165,28 @@ $(function () {
return selectedDids.join(",");
}
// 拉取现有配置
$.get("/getsetting?need_device_list=true", function (data, status) {
console.log(data, status);
const accountPassValid = data.account && data.password;
updateCheckbox("#mi_did", data.mi_did, data.device_list, accountPassValid);
// 获取设备列表(供“获取设备列表”按钮和初始加载共用)
function fetchDeviceList(callback) {
$.get("/getsetting?need_device_list=true", function (data, status) {
if (typeof callback === "function") {
callback(data, status);
}
}).fail(function (xhr) {
alert(
"获取设备列表失败: " +
(xhr.responseJSON && xhr.responseJSON.detail
? xhr.responseJSON.detail
: xhr.statusText)
);
});
}
// 初始加载:拉取配置并填充表单与设备列表
fetchDeviceList(function (data, status) {
console.log(data, status);
var accountPassValid = data.account && data.password;
updateCheckbox("#mi_did", data.mi_did || "", data.device_list || [], accountPassValid);
// 初始化显示
for (const key in data) {
const $element = $("#" + key);
if ($element.length) {
@@ -117,6 +203,29 @@ $(function () {
autoSelectOne();
});
$("#update-devices").on("click", function () {
var $btn = $(this);
var oldText = $btn.text();
$btn.prop("disabled", true).text("更新中…");
$.get("/device_list")
.done(function (data) {
var currentMiDid = getSelectedDids("#mi_did");
var raw = data.devices || {};
var deviceList = Object.keys(raw).map(function (did) {
var d = raw[did];
return { miotDID: d.did || did, hardware: d.hardware || "", name: d.name || "" };
});
updateCheckbox("#mi_did", currentMiDid, deviceList);
})
.fail(function (xhr) {
alert("更新设备列表失败: " + (xhr.responseJSON && xhr.responseJSON.detail ? xhr.responseJSON.detail : xhr.statusText));
})
.always(function () {
$btn.prop("disabled", false).text(oldText);
});
});
$(".save-button").on("click", () => {
var setting = $("#setting");
var inputs = setting.find("input, select, textarea");
@@ -311,6 +420,25 @@ $(function () {
$("#tab-" + tabName).addClass("active");
});
// 高级配置 Tab 切换:委托到高级配置容器,阻止冒泡并强制切换面板
$("#advancedConfigContent").on("click", ".config-tab-button", function (e) {
e.preventDefault();
e.stopPropagation();
var $btn = $(this);
var panelId = $btn.attr("aria-controls") || ("tab-" + $btn.data("tab"));
if (!panelId) return;
$("#advancedConfigContent .config-tab-button").removeClass("active").attr("aria-selected", "false");
$btn.addClass("active").attr("aria-selected", "true");
$("#advancedConfigContent .config-tab-content").removeClass("active");
var $panel = $("#" + panelId);
if ($panel.length) {
$panel.addClass("active");
}
});
// 功能操作区域折叠功能
const operationToggle = $("#operationToggle");
const operationContent = $("#operationContent");
@@ -514,40 +642,3 @@ $(function () {
}
});
});
// 获取二维码的函数(点击「获取二维码」后再请求并显示)
function fetchQRCode() {
const qrcodeImage = document.getElementById("qrcode-image");
const qrcodeStatus = document.getElementById("qrcode-status");
const refreshBtn = document.getElementById("refresh-qrcode");
if (!qrcodeImage || !qrcodeStatus) return;
qrcodeImage.src = "";
qrcodeStatus.textContent = "正在生成二维码...";
if (refreshBtn) refreshBtn.textContent = "刷新二维码";
fetch("/api/get_qrcode")
.then((response) => response.json())
.then((data) => {
if (data.success) {
if (data.already_logged_in) {
qrcodeStatus.textContent = data.message || "已登录,无需扫码";
qrcodeImage.classList.add("qrcode-image-hidden");
} else {
qrcodeImage.src = data.qrcode_url || "";
qrcodeImage.classList.remove("qrcode-image-hidden");
qrcodeStatus.textContent = "请使用米家App扫码登录扫码成功后请刷新页面以获取设备列表";
}
} else {
qrcodeStatus.textContent = data.message || "二维码生成失败,请稍后重试";
}
})
.catch((error) => {
console.error("获取二维码失败:", error);
qrcodeStatus.textContent = "网络错误,请检查连接";
});
// 显示二维码区域并进入加载状态
}