From 3ed4da7571cec3ec906dfc972879b193c7385fb5 Mon Sep 17 00:00:00 2001 From: zpc Date: Sun, 30 Mar 2025 02:11:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A43123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ShengShengBuXi.ConsoleApp/appsettings.json | 2 +- ShengShengBuXi/Hubs/AudioHub.cs | 192 +++++++++++++++++++++ ShengShengBuXi/Pages/Admin.cshtml | 175 +++++++++++++++++++ ShengShengBuXi/config/sentences.txt | Bin 5326 -> 6906 bytes 4 files changed, 368 insertions(+), 1 deletion(-) diff --git a/ShengShengBuXi.ConsoleApp/appsettings.json b/ShengShengBuXi.ConsoleApp/appsettings.json index f1dd3dd..7700478 100644 --- a/ShengShengBuXi.ConsoleApp/appsettings.json +++ b/ShengShengBuXi.ConsoleApp/appsettings.json @@ -1,5 +1,5 @@ { - "SignalRHubUrl": "http://localhost:81/audiohub", + "SignalRHubUrl": "http://115.159.44.16/audiohub", "ConfigBackupPath": "config.json", "AutoConnectToServer": true, "AllowOfflineStart": false diff --git a/ShengShengBuXi/Hubs/AudioHub.cs b/ShengShengBuXi/Hubs/AudioHub.cs index a8bfcc2..bd09c44 100644 --- a/ShengShengBuXi/Hubs/AudioHub.cs +++ b/ShengShengBuXi/Hubs/AudioHub.cs @@ -2130,5 +2130,197 @@ namespace ShengShengBuXi.Hubs var clients = _clients.Values.ToList(); await Clients.Caller.SendAsync("ClientList", clients); } + + /// + /// 获取预设句子列表 + /// + /// 处理任务 + public async Task GetPresetSentences() + { + if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo)) + { + _logger.LogWarning($"未注册的客户端尝试获取预设句子列表: {Context.ConnectionId}"); + await Clients.Caller.SendAsync("Error", "请先注册客户端"); + return; + } + + if (clientInfo.ClientType != ClientType.WebAdmin && clientInfo.ClientType != ClientType.Monitor) + { + _logger.LogWarning($"非管理端或监控端客户端尝试获取预设句子列表: {Context.ConnectionId}, 类型: {clientInfo.ClientType}"); + await Clients.Caller.SendAsync("Error", "只有管理端或监控端客户端可以获取预设句子列表"); + return; + } + + _logger.LogInformation($"获取预设句子列表: {Context.ConnectionId}"); + + // 确保列表已加载 + if (_presetSentences.Count == 0) + { + LoadPresetSentencesFromFile(); + } + + // 发送预设句子列表 + await Clients.Caller.SendAsync("ReceivePresetSentences", _presetSentences); + } + + /// + /// 保存预设句子列表 + /// + /// 句子列表 + /// 处理任务 + public async Task SavePresetSentences(List sentences) + { + sentences = sentences.Where(it => !string.IsNullOrEmpty(it)).ToList(); + if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo)) + { + _logger.LogWarning($"未注册的客户端尝试保存预设句子列表: {Context.ConnectionId}"); + await Clients.Caller.SendAsync("PresetSentencesSaved", false, "请先注册客户端"); + return; + } + + if (clientInfo.ClientType != ClientType.WebAdmin && clientInfo.ClientType != ClientType.Monitor) + { + _logger.LogWarning($"非管理端或监控端客户端尝试保存预设句子列表: {Context.ConnectionId}, 类型: {clientInfo.ClientType}"); + await Clients.Caller.SendAsync("PresetSentencesSaved", false, "只有管理端或监控端客户端可以保存预设句子列表"); + return; + } + + _logger.LogInformation($"保存预设句子列表: {Context.ConnectionId}, 句子数量: {sentences?.Count ?? 0}"); + + if (sentences == null) + { + await Clients.Caller.SendAsync("PresetSentencesSaved", false, "句子列表不能为空"); + return; + } + + try + { + // 更新内存中的列表 + _presetSentences.Clear(); + foreach (var sentence in sentences) + { + if (!string.IsNullOrWhiteSpace(sentence)) + { + _presetSentences.Add(sentence.Trim()); + } + } + + // 确保目录存在 + Directory.CreateDirectory(Path.GetDirectoryName(_sentencesFilePath)); + + // 保存到文件 + File.WriteAllLines(_sentencesFilePath, _presetSentences); + + _logger.LogInformation($"成功保存预设句子到文件: {_presetSentences.Count} 条"); + + // 通知调用客户端保存成功 + await Clients.Caller.SendAsync("PresetSentencesSaved", true, $"成功保存 {_presetSentences.Count} 条预设句子"); + + // 通知其他管理端和监控端客户端预设句子已更新 + await Clients.OthersInGroup("webadmin").SendAsync("PresetSentencesUpdated"); + await Clients.OthersInGroup("monitor").SendAsync("PresetSentencesUpdated"); + } + catch (Exception ex) + { + _logger.LogError($"保存预设句子失败: {ex.Message}"); + await Clients.Caller.SendAsync("PresetSentencesSaved", false, $"保存预设句子失败: {ex.Message}"); + } + } + + /// + /// 保存预设句子2列表 + /// + /// 句子列表 + /// 处理任务 + public async Task SavePresetSentences2(List sentences) + { + sentences = sentences.Where(it => !string.IsNullOrEmpty(it)).ToList(); + if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo)) + { + _logger.LogWarning($"未注册的客户端尝试保存预设句子2列表: {Context.ConnectionId}"); + await Clients.Caller.SendAsync("PresetSentences2Saved", false, "请先注册客户端"); + return; + } + + if (clientInfo.ClientType != ClientType.WebAdmin && clientInfo.ClientType != ClientType.Monitor) + { + _logger.LogWarning($"非管理端或监控端客户端尝试保存预设句子2列表: {Context.ConnectionId}, 类型: {clientInfo.ClientType}"); + await Clients.Caller.SendAsync("PresetSentences2Saved", false, "只有管理端或监控端客户端可以保存预设句子2列表"); + return; + } + + _logger.LogInformation($"保存预设句子2列表: {Context.ConnectionId}, 句子数量: {sentences?.Count ?? 0}"); + + if (sentences == null) + { + await Clients.Caller.SendAsync("PresetSentences2Saved", false, "句子列表不能为空"); + return; + } + + try + { + // 更新内存中的列表 + _presetSentences2.Clear(); + foreach (var sentence in sentences) + { + if (!string.IsNullOrWhiteSpace(sentence)) + { + _presetSentences2.Add(sentence.Trim()); + } + } + + // 确保目录存在 + Directory.CreateDirectory(Path.GetDirectoryName(_sentences2FilePath)); + + // 保存到文件 + File.WriteAllLines(_sentences2FilePath, _presetSentences2); + + _logger.LogInformation($"成功保存预设句子2到文件: {_presetSentences2.Count} 条"); + + // 通知调用客户端保存成功 + await Clients.Caller.SendAsync("PresetSentences2Saved", true, $"成功保存 {_presetSentences2.Count} 条预设句子2"); + + // 通知其他管理端和监控端客户端预设句子2已更新 + await Clients.OthersInGroup("webadmin").SendAsync("PresetSentencesUpdated"); + await Clients.OthersInGroup("monitor").SendAsync("PresetSentencesUpdated"); + } + catch (Exception ex) + { + _logger.LogError($"保存预设句子2失败: {ex.Message}"); + await Clients.Caller.SendAsync("PresetSentences2Saved", false, $"保存预设句子2失败: {ex.Message}"); + } + } + + /// + /// 获取预设句子2列表 + /// + /// 处理任务 + public async Task GetPresetSentences2() + { + if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo)) + { + _logger.LogWarning($"未注册的客户端尝试获取预设句子2列表: {Context.ConnectionId}"); + await Clients.Caller.SendAsync("Error", "请先注册客户端"); + return; + } + + if (clientInfo.ClientType != ClientType.WebAdmin && clientInfo.ClientType != ClientType.Monitor) + { + _logger.LogWarning($"非管理端或监控端客户端尝试获取预设句子2列表: {Context.ConnectionId}, 类型: {clientInfo.ClientType}"); + await Clients.Caller.SendAsync("Error", "只有管理端或监控端客户端可以获取预设句子2列表"); + return; + } + + _logger.LogInformation($"获取预设句子2列表: {Context.ConnectionId}"); + + // 确保列表已加载 + if (_presetSentences2.Count == 0) + { + LoadPresetSentences2FromFile(); + } + + // 发送预设句子2列表 + await Clients.Caller.SendAsync("ReceivePresetSentences2", _presetSentences2); + } } } \ No newline at end of file diff --git a/ShengShengBuXi/Pages/Admin.cshtml b/ShengShengBuXi/Pages/Admin.cshtml index 751e1a5..71b5514 100644 --- a/ShengShengBuXi/Pages/Admin.cshtml +++ b/ShengShengBuXi/Pages/Admin.cshtml @@ -53,6 +53,18 @@ data-bs-target="#user-records" type="button" role="tab" aria-controls="user-records" aria-selected="false">用户记录 + + + +
@@ -548,6 +560,43 @@
+ + +
+
+
+

大屏文本管理 - 文本集1

+
+ + +
+
+
+
+

每行一条文本,保存后将覆盖原有内容并写入文件

+ +
+
+
+
+ +
+
+
+

大屏文本管理 - 文本集2

+
+ + +
+
+
+
+

每行一条文本,保存后将覆盖原有内容并写入文件

+ +
+
+
+
@@ -1476,6 +1525,34 @@ showMessage("解析显示配置失败: " + error, "danger"); } }); + + // 接收预设句子列表 + connection.on("ReceivePresetSentences", (sentences) => { + log("接收到预设句子列表,数量: " + (sentences ? sentences.length : 0)); + const textArea = document.getElementById("preset-sentences"); + textArea.value = sentences ? sentences.join('\n') : ''; + showMessage("成功获取预设句子列表", "success"); + }); + + // 接收预设句子2列表 + connection.on("ReceivePresetSentences2", (sentences) => { + log("接收到预设句子2列表,数量: " + (sentences ? sentences.length : 0)); + const textArea = document.getElementById("preset-sentences2"); + textArea.value = sentences ? sentences.join('\n') : ''; + showMessage("成功获取预设句子2列表", "success"); + }); + + // 预设句子保存结果 + connection.on("PresetSentencesSaved", (success, message) => { + log("预设句子保存结果: " + message); + showMessage(message, success ? "success" : "danger"); + }); + + // 预设句子2保存结果 + connection.on("PresetSentences2Saved", (success, message) => { + log("预设句子2保存结果: " + message); + showMessage(message, success ? "success" : "danger"); + }); } // 更新客户端列表 @@ -1897,5 +1974,103 @@ setTimeout(getClientList, 500); } }); + + // 切换到大屏文本标签页时自动刷新预设句子 + document.getElementById('screen-text-tab').addEventListener('click', function () { + if (connection && connection.state === signalR.HubConnectionState.Connected) { + log("切换到大屏文本标签页,自动刷新预设句子"); + setTimeout(getPresetSentences, 500); + } + }); + + // 切换到大屏文本2标签页时自动刷新预设句子2 + document.getElementById('screen-text2-tab').addEventListener('click', function () { + if (connection && connection.state === signalR.HubConnectionState.Connected) { + log("切换到大屏文本2标签页,自动刷新预设句子2"); + setTimeout(getPresetSentences2, 500); + } + }); + + // 获取预设句子列表 + function getPresetSentences() { + if (!connection || connection.state !== signalR.HubConnectionState.Connected) { + showMessage("无法获取预设句子:未连接到服务器", "warning"); + return; + } + + log("正在获取预设句子列表..."); + + connection.invoke("GetPresetSentences") + .then(() => { + log("已成功发送获取预设句子列表请求"); + }) + .catch(err => { + log("获取预设句子列表失败: " + err); + showMessage("获取预设句子列表失败: " + err, "danger"); + }); + } + + // 保存预设句子列表 + function savePresetSentences() { + if (!connection || connection.state !== signalR.HubConnectionState.Connected) { + showMessage("无法保存预设句子:未连接到服务器", "warning"); + return; + } + + const textArea = document.getElementById("preset-sentences"); + const sentences = textArea.value.split('\n').filter(line => line.trim() !== ''); + + log(`正在保存预设句子列表,共${sentences.length}条...`); + + connection.invoke("SavePresetSentences", sentences) + .then(result => { + log("预设句子保存请求已发送"); + }) + .catch(err => { + log("保存预设句子列表失败: " + err); + showMessage("保存预设句子列表失败: " + err, "danger"); + }); + } + + // 获取预设句子2列表 + function getPresetSentences2() { + if (!connection || connection.state !== signalR.HubConnectionState.Connected) { + showMessage("无法获取预设句子2:未连接到服务器", "warning"); + return; + } + + log("正在获取预设句子2列表..."); + + connection.invoke("GetPresetSentences2") + .then(() => { + log("已成功发送获取预设句子2列表请求"); + }) + .catch(err => { + log("获取预设句子2列表失败: " + err); + showMessage("获取预设句子2列表失败: " + err, "danger"); + }); + } + + // 保存预设句子列表 + function savePresetSentences2() { + if (!connection || connection.state !== signalR.HubConnectionState.Connected) { + showMessage("无法保存预设句子2:未连接到服务器", "warning"); + return; + } + + const textArea = document.getElementById("preset-sentences2"); + const sentences = textArea.value.split('\n').filter(line => line.trim() !== ''); + + log(`正在保存预设句子2列表,共${sentences.length}条...`); + + connection.invoke("SavePresetSentences2", sentences) + .then(result => { + log("预设句子2保存请求已发送"); + }) + .catch(err => { + log("保存预设句子2列表失败: " + err); + showMessage("保存预设句子2列表失败: " + err, "danger"); + }); + } } \ No newline at end of file diff --git a/ShengShengBuXi/config/sentences.txt b/ShengShengBuXi/config/sentences.txt index aa7170c3cbf29fd4604b42e1921d631ee34306ab..2ee82c1cf23fc71b0ab2e62c68bb9ab259674c21 100644 GIT binary patch literal 6906 zcmZWuX>#h!lKtPyU;!P`9nsPKcE?~qViJ=X#S8`;5MVF}iD5CHYWnrI@J?2NFYoIJ z$Bm0ArLrLT!2f60P(Ry^={$Xgq6Z^_&FuXyr5SY{=&d+yj`zY)8=__lfT2wU9FM}AJ; zTjIS9TRgeo?`@doe5cq0pQR@{;~RUM1+0Z;OMSA}onY2Ia}HxZ_y z^N@GHwP{<8ezeC2BCrwmX(`ShtU3a1mh?sWsnp+36-i9fR73eDm>$SkBn_z>-DY?vQDj z{bIRAbm_AmrG(o>=2Ya|rb{Om(cL3QzR$=9ok<^=*XP}X2px87%AXVO98`CqZ|&ys z@)+(WixU-(N0&+r&#HHqk0{eTwWssR1xuXp!*e-vNM$Q*)R*@zG8LUaSRqXpD|J}# zL1x)(&))8;>BS;Hh{AYuwzz9WfhSJ`1WW#GV`SW_$hRGHCNf@N*?``1t>K<@;dPyl ztQhMKVSsrl_`%96{+^D4yiONy&Jig&n!r6#P8nRXOp$+&q9EYbfnF0&AoO&X^q77s zEmjIuauNkydbZ%>hu)R?;E9WNJF^x0hPamHJOLCNfFiP@w8bw8*B0fP9L*q^z|zLy z%~|9ZDXzyfz!CYj_^xUf;V@$RS3D=|QRJVaVEtD-&+0qw{TR#Qt0{sVAQXl7=-NdM zJg{mCU{hyS6>s6&gB&;5b@#+o?Z|r$bB@6MqlV(4s<)B<$d418rUyVvrmn)x<0j

U=5JORj%!*B2 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 QzD(bUAvH~50(e06Ul9+$00000 literal 5326 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