feat: add SFX volume control and improve audio feedback

- Implemented separate sound effects volume control in settings.
- Updated backend to handle new `sfx_volume` parameter.
- Enhanced UI to include sound effects volume slider in admin panel.
- Integrated sound effects for various user interactions (clicks, navigation, alerts).
- Added a new global sound engine to manage audio synthesis without external files.
- Updated documentation for Docker deployment and Windows packaging.
- Refactored audio context management to ensure sound effects are available post user interaction.
This commit is contained in:
Pine
2026-08-18 07:14:13 +08:00
parent 18d28be943
commit d70d9d5fbd
22 changed files with 561 additions and 28 deletions
+33 -15
View File
@@ -2,27 +2,45 @@ import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { ThemeProvider } from "@appica/ui-react/providers/theme-provider";
import { API_BASE } from "./config";
import { getApiBase } from "./config";
import { invoke } from "@tauri-apps/api/core";
import "./styles/global.css";
/* =========================================================
启动引导(打包部署关键):
渲染前先从后端 GET /api/config 拉取运行时配置
(MQTT 地址/账号、API 基址),写入 window.__DPM_*__
之后 api.js / mqtt.js 均以惰性读取方式生效。
后端不可达时静默使用内置默认(Tauri 打包回退 127.0.0.1
启动引导(打包部署关键)—— 地址解析优先级
1. exe 旁 config.jsonTauri 命令读取,部署免重打包改 IP)★
2. 后端 GET /api/configMQTT 地址/账号,后端 .env 为准)
3. 内置默认(Tauri 打包回退 127.0.0.1 / 浏览器用当前主机)
结果写入 window.__DPM_*__api.js / mqtt.js 惰性读取生效
========================================================= */
async function bootstrapRuntimeConfig() {
// 1) exe 旁 config.json(最高优先):{api_base, mqtt_url, mqtt_username, mqtt_password}
try {
const res = await fetch(`${API_BASE()}/api/config`, { cache: 'no-store' });
if (!res.ok) return;
const cfg = await res.json();
// 仅覆盖 MQTT 连接信息(后端 .env 为准);API 基址以前端实际可达地址为准
if (cfg.mqtt_url) window.__DPM_MQTT__ = cfg.mqtt_url;
if (cfg.mqtt_username) window.__DPM_MQTT_USER__ = cfg.mqtt_username;
if (cfg.mqtt_password) window.__DPM_MQTT_PASS__ = cfg.mqtt_password;
// eslint-disable-next-line no-console
if (cfg.mqtt_url) console.info(`[bootstrap] MQTT -> ${cfg.mqtt_url}`);
const raw = await invoke("read_runtime_config");
if (raw) {
const cfg = JSON.parse(raw);
if (cfg.api_base) window.__DPM_API__ = cfg.api_base;
if (cfg.mqtt_url) window.__DPM_MQTT__ = cfg.mqtt_url;
if (cfg.mqtt_username) window.__DPM_MQTT_USER__ = cfg.mqtt_username;
if (cfg.mqtt_password) window.__DPM_MQTT_PASS__ = cfg.mqtt_password;
// eslint-disable-next-line no-console
if (cfg.mqtt_url) console.info(`[bootstrap] config.json MQTT -> ${cfg.mqtt_url}`);
}
} catch {
/* 浏览器开发环境或未注册命令:跳过 */
}
// 2) 后端 /api/configMQTT 地址/账号;API 基址以前端实际可达地址为准)
try {
const res = await fetch(`${getApiBase()}/api/config`, { cache: 'no-store' });
if (res.ok) {
const cfg = await res.json();
if (cfg.mqtt_url) window.__DPM_MQTT__ = cfg.mqtt_url;
if (cfg.mqtt_username) window.__DPM_MQTT_USER__ = cfg.mqtt_username;
if (cfg.mqtt_password) window.__DPM_MQTT_PASS__ = cfg.mqtt_password;
// eslint-disable-next-line no-console
if (cfg.mqtt_url) console.info(`[bootstrap] /api/config MQTT -> ${cfg.mqtt_url}`);
}
} catch {
/* 后端不可达:使用内置默认地址 */
}