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:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
188
xiaomusic/static/default/setting.css
vendored
188
xiaomusic/static/default/setting.css
vendored
@@ -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;
|
||||
}
|
||||
|
||||
1323
xiaomusic/static/default/setting.html
vendored
1323
xiaomusic/static/default/setting.html
vendored
File diff suppressed because it is too large
Load Diff
179
xiaomusic/static/default/setting.js
vendored
179
xiaomusic/static/default/setting.js
vendored
@@ -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 = "网络错误,请检查连接";
|
||||
});
|
||||
// 显示二维码区域并进入加载状态
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user