Skip to content

Commit 61ef8f7

Browse files
authored
feat(NetworkMonitor): add NetworkMonitor component (#6405)
* refactor: 增加 NetworkMonitor 组件 * doc: 增加使用示例 * feat: 增加 Indicator 参数 * style: 增加样式 * feat: 增加网络状态指示器组件 * feat: 增加离线状态指示 * refactor: 撤销更改 * doc: 更新示例 * feat: 增加恢复 Online 状态后重置数据方法 * refactor: 减少一次网络请求 * style: 更新样式 * refactor: 调整参数顺序 * refactor: 复用代码 * doc: 增加资源文件 * test: 增加单元测试 * test: 更新单元测试 * doc: 更新资源文件 * test: 增加单元测试 * doc: 更新示例 * refactor: 重构代码
1 parent 7441314 commit 61ef8f7

File tree

12 files changed

+387
-2
lines changed

12 files changed

+387
-2
lines changed

src/BootstrapBlazor.Server/Components/Layout/HomeLayout.razor

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
<div class="ms-1">BB @VersionService.Version</div>
6565
<FooterCounter></FooterCounter>
6666
<CacheCounter></CacheCounter>
67+
<div class="ms-2">
68+
<NetworkMonitorIndicator></NetworkMonitorIndicator>
69+
</div>
6770
</div>
6871
<div class="d-flex flex-fill align-items-center justify-content-center">
6972
<a class="d-none d-md-block me-3" href="@WebsiteOption.CurrentValue.GiteeRepositoryUrl" target="_blank">@Localizer["Footer"]</a>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// 客户端链接组件
10+
/// </summary>
11+
[BootstrapModuleAutoLoader(ModuleName = "net", JSObjectReference = true)]
12+
public class NetworkMonitor : BootstrapModuleComponentBase
13+
{
14+
/// <summary>
15+
/// Gets or sets the callback function that is invoked when the network state changes.
16+
/// </summary>
17+
[Parameter]
18+
public Func<NetworkMonitorState, Task>? OnNetworkStateChanged { get; set; }
19+
20+
/// <summary>
21+
/// Gets or sets the list of indicators used for display info.
22+
/// </summary>
23+
[Parameter]
24+
public List<string>? Indicators { get; set; }
25+
26+
private NetworkMonitorState _state = new();
27+
28+
/// <summary>
29+
/// <inheritdoc/>
30+
/// </summary>
31+
/// <returns></returns>
32+
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, new
33+
{
34+
Invoke = Interop,
35+
OnlineStateChangedCallback = nameof(TriggerOnlineStateChanged),
36+
OnNetworkStateChangedCallback = nameof(TriggerNetworkStateChanged),
37+
Indicators
38+
});
39+
40+
/// <summary>
41+
/// JSInvoke 回调方法
42+
/// </summary>
43+
/// <returns></returns>
44+
[JSInvokable]
45+
public async Task TriggerOnlineStateChanged(bool online)
46+
{
47+
_state.IsOnline = online;
48+
if (OnNetworkStateChanged != null)
49+
{
50+
await OnNetworkStateChanged(_state);
51+
}
52+
}
53+
54+
/// <summary>
55+
/// JSInvoke 回调方法
56+
/// </summary>
57+
/// <returns></returns>
58+
[JSInvokable]
59+
public async Task TriggerNetworkStateChanged(NetworkMonitorState state)
60+
{
61+
// 网络状态变化回调方法
62+
_state = state;
63+
_state.IsOnline = true;
64+
if (OnNetworkStateChanged != null)
65+
{
66+
await OnNetworkStateChanged(_state);
67+
}
68+
}
69+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@namespace BootstrapBlazor.Components
2+
@inherits IdComponentBase
3+
4+
<Popover Title="@Title" Trigger="@Trigger" Placement="@PopoverPlacement">
5+
<ChildContent>
6+
<span @attributes="@AdditionalAttributes" id="@Id" tabindex="0" class="@ClassString"></span>
7+
</ChildContent>
8+
<Template>
9+
<div class="bb-nt-main">
10+
<div class="bb-nt-item">
11+
<span>NetworkType:</span>
12+
<div>@_state.NetworkType</div>
13+
</div>
14+
<div class="bb-nt-item">
15+
<span>Downlink:</span>
16+
<div>@_state.Downlink Mbps</div>
17+
</div>
18+
<div class="bb-nt-item">
19+
<span>RTT:</span>
20+
<div>@_state.RTT ms</div>
21+
</div>
22+
</div>
23+
</Template>
24+
</Popover>
25+
26+
<NetworkMonitor OnNetworkStateChanged="OnNetworkStateChanged" Indicators="@_indicators"></NetworkMonitor>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
using Microsoft.Extensions.Localization;
7+
8+
namespace BootstrapBlazor.Components;
9+
10+
/// <summary>
11+
/// Represents a network monitor indicator with customizable tooltip settings.
12+
/// </summary>
13+
/// <remarks>This component allows you to configure the text, placement, and trigger behavior of a tooltip that
14+
/// appears when interacting with the network monitor indicator. The tooltip can be customized to provide additional
15+
/// information to users.</remarks>
16+
public partial class NetworkMonitorIndicator
17+
{
18+
/// <summary>
19+
/// 获得/设置 Popover 弹窗标题 默认为 null
20+
/// </summary>
21+
[Parameter]
22+
public string? Title { get; set; }
23+
24+
/// <summary>
25+
/// 获得/设置 Popover 显示位置 默认为 Top
26+
/// </summary>
27+
[Parameter]
28+
public Placement PopoverPlacement { get; set; } = Placement.Top;
29+
30+
/// <summary>
31+
/// 获得/设置 Popover 触发方式 默认为 hover focus
32+
/// </summary>
33+
[Parameter]
34+
[NotNull]
35+
public string? Trigger { get; set; }
36+
37+
[Inject, NotNull]
38+
private IStringLocalizer<NetworkMonitorIndicator>? Localizer { get; set; }
39+
40+
private NetworkMonitorState _state = new();
41+
private readonly List<string> _indicators = [];
42+
private string _networkTypeString = "";
43+
private string _downlinkString = "";
44+
private string _rttString = "";
45+
46+
private string? ClassString => CssBuilder.Default("bb-nt-indicator")
47+
.AddClass("bb-nt-indicator-4g", _state.NetworkType == "4g")
48+
.AddClass("bb-nt-indicator-3g", _state.NetworkType == "3g")
49+
.AddClass("bb-nt-indicator-2g", _state.NetworkType == "2g")
50+
.AddClassFromAttributes(AdditionalAttributes)
51+
.Build();
52+
53+
/// <summary>
54+
/// <inheritdoc/>
55+
/// </summary>
56+
protected override void OnInitialized()
57+
{
58+
base.OnInitialized();
59+
60+
_indicators.Add(Id);
61+
}
62+
63+
/// <summary>
64+
/// <inheritdoc/>
65+
/// </summary>
66+
protected override void OnParametersSet()
67+
{
68+
base.OnParametersSet();
69+
70+
Trigger ??= "hover focus";
71+
Title ??= Localizer["Title"];
72+
_networkTypeString = Localizer["NetworkType"];
73+
_downlinkString = Localizer["Downlink"];
74+
_rttString = Localizer["RTT"];
75+
}
76+
77+
private Task OnNetworkStateChanged(NetworkMonitorState state)
78+
{
79+
_state = state;
80+
StateHasChanged();
81+
return Task.CompletedTask;
82+
}
83+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
.bb-nt-indicator {
2+
--bb-nt-indicator-width: .5rem;
3+
--bb-nt-indicator-border-radius: 50%;
4+
--bb-nt-indicator-bg: var(--bs-secondary);
5+
background: var(--bb-nt-indicator-bg);
6+
cursor: pointer;
7+
width: var(--bb-nt-indicator-width);
8+
height: var(--bb-nt-indicator-width);
9+
border-radius: var(--bb-nt-indicator-border-radius);
10+
display: inline-block;
11+
12+
&.bb-nt-indicator-4g {
13+
background-color: var(--bs-success);
14+
}
15+
16+
&.bb-nt-indicator-3g {
17+
background-color: var(--bs-warning);
18+
}
19+
20+
&.bb-nt-indicator-2g {
21+
background-color: var(--bs-danger);
22+
}
23+
24+
&.offline {
25+
background-color: var(--bs-secondary);
26+
}
27+
}
28+
29+
[data-bs-toggle="popover"]:has(.offline) {
30+
pointer-events: none;
31+
}
32+
33+
.bb-nt-main {
34+
.bb-nt-item {
35+
display: flex;
36+
align-items: center;
37+
38+
> span {
39+
width: 120px;
40+
}
41+
42+
> div {
43+
flex: 1;
44+
min-width: 0;
45+
width: 1%;
46+
}
47+
48+
&:not(:last-child) {
49+
margin-bottom: .5rem;
50+
}
51+
}
52+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// 网络状态信息类
10+
/// </summary>
11+
public class NetworkMonitorState
12+
{
13+
/// <summary>
14+
/// Gets or sets a value indicating whether the network is online
15+
/// </summary>
16+
public bool IsOnline { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets the current network type
20+
/// </summary>
21+
public string? NetworkType { get; set; }
22+
23+
/// <summary>
24+
/// Gets or sets the downlink speed in megabits per second (Mbps).
25+
/// </summary>
26+
public double? Downlink { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets the round-trip time (RTT) in milliseconds.
30+
/// </summary>
31+
public int RTT { get; set; }
32+
}

src/BootstrapBlazor/Locales/en.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,5 +389,11 @@
389389
},
390390
"BootstrapBlazor.Components.ValidateBase": {
391391
"DefaultRequiredErrorMessage": "{0} is required."
392+
},
393+
"BootstrapBlazor.Components.NetworkMonitorIndicator": {
394+
"NTitle": "Network",
395+
"NetworkType": "NetworkType",
396+
"Downlink": "Downlink",
397+
"RTT": "RTT"
392398
}
393399
}

src/BootstrapBlazor/Locales/zh.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,5 +389,11 @@
389389
},
390390
"BootstrapBlazor.Components.ValidateBase": {
391391
"DefaultRequiredErrorMessage": "{0}是必填项"
392+
},
393+
"BootstrapBlazor.Components.NetworkMonitorIndicator": {
394+
"Title": "网络状态",
395+
"NetworkType": "网络类型",
396+
"Downlink": "下载速度",
397+
"RTT": "响应时间"
392398
}
393399
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Data from "./data.js"
2+
import EventHandler from "./event-handler.js";
3+
4+
export function init(id, options) {
5+
const { invoke, onlineStateChangedCallback, onNetworkStateChangedCallback, indicators } = options;
6+
const updateState = nt => {
7+
const { downlink, effectiveType, rtt } = nt;
8+
invoke.invokeMethodAsync(onNetworkStateChangedCallback, {
9+
downlink, networkType: effectiveType, rTT: rtt
10+
});
11+
}
12+
navigator.connection.onchange = e => {
13+
updateState(e.target);
14+
}
15+
16+
const onlineStateChanged = () => {
17+
if (Array.isArray(indicators)) {
18+
indicators.forEach(indicator => {
19+
const el = document.getElementById(indicator);
20+
if (el) {
21+
el.classList.remove('offline');
22+
}
23+
});
24+
}
25+
invoke.invokeMethodAsync(onlineStateChangedCallback, true);
26+
}
27+
const offlineStateChanged = () => {
28+
if (Array.isArray(indicators)) {
29+
indicators.forEach(indicator => {
30+
const el = document.getElementById(indicator);
31+
if (el) {
32+
el.classList.add('offline');
33+
}
34+
});
35+
}
36+
invoke.invokeMethodAsync(onlineStateChangedCallback, false);
37+
}
38+
EventHandler.on(window, 'online', onlineStateChanged);
39+
EventHandler.on(window, 'offline', offlineStateChanged);
40+
41+
Data.set(id, {
42+
onlineStateChanged,
43+
offlineStateChanged
44+
});
45+
46+
updateState(navigator.connection);
47+
}
48+
49+
export async function dispose(id) {
50+
const nt = Data.get(id);
51+
Data.remove(id);
52+
53+
if (nt) {
54+
const { onlineStateChanged, offlineStateChanged } = nt;
55+
EventHandler.off(window, 'online', onlineStateChanged);
56+
EventHandler.off(window, 'offline', offlineStateChanged);
57+
}
58+
}

src/BootstrapBlazor/wwwroot/scss/components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
@import "../../Components/Menu/Menu.razor.scss";
7070
@import "../../Components/Message/Message.razor.scss";
7171
@import "../../Components/Modal/Modal.razor.scss";
72+
@import "../../Components/NetworkMonitor/NetworkMonitorIndicator.razor.scss";
7273
@import "../../Components/Pagination/Pagination.razor.scss";
7374
@import "../../Components/Popover/Popover.razor.scss";
7475
@import "../../Components/QueryBuilder/QueryBuilder.razor.scss";

0 commit comments

Comments
 (0)