Compare commits

...

158 Commits

Author SHA1 Message Date
Argo Zhang
4fffebd10b feat(AvatarUpload): preview function may be required (#6418)
* chore: 更新 Logging 到最新版本

* doc: 更新单词拼写错误

* refactor: 增加预览功能

* feat: 增加预览实例方法

* test: 增加单元测试

* feat: bump version 9.8.1
2025-07-15 09:57:51 +08:00
Argo Zhang
c47c61beca refactor(INetworkMonitor): update return type nullable NetworkMonitorState (#6417)
* refactor: 更改返回值为可为空对象

* test: 更新单元测试
2025-07-14 04:20:36 +00:00
Argo Zhang
a1b8cbf08e feat(CherryMarkdown): add Language parameter (#6413)
* feat(CherryMarkdown): add Language parameter

* chore: bump version 9.0.3
2025-07-14 11:04:14 +08:00
Argo Zhang
9465cae520 feat(DockView): add shadow for float panel (#6415) 2025-07-14 10:44:47 +08:00
Argo Zhang
56f936e2a0 test(IThrottleDispatcherFactory): improve code coverage (#6411)
* test: 更新单元测试

* test: 更新单元测试
2025-07-13 20:08:38 +08:00
Argo Zhang
cd6c8fd1f5 refactor(INetworkMonitorService): add reuse state function (#6409)
* doc: 移除 SDK 文本

* doc: 增加网络状态菜单

* doc: 增加网络状态源码映射

* doc: 增加网络状态示例

* doc: 增加指示器示例文档

* feat: 增加 INetworkMonitorService 服务

* refactor: 实现 INetworkMonitorService 逻辑

* doc: 更新示例

* refactor: 支持离线状态

* doc: 更新文档

* refactor: 更新细节

* test: 更新单元测试

* doc: 更新文档

* feat: 增加状态复用逻辑

* refactor: 增加超时设置
2025-07-13 17:54:48 +08:00
Argo Zhang
8daec76686 feat(INetworkMonitorService): add INetworkMonitorService service (#6407)
* doc: 移除 SDK 文本

* doc: 增加网络状态菜单

* doc: 增加网络状态源码映射

* doc: 增加网络状态示例

* doc: 增加指示器示例文档

* feat: 增加 INetworkMonitorService 服务

* refactor: 实现 INetworkMonitorService 逻辑

* doc: 更新示例

* refactor: 支持离线状态

* doc: 更新文档

* refactor: 更新细节

* test: 更新单元测试
2025-07-13 17:38:55 +08:00
Argo Zhang
61ef8f7409 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: 重构代码
2025-07-13 09:34:29 +08:00
Argo Zhang
7441314758 doc(WebClientService): add IpLocator function documentation (#6403)
* doc: 增加内置定位器描述说明

* doc: 更新客户端连接信息服务文档
2025-07-12 11:17:17 +08:00
Argo Zhang
146778f0f0 doc(IIpLocatorProvider): update use locator documentation (#6401)
* refactor: 重构代码提高可读性

* doc: 更新定位器文档
2025-07-12 10:58:53 +08:00
Argo Zhang
2f59a8db16 feat(Table): disable editing in virtual scroll mode (#6399)
* refactor: 重构逻辑保证虚拟滚动模式下禁止编辑

* feat: 虚拟滚动模式下禁止编辑

* chore: bump version 9.8.1-beta06

Co-Authored-By: shakugans <81663852+shakugans@users.noreply.github.com>

---------

Co-authored-by: shakugans <81663852+shakugans@users.noreply.github.com>
2025-07-11 20:23:33 +08:00
Argo Zhang
c70eca956f fix(Table): UI not refreshed after save when set ScrollMode to Virtual (#6397)
* refactor: 改用 QueryAsync 方法

* chore: bump version 9.8.1-beta05

Co-Authored-By: shakugans <81663852+shakugans@users.noreply.github.com>

---------

Co-authored-by: shakugans <81663852+shakugans@users.noreply.github.com>
2025-07-11 19:24:42 +08:00
Argo Zhang
5d2ed311fb feat(ITcpSocketClient): add OnConnecting callback (#6392)
* doc: 增加数据处理器文档说明

* doc: 增加自动重连示例

* doc: 增加自动重连菜单

* feat: 增加 Connect 连接回调

* feat: 拆分自动重连逻辑

* feat: 远端关闭后销毁 TcpClient 实例

* doc: 增加重连示例

* test: 增加 OnConnect 回调单元测试

* refactor: 更新日志开启逻辑

* test: 增加服务器断开单元测试

* doc: 增加自动重连菜单

* doc: 增加文件映射

* doc: 更新自动重连文档

* refactor: 删除样式文件
2025-07-11 08:18:25 +00:00
Argo Zhang
39578edde6 refactor(Select): rename IsUseActiveWhenValueIsNull parameter (#6390)
* feat: 更改参数 IsUseActiveWhenValueIsNull 为 IsUseDefaultItemWhenValueIsNull

* test: 更新单元测试

* chore: bump version 9.8.1-beta04

---------

Co-Authored-By: Francisco Lima <36736375+franciscojml@users.noreply.github.com>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-07-11 13:02:16 +08:00
Argo Zhang
c8e95e9fa0 test(ITcpSocketFactory): improve code coverage (#6387)
* test: 增加连接延时

* refactor: 精简单元测试

* wip: 临时注移除单元测试

* refactor: 精简代码移除 ITcpSocketClient 实现类

* test: 更新单元测试

* test: 增加连接取消单元测试

* revert: 撤销单元测试

* test: 更新单元测试
2025-07-11 12:14:37 +08:00
Symin
b0245db885 feat(TreeView): scripts to implement drag and drop function (#6380)
* feat:DraggableTree

* fix:修正格式错误

* 添加TreeView拖拽功能测试

* Enhance TreeView drag-and-drop and add comprehensive tests

Improved TreeView drag-and-drop logic to handle moving nodes without parents and updated node removal accordingly. Added and expanded unit tests to cover moving nodes as last child, first child, and as siblings below, ensuring correct parent/child relationships and node ordering. Also added clarifying comments in TreeViewRow for drag-and-drop preview UI.

* Add draggable TreeView demo with drop restrictions

Introduced a new demo block showcasing draggable TreeView nodes, including logic to restrict certain drag-and-drop operations. Added localized strings for the new demo in both English and Chinese. Updated the backend to provide a sample data set and drop event handler enforcing the restrictions.

* Refactor TreeView drag-and-drop and improve tests

Refactored TreeView and TreeViewRow to require OnItemDrop and remove null checks, simplifying drag-and-drop logic. Updated tests to cover cases where OnItemDrop is not set and to verify drag-and-drop visual feedback.

* refactor: 代码格式化

* refactor: 代码规范化

* refactor: 提高可读性

* refactor: 代码格式化

* refactor: 代码重构调整位置

* refactor: 更改参数名称为 AllowDrag

* revert: 重构 Row 对拖动支持

* refactor: 移除预留占位节点

* revert: 撤销更改

* revert: 撤销更改

* revert: 撤销 TreeDropType 类型

* doc: 更新文档

* test: 撤销单元测试更改

* refactor: 移除命名空间

* revert: 撤销 AllowDrag 参数

* revert: 撤销 AllowDrag 参数

* style: 调整样式

* style: 整理样式

* feat: JS 实现客户端拖动动画样式

* style: 精简样式

* feat: 增加 TriggerDragEnd 逻辑

* feat: JavaScript 实现拖动逻辑

* feat: 实现 OnDragItemEndAsync 逻辑

* doc: 更新示例

* feat: 增加重置客户端 DOM 逻辑

* doc: 更新示例

* doc: 撤销示例注释

* doc: 增加注释

* test: 更新单元测试

* fix: 增加逻辑保护

* doc: 更新文档

* chore: bump version 9.8.1-beta03

---------

Co-Authored-By: Symin <syminomega@outlook.com>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-07-10 19:30:31 +08:00
Argo Zhang
694361509d doc(Table): remove SortList sample (#6384) 2025-07-10 12:07:26 +08:00
Argo Zhang
b84dd4cda9 doc(Uploader): add UseUploaderStaticFiles extension method (#6382)
* doc: 更新资源文件

* doc(Uploader): add UseUploaderStaticFiles extension method
2025-07-09 14:31:15 +08:00
Symin
b442af2a57 feat(TreeView): support Darg and Drop function (#6121)
* feat:DraggableTree

* fix:修正格式错误

* 添加TreeView拖拽功能测试

* Enhance TreeView drag-and-drop and add comprehensive tests

Improved TreeView drag-and-drop logic to handle moving nodes without parents and updated node removal accordingly. Added and expanded unit tests to cover moving nodes as last child, first child, and as siblings below, ensuring correct parent/child relationships and node ordering. Also added clarifying comments in TreeViewRow for drag-and-drop preview UI.

* Add draggable TreeView demo with drop restrictions

Introduced a new demo block showcasing draggable TreeView nodes, including logic to restrict certain drag-and-drop operations. Added localized strings for the new demo in both English and Chinese. Updated the backend to provide a sample data set and drop event handler enforcing the restrictions.

* Refactor TreeView drag-and-drop and improve tests

Refactored TreeView and TreeViewRow to require OnItemDrop and remove null checks, simplifying drag-and-drop logic. Updated tests to cover cases where OnItemDrop is not set and to verify drag-and-drop visual feedback.

* refactor: 代码格式化

* refactor: 代码规范化

* refactor: 提高可读性

* refactor: 代码格式化

* Revert "Enhance TreeView drag-and-drop and add comprehensive tests"

This reverts commit 3c8f5a153f.

* Revert "Merge branch 'main' into main"

This reverts commit 7ea39da6e7, reversing
changes made to 3c8f5a153f.

* Revert "Add draggable TreeView demo with drop restrictions"

This reverts commit 906f2aa5a6.

# Conflicts:
#	src/BootstrapBlazor.Server/Components/Samples/TreeViews.razor
#	src/BootstrapBlazor.Server/Components/Samples/TreeViews.razor.cs

* Revert "Refactor TreeView drag-and-drop and improve tests"

This reverts commit 81235e91b5.

# Conflicts:
#	src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs

* Revert "添加TreeView拖拽功能测试"

This reverts commit de2be13a62.

* Revert "feat:DraggableTree"

This reverts commit 8ae88250d1.

# Conflicts:
#	src/BootstrapBlazor.Server/Components/Samples/TreeViews.razor.cs
#	src/BootstrapBlazor/Components/TreeView/TreeView.razor
#	src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs

* revert: 撤销更改

* revert: 撤销更改

* revert: 撤销更改

* revert: 撤销更改

---------

Co-Authored-By: Symin <syminomega@outlook.com>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-07-09 10:02:07 +08:00
Argo Zhang
1ae85795a8 feat(ThrottleDispatcher): refine code improve readability (#6379)
* refactor: 精简代码逻辑

* test: 更新单元测试

* test: 精简命名空间
2025-07-08 16:01:40 +08:00
Argo Zhang
e3d081edd9 feat(ITcpSocketClient): add concurrency connect logic (#6377)
* feat: 增加锁控制并发

* refactor: 合并重连方法提高可读性

* feat: 使用 SemaphoreSlim 信号量控制重连并发逻辑

* test: 增加重连单元测试
2025-07-08 15:51:43 +08:00
Argo Zhang
84764e3a02 feat(ITcpSocketClient): add AutoConnect parameter (#6372)
* feat: 增加自动重连配置

* refactor: 使用取消令牌

* refactor: 更改参数为 IsAutoReconnect

* test: 更新单元测试

* feat: 断线时也需要触发 ReceivedCallBack 回调

* refactor: 提高性能避免线程切换

* feat: 连接方法增加保护

* refactor: 增加 Reconnect 方法

* test: 增加重连单元测试

* feat: 增加发送接收时断线重连机制

* test: 增加发送接收时断开重连单元测试
2025-07-07 19:11:15 +08:00
Argo Zhang
7e2f1f1567 feat(SocketClientOptions): add EnableLog parameter (#6371)
* feat(SocketClientOptions): add EnableLog parameter

* test: 增加单元测试
2025-07-07 14:25:11 +08:00
Argo Zhang
786b2c15aa fix(MultiselectGeneric): two-way binding not working (#6370)
* refactor: 移除 EditSubmitKeyString 字符串

* refactor: 格式化代码

* fix: 增加选中值与 Value 同步机制

* chore: bump version 9.8.1-beta02
2025-07-07 12:37:31 +08:00
Argo Zhang
20a64443f0 chore(DockView): bump version 9.1.16 (#6369) 2025-07-06 14:30:14 +08:00
Argo Zhang
fb36b0468a doc(DataPackageAdapter): update ReceiveAsync comment (#6361)
* refactor: 移除 ReceiveAsync 方法

* Revert "refactor: 移除 ReceiveAsync 方法"

This reverts commit 6cba73eca3.

* refactor: 调整目录结构

* test: 增加单元测试注释

* revert: 撤销接收方法

* refactor: 代码重构

* test: 更新单元测试

* doc: 增加手动接收数据示例

* doc: 更改关键字

* doc: 增加一发一收的模拟服务

* doc: 更新手动接收示例

* doc: 更新说明文档

* doc: 更新示例

* doc: 更新图标

* test: 更新单元测试

* test: 增加连接超时单元测试

* doc: 更新日志格式
2025-07-06 11:16:50 +08:00
Argo Zhang
c3caf7d7c7 fix(Progress): culture causes decimal points to be changed to commas (#6362)
* refactor: 格式化增加文化信息

* doc: 更新示例

* chore: bump version 9.8.1-beta01
2025-07-05 10:44:14 +08:00
Argo Zhang
48f8936027 feat(DirectoryInfoExtensions): add recursive parameter (#6358)
* refactor: 增加文件夹拷贝扩展方法

* test: 更新单元测试

* test: 更新单元测试
2025-07-04 14:09:59 +08:00
Argo Zhang
6aa2bd6e7b test(ITcpSocketClient): improve code coverage (#6356)
* test: 重构代码提高覆盖率

* refactor: 更改接口方法名称

* refactor: 增加 SetDataPackageAdapter 扩展方法

* refactor: 精简代码

* feat: 增加 SetDataPackageAdapter 方法

* test: 更新单元测试

* test: 增加单元测试

* doc: 增加移除的路由
2025-07-04 08:27:24 +08:00
yacper
1afd090438 fix(Search): DisplayText needs to be shown on first load (#6351)
* fix bug #6350

* refactor: 重构 OnSearch 参数更改为可为空

---------
Co-Authored-By: yacper <668255+yacper@users.noreply.github.com>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-07-03 15:15:46 +08:00
Argo Zhang
e755e92239 test(DataPackageAdapter): add unit test (#6353)
* test: 更新发送失败单元测试

* test: 更新单元测试

* test: 修复 FixLengthDataPackageHandler 单元测试

* test: 更新粘包通讯单元测试

* test: 更新终止符数据处理器单元测试

* refactor: 移除 SendAsync 方法

* test: 精简单元测试

* test: 补充单元测试提高覆盖率

* refactor: 更改代码提高代码覆盖率
2025-07-03 14:35:16 +08:00
Argo Zhang
ad6f782c58 doc(Coms): remove Coms page (#6349)
* doc: 移除组件总览页面

* doc: 移除相关组件图片

* refactor: 快速上手增加组件总数信息

* doc: 移除组件菜单

* doc: 全屏按钮 Tooltip 增加本地化

* doc: 更改布局模拟器返回链接
2025-07-03 12:29:26 +08:00
Argo Zhang
1dd1e19644 doc(IDataPackageAdapter): add DataPackageAdapter sample code (#6345)
* refactor: 移除数据处理

* refactor: 增加数据适配器接口

* refactor: 重构 ReceiveAsync 方法

* refactor: 更改为实类

* feat: 增加模拟分包逻辑

* doc: 实现接收逻辑

* doc: 更新示例

* doc: 更新示例代码
2025-07-02 19:40:43 +08:00
Mydashixiong
37db88ab9f feat(DateTimePicker): add Color parameter (#6342)
* feat(DateTimePicker):DateTimePicker增加Color样式属性

DateTimePicker增加Color样式属性,默认Color.None

* test: 增加单元测试

---------

Co-authored-by: Argo Zhang <argo@live.ca>
2025-07-02 15:11:37 +08:00
Argo Zhang
889410f642 feat(FullScreenButton): adjust the gap (#6341)
* refactor: 调整 Tab 工具栏全屏按钮样式

* style: 增加等宽样式
2025-07-02 00:58:46 +00:00
Argo Zhang
a408e98676 fix(Dialog): not trigger OnCloseAsync multiple dialog (#6339)
* fix: 修复未触发 OnCloseAsync 回调问题

* chore: bump version 9.8.0
2025-07-01 05:20:58 +00:00
Argo Zhang
f8d6acccb7 fix(LookupFilter): use first item as default value when value is null (#6338)
* refactor: 调整参数位置

* Revert "fix(EnumFilter): incorrect selected value (#6334)"

This reverts commit daf9af9b38.

* Revert "fix(BoolFilter): set default value is empty"

This reverts commit 26e33c3876.

* Reapply "fix(BoolFilter): set default value is empty"

This reverts commit 4509634ed2.

* Reapply "fix(EnumFilter): incorrect selected value (#6334)"

This reverts commit 3f3e12513f.

* refactor: 增加中文注释

* refactor: 支持默认值

* test: 增加单元测试
2025-07-01 13:17:37 +08:00
Argo Zhang
1bcf624cc9 fix(BoolFilter): set default value is empty (#6336) 2025-07-01 02:15:34 +00:00
Argo Zhang
daf9af9b38 fix(EnumFilter): incorrect selected value (#6334) 2025-07-01 09:58:56 +08:00
Argo Zhang
8775218a3b doc(ITcpSocketClient): add DataHandler sample code (#6331)
* doc: 增加接收数据示例文档说明

* refactor: 重构代码

* doc: 更新配置类说明

* doc: 增加数据适配器文档

* refactor: 更新接收数据示例代码

* doc: 增加数据适配器菜单

* doc: 更新示例

* doc: 增加后台服务
2025-06-30 20:29:12 +08:00
jin momiji
ef8270975d feat(Step): add CurrentStepIndex property (#6316)
* feat(Step): add 'CurrentStepIndex' to obtain the real-time value of _currentStepIndex

* test: 增加单元测试

---------

Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-29 09:07:40 +08:00
Argo Zhang
618b956c9c feat(ISocketClientProvider): add ISocketClientProvider service (#6329)
* refactor: 重构实现逻辑增加 ISocketClientProvider 服务

* wip: 移除注释

* test: 增加单元测试

* refactor: 精简代码

* test: 测试连接超时
2025-06-28 23:32:54 +08:00
Argo Zhang
f43449cffb feat(TcpSocketClientBase): update generic class (#6327)
* feat: 增加 SocketClientOptions 配置类

* feat: TcpSocketClientBase 更改为泛型类提高复用能力

* test: 更新单元测试

* refactor: 增加 SocketClient 基类

* test: 增加 空 Logger 单元测试

* test: 增加 DefaultSocketClient 单元测试

* test: 增加 DefaultSocketClient 单元测试

* test: 移除命名空间

* test: 更新单元测试
2025-06-28 18:25:43 +08:00
Argo Zhang
5d1457b33f doc(ITcpSocketClient): add Receive documentation (#6322)
* refactor: 精简代码提高可读性

* refactor: 更新连接状态返回值

* feat: 更新读取数据长度

* feat: 更新远端节点数据

* refactor: 更新自动接收逻辑增加已连接逻辑判断

* chore: bump version 9.8.0-beta07

* doc: 更新文档

* feat: 增加模拟 Socket 服务端服务

* doc: 更新文档

* doc: 增加 Socket 实战

* refactor: 代码格式化

* refactor: 更改 Func 类型支持异步

* refactor: 更改为私有属性

* doc: 更新示例

* test: 更新单元测试

* refactor: 重构 Socket Server 模拟器

* test: 更新单元测试

* doc: 完善示例

* refactor: 微调日志逻辑

* refactor: 增加异常捕获

* doc: 更新示例

* doc: 增加接收数据示例文档
2025-06-28 11:02:38 +08:00
Argo Zhang
2abfb68a85 feat(Console): update OnClear support async (#6325)
* refactor: 代码格式化

* refactor: 更改 Func 类型支持异步

* refactor: 更改为私有属性

* doc: 更新示例

* test: 更新单元测试
2025-06-28 08:51:23 +08:00
Argo Zhang
a8c8dc7339 feat(TcpSocketClientBase): add TcpSocketClientBase class (#6320)
* refactor: 更改为异步销毁接口

* feat: 增加基类

* refactor: 更改 Close 为 CloseAsync

* test: 更新单元测试

* refactor: 代码重构精简逻辑

* refactor: 精简代码逻辑
2025-06-27 09:11:18 +08:00
Argo Zhang
45c99798c7 feat(ITcpSocketClient): add Timeout parameter (#6318)
* feat: 增加 ReceiveAsync 方法

* doc: 增加默认值说明

* feat: 增加是否自动接收参数

* feat: 增加超时设置

* feat: 根据接口增加新设置

* feat: 增加接收数据实现

* feat: 缓存更改为 64K

* feat: 增加连接超时功能

* test: 增加连接超时单元测试

* feat: 实现发送超时逻辑

* feat: 增加接收数据逻辑

* test: 增加单元测试

* test: 增加重连功能

* test: 更新单元测试

* feat: LocalEndPoint 更改未可为空
2025-06-27 08:33:35 +08:00
Argo Zhang
342ff517fc pef(Upload): add bufferSize parameter for improve save file performance (#6314)
* refactor: 精简代码

* feat: 增加 BufferSize 参数

* doc: 更新示例文档

* chore: bump version 9.8.0-beta06
2025-06-26 10:48:49 +08:00
Argo Zhang
5779d48485 doc(OfficeViewer): update nuget download link (#6312) 2025-06-26 10:35:47 +08:00
Argo Zhang
db3423995b refactor(Tab): remove tabs-body-content-wrap html node (#6310)
* refactor: 移除 tabs-body-content-wrap 恢复老版本结构

* revert: 恢复单元测试

* chore: bump version 9.8.0-beta05

Co-Authored-By: Kim Kokholm <kokholm@gmail.com>

---------

Co-authored-by: Kim Kokholm <kokholm@gmail.com>
2025-06-26 08:49:37 +08:00
Argo Zhang
560bb21327 feat(Table): add AutoSearchOnInput parameter (#6306)
* feat: add AutoSearchOnInput parameter

* feat: 更新代码

* test: 更新单元测试

* chore: bump version 9.8.0-beta04

Co-Authored-By: Jaders77 <jaders77@hotmail.fr>

---------

Co-authored-by: Jaders77 <jaders77@hotmail.fr>
2025-06-25 14:48:47 +08:00
Argo Zhang
29d02df022 feat(Select): add IsUseActiveWhenValueIsNull parameter (#6304)
* feat: 增加 IsUseActiveWhenValueIsNull 参数

* test: 增加单元测试
2025-06-25 14:08:31 +08:00
Argo Zhang
10fc77cce5 feat(Table): add NotSupportedColumnFilterMessage parameter (#6302)
* feat(Table): 增加  NotSupportedMessage 参数

* doc: 增加文档地址提示

* test: 更新单元测试

* refactor: 更改参数名称

* doc: 增加 html 支持

* refactor: 增加标签
2025-06-25 10:48:01 +08:00
Argo Zhang
2dc0cfffef fix(Table): unsupported filter data type cause filter icon misalignment (#6298)
* wip: 临时提交

* chore: bump version 9.8.0-beta03

Co-Authored-By: shakugans <81663852+shakugans@users.noreply.github.com>

* refactor: 重构 NotSupportFilter 组件

* refactor: 重构 TableColumnFilter 组件

* refactor: 增加搜索重置按钮样式

* test: 更新单元测试

* test: 更新单元测试

* refactor: 使用基类本地化服务

* test: 补充单元测试

* test: 更新单元测试

---------

Co-authored-by: shakugans <81663852+shakugans@users.noreply.github.com>
2025-06-25 10:11:46 +08:00
Argo Zhang
79b91d90a9 feat(Table): add default css for Reset/Search button (#6300)
* refactor: 增加搜索重置按钮样式

* test: 更新单元测试
2025-06-25 09:29:10 +08:00
Argo Zhang
de03bba9a0 feat(Table): update TableResetSearchButtonIcon icon (#6297)
* feat: 更改重置搜索按钮图标

* feat: 更改重置搜索条件图标
2025-06-25 08:40:52 +08:00
Argo Zhang
72186d342d fix(Tab): should not rerender tabitem after close (#6294)
* refactor: 更改 Key 为 Guid

* refactor: 增加 wrap 样式

* refactor: 使用 Find 方法

* fix: 修复关闭标签页导致后续标签页重绘问题

* chore: bump version 9.8.0-beta02

* chore: bump version 9.8.0-beta03

Co-Authored-By: Ockyd007 <71997133+ockyd007@users.noreply.github.com>

* Revert "chore: bump version 9.8.0-beta03"

This reverts commit 25b6158e8d.

---------

Co-authored-by: Ockyd007 <71997133+ockyd007@users.noreply.github.com>
2025-06-24 18:13:49 +08:00
Argo Zhang
efa9089f21 feat(Upload): support drag/drop upload function (#6290)
* doc: 代码格式化

* doc: 代码格式化

* refactor: 代码格式化

* feat: 增加拖动支持

* feat: 增加拖动支持

* refactor: 头像上传框精简代码

* refactor: 精简 dom 结构

* refactor: 精简 CardUpload 结构

* chore: bump version 9.8.0-beta01

Co-Authored-By: movieliang <16262928+movieliang@users.noreply.github.com>

---------

Co-authored-by: movieliang <16262928+movieliang@users.noreply.github.com>
2025-06-24 05:02:47 +00:00
Argo Zhang
3a3b8cc5ea feat(OfficeViewer): add OfficeViewer component (#6287)
* wip: 临时提交

* doc: 增加示例文件

* doc: 增加菜单

* doc: 增加 Office 文档预览组件资源文件

* doc: 增加源码映射

* doc: 更新示例文件减小文件体积

* doc: 更正预览文档链接

* doc: 依赖更改为包

* chore: bump version 9.7.4

* chore: 更改组件名称
2025-06-24 11:18:58 +08:00
Mydashixiong
547980d7a9 refactor(ImageViewer): image element is not closed (#6285)
* ImagePreviewer 修复html标签 img

修复img没有结束符

* chore: 代码格式化

Co-Authored-By: Mydashixiong <136679228+Mydashixiong@users.noreply.github.com>

---------

Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-24 11:17:49 +08:00
Argo Zhang
ed4a2a4043 feat(OtpInput ): support responsive layout (#6284)
* refactor: 移动变量节点位置方便覆盖

* refactor: 更改样式名称纠正单词拼写错误

* doc: 更新样式
2025-06-24 09:11:45 +08:00
Argo Zhang
d4d76e49ad feat(ITcpSocketClient): add TouchSocket extensions (#6268)
* chore: 更新解决方案增加扩展包

* feat: 增加 ReceivedCallBack 回调方法

* test: 增加单元测试

* doc: 更新示例文档

* test: 增加 TouchSocket 单元测试

实现功能后单元测试应该可以通过的

* doc: 更新粘包分包文档

* feat: 增加 SendAsync 扩展方法

* test: 增加单元测试

* feat(tcp): 替换同步关闭方法为异步方法

将 `Close` 方法替换为异步的 `CloseAsync` 方法,以支持异步关闭操作并释放资源。`CloseAsync` 方法接受一个消息和一个可选的取消令牌作为参数,并返回一个 `ValueTask<bool>`,指示关闭操作的结果。原有的 `Close` 方法被移除,相关的资源释放逻辑被整合到新的异步方法中。

* refactor(tests): 使用异步方法关闭连接

在 TcpSocketFactoryTest.cs 和 TouchSocketTest.cs 中,将连接关闭逻辑从 client.Close() 更改为 await client.CloseAsync(string.Empty),以支持异步关闭连接并提高性能

* doc: 更新数据处理器相关文档

* refactor: 增加 ConnectAsync 扩展方法

* refactor: 更改关闭方法为同步方法

* test: 更新单元测试

* doc: 增加数据适配器文档

* doc: 增加新服务标记

* refactor: 更新 ITcpSocketFactory 接口定义

* feat: 增加 GetOrCreate 扩展方法

* test: 更新单元测试

* test: 增加单元测试

* refactor: 更改 GetOrCreate 签名

* test: 更新单元测试

* refactor: 根据最新设计重构 TouchSocket 实现

* refactor: 整理项目文件

* wip: 临时移除稍后再开分支合并到另外分支上

* chore: bump version 9.7.4-beta08

Co-Authored-By: 若汝棋茗 <76547834+rrqm@users.noreply.github.com>

---------
Co-authored-by: 若汝棋茗 <76547834+rrqm@users.noreply.github.com>
2025-06-23 15:00:57 +08:00
Argo Zhang
0df17f52cf feat(ITcpSocketFactory): redesign GetOrCreate method (#6282)
* refactor: 更改 GetOrCreate 签名

* test: 更新单元测试
2025-06-23 14:07:03 +08:00
Argo Zhang
7826f2281d chore(TableExport): bump TableExport version 9.2.6 (#6280) 2025-06-23 13:36:50 +08:00
Argo Zhang
556fcf3abe refactor(ITcpSocketFactory): add GetOrCreate extension method (#6277)
* refactor: 更新 ITcpSocketFactory 接口定义

* feat: 增加 GetOrCreate 扩展方法

* test: 更新单元测试

# Conflicts:
#	test/UnitTest/Services/TcpSocketFactoryTest.cs

* test: 增加单元测试
2025-06-22 19:39:04 +08:00
Argo Zhang
0caed5aa3a doc(ITcpSocketFactory): add DataAdapter documentation (#6275)
* doc: 增加数据适配器文档

* doc: 增加新服务标记
2025-06-22 19:34:26 +08:00
Argo Zhang
4fdc480909 feat(ITcpSocketClient): add ConnectAsync extension method (#6273)
* doc: 更新数据处理器相关文档

* refactor: 增加 ConnectAsync 扩展方法

* feat: 增加 ConvertToIPAddress 扩展方法

* feat: 更改 DefaultTcpSocketClient 构造函数

* test: 纠正冗余代码

* test: 更新单元测试
2025-06-21 20:38:40 +08:00
Argo Zhang
ea1c142496 feat(ITcpSocketClient): add SendAsync extensions method (#6269)
* doc: 更新粘包分包文档

* feat: 增加 SendAsync 扩展方法

* test: 增加单元测试
2025-06-20 12:55:13 +08:00
Argo Zhang
4ce223e19b feat(ITcpSocketClient): add ReceivedCallback property (#6266)
* feat: 增加 ReceivedCallBack 回调方法

* test: 增加单元测试

* doc: 更新示例文档
2025-06-20 12:24:49 +08:00
Argo Zhang
31a2a958b6 feat(ITcpSocketClient): use ReadOnlyMemory improve performance (#6264)
* refactor: 使用 ReadOnlyMemory 只读类提供性能

* test: 更新单元测试
2025-06-20 11:15:15 +08:00
Argo Zhang
c2343f5e38 feat(FixLengthDataPackageHandler): support multiple sticky package (#6262)
* feat: 支持多次粘包处理功能

* test: 增加单元测试

* refactor: 代码重构提高可读性

* refactor: 精简代码提高可读性
2025-06-20 10:20:35 +08:00
Argo Zhang
62ed6e6ebc feat(DelimiterDataPackageHandler): add DelimiterDataPackageHandler class (#6260)
* feat: 增加分隔符数据处理器

* refactor: 更新分隔符数据处理器

* test: 更新单元测试

* test: 增加单元测试

* refactor: 支持多分隔符

* test: 更新单元测试
2025-06-19 19:12:44 +08:00
Argo Zhang
9bd5946050 perf(TreeView): use Remove method improve performance (#6258)
* perf: 优化性能

* chore: bump version 9.7.4-bta09

Co-Authored-By: Diego2098 <82756760+kimdiego2098@users.noreply.github.com>

---------

Co-authored-by: Diego2098 <82756760+kimdiego2098@users.noreply.github.com>
2025-06-19 16:26:53 +08:00
Argo Zhang
e3971e7af3 doc(ITcpSocketFactory): add ITcpSocketFactory documentation (#6256)
* doc: 增加 Socket 示例

* chore: 增加 Socket 服务菜单

* doc: 增加源码映射文件
2025-06-19 15:35:51 +08:00
Argo Zhang
07d63755b1 feat(ITcpSocketFactory): add ITcpSocketFactory service (#6254)
* feat: 增加扩展方法判断当前环境是否为 IsWasm

* feat: 增加 ITcpSocketFactory 服务

* refactor: 更新 ConnectAsync 接口

* test: 更新单元测试

* refactor: 增加 ITcpSocketClient 服务

* test: 增加单元测试

* refactor: 重构日志实例逻辑

* refactor: 精简代码

* refactor: 增加取消记录日志逻辑

* refactor: 增加 Close 方法

* test: 增加实例单元测试

* feat: 增加 IDataPackageAdapter 接口

* refactor: 增加设置本地节点逻辑

* refactor: 增加数据处理器功能

* refactor: 增加 virtual 关键字

* test: 增加单元测试

* test: 更新单元测试

* feat: 增加数据处理类

* refactor: 增加连接后自动接收逻辑

* test: 增加单元测试

* refactor: 增加接收任务取消逻辑

* refactor: 精简代码逻辑

* test: 更新单元测试

* refactor: 实现拆包粘包处理逻辑

* refactor: 优化代码 Logger 不为空

* test: 更新单元测试

* test: 增加 SendAsync 单元测试

* test: 增加 Factory 单元测试

* test: 精简单元测试

* test: 增加 IsWasm 单元测试

* refactor: 接收方法内异常改为日志

* refactor: 防止缓存区被释放

* refactor: 精简代码提高可读性

* Revert "refactor: 接收方法内异常改为日志"

This reverts commit 44e4bd3768.

* refactor: 更正方法名称为 HandleStickyPackage

* refactor: 更改申请缓存区代码

* refactor: 重构拆包方法名称
2025-06-19 14:43:34 +08:00
jin momiji
48a7777fd9 feat(Table): add ShowColorWhenToolbarButtonsCollapsed parameter (#6251)
* feat(Table): add 'ShowColorWhenToolbarButtonsCollapsed' attribute in 'table' component, add some css style

* chore: bump version 9.7.4-beta08

Co-Authored-By: jin momiji <105467851+momijijin@users.noreply.github.com>

* test: 增加单元测试

---------

Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-18 18:37:48 +08:00
yacper
1469626de4 fix(Table): update table column order in OnColumnCreating not work (#6242)
* bugfix: Table控件中,如果OnColumnCreating回调中修改了Column项的Order,并不会起作用

* revert: 撤销更改

* refactor: 代码格式化

* refactor: 私有变量内部化提高可读性

* refactor: 更改为结构体

* refactor: 使用中间变量传递列集合

* fix: 修复排序逻辑混乱问题

* chore: bump version 9.7.4-beta07

Co-Authored-By: yacper <668255+yacper@users.noreply.github.com>

---------

Co-authored-by: Argo Zhang <argo@live.ca>
Co-authored-by: yacper <668255+yacper@users.noreply.github.com>
2025-06-18 09:39:07 +08:00
jin momiji
e6c8012704 feat(IpAddress): support ArrowLeft/Right key (#6244)
* feat(IpAddress): add keydownEvent.code ['Space','ArrowLeft','ArrowRight','Backspace'] expansion operations

* fix(IpAddress): Fix the issue where pressing 'Backspace' would delete an extra character when switching to the previous cell.

* fix(IpAddress): Re-write the validation for cells that have content consisting solely of blank spaces.

* refactor: 撤销更改

* refactor: 清除空格

* refactor: 删除中文注释

* refactor: 更新选中部分数字逻辑

* feat: 增加获得焦点后全选逻辑

---------
Co-Authored-By: jin momiji <105467851+momijijin@users.noreply.github.com>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-17 16:19:44 +08:00
Argo Zhang
863a2089a3 doc(Select): update ShowSwal sample code (#6248)
* refactor: 更新示例

* doc: 更新文档
2025-06-17 15:23:14 +08:00
Argo Zhang
b7e67a8690 feat(ErrorLogger): add EnableILogger parameter (#6246)
* feat: 增加 EnableErrorLoggerILogger 配置项

* feat: 增加 EnableILogger 参数

* feat: 增加 EnableILogger 参数

* feat: 增加 EnableErrorLoggerILogger 参数

* test: 增加单元测试
2025-06-17 14:22:06 +08:00
Argo Zhang
d97672f33d feat(DockView): add overflow style for floating window (#6241) 2025-06-16 18:07:06 +08:00
Diego2098
bed5200193 perf(Table): use ReferenceEqualityComparer instance (#6238)
* 缓存字典设置引用比较器

* 添加比较器

* chore: bump version 9.7.4-beta06

Co-Authored-By: Diego2098 <82756760+kimdiego2098@users.noreply.github.com>

---------

Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-16 17:22:46 +08:00
Argo Zhang
21048883aa feat(cherry-markdown): add IsSupportMath parameter (#6237)
* doc: 增加 IsSupportMath 参数

* chore: bump version 9.0.1

Co-Authored-By: j4587698 <24642446+j4587698@users.noreply.github.com>

---------

Co-authored-by: j4587698 <24642446+j4587698@users.noreply.github.com>
2025-06-16 13:20:35 +08:00
Argo Zhang
9b51e50374 fix(Select): trigger OnSelectedItemsChanged after clear (#6234)
* feat: trigger OnSelectedItemChanged callback

* feat: 增加 OnSelectedItemsChanged 回调逻辑

* refactor: 触发 OnSelectedItemChanged 回调

* refactor: 增加 OnSelectedItemChanged 回调方法

* test: 更新单元测试

* chore: bump version 9.7.4-beta05
2025-06-15 09:56:57 +08:00
Argo Zhang
6850f1a89b feat(Light): rename class name light to bb-light (#6232)
* refactor: 更改样式类名为 bb-light 防止冲突

* chore: bump version 9.7.4-beta04

* test: 更新单元测试
2025-06-14 18:16:35 +08:00
Argo Zhang
c926f58725 feat(Vditor): add Vditor component (#6228)
* doc: 更新示例说明文档

* doc: 增加示例文档

* test: 增加单元测试

* doc: 增加菜单

* doc: 增加源码映射

* doc: 增加方法示例

* refactor: 更新 Pre 样式

* doc: 更新 Vditor 组件示例

* chore: 增加 Vditor 依赖
2025-06-14 17:26:46 +08:00
Argo Zhang
a3f422712e doc(Pre): update Pre style (#6230) 2025-06-14 17:25:45 +08:00
Argo Zhang
98384ca8de chore(meta): update keywork for google seo (#6226) 2025-06-13 14:13:17 +08:00
Argo Zhang
6f84d88f76 feat(FlipClock): add ShowYear parameter (#6224)
* feat: 增加 ShowYear 参数

* doc: 更新示例

* test: 更新单元测试

* refactor: 重构脚本

* doc: 更新示例

* refactor: 精简代码减少依赖项
2025-06-13 13:34:38 +08:00
Argo Zhang
48289a4f4e feat(FlipClock): add ShowMonth parameter (#6222)
* feat: 增加 ShowMonth 参数

* doc: 增加示例

* test: 增加单元测试

* refactor: 更新变量名称
2025-06-13 11:48:01 +08:00
Mydashixiong
1c0abc46ab feat(FlipClock): add ShowDay parameter (#6218)
* 修改FlipClock

增加ShowDay参数

* refactor: 代码格式化

* doc: 增加 ShowDay 示例

* refactor: 精简代码

* refactor: 增加 Day 逻辑

* feat: 增加容器 overflow 样式防止溢出

* doc: 增加 ShowDay 示例

* doc: 参数文档本地化

* test: 增加单元测试

* chore: bump version 9.7.4-beta03

Co-Authored-By: Mydashixiong <136679228+Mydashixiong@users.noreply.github.com>

---------

Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-13 11:22:33 +08:00
Ralf Baussenwein
280219afb9 doc(Localization): update german translations (#6217)
* Update german translations

* chore: bump version 9.7.4-beta02

Co-Authored-By: Ralf Baussenwein <14016098+rabauss@users.noreply.github.com>

---------

Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-13 09:43:46 +08:00
Argo Zhang
c0e88780bd fix(Tab): add OrdinalIgnoreCase parameter compatible locales (#6215)
* refactor: 移除 RouteTabFactory 老代码

* fix: 增加忽略文化信息代码

* chore: bump version 9.7.3-beta01

* refactor: 提升性能
2025-06-12 08:47:51 +08:00
Argo Zhang
f120ef67ad doc(Bootstrap): remove dynamic load bootstrap doc (#6214) 2025-06-12 08:18:18 +08:00
Argo Zhang
e4203dd7a2 fix(OtpInput): should show mask char in readonly/disabled status (#6212)
* refactor: 重构 DOM 使用 input 元素

* chore: 移除 console.log 语句

* refactor: 增加只读禁用逻辑

* style: 精简样式

* refactor: 格式化逻辑

* test: 更新单元测试
2025-06-11 19:19:45 +08:00
Argo Zhang
80ef545dce fix(ErrorLogger): throw exception on wasm mode (#6210)
* fix: 修复 Wasm 下无法使用 Host 问题

* chore: bump version 9.7.3

* refactor: 注入 MockHostEnvironment 实现类

* Revert "fix: 修复 Wasm 下无法使用 Host 问题"

This reverts commit a54d3d4a55.

* feat: 增加调试环境支持

* refactor: 增加排除标签
2025-06-11 17:42:16 +08:00
Argo Zhang
d5cd54c2ce doc(IpLocator): add WebClientOptions usage documentation (#6208) 2025-06-11 14:50:18 +08:00
Argo Zhang
f94e3c0491 revert(Bootstrap): revert dynamic load bootstrap module (#6205)
* chore: 更新自动化脚本

* chore: bump version 9.7.2

* Revert "chore: 更新自动化脚本"

This reverts commit 697b1d28e3.

* chore: 更新脚本

* Revert "feat(Bootstrap): support dynamic load bootstrap javascript (#6201)"

This reverts commit 5cdf89afeb.

* revert: 撤销更改
2025-06-11 13:06:20 +08:00
Argo Zhang
a828f38b7f feat(Print): support cavas html element (#6203)
* refactor: 移除 console.log 语句

* refactor: 兼容 canvas 元素

* chore: bump version 9.7.1
2025-06-11 10:36:09 +08:00
Argo Zhang
5cdf89afeb feat(Bootstrap): support dynamic load bootstrap javascript (#6201)
* chore: 增加模块动态导入

* refactor: 移除脚本显式加载
2025-06-11 09:25:22 +08:00
Argo Zhang
75553b846c feat(Select): redesign OnBeforeSelectedItemChange logic (#6199)
* refactor: 重构 ShowSwal 逻辑

* refactor: 更改 ShowSwal 默认值
2025-06-10 15:34:58 +08:00
Argo Zhang
7ba4e74ac7 feat(BootstrapServiceBase): update exception message (#6197)
* refactor: 更新变量名称

* refactor: 变量规范化消除警告信息

* doc: 更新提示信息
2025-06-10 14:44:19 +08:00
Argo Zhang
6549430f2e doc(BootstrapBlazorRoot): update usage documentation (#6195)
* doc(Download): update download component documentation

* refactor: 增加 RootTemplate 细化 Root 组件使用方法
2025-06-10 13:20:04 +08:00
Argo Zhang
684400448c doc(Upload): update drop upload component documetation (#6193) 2025-06-09 19:03:24 +08:00
Argo Zhang
d3a9ef4188 feat(Bootstrap): bump dependence 5.3.6 (#6191)
* chore: 更新样式

* chore: 更新脚本

* chore: 移除 bundle 脚本

* refactor: 使用模块化脚本

* Revert "refactor: 使用模块化脚本"

This reverts commit 71fc38c611.

* Revert "chore: 移除 bundle 脚本"

This reverts commit b7ebfa53d1.

* chore: 删除文件
2025-06-09 18:55:58 +08:00
Argo Zhang
015b93de24 feat(Stack): rename bb_stack to bb-stack (#6189)
* refactor(Stack): 更新样式名称

* test: 更新单元测试
2025-06-09 14:59:27 +08:00
MadLongTom
f201bff143 fix(Login): update Template5 login template return button style (#6186)
* fix(template5): fix return button css style

* chore: bump version 9.7.1-beta07

---------

Co-Authored-By: MadLongTom <36219016+MadLongTom@users.noreply.github.com>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-09 13:02:20 +08:00
Argo Zhang
8738cafff4 feat(PdfViewer): add embedded parameter (#6185) 2025-06-08 15:32:21 +08:00
Argo Zhang
0ad43d2884 feat(PdfViewer): use HTTPS protocal (#6183) 2025-06-08 14:51:52 +08:00
Argo Zhang
fdbfbc8cf9 chore(Docker): update ENV lang variable (#6181) 2025-06-07 20:17:42 +08:00
Argo Zhang
a6e1b04dad feat(ImageCropper): add OnCropChangedAsync parameter (#6179)
* chore: bump version 9.0.3

* feat(ImageCropper): add OnCropChangedAsync parameter
2025-06-07 20:02:56 +08:00
Argo Zhang
9a88fa62f3 feat(ImageCropper): add Preview parameter (#6177)
* refactor: bump version 9.0.2

* doc: 增加预览功能

* doc: 增加文档
2025-06-07 12:27:08 +08:00
Argo Zhang
2e6e63ceee feat(ImageCorpper): update AspectRatio type to float (#6175)
* refactor: 增加比率参数示例

* chore: bump version 9.0.1
2025-06-06 16:25:35 +08:00
Argo Zhang
4353628cb7 fix(Select): search icon not vertical center when use BI icon theme (#6173)
* doc: 更改默认值

* refactor: 修复 bi md 图标库搜索按钮不居中问题

* chore: bump version 9.7.1-beta06
2025-06-06 15:57:33 +08:00
Argo Zhang
c507f68b5c feat(PdfViewer): add UseGoogleDocs parameter (#6170)
* chore: bump version 9.0.1

* feat: 增加 UseGoogleDocs 参数
2025-06-06 14:59:37 +08:00
Argo Zhang
16b02fe94a test(Upload): improve code coverage (#6168)
* refactor: 获得文件扩展名方法

* test: 测试单元测试

* Revert "test: 测试单元测试"

This reverts commit 83be2fa166.

* chore: 更新 codecov action 版本

* refactor: 更改可为空检查

* test: 增加 Flags 参数

* chore: 更新 action 版本

* revert: 恢复代码

* test: 更新单元测试
2025-06-06 13:30:46 +08:00
Argo Zhang
d139e8cf9c doc(PdfViewer): add OnLoaded event callback sample code (#6166) 2025-06-06 11:14:38 +08:00
XEE LEE
c3330247b3 doc(Login): add microsoft login template (#6162)
* Add transitional login page and update navigation menu

* refactor: 重构模板名称

* refactor: 精简代码

* doc: 代码格式化

* refactor: 增加移动端适配

* doc: 更新打包内容

---------

Co-Authored-By: Ethan Lee <33386249+h2ls@users.noreply.github.com>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-06 08:52:17 +08:00
Argo Zhang
30110267d2 feat(PdfViewer): add PdfViewer component (#6164)
* chore: 增加 PdfViewer 包

* doc: 增加 PdfViewer 示例

* chore: 增加源码映射

* doc: 增加菜单
2025-06-06 08:19:49 +08:00
Argo Zhang
406eb19923 feat(IFrame): add attributes support (#6160) 2025-06-05 13:08:27 +08:00
Argo Zhang
16cc7d49a9 feat(Upload): use local preview instead of base64 format (#6157)
* feat: 增加 readFileToBlobAsync 方法

* chore: 方法重命名

* feat: 增加 getPreviewUrl 方法

* refactor: 改用客户端预览方法

* doc: 更新头像上传示例

* refactor: 增加 CanPreviewCallback 回调方法

* refactor: 增加图片预览功能

* refactor: 增加返回值注释

* test: 更新单元测试

* chore: bump version 9.7.1-beta05
2025-06-05 12:46:08 +08:00
Argo Zhang
7e580f94cb feat(Upload): redesign upload components (#6049) 2025-06-05 10:36:09 +08:00
Argo Zhang
e8d8aca4f0 doc(Icon): reorder icon category (#6155) 2025-06-05 09:49:31 +08:00
WilliamLiu1997
c234b76292 feat(SelectTree): add CanExpandWhenDisabled parameter (#6128)
* 获取或设置当SelectTree 节点处于禁用状态时,节点是否可以展开或折叠。默认值为 false。

* test: 增加单元测试

* chore: bump version beta02

Co-Authored-By: WilliamLiu1997 <55923229+williamliu1997@users.noreply.github.com>

* chore: bump version 9.7.1-beta03

---------

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: Admin <Admin>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-05 01:06:01 +00:00
Argo Zhang
8b477838ee feat(IErrorLogger): support Release mode only show exception message (#6152)
* chore: 增加 Hosting 依赖

* refactor: 增加开发模式检查

* test: 更新单元测试

* refactor: 重构代码
2025-06-04 18:31:35 +08:00
Argo Zhang
4640cae828 refactor(Tab): remove ShowErrorLoggerToast parameter (#6150)
* refactor: 关闭 TabItem 的 Toast 弹窗

* refactor: 更改变量

* refactor: ShowToast 默认值更改为 false

* refactor: 移除 ShowErrorLoggerToast 参数
2025-06-04 13:20:47 +08:00
Argo Zhang
3167b47a42 feat(TabItem): Implement IHandlerException interface (#6148)
* refactor: 更改参数值

* feat: 增加 HandlerException 实现逻辑

* test: 补充单元测试

* feat: 增加全局配置参数

* test: 更新单元测试

* test: 增加 TabItemContent 单元测试
2025-06-04 11:25:44 +08:00
XEE LEE
2df033c050 feat(ImageViewer): support change zoomSpeed (#6145)
* add zoomSpeed parameter

* Add ZoomSpeed parameter and remove zoomSpeed from viewer

* update demo

* refactor: 调整格式

* refactor: 移除注释

* refactor: 更改参数为可为空

* refactor: 移除预编译

* refactor: 增加缩放率检查逻辑

* fix: 修复 keydown 事件销毁导致其他钩子失效问题

---------

Co-Authored-By: Ethan Lee <33386249+h2ls@users.noreply.github.com>
Co-authored-by: Argo Zhang <argo@live.ca>
2025-06-04 09:19:02 +08:00
Argo Zhang
5acba45e55 feat(TabItem): add ErrorLogger support on TabItem (#6143)
* refactor: 重构 IErrorLogger 数据类型

* refactor: 增加 Tab 组件异常捕获参数

* refactor: TabItem 增加独立捕获异常能力
2025-06-03 17:32:09 +08:00
Argo Zhang
7336b9ef9f revert(IErrorLogger): revert update bool to nullable bool (#6141) 2025-06-03 17:29:15 +08:00
Argo Zhang
b63ed8adb7 feat(TabItem): support ErrorLogger catch exception (#6139)
* refactor: 重构代码

* refactor: 更改 IErrorLogger 接口

* refactor: 改造 Layout 组件

* refactor: 增强 TabItemContent 支持生命周期异常捕获

* chore: bump version 9.7.1-beta03

* wip: 临时提交

* Revert "wip: 临时提交"

This reverts commit d841341604.

* refactor: 撤销更新

* refactor: 代码格式化

* test: 撤销单元测试更改

* test: 更新单元测试

* refactor: 更新 ErrorLogger 是否开启逻辑

* refactor: 移除 IHandlerException 接口

* test: 增加单元测试
2025-06-03 14:50:54 +08:00
Argo Zhang
1f5e494b14 chore(Bootstrap): load bootstrap unbundle javascript on debug mode (#6137)
* refactor: 删除打包脚本

* chore: 更新打包脚本

* chore: 忽略打包脚本

* chore: 更新忽略文件
2025-06-03 13:34:48 +08:00
Argo Zhang
450937c59f doc(SelectGeneric): update IsEditable paraemeter sample code (#6135)
* refactor: 精简代码

* doc: 增加注释

* doc: 更新示例

* chore: bump version 9.7.1-beta03

Co-Authored-By: yswCode <56284285+yswcode@users.noreply.github.com>

---------

Co-authored-by: yswCode <56284285+yswcode@users.noreply.github.com>
2025-06-03 13:00:54 +08:00
Argo Zhang
f81d4008a8 feat(IErrorLogger): redesign parameter data type (#6132)
* refactor: 更改 IErrorLogger 接口

* refactor: 改造 Layout 组件

* test: 更新单元测试
2025-06-02 16:58:30 +08:00
Argo Zhang
6f83053df3 refactor(DynamicElement): refactor code to improve readability (#6130)
* refactor: 重构代码提高可读性

* refactor: 更新逻辑

* Revert "refactor: 更新逻辑"

This reverts commit 042c3033fa.

* Revert "refactor: 重构代码提高可读性"

This reverts commit 92bc40ddd4.

* Reapply "refactor: 重构代码提高可读性"

This reverts commit c929fbe496.
2025-06-02 14:13:35 +08:00
Argo Zhang
16731b9e58 refactor(IdComponentBase): update Id generator (#6126) 2025-06-01 19:10:32 +08:00
Argo Zhang
5fdaee0d23 feat(TabItem): add Id parameter (#6124)
* chore: bump version 9.7.1-beta02

Co-Authored-By: MusaZ <6318180+musaz@users.noreply.github.com>

* feat-tab

---------

Co-authored-by: MusaZ <6318180+musaz@users.noreply.github.com>
2025-06-01 09:48:57 +08:00
Argo Zhang
0289df5047 feat(SelectGeneric): add reset logic when TextConvertToValueCallback return null (#6123)
* feat: 增加 resetValue 方法

* refactor: 移除 tabindex 参数

* refactor: 增加输入 TextConvertToValueCallback 返回空时重置逻辑

* test: 增加单元测试

* test: 更新单元测试

---------

Co-Authored-By: j4587698 <24642446+j4587698@users.noreply.github.com>
2025-05-31 20:08:21 +08:00
John
dae37ad10c fix(BootstrapBlazorErrorBoundary): improve BuildRenderTree logic to render error or fallback content (#6114)
* fix(BootstrapBlazorErrorBoundary): improve BuildRenderTree logic to render error or fallback content (#6113)

- Only renders ChildContent when there is no exception.
- Renders custom ErrorContent if provided, otherwise renders default error content.

* test: 更新单元测试

* doc: 更新全局异常示例

* feat: 增加基类重置方法

* refactor: 消除警告信息

* chore: bump version 9.7.1-beta01

Co-Authored-By: John <1931909+flyliononline@users.noreply.github.com>

* doc: 更新文档

* refactor: 恢复模板代码

---------

Co-authored-by: Argo Zhang <argo@live.ca>
Co-authored-by: John <1931909+flyliononline@users.noreply.github.com>
2025-05-30 13:04:56 +00:00
Argo Zhang
b80e875f0a feat(Layout): integrated ErrorLogger handler global exception (#6119)
* refactor: 代码格式化

* feat: Layout 内置 ErrorLogger

* Revert "feat: Layout 内置 ErrorLogger"

This reverts commit 945c9526e2.

* doc: 更新文档

* refactor: 更新逻辑

* Reapply "feat: Layout 内置 ErrorLogger"

This reverts commit 041f6c27d1.

* test: 增加单元测试

* refactor: 增加页面生命周期报错时自定义异常处理逻辑

* test: 更新单元测试

* refactor: 增加单元测试

* test: 移除单元测试

* doc: 移除命名空间
2025-05-30 12:27:40 +08:00
Argo Zhang
c06050cd3e feat(BootstrapBlazorOptions): add ShowErrorLoggerToast parameter (#6117)
* feat: 增加 ShowErrorLoggerToast 参数

* test: 增加单元测试
2025-05-30 09:10:28 +08:00
Argo Zhang
7065ff2df1 feat(IValidateComponent): update ToggleMessage return value as Task (#6112)
* chore: bump version 9.7.0

* refactor: ToggleMessage 方法更改为 Task

# Conflicts:
#	src/BootstrapBlazor/Components/Validate/IValidateComponent.cs
#	src/BootstrapBlazor/Components/Validate/ValidateBase.cs

* refactor: 增加 await 关键字

* refactor: 更改为异步方法
2025-05-29 10:15:01 +08:00
Argo Zhang
a6167cd7cb fix(SelectGeneric): add dropdown-menu-body class (#6110)
* fix(SelectGeneric): 修复 body 丢失高度问题

* chore: bump version 9.6.5-beta03

Co-Authored-By: j4587698 <24642446+j4587698@users.noreply.github.com>

---------

Co-authored-by: j4587698 <24642446+j4587698@users.noreply.github.com>
2025-05-29 09:54:51 +08:00
Argo Zhang
a0834cda42 refactor(Validate): update dispose method (#6108) 2025-05-28 10:50:06 +08:00
Argo Zhang
4ad4ea4969 feat(Card): add HeaderPaddingY parameter (#6106)
* feat: 增加 HeaderPaddingY 参数调整 header y 轴间隙

* refactor: 代码格式化

* doc: 增加文档

* test: 增加单元测试

* test: 更新单元测试
2025-05-28 09:15:48 +08:00
Argo Zhang
a7d575b8c1 feat(MultiSelectGeneric): add MultiSelectGeneric component (#6103)
* refactor: 删除冗余样式

* feat: 增加多选泛型组件

* doc: 更新示例

* doc: 增加文档
2025-05-27 16:27:08 +08:00
Argo Zhang
89d90772f2 refactor(BootstrapInputGroupLabel): use DynamicElement unified tag (#6101)
* refactor: 精简代码

* style: 代码格式化
2025-05-27 14:43:05 +08:00
Argo Zhang
2ba3fc8e08 refactor(Fork): update custom command to local (#6099)
* refactor: 增加 ValidateForm 判断条件

* refactor: 撤销更新

* refactor: 移除自定义脚本
2025-05-27 14:07:28 +08:00
Argo Zhang
b331fc3db0 feat(MultiSelectFilter): add MultiSelectFilter component (#6097)
* refactor: 移除 bind-value event 语句

* refactor: 增加 MultiSelectFilter 组件

* test: 增加单元测试

* test: 更新单元测试

* test: 更新单元测试

* test: 更新单元测试

* test: 更新单元测试
2025-05-26 20:46:38 +08:00
Argo Zhang
ac20831cd0 doc(FilterProvider): add documentation for FilterProvider (#6095) 2025-05-26 12:26:20 +08:00
Argo Zhang
5246cce79b feat(Meeting): add Meeting component (#6092)
* 添加Meet示例

* 例子中添加更多说明以及支持自定义服务器地址

* doc: 增加菜单

* chore: 增加源码映射关系

* doc: 更新示例

* chore: 更新依赖

* refactor: 精简代码

---------

Co-authored-by: jx <jx@jvxiang.com>
2025-05-25 09:51:03 +08:00
Argo Zhang
ea34fd6aec fix(Table): remove ShowExtendEditButton/ShowExtendDeleteButton check condition (#6090)
* fix: 移除 ShowExtendEditButton ShowExtendDeleteButton 判断条件

* test: 更新单元测试

* chore: bump version 9.6.5-beta02

Co-Authored-By: Mick Bao <78190214+bgycn@users.noreply.github.com>

---------

Co-authored-by: Mick Bao <78190214+bgycn@users.noreply.github.com>
2025-05-25 01:08:05 +00:00
Argo Zhang
de715633c0 chore(TableExport): bump version 9.2.5 (#6088) 2025-05-24 14:29:27 +08:00
Argo Zhang
a86514f247 fix(Layout): authorization failure in virtual directory mode (#6086)
* feat: 增加扩展方法

* fix: 修复二级虚拟目录 Layout 无法显示问题

* chore: bump version 9.6.5-beta01

Co-Authored-By: kid5791 <171754454+kid5791@users.noreply.github.com>

* chore: bump version 9.6.5-beta02

Co-Authored-By: dejan <huangyy98@outlook.com>

* test: 增加单元测试

* refactor: 更改方法名称

* test: 更新单元测试

---------

Co-authored-by: kid5791 <171754454+kid5791@users.noreply.github.com>
Co-authored-by: dejan <huangyy98@outlook.com>
2025-05-23 19:56:25 +08:00
Argo Zhang
2b7554a228 chore(UniverSheet): bump version 9.0.5 (#6085)
remove console log
2025-05-23 11:56:32 +08:00
Argo Zhang
c5be0c75cb doc(UniverSheet): update documentation remove UnSubscribe call (#6083) 2025-05-23 11:41:14 +08:00
Argo Zhang
863017cdc2 chore(Fork): update branch variable in script (#6081) 2025-05-23 10:46:54 +08:00
Argo Zhang
03e52bda1c fix(Splitting): bump version 9.0.3 (#6079) 2025-05-22 18:02:31 +08:00
439 changed files with 13362 additions and 6209 deletions

View File

@@ -1,32 +0,0 @@
[
{
"version" : 2
},
{
"action" : {
"script" : "git branch -d localBranchName\ngit push gitee.com --delete refs/heads/${ref}",
"showOutput" : false,
"type" : "sh",
"waitForExit" : true
},
"name" : "Delete All Branch",
"refTargets" : [
"localbranch",
"remotebranch"
],
"target" : "ref"
},
{
"action" : {
"script" : "git push origin ${ref}\ngit push gitee.com ${ref}",
"showOutput" : false,
"type" : "sh",
"waitForExit" : true
},
"name" : "Push All Branch",
"refTargets" : [
"localbranch"
],
"target" : "ref"
}
]

View File

@@ -25,6 +25,7 @@ jobs:
dotnet test test/UnitTest --collect:"XPlat Code Coverage"
- name: Upload to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: BB

View File

@@ -32,7 +32,7 @@ jobs:
dotnet test test/UnitTest --collect:"XPlat Code Coverage"
- name: Upload to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

3
.gitignore vendored
View File

@@ -381,3 +381,6 @@ src/**/wwwroot/**/uploader
**/BootstrapBlazor/wwwroot/css/sweetalert2.css
**/BootstrapBlazor/wwwroot/css/motronic.min.css
**/BootstrapBlazor/wwwroot/css/nano.min.css
# Bootstrap
**/BootstrapBlazor/wwwroot/js/bootstrap.blazor.bundle.min.js

View File

@@ -114,3 +114,7 @@ inputmode
Totp
otpauth
Hotp
univer
rdkit
webkitdirectory
dotx

View File

@@ -30,7 +30,7 @@
"WeekText": "Woche",
"NextWeek": "Nächste Woche",
"WeekHeaderText": "",
"WeekLists": "Son,Mon,Die,Mit,Don,Fre,Sam",
"WeekLists": "So,Mo,Di,Mi,Do,Fr,Sa",
"WeekNumberText": "{0} Woche(n)",
"Months": "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
"Title": "{0} {1}"
@@ -65,7 +65,7 @@
"YearPeriodText": "{0} - {1}",
"Months": "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
"MonthLists": "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
"WeekLists": "Son,Mon,Die,Mit,Don,Fre,Sam",
"WeekLists": "So,Mo,Di,Mi,Do,Fr,Sa",
"GenericTypeErroMessage": "DateTimePicker unterstützt nur DateTime oder Nullable<DateTime>",
"Today": "Heute",
"Yesterday": "Gestern",
@@ -78,6 +78,7 @@
"ClearButtonText": "Leeren",
"TodayButtonText": "Heute",
"ConfirmButtonText": "Ok",
"DateTimeFormat": "dd\\.MM\\.yyyy HH\\:mm\\:ss",
"DateFormat": "dd\\.MM\\.yyyy",
"Last7Days": "Letzte 7 Tage",
"Last30Days": "Letzte 30 Tage",
@@ -109,7 +110,7 @@
"ErrorMessage": "Bitte geben Sie denselben Wert nochmals ein"
},
"BootstrapBlazor.Components.ErrorLogger": {
"ToastTitle": "Anwendungfehler"
"ToastTitle": "Anwendungsfehler"
},
"BootstrapBlazor.Components.GoTop": {
"TooltipText": "Nach oben"
@@ -119,7 +120,7 @@
},
"BootstrapBlazor.Components.Logout": {
"PrefixDisplayNameText": "Willkommen",
"PrefixUserNameText": "Account:"
"PrefixUserNameText": "Benutzername:"
},
"BootstrapBlazor.Components.LogoutLink": {
"Text": "Ausloggen"
@@ -130,7 +131,8 @@
"BootstrapBlazor.Components.ModalDialog": {
"CloseButtonText": "Schließen",
"SaveButtonText": "Speichern",
"PrintButtonText": "Drucken"
"PrintButtonText": "Drucken",
"ExportPdfButtonText": "PDF exportieren"
},
"BootstrapBlazor.Components.MultiSelect": {
"PlaceHolder": "Klicken, um auszuwählen ...",
@@ -138,7 +140,8 @@
"ReverseSelectText": "Umkehren",
"ClearText": "Leeren",
"MinErrorMessage": "Wählen Sie wenigstens {0} Elemente",
"MaxErrorMessage": "Es können maximal {0} Elemente selektiert werden"
"MaxErrorMessage": "Es können maximal {0} Elemente selektiert werden",
"NoSearchDataText": "Kein Ergebnis"
},
"BootstrapBlazor.Components.Pagination": {
"GotoNavigatorLabelText": "Zu"
@@ -151,11 +154,16 @@
"BootstrapBlazor.Components.PrintButton": {
"Text": "Drucken"
},
"BootstrapBlazor.Components.Repeater": {
"EmptyText": "Keine Daten"
},
"BootstrapBlazor.Components.Search": {
"SearchButtonText": "Suchen"
"SearchButtonText": "Suchen",
"NoDataTip": "Keine Einträge gefunden"
},
"BootstrapBlazor.Components.Select": {
"PlaceHolder": "Zum Auswählen klicken ..."
"PlaceHolder": "Zum Auswählen klicken ...",
"NoSearchDataText": "Kein Ergebnis"
},
"BootstrapBlazor.Components.SelectTree": {
"PlaceHolder": "Zum Auswählen klicken ..."
@@ -179,7 +187,17 @@
"CloseCurrentTabText": "Abbrechen",
"CloseOtherTabsText": "Andere schließen",
"CloseAllTabsText": "Alle schließen",
"NotFoundTabText": "Nicht gefunden"
"NotFoundTabText": "Nicht gefunden",
"RefreshToolbarTooltipText": "Aktualisieren",
"FullscreenToolbarTooltipText": "Vollbild",
"PrevTabNavLinkTooltipText": "Vorheriger Tab",
"NextTabNavLinkTooltipText": "Nächster Tab",
"CloseTabNavLinkTooltipText": "Schließen",
"ContextRefresh": "Aktualisieren",
"ContextClose": "Schließen",
"ContextCloseOther": "Andere Tabs schließen",
"ContextCloseAll": "Alle Tabs schließen",
"ContextFullScreen": "Vollbild"
},
"BootstrapBlazor.Components.Table": {
"AddButtonText": "Hinzufügen",
@@ -201,11 +219,13 @@
"SearchButtonText": "Suchen",
"ResetSearchButtonText": "Zurücksetzen",
"AdvanceButtonText": "Erweiterte Suche",
"AdvancedSortModalTitle": "Sortieren",
"AdvancedSortButtonText": "Erweitertes Sortieren",
"CheckboxDisplayText": "Alle",
"EditModalTitle": "Bearbeiten",
"AddModalTitle": "Neu",
"LineNoText": "Zeilen",
"ColumnButtonTemplateHeaderText": "",
"ColumnButtonTemplateHeaderText": "Aktionen",
"SearchTooltip": "<div class='search-input-tooltip'>Bitte eingeben ...</br><kbd>Enter</kbd> Suche <kbd>ESC</kbd> Leeren</div>",
"SearchModalTitle": "Suche",
"AddButtonToastTitle": "Daten hinzufügen",
@@ -236,17 +256,27 @@
"ExportCsvDropdownItemText": "MS-Csv",
"ExportExcelDropdownItemText": "MS-Excel",
"ExportPdfDropdownItemText": "Pdf",
"PageInfoText": "{0} - {1} Total {2}",
"PageItemsText": "{0}/page"
"PageInfoText": "{0} - {1} Insgesamt {2}",
"PageItemsText": "{0}/Seite",
"CopyColumnTooltipText": "Ganze Spalte in die Zwischenablage kopieren",
"CopyColumnCopiedTooltipText": "Kopiert!",
"ColumnWidthTooltipPrefix": "Breite: ",
"ColumnToolboxTitle": "Werkzeuge",
"AlignLeftText": "Links",
"AlignLeftTooltipText": "Klicken, um den Text in dieser Spalte links auszurichten",
"AlignCenterText": "Zentriert",
"AlignCenterTooltipText": "Klicken, um den Text in dieser Spalte zentriert auszurichten",
"AlignRightText": "Rechts",
"AlignRightTooltipText": "Klicken, um den Text in dieser Spalte rechts auszurichten"
},
"BootstrapBlazor.Components.EditDialog": {
"CloseButtonText": "Schließen",
"SaveButtonText": "Speichern"
},
"BootstrapBlazor.Components.TableFilter": {
"BootstrapBlazor.Components.TableColumnFilter": {
"Title": "Filter",
"ClearButtonText": "Leeren",
"FilterButtonText": "Übernehmen",
"FilterButtonText": "Filtern",
"BoolFilter.AllText": "Alle",
"BoolFilter.TrueText": "Wahr",
"BoolFilter.FalseText": "Unwahr",
@@ -258,7 +288,10 @@
"NotEqual": "Ungleich",
"Contains": "Beinhaltet",
"NotContains": "Beinhaltet nicht",
"EnumFilter.AllText": "Alle"
"EnumFilter.AllText": "Alle",
"NotSupportedMessage": "Nicht unterstützter Filtertyp. Bitte passen Sie den Filter mit FilterTemplate an",
"MultiFilterSearchPlaceHolderText": "Bitte eingeben ...",
"MultiFilterSelectAllText": "Alle auswählen"
},
"BootstrapBlazor.Components.FilterLogicItem": {
"And": "Und",
@@ -284,7 +317,9 @@
},
"BootstrapBlazor.Components.Transfer": {
"LeftPanelText": "Alle",
"RightPanelText": "Markierte"
"RightPanelText": "Markierte",
"MinErrorMessage": "Bitte wählen Sie mindestens {0} Elemente aus",
"MaxErrorMessage": "Es können bis zu {0} Elemente ausgewählt werden"
},
"BootstrapBlazor.Components.TransferPanel": {
"SearchPlaceHolderString": "Bitte eingeben ...",
@@ -293,18 +328,24 @@
"BootstrapBlazor.Components.Tree": {
"NotSetOnTreeExpandErrorMessage": "OnExpandNodeAsync-Parameter nicht gesetzt"
},
"BootstrapBlazor.Components.TreeView": {
"NotSetOnTreeExpandErrorMessage": "OnExpandNodeAsync-Parameter nicht gesetzt",
"ToolbarEditTitle": "Knoten bearbeiten",
"ToolbarEditLabelText": "Umbenennen"
},
"BootstrapBlazor.Components.UploadBase": {
"DeleteButtonText": "Löschen",
"BrowserButtonText": "Browser",
"FileExtensions": "Datei muss folgende Endung haben: {0}",
"FileSizeValidation": "Dateigrößer muss kleiner sein als {0}"
"FileSizeValidation": "Dateigröße muss kleiner sein als {0}",
"DropUploadText": "Dateien hierher ziehen oder <em>klicken, um hochzuladen</em>"
},
"BootstrapBlazor.Components.Handwritten": {
"SaveButtonText": "Speichern",
"ClearButtonText": "Leeren"
},
"BootstrapBlazor.Components.SignaturePad": {
"SignAboveLabel": "In das Feld eintragen",
"SignAboveLabel": "Im Feld unterschreiben",
"ClearBtnTitle": "Löschen",
"SignatureAlertText": "Bitte geben Sie zuerst eine Unterschrift an",
"ChangeColorBtnTitle": "Farbe ändern",
@@ -316,7 +357,7 @@
"SaveSVGBtnTitle": "SVG"
},
"BootstrapBlazor.Components.NullableBoolItemsAttribute": {
"NullValueDisplayText": "Bitte wählen",
"NullValueDisplayText": "Bitte auswählen ...",
"TrueValueDisplayText": "Wahr",
"FalseValueDisplayText": "Falsch"
},
@@ -330,5 +371,38 @@
"ButtonText": "Kopieren",
"DialogHeaderText": "Ausgewähltes Symbol",
"CopiedTooltipText": "Kopieren erfolgreich"
},
"BootstrapBlazor.Components.Splitting": {
"Text": "Laden ..."
},
"BootstrapBlazor.Components.QueryBuilder": {
"And": "und",
"Or": "oder",
"GreaterThanOrEqual": "Größer oder gleich",
"LessThanOrEqual": "Kleiner oder gleich",
"GreaterThan": "Größer",
"LessThan": "Kleiner",
"Equal": "Gleich",
"NotEqual": "Ungleich",
"Contains": "Beinhaltet",
"NotContains": "Beinhaltet nicht",
"GroupText": "Gruppe",
"ItemText": "Element"
},
"BootstrapBlazor.Components.TableAdvancedSortDialog": {
"AscText": "Aufsteigend",
"DescText": "Absteigend"
},
"BootstrapBlazor.Components.ClockPicker": {
"AMText": "Vormittag",
"PMText": "Nachmittag"
},
"BootstrapBlazor.Components.ThemeProvider": {
"AutoModeText": "Auto",
"DarkModeText": "Dunkel",
"LightModeText": "Hell"
},
"BootstrapBlazor.Components.ValidateBase": {
"DefaultRequiredErrorMessage": "{0} ist erforderlich."
}
}

View File

@@ -31,9 +31,9 @@
<PackageReference Include="BootstrapBlazor.BootstrapIcon" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.ChatBot" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.CherryMarkdown" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.CherryMarkdown" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.Dock" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.DockView" Version="9.1.13" />
<PackageReference Include="BootstrapBlazor.DockView" Version="9.1.17" />
<PackageReference Include="BootstrapBlazor.DriverJs" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.ElementIcon" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.FileViewer" Version="9.0.0" />
@@ -43,8 +43,9 @@
<PackageReference Include="BootstrapBlazor.Html2Image" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor.Html2Pdf" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor.IconPark" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.ImageCropper" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.ImageCropper" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.IP2Region" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.JitsiMeet" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.JuHeIpLocatorProvider" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.Live2DDisplay" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.Markdown" Version="9.0.2" />
@@ -55,23 +56,26 @@
<PackageReference Include="BootstrapBlazor.MindMap" Version="9.1.6" />
<PackageReference Include="BootstrapBlazor.MouseFollower" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.OctIcon" Version="9.0.4" />
<PackageReference Include="BootstrapBlazor.OfficeViewer" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.OnScreenKeyboard" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.PdfReader" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.PdfViewer" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.Player" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.RDKit" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor.SignaturePad" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.SmilesDrawer" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor.Sortable" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor.Splitting" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor.Splitting" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.SvgEditor" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.SummerNote" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.4" />
<PackageReference Include="BootstrapBlazor.SummerNote" Version="9.0.4" />
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
<PackageReference Include="BootstrapBlazor.Topology" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.UniverIcon" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.4" />
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
<PackageReference Include="BootstrapBlazor.Vditor" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.VideoPlayer" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
<PackageReference Include="Longbow.Logging" Version="9.0.0" />
<PackageReference Include="Longbow.Logging" Version="9.0.1" />
<PackageReference Include="Longbow.Tasks" Version="9.0.0" />
</ItemGroup>

View File

@@ -10,7 +10,7 @@
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="keywords" content="bootstrapblazor,bootstrap,blazor,wasm,webassembly,UI,netcore,web,assembly">
<meta name="keywords" content="bootstrapblazor,blazorbootstrap,bootstrap blazor,blazor bootstrap,bootstrap,blazor,wasm,webassembly,UI,netcore,web,assembly">
<meta name="description" content="基于 Bootstrap 风格的 Blazor UI 组件库,用于研发企业级中后台产品。">
<meta name="author" content="argo (argo@live.ca)">
<meta name="theme-color" content="#712cf9">

View File

@@ -14,18 +14,9 @@
<li class="nav-item">
<a class="nav-link" href="introduction">@IntroductionText</a>
</li>
<li class="nav-item">
<a class="nav-link" href="components">@ComponentsText</a>
</li>
<li class="nav-item">
<a class="nav-link" href="tutorials">@TutorialsText</a>
</li>
@* @if (CultureInfo.CurrentUICulture.Name == "zh-CN")
{
<li class="nav-item">
<a class="nav-link" href="https://theme.blazor.zone"></a>
</li>
} *@
</ul>
</div>
<div class="d-flex flex-fill"></div>
@@ -43,7 +34,7 @@
</a>
</li>
<li class="nav-item">
<FullScreenButton class="nav-link p-2" TooltipText="点击切换全屏模式" />
<FullScreenButton class="nav-link p-2" TooltipText="@Localizer["FullScreenTooltipText"]" />
</li>
</ul>
<a class="btn btn-bd-download d-none d-lg-block mb-3 mb-md-0 ms-md-3" target="_blank" href="@DownloadUrl">@DownloadText</a>

View File

@@ -30,9 +30,6 @@ public partial class Header
[NotNull]
private string? IntroductionText { get; set; }
[NotNull]
private string? ComponentsText { get; set; }
[NotNull]
private string? DownloadText { get; set; }
@@ -53,7 +50,6 @@ public partial class Header
DownloadText ??= Localizer[nameof(DownloadText)];
HomeText ??= Localizer[nameof(HomeText)];
IntroductionText ??= Localizer[nameof(IntroductionText)];
ComponentsText ??= Localizer[nameof(ComponentsText)];
TutorialsText ??= Localizer[nameof(TutorialsText)];
_versionString = $"v{PackageVersionService.Version}";
}

View File

@@ -1,26 +1,16 @@
@using Microsoft.Extensions.DependencyInjection
@inject PackageVersionService VersionManager
@inject IStringLocalizer<InstallContent> Localizer
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
<h3>@Title</h3>
<h4>@Localizer["Heading"]</h4>
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
<h5>@Localizer["P5"]</h5>
<p>@((MarkupString)Localizer["InstallByTemplate"].Value)</p>
<p>@Localizer["P1"]</p>
<ul class="ul-demo">
<li><code>visual studio</code> @Localizer["P2"]</li>
<li><code>net5</code> @Localizer["P3"] <code>net6</code> <code>net7</code></li>
</ul>
<p><code>BootstrapBlazor</code> @Localizer["P4"] <code>NET6/NET7/NET8/NET9</code></p>
<h5 class="code-label">@Localizer["P9"]</h5>
<h4>@Localizer["P5"]</h4>
<p>@Localizer["P6"] <a href="template">[@Localizer["P7"]]</a> @Localizer["P8"]</p>
<h4>@Localizer["P9"]</h4>
<h5>@Localizer["P10"]</h5>
<p class="code-label">@Localizer["P10"]</p>
<p class="code-label">1. @Localizer["P11"]</p>
<p class="code-label">2. @Localizer["P12"]</p>
<p class="code-label">3. @Localizer["P13"] <b>Blazor App</b> @Localizer["P14"] <b>@Localizer["P15"]</b>, @Localizer["P16"] <b>Create</b></p>
@@ -54,7 +44,7 @@
&lt;link href="BlazorApp1.styles.css" rel="stylesheet"&gt;
&lt;/head&gt;</Pre>
<p class="code-label">4. @Localizer["P25"]</p>
<p class="code-label">4. @((MarkupString)Localizer["P25"].Value)</p>
@ScriptsTemplate
<Pre>&lt;body&gt;
...
@@ -68,25 +58,11 @@
@ServicesTemplate
<p class="code-label">6. @Localizer["P28"]</p>
<p>@Localizer["P29"] <code>~/_Imports.razor</code> @Localizer["P30"] <code>Razor</code> @Localizer["P31"]</p>
<p>@Localizer["P29"] <code>_Imports.razor</code> @Localizer["P30"] <code>Razor</code> @Localizer["P31"]</p>
<Pre><b>@@using BootstrapBlazor.Components</b></Pre>
<p class="code-label">7. @Localizer["P32"] <code>BootstrapBlazorRoot</code> @Localizer["P33"] <code>~/App.razor</code> @Localizer["P34"]</p>
<Pre>&lt;BootstrapBlazorRoot&gt;
&lt;Router AppAssembly="@@typeof(App).Assembly"&gt;
&lt;Found Context="routeData"&gt;
&lt;PageTitle&gt;Title&lt;/PageTitle&gt;
&lt;RouteView RouteData="@@routeData" DefaultLayout="@@typeof(MainLayout)" /&gt;
&lt;FocusOnNavigate RouteData="@@routeData" Selector="h1" /&gt;
&lt;/Found&gt;
&lt;NotFound&gt;
&lt;PageTitle&gt;Not found&lt;/PageTitle&gt;
&lt;LayoutView Layout="@@typeof(MainLayout)"&gt;
&lt;p&gt; @Localizer["P35"] ...&lt;/p&gt;
&lt;/LayoutView&gt;
&lt;/NotFound&gt;
&lt;/Router&gt;
&lt;/BootstrapBlazorRoot&gt;</Pre>
<p class="code-label">7. @((MarkupString)Localizer["AddRootText"].Value)</p>
@RootTemplate
<h5>@Localizer["P36"]</h5>
<p>@Localizer["P37"] <code>BootstrapBlazor</code> @Localizer["P38"]</p>

View File

@@ -51,6 +51,12 @@ public sealed partial class InstallContent
[Parameter]
public RenderFragment? ServicesTemplate { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public RenderFragment? RootTemplate { get; set; }
/// <summary>
///
/// </summary>

View File

@@ -19,6 +19,12 @@
display: none;
}
.pre-code > pre {
color: #e83e8c;
margin-bottom: 0;
max-height: 260px;
}
::deep .btn-group {
position: absolute;
top: 0;

View File

@@ -1,10 +1,4 @@
::deep pre {
color: #e83e8c;
margin-bottom: 0;
max-height: 260px;
}
main {
main {
min-height: calc(100vh - var(--bs-header-height));
position: relative;
z-index: 10;

View File

@@ -60,10 +60,13 @@
</div>
<div class="footer-info">
<div class="d-flex">
<div>SDK .NET @Version on @OS</div>
<div>.NET @Version on @OS</div>
<div class="ms-1">BB @VersionService.Version</div>
<FooterCounter></FooterCounter>
<CacheCounter></CacheCounter>
<div class="ms-2">
<NetworkMonitorIndicator></NetworkMonitorIndicator>
</div>
</div>
<div class="d-flex flex-fill align-items-center justify-content-center">
<a class="d-none d-md-block me-3" href="@WebsiteOption.CurrentValue.GiteeRepositoryUrl" target="_blank">@Localizer["Footer"]</a>

View File

@@ -6,9 +6,11 @@
</HeadContent>
<CascadingValue Value="this" IsFixed="true">
<Layout IsPage="true" IsFullSide="@IsFullSide" IsFixedHeader="@IsFixedHeader" IsFixedFooter="@IsFixedFooter" IsFixedTabHeader="IsFixedTabHeader"
<Layout IsPage="true" IsFullSide="@IsFullSide" IsFixedHeader="@IsFixedHeader"
IsFixedFooter="@IsFixedFooter" IsFixedTabHeader="IsFixedTabHeader"
ShowFooter="@ShowFooter" ShowGotoTop="true" ShowCollapseBar="true" Menus="@Menus"
UseTabSet="@UseTabSet" TabDefaultUrl="layout-page" AdditionalAssemblies="new[] { GetType().Assembly }" class="@LayoutClassString">
UseTabSet="@UseTabSet" TabDefaultUrl="layout-page" AdditionalAssemblies="new[] { GetType().Assembly }"
class="@LayoutClassString">
<Header>
<span class="ms-3 flex-fill">Bootstrap of Blazor</span>
<Widget></Widget>

View File

@@ -61,7 +61,7 @@ public sealed partial class PageLayout
Menus = new List<MenuItem>
{
new() { Text = "返回组件库", Icon = "fa-fw fa-solid fa-house", Url = "components" },
new() { Text = "返回文档", Icon = "fa-fw fa-solid fa-house", Url = "introduction" },
new() { Text = "后台模拟器", Icon = "fa-fw fa-solid fa-desktop", Url = "layout-page" },
new() { Text = "示例网页", Icon = "fa-fw fa-solid fa-laptop", Url = "layout-demo/text=Parameter1" }
};

View File

@@ -80,6 +80,12 @@ public partial class TutorialsNavMenu
Template = CreateDownloadButtonComponent("template4", _template4),
Text = "Template 4",
Url = "tutorials/template4"
},
new()
{
Template = CreateDownloadButtonComponent("template5", _template5),
Text = "Template 5",
Url = "/tutorials/template5"
}
]
},
@@ -215,6 +221,14 @@ public partial class TutorialsNavMenu
.. _layoutFileList
];
private readonly string[] _template5 =
[
"Tutorials/LoginAndRegister/Template5.razor",
"Tutorials/LoginAndRegister/Template5.razor.css",
"../Layout/TutorialsLayout.razor",
"../Layout/TutorialsLayout.razor.css"
];
private readonly string[] _waterfallFileList =
[
"Tutorials/Waterfall.razor",

View File

@@ -1,159 +1,122 @@
@page "/components"
@inject IStringLocalizer<Coms> Localizer
<div class="coms-search">
<div class="row">
<div class="col-12">
<Search @bind-Value="@SearchText" PlaceHolder="@Localizer["Search"]"
OnSearch="@OnSearch" OnClear="OnClear" ShowClearButton="true"></Search>
</div>
</div>
<div class="coms-search-filter">
</div>
</div>
<CascadingValue Value="ComponentItems" IsFixed="true">
<CascadingValue Value="@SearchText">
<ComponentCategory Text="@Localizer["Text1"]">
<ComponentCard Text="@Localizer["DividerText"]" Image="Divider.svg" Url="divider"></ComponentCard>
<ComponentCard Text="@Localizer["LayoutText"]" Image="Layout.svg" Url="layout"></ComponentCard>
<ComponentCard Text="@Localizer["FooterText"]" Image="Footer.jpg" Url="footer"></ComponentCard>
<ComponentCard Text="@Localizer["RowText"]" Image="Row.jpg" Url="row"></ComponentCard>
<ComponentCard Text="@Localizer["ScrollText"]" Image="Scroll.png" Url="scroll"></ComponentCard>
<ComponentCard Text="@Localizer["SkeletonText"]" Image="Skeleton.png" Url="skeleton"></ComponentCard>
<ComponentCard Text="@Localizer["SplitText"]" Image="Split.png" Url="split"></ComponentCard>
</ComponentCategory>
<ComponentCategory Text="@Localizer["Text2"]">
<ComponentCard Text="@Localizer["AnchorText"]" Image="Anchor.png" Url="anchor"></ComponentCard>
<ComponentCard Text="@Localizer["AnchorLinkText"]" Image="AnchorLink.jpg" Url="anchor-link"></ComponentCard>
<ComponentCard Text="@Localizer["BreadcrumbText"]" Image="Breadcrumb.png" Url="breadcrumb"></ComponentCard>
<ComponentCard Text="@Localizer["DropdownText"]" Image="Dropdown.svg" Url="dropdown"></ComponentCard>
<ComponentCard Text="@Localizer["FullScreenText"]" Image="FullScreen.jpg" Url="fullscreen"></ComponentCard>
<ComponentCard Text="@Localizer["GoTopText"]" Image="GoTop.png" Url="go-top"></ComponentCard>
<ComponentCard Text="@Localizer["LogoutText"]" Image="Logout.png" Url="logout"></ComponentCard>
<ComponentCard Text="@Localizer["MenuText"]" Image="Menu.svg" Url="menu"></ComponentCard>
<ComponentCard Text="@Localizer["NavText"]" Image="Space.svg" Url="navigation"></ComponentCard>
<ComponentCard Text="@Localizer["PaginationText"]" Image="Pagination.svg" Url="pagination"></ComponentCard>
<ComponentCard Text="@Localizer["StepsText"]" Image="Steps.svg" Url="step"></ComponentCard>
<ComponentCard Text="@Localizer["TabText"]" Image="Tabs.svg" Url="tab"></ComponentCard>
</ComponentCategory>
<ComponentCategory Text="@Localizer["Text3"]">
<ComponentCard Text="@Localizer["AutoCompleteText"]" Image="AutoComplete.svg" Url="auto-complete"></ComponentCard>
<ComponentCard Text="@Localizer["AutoFillText"]" Image="AutoFill.jpg" Url="auto-fill"></ComponentCard>
<ComponentCard Text="@Localizer["ButtonText"]" Image="Button.svg" Url="button"></ComponentCard>
<ComponentCard Text="@Localizer["CascaderText"]" Image="Cascader.png" Url="cascader"></ComponentCard>
<ComponentCard Text="@Localizer["CheckboxText"]" Image="CheckBox.svg" Url="checkbox"></ComponentCard>
<ComponentCard Text="@Localizer["CheckboxListText"]" Image="CheckboxList.png" Url="checkbox-list"></ComponentCard>
<ComponentCard Text="@Localizer["ColorPickerText"]" Image="ColorPicker.jpg" Url="color-picker"></ComponentCard>
<ComponentCard Text="@Localizer["DateTimePickerText"]" Image="DatePicker.svg" Url="datetime-picker"></ComponentCard>
<ComponentCard Text="@Localizer["DateTimeRangeText"]" Image="DateTimeRange.png" Url="datetime-range"></ComponentCard>
<ComponentCard Text="@Localizer["EditorText"]" Image="Editor.png" Url="editor"></ComponentCard>
<ComponentCard Text="@Localizer["EditorFormText"]" Image="EditorForm.png" Url="editor-form"></ComponentCard>
<ComponentCard Text="@Localizer["FloatingLabelText"]" Image="FloatingLabel.jpg" Url="floating-label"></ComponentCard>
<ComponentCard Text="@Localizer["InputText"]" Image="Input.svg" Url="input"></ComponentCard>
<ComponentCard Text="@Localizer["InputNumberText"]" Image="InputNumber.png" Url="input-number"></ComponentCard>
<ComponentCard Text="@Localizer["InputGroupText"]" Image="InputGroup.png" Url="input-group"></ComponentCard>
<ComponentCard Text="@Localizer["MarkdownText"]" Image="Markdown.png" Url="markdown"></ComponentCard>
<ComponentCard Text="@Localizer["MultiSelectText"]" Image="MultiSelect.png" Url="multi-select"></ComponentCard>
<ComponentCard Text="@Localizer["OnScreenKeyboardText"]" Image="OnScreenKeyboard.png" Url="onscreen-keyboard"></ComponentCard>
<ComponentCard Text="@Localizer["RadioText"]" Image="Radio.svg" Url="radio"></ComponentCard>
<ComponentCard Text="@Localizer["RateText"]" Image="Rate.jpg" Url="rate"></ComponentCard>
<ComponentCard Text="@Localizer["SelectText"]" Image="Select.svg" Url="select"></ComponentCard>
<ComponentCard Text="@Localizer["SliderText"]" Image="Slider.svg" Url="slider"></ComponentCard>
<ComponentCard Text="@Localizer["SwitchText"]" Image="Switch.svg" Url="switch"></ComponentCard>
<ComponentCard Text="@Localizer["TextareaText"]" Image="Textarea.png" Url="textarea"></ComponentCard>
<ComponentCard Text="@Localizer["ToggleText"]" Image="Toggle.png" Url="toggle"></ComponentCard>
<ComponentCard Text="@Localizer["TransferText"]" Image="Transfer.svg" Url="transfer"></ComponentCard>
<ComponentCard Text="@Localizer["UploadText"]" Image="Upload.svg" Url="upload"></ComponentCard>
<ComponentCard Text="@Localizer["ValidateFormText"]" Image="ValidateForm.png" Url="validate-form"></ComponentCard>
</ComponentCategory>
<ComponentCategory Text="@Localizer["Text4"]">
<ComponentCard Text="@Localizer["AvatarText"]" Image="Avatar.svg" Url="avatar"></ComponentCard>
<ComponentCard Text="@Localizer["BadgeText"]" Image="Badge.svg" Url="badge"></ComponentCard>
<ComponentCard Text="@Localizer["BarcodeReaderText"]" Image="BarcodeReader.png" Url="barcode-reader"></ComponentCard>
<ComponentCard Text="@Localizer["BlockText"]" Image="Block.jpg" Url="block"></ComponentCard>
<ComponentCard Text="@Localizer["BluetoothText"]" Image="Bluetooth.jpg" Url="blue-tooth"></ComponentCard>
<ComponentCard Text="@Localizer["CardText"]" Image="Card.svg" Url="card"></ComponentCard>
<ComponentCard Text="@Localizer["CalendarText"]" Image="Calendar.svg" Url="calendar"></ComponentCard>
<ComponentCard Text="@Localizer["CameraText"]" Image="Camera.png" Url="Camera"></ComponentCard>
<ComponentCard Text="@Localizer["CaptchaText"]" Image="Captcha.png" Url="captcha"></ComponentCard>
<ComponentCard Text="@Localizer["CarouselText"]" Image="Carousel.svg" Url="carousel"></ComponentCard>
<ComponentCard Text="@Localizer["ClientText"]" Image="Client.jpg" Url="client"></ComponentCard>
<ComponentCard Text="@Localizer["CircleText"]" Image="Circle.png" Url="circle"></ComponentCard>
<ComponentCard Text="@Localizer["CollapseText"]" Image="Collapse.svg" Url="collapse"></ComponentCard>
<ComponentCard Text="@Localizer["DisplayText"]" Image="Display.jpg" Url="display"></ComponentCard>
<ComponentCard Text="@Localizer["DownloadText"]" Image="Download.png" Url="download"></ComponentCard>
<ComponentCard Text="@Localizer["DropdownWidgetText"]" Image="DropdownWidget.png" Url="dropdown-widget"></ComponentCard>
<ComponentCard Text="@Localizer["EmptyText"]" Image="Empty.jpg" Url="empty"></ComponentCard>
<ComponentCard Text="@Localizer["FileViewerText"]" Image="FileViewer.jpg" Url="file-viewer"></ComponentCard>
<ComponentCard Text="@Localizer["GeolocationText"]" Image="Geolocation.jpg" Url="geolocation"></ComponentCard>
<ComponentCard Text="@Localizer["GroupBoxText"]" Image="GroupBox.png" Url="group-box"></ComponentCard>
<ComponentCard Text="@Localizer["HandwrittenPageText"]" Image="Handwritten.jpg" Url="handwritten"></ComponentCard>
<ComponentCard Text="@Localizer["IpText"]" Image="IP.jpg" Url="ips"></ComponentCard>
<ComponentCard Text="@Localizer["LinkButtonText"]" Image="LinkButton.png" Url="link-button"></ComponentCard>
<ComponentCard Text="@Localizer["ListViewText"]" Image="ListView.png" Url="list-view"></ComponentCard>
<ComponentCard Text="@Localizer["LocatorText"]" Image="Locator.jpg" Url="locator"></ComponentCard>
<ComponentCard Text="@Localizer["ImageViewerText"]" Image="Image.png" Url="image-viewer"></ComponentCard>
<ComponentCard Text="@Localizer["MindMapText"]" Image="MindMap.jpg" Url="mind-map"></ComponentCard>
<ComponentCard Text="@Localizer["PdfReaderText"]" Image="PdfReader.jpg" Url="pdf-reader"></ComponentCard>
<ComponentCard Text="@Localizer["PrintText"]" Image="Print.jpg" Url="print"></ComponentCard>
<ComponentCard Text="@Localizer["QRCodeText"]" Image="QRCode.png" Url="qr-code"></ComponentCard>
<ComponentCard Text="@Localizer["RecognizerText"]" Image="Recognizer.png" Url="recognizer"></ComponentCard>
<ComponentCard Text="@Localizer["SearchText"]" Image="Search.png" Url="search"></ComponentCard>
<ComponentCard Text="@Localizer["SignaturePadText"]" Image="SignaturePad.png" Url="signature-pad"></ComponentCard>
<ComponentCard Text="@Localizer["SpeechWaveText"]" Image="SpeechWave.png" Url="speech-wave"></ComponentCard>
<ComponentCard Text="@Localizer["SwitchButtonText"]" Image="LinkButton.png" Url="switch-button"></ComponentCard>
<ComponentCard Text="@Localizer["TableText"]" Image="Table.svg" Url="table"></ComponentCard>
<ComponentCard Text="@Localizer["TagText"]" Image="Tag.svg" Url="tag"></ComponentCard>
<ComponentCard Text="@Localizer["TimelineText"]" Image="Timeline.svg" Url="timeline"></ComponentCard>
<ComponentCard Text="@Localizer["TitleText"]" Image="Title.jpg" Url="title"></ComponentCard>
<ComponentCard Text="@Localizer["TreeViewText"]" Image="Tree.svg" Url="tree-view"></ComponentCard>
<ComponentCard Text="@Localizer["TransitionText"]" Image="Transition.jpg" Url="transition"></ComponentCard>
<ComponentCard Text="@Localizer["VideoPlayerText"]" Image="VideoPlayer.jpg" Url="video-player"></ComponentCard>
<ComponentCard Text="@Localizer["FileViewerText"]" Image="FileViewer.jpg" Url="file-viewer"></ComponentCard>
<ComponentCard Text="@Localizer["WebSerialText"]" Image="WebSerial.jpg" Url="web-serial"></ComponentCard>
<ComponentCard Text="@Localizer["WebSpeechText"]" Image="WebSpeech.jpg" Url="web-speech"></ComponentCard>
<ComponentCard Text="@Localizer["ImageCropperText"]" Image="ImageCropper.jpg" Url="image-cropper"></ComponentCard>
<ComponentCard Text="@Localizer["BarcodeGeneratorText"]" Image="BarcodeGenerator.jpg" Url="barcode-generator"></ComponentCard>
<ComponentCard Text="@Localizer["MermaidText"]" Image="Mermaid.png" Url="mermaid"></ComponentCard>
</ComponentCategory>
<ComponentCategory Text="@Localizer["Text6"]">
<ComponentCard Text="@Localizer["ChartSummaryText"]" Image="Chart.png" Url="chart/index"></ComponentCard>
<ComponentCard Text="@Localizer["ChartLineText"]" Image="Line.jpg" Url="chart/line"></ComponentCard>
<ComponentCard Text="@Localizer["ChartBarText"]" Image="Bar.jpg" Url="chart/bar"></ComponentCard>
<ComponentCard Text="@Localizer["ChartPieText"]" Image="Pie.jpg" Url="chart/pie"></ComponentCard>
<ComponentCard Text="@Localizer["ChartDoughnutText"]" Image="Doughnut.jpg" Url="chart/doughnut"></ComponentCard>
<ComponentCard Text="@Localizer["ChartBubbleText"]" Image="Bubble.jpg" Url="chart/bubble"></ComponentCard>
</ComponentCategory>
<ComponentCategory Text="@Localizer["Text5"]">
<ComponentCard Text="@Localizer["AlertText"]" Image="Alert.svg" Url="alert"></ComponentCard>
<ComponentCard Text="@Localizer["ConsoleText"]" Image="Console.png" Url="console"></ComponentCard>
<ComponentCard Text="@Localizer["DialogText"]" Image="Notification.svg" Url="dialog"></ComponentCard>
<ComponentCard Text="@Localizer["DispatchText"]" Image="Dispatch.jpg" Url="dispatch"></ComponentCard>
<ComponentCard Text="@Localizer["DrawerText"]" Image="Drawer.svg" Url="drawer"></ComponentCard>
<ComponentCard Text="@Localizer["EditDialogText"]" Image="Notification.svg" Url="edit-dialog"></ComponentCard>
<ComponentCard Text="@Localizer["LightText"]" Image="Light.png" Url="light"></ComponentCard>
<ComponentCard Text="@Localizer["MessageText"]" Image="Message.svg" Url="message"></ComponentCard>
<ComponentCard Text="@Localizer["ModalText"]" Image="Modal.svg" Url="modal"></ComponentCard>
<ComponentCard Text="@Localizer["NotificationsText"]" Image="Notifications.jpg" Url="notification"></ComponentCard>
<ComponentCard Text="@Localizer["PopoverConfirmText"]" Image="Pop-confirm.svg" Url="pop-confirm"></ComponentCard>
<ComponentCard Text="@Localizer["PopoverText"]" Image="Popover.svg" Url="popover"></ComponentCard>
<ComponentCard Text="@Localizer["ProgressText"]" Image="Progress.svg" Url="progress"></ComponentCard>
<ComponentCard Text="@Localizer["ReconnectorText"]" Image="Reconnector.png" Url="reconnector"></ComponentCard>
<ComponentCard Text="@Localizer["ResponsiveText"]" Image="Responsive.png" Url="responsive"></ComponentCard>
<ComponentCard Text="@Localizer["SearchDialogText"]" Image="SearchDialog.png" Url="search-dialog"></ComponentCard>
<ComponentCard Text="@Localizer["SpinnerText"]" Image="Spinner.gif" Url="spinner"></ComponentCard>
<ComponentCard Text="@Localizer["SweetAlertText"]" Image="SweetAlert.png" Url="sweet-alert"></ComponentCard>
<ComponentCard Text="@Localizer["TimerText"]" Image="Timer.png" Url="timer"></ComponentCard>
<ComponentCard Text="@Localizer["ToastText"]" Image="Toast.png" Url="toast"></ComponentCard>
<ComponentCard Text="@Localizer["TooltipText"]" Image="Tooltip.svg" Url="tooltip"></ComponentCard>
</ComponentCategory>
</CascadingValue>
</CascadingValue>
Divider.svg
Layout.svg
Footer.jpg
Row.jpg
Scroll.png
Skeleton.png
Split.png
Anchor.png
AnchorLink.jpg
Breadcrumb.png
Dropdown.svg
FullScreen.jpg
GoTop.png
Logout.png
Menu.svg
Space.svg
Pagination.svg
Steps.svg
Tabs.svg
AutoComplete.svg
AutoFill.jpg
Button.svg
Cascader.png
CheckBox.svg
CheckboxList.png
ColorPicker.jpg
DatePicker.svg
DateTimeRange.png
Editor.png
EditorForm.png
FloatingLabel.jpg
Input.svg
InputNumber.png
InputGroup.png
Markdown.png
MultiSelect.png
OnScreenKeyboard.png
Radio.svg
Rate.jpg
Select.svg
Slider.svg
Switch.svg
Textarea.png
Toggle.png
Transfer.svg
Upload.svg
ValidateForm.png
Avatar.svg
Badge.svg
BarcodeReader.png
Block.jpg
Bluetooth.jpg
Card.svg
Calendar.svg
Camera.png
Captcha.png
Carousel.svg
Client.jpg
Circle.png
Collapse.svg
Display.jpg
Download.png
DropdownWidget.png
Empty.jpg
FileViewer.jpg
Geolocation.jpg
GroupBox.png
Handwritten.jpg
IP.jpg
LinkButton.png
ListView.png
Locator.jpg
Image.png
MindMap.jpg
PdfReader.jpg
Print.jpg
QRCode.png
Recognizer.png
Search.png
SignaturePad.png
SpeechWave.png
LinkButton.png
Table.svg
Tag.svg
Timeline.svg
Title.jpg
Tree.svg
Transition.jpg
VideoPlayer.jpg
FileViewer.jpg
WebSerial.jpg
WebSpeech.jpg
ImageCropper.jpg
BarcodeGenerator.jpg
Mermaid.png
Chart.png
Line.jpg
Bar.jpg
Pie.jpg
Doughnut.jpg
Bubble.jpg
Alert.svg
Console.png
Notification.svg
Dispatch.jpg
Drawer.svg
Notification.svg
Light.png
Message.svg
Modal.svg
Notifications.jpg
Pop-confirm.svg
Popover.svg
Progress.svg
Reconnector.png
Responsive.png
SearchDialog.png
Spinner.gif
SweetAlert.png
Timer.png
Toast.png
Tooltip.svg

View File

@@ -0,0 +1,3 @@
@page "/error-page"
<h3>ErrorPage</h3>

View File

@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Pages;
/// <summary>
/// ErrorPage 组件用于测试全局异常处理功能
/// </summary>
public partial class ErrorPage
{
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
var a = 1;
var b = 0;
// 这里会抛出异常
var c = a / b;
}
}

View File

@@ -77,7 +77,7 @@ return builder.Build();
<p>@Localizer["P24"] <code>~/_Imports.razor</code> @Localizer["P25"] <code>Razor</code> @Localizer["P26"]</p>
<Pre><b>@@using BootstrapBlazor.Components</b></Pre>
<p class="code-label">@Localizer["P27"] <code>BootstrapBlazorRoot</code> @Localizer["P28"] <code>~/Components/Routes.razor</code> @Localizer["P29"]</p>
<p class="code-label">@((MarkupString)Localizer["AddRootText"].Value)</p>
<Pre>&lt;BootstrapBlazorRoot&gt;
&lt;Router AppAssembly="@@typeof(App).Assembly"&gt;
&lt;Found Context="routeData"&gt;

View File

@@ -9,20 +9,19 @@
</ChooseTemplate>
<SheetTemplate>
<ul class="ul-demo">
<li><code>~/Pages/_Host.cshtml</code> <b>.NET5</b></li>
<li><code>~/Pages/_Layout.cshtml</code> <b>.NET6/.NET7</b></li>
<li><code>~/Pages/_Layout.cshtml</code> <b>NET6/NET7</b></li>
<li><code>App.razor</code> <b>NET8/NET9</b></li>
</ul>
</SheetTemplate>
<ScriptsTemplate>
<ul class="ul-demo">
<li><code>~/Pages/_Host.cshtml</code> <b>.NET5</b></li>
<li><code>~/Pages/_Layout.cshtml</code> <b>.NET6/.NET7</b></li>
<li><code>~/Pages/_Layout.cshtml</code> <b>NET6/NET7</b></li>
<li><code>App.razor</code> <b>NET8/NET9</b></li>
</ul>
</ScriptsTemplate>
<ServicesTemplate>
<ul class="ul-demo">
<li><code>Starup.cs</code> <b>.NET5</b></li>
<li><code>Program.cs</code> <b>.NET6/.NET7</b></li>
<li><code>Program.cs</code> <b>NET6/NET7/NET8/NET9</b></li>
</ul>
<p><b>Startup.cs</b></p>
<Pre>namespace MyBlazorAppName
@@ -51,4 +50,21 @@ builder.Services.AddServerSideBlazor();
var app = builder.Build();
//more code may be present here</Pre>
</ServicesTemplate>
<RootTemplate>
<ul class="ul-demo">
<li><code>App.razor</code> <b>NET6/NET7</b></li>
<li><code>MainLayout.razor</code> <b>NET8/NET9</b></li>
</ul>
<Pre>// NET6/NET7
&lt;BootstrapBlazorRoot&gt;
&lt;Router&gt;&lt;Router&gt;
&lt;/BootstrapBlazorRoot&gt;
</Pre>
<Pre>// NET8/NET9
&lt;BootstrapBlazorRoot&gt;
&lt;@@Body
&lt;/BootstrapBlazorRoot&gt;</Pre>
</RootTemplate>
</InstallContent>

View File

@@ -25,18 +25,17 @@
<h5>3. </h5>
<p><code>App.razor</code> <code>head</code> </p>
<Pre class="mb-3">// FontAwesoem 字体样式 注意需要引用 BootstrapBlazor.FontAwesome 包
<Pre class="mb-3">// FontAwesome 字体图标样式 注意需要引用 BootstrapBlazor.FontAwesome 包
&lt;link rel="stylesheet" href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" /&gt;
// 组件样式已集成 Bootstrap 最新版
&lt;link rel="stylesheet" href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" /&gt;
// Motronic 样式可不添加
// Motronic 主题可选建议添加
&lt;link rel="stylesheet" href="_content/BootstrapBlazor/css/motronic.min.css" /&gt;</Pre>
<h5>4. </h5>
<p><code>App.razor</code> <code>body</code> blazor </p>
<Pre class="mb-3">// 添加 BootstrapBlazor 脚本
&lt;script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"&gt;&lt;/script&gt;
&lt;script src="_framework/blazor.web.js"&gt;&lt;/script&gt;</Pre>
&lt;script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"&gt;&lt;/script&gt;</Pre>
<h5>5. </h5>
<p><code>App.razor</code> bootstrap bootstrap open-iconic </p>
@@ -46,7 +45,7 @@
<Pre class="mb-3">// 增加 BootstrapBlazor 服务
builder.Services.AddBootstrapBlazor();</Pre>
<h5>7. </h5>
<h5>7. <code>BootstrapBlazorRoot</code> </h5>
<p> <code>BootstrapBlazorRoot</code> <code>MainLayout</code> 使 <code>BootstrapBlazorRoot</code> </p>
<Pre class="mb-3">&lt;BootstrapBlazorRoot&gt;
&lt;Header&gt;&lt;/Header&gt;

View File

@@ -44,4 +44,15 @@ static async Task SetCultureAsync(WebAssemblyHost host)
}
</Pre>
</ServicesTemplate>
<RootTemplate>
<ul class="ul-demo">
<li><code>App.razor</code> <b>NET6/NET7/NET8/NET9</b></li>
</ul>
<Pre>// NET6/NET7
&lt;BootstrapBlazorRoot&gt;
&lt;Router&gt;&lt;Router&gt;
&lt;/BootstrapBlazorRoot&gt;
</Pre>
</RootTemplate>
</InstallContent>

View File

@@ -1,5 +1,6 @@
@page "/docs"
@page "/introduction"
@page "/components"
<h3>@Localizer["Title"]</h3>

View File

@@ -49,6 +49,14 @@ public sealed partial class Cards
DefaultValue = " — "
},
new()
{
Name = nameof(Card.HeaderPaddingY),
Description = Localizer["HeaderPaddingY"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "Color",
Description = Localizer["Color"],

View File

@@ -3,12 +3,15 @@
<h3>@Localizer["Header"]</h3>
<h4>@Localizer["Tip"]</h4>
<PackageTips Name="BootstrapBlazor.CherryMarkdown" />
<p>@((MarkupString)Localizer["MarkdownsNote"].Value)</p>
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
<DemoBlock Title="@Localizer["NormalTitle"]" Introduction="@Localizer["NormalIntro"]" Name="Normal">
<CherryMarkdown @bind-Value="MarkdownString" @bind-Html="HtmlString" style="height: 400px" />
<CherryMarkdown @bind-Value="MarkdownString" @bind-Html="HtmlString" IsSupportMath="true" style="height: 400px" />
<div class="mt-3">
<textarea class="form-control" rows="6" disabled="disabled">@MarkdownString</textarea>
</div>

View File

@@ -6,13 +6,14 @@
<h4>@Localizer["SubTitle"]</h4>
<DemoBlock Title="@Localizer["BasicUsageTitle"]" Introduction="@Localizer["BasicUsageIntro"]" Name="Normal">
<p>@Localizer["BasicUsageP1"]</p>
<div class="mb-3">
<section ignore>
<p>@Localizer["BasicUsageP1"]</p>
<p class="code-label">@((MarkupString)Localizer["BasicUsageP2"].Value)</p>
<Pre>public void Configure(IApplicationBuilder app)
{
// ...
// 增加下面这一行
// add this line
app.UseBootstrapBlazor();
app.UseEndpoints(endpoints =>
{
@@ -21,13 +22,11 @@
endpoints.MapFallbackToPage("/_Host");
});
}</Pre>
</div>
<Tips>
<p>@((MarkupString)Localizer["BasicUsageTips"].Value)</p>
</Tips>
<Tips>
<p>@((MarkupString)Localizer["BasicUsageTips"].Value)</p>
</Tips>
<div class="mb-3">
<p class="code-label">@((MarkupString)Localizer["BasicUsageP3"].Value)</p>
<Pre>[Inject]
[NotNull]
@@ -44,9 +43,29 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
ClientInfo = await ClientService.GetClientInfo();
StateHasChanged();
}
}
</Pre>
</div>
}</Pre>
<p class="code-label">@((MarkupString)Localizer["BasicUsageP4"].Value)</p>
<p>@((MarkupString)Localizer["LocatorsProviderOptions"].Value)</p>
<p>@((MarkupString)Localizer["LocatorsProviderDesc1"].Value)</p>
<Pre>{
"BootstrapBlazorOptions": {
"WebClientOptions": {
"EnableIpLocator": true
}
}</Pre>
<p>@((MarkupString)Localizer["LocatorsProviderDesc2"].Value)</p>
<Pre>services.AddBootstrapBlazor(op =>
{
op.WebClientOptions.EnableIpLocator = true;
});</Pre>
<p>@((MarkupString)Localizer["LocatorsProviderDesc3"].Value)</p>
<Pre>services.Configure&lt;BootstrapBlazorOptions&gt;(op =>
{
op.WebClientOptions.EnableIpLocator = true;
});</Pre>
</section>
<GroupBox Title="@Localizer["GroupBoxTitle"]">
<p class="code-label">@Localizer["IpLocatorFactoryDesc"] <a href="locator" target="_blank">IpLocatorFactory</a></p>

View File

@@ -19,7 +19,11 @@ public sealed partial class Consoles
/// <summary>
/// OnClear
/// </summary>
private void OnClear() => Messages.Clear();
private Task OnClear()
{
Messages.Clear();
return Task.CompletedTask;
}
/// <summary>
/// GetColor

View File

@@ -25,9 +25,6 @@ public partial class FileViewers
ExcelSampleFile = CombineFilename("sample.xlsx");
FileList.Add("sample.xlsx");
FileList.Add("sample2.xlsx");
FileList.Add("sample3.xlsx");
FileList.Add("sample2.docx");
FileList.Add("sample.docx");
Url = FileList[0];

View File

@@ -5,18 +5,6 @@
<h4>@((MarkupString)Localizer["FlipClocksDescription"].Value)</h4>
<DemoBlock Title="@Localizer["BaseUsageText"]" Introduction="@Localizer["BaseUsageIntro"]" Name="Normal">
<FlipClock></FlipClock>
</DemoBlock>
<DemoBlock Title="@Localizer["ShowMinuteText"]" Introduction="@Localizer["ShowMinuteIntro"]" Name="ShowMinute">
<FlipClock ShowHour="false" ShowMinute="false"></FlipClock>
</DemoBlock>
<DemoBlock Title="@Localizer["ShowSecondText"]" Introduction="@Localizer["ShowSecondIntro"]" Name="ShowSecond">
<FlipClock ShowSecond="false"></FlipClock>
</DemoBlock>
<DemoBlock Title="@Localizer["CountText"]" Introduction="@Localizer["CountIntro"]" Name="Count">
<FlipClock ViewMode="FlipClockViewMode.Count"></FlipClock>
</DemoBlock>
@@ -71,10 +59,50 @@
<Slider @bind-Value="CardGroupMarginValue" Max="28" Min="18"></Slider>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="@Localizer["ShowYear"]" Width="150"></BootstrapInputGroupLabel>
<Switch @bind-Value="_showYear"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="@Localizer["ShowMonth"]" Width="150"></BootstrapInputGroupLabel>
<Switch @bind-Value="_showMonth"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="@Localizer["ShowDay"]" Width="150"></BootstrapInputGroupLabel>
<Switch @bind-Value="_showDay"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="@Localizer["ShowHour"]" Width="150"></BootstrapInputGroupLabel>
<Switch @bind-Value="_showHour"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="@Localizer["ShowMinute"]" Width="150"></BootstrapInputGroupLabel>
<Switch @bind-Value="_showMinute"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="@Localizer["ShowSecond"]" Width="150"></BootstrapInputGroupLabel>
<Switch @bind-Value="_showSecond"></Switch>
</BootstrapInputGroup>
</div>
</div>
</GroupBox>
</section>
<FlipClock BackgroundColor="radial-gradient(ellipse at center, #ac85f1 0%, #833bf8 100%)" Height="@HeightValueString" FontSize="@FontSizeValueString" CardHeight="@CardHeightValueString" CardWidth="@CardWidthValueString" CardMargin="@CardMarginValueString" CardGroupMargin="@CardGroupMarginValueString"></FlipClock>
<FlipClock BackgroundColor="radial-gradient(ellipse at center, #ac85f1 0%, #833bf8 100%)" Height="@HeightValueString"
FontSize="@FontSizeValueString" CardHeight="@CardHeightValueString" CardWidth="@CardWidthValueString"
CardMargin="@CardMarginValueString" CardGroupMargin="@CardGroupMarginValueString"
ShowYear="_showYear" ShowMonth="_showMonth" ShowDay="_showDay"
ShowHour="_showHour" ShowMinute="_showMinute" ShowSecond="_showSecond"></FlipClock>
</DemoBlock>
<AttributeTable Items="@GetAttributes()" />

View File

@@ -36,6 +36,12 @@ public partial class FlipClocks
private bool _isCompleted;
private bool _showYear = false;
private bool _showMonth = false;
private bool _showDay = false;
private bool _showHour = true;
private bool _showMinute = true;
private bool _showSecond = true;
private Task OnCompletedAsync()
{
@@ -48,12 +54,36 @@ public partial class FlipClocks
/// GetAttributes
/// </summary>
/// <returns></returns>
private static AttributeItem[] GetAttributes() =>
private AttributeItem[] GetAttributes() =>
[
new()
{
Name = nameof(FlipClock.ShowYear),
Description = Localizer["ShowYear_Description"],
Type = "boolean",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = nameof(FlipClock.ShowMonth),
Description = Localizer["ShowMonth_Description"],
Type = "boolean",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = nameof(FlipClock.ShowDay),
Description = Localizer["ShowDay_Description"],
Type = "boolean",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = nameof(FlipClock.ShowHour),
Description = "是否显示小时",
Description = Localizer["ShowHour_Description"],
Type = "boolean",
ValueList = "true|false",
DefaultValue = "true"
@@ -61,7 +91,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.ShowMinute),
Description = "是否显示分钟",
Description = Localizer["ShowMinute_Description"],
Type = "boolean",
ValueList = "true|false",
DefaultValue = "true"
@@ -69,7 +99,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.ViewMode),
Description = "是否显示分钟",
Description = Localizer["ViewMode_Description"],
Type = "FlipClockViewMode",
ValueList = "DateTime|Count|CountDown",
DefaultValue = "DateTime"
@@ -77,7 +107,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.StartValue),
Description = "开始时间",
Description = Localizer["StartValue_Description"],
Type = "TimeSpan",
ValueList = " — ",
DefaultValue = " — "
@@ -85,7 +115,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.OnCompletedAsync),
Description = "计时结束回调方法",
Description = Localizer["OnCompletedAsync_Description"],
Type = "Func<Task>",
ValueList = " — ",
DefaultValue = " — "
@@ -93,7 +123,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.Height),
Description = "组件高度",
Description = Localizer["Height_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -101,7 +131,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.BackgroundColor),
Description = "组件背景色",
Description = Localizer["BackgroundColor_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -109,7 +139,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.FontSize),
Description = "组件字体大小",
Description = Localizer["FontSize_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -117,7 +147,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.CardWidth),
Description = "组件卡片宽度",
Description = Localizer["CardWidth_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -125,7 +155,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.CardHeight),
Description = "组件卡片高度",
Description = Localizer["CardHeight_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -133,7 +163,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.CardColor),
Description = "组件卡片字体颜色",
Description = Localizer["CardColor_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -141,7 +171,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.CardBackgroundColor),
Description = "组件卡片背景颜色",
Description = Localizer["CardBackgroundColor_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -149,7 +179,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.CardDividerHeight),
Description = "组件卡片分割线高度",
Description = Localizer["CardDividerHeight_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -157,7 +187,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.CardDividerColor),
Description = "组件卡片分割线颜色",
Description = Localizer["CardDividerColor_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -165,7 +195,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.CardMargin),
Description = "组件卡片间隔",
Description = Localizer["CardMargin_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
@@ -173,7 +203,7 @@ public partial class FlipClocks
new()
{
Name = nameof(FlipClock.CardGroupMargin),
Description = "组件卡片组间隔",
Description = Localizer["CardGroupMargin_Description"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "

View File

@@ -72,4 +72,8 @@
<Button Icon="fa-solid fa-font-awesome" Text="@Localizer["DialogText"]" OnClick="OnShowDialog" />
</DemoBlock>
<DemoBlock Title="@Localizer["PageErrorTitle"]" Introduction="@Localizer["PageErrorIntro"]" Name="Page">
<Button Icon="fa-solid fa-font-awesome" Text="@Localizer["ButtonText"]" OnClick="OnGotoPage" />
</DemoBlock>
<AttributeTable Items="@GetAttributes()" />

View File

@@ -10,6 +10,10 @@ namespace BootstrapBlazor.Server.Components.Samples;
/// </summary>
public partial class GlobalException
{
[Inject]
[NotNull]
private NavigationManager? NavigationManager { get; set; }
[Inject]
[NotNull]
private SwalService? SwalService { get; set; }
@@ -19,7 +23,6 @@ public partial class GlobalException
private static void OnClick()
{
// NET6.0 采用 ErrorLogger 统一处理
var a = 0;
_ = 1 / a;
}
@@ -39,6 +42,12 @@ public partial class GlobalException
Component = BootstrapDynamicComponent.CreateComponent<MockError>()
});
private Task OnGotoPage()
{
NavigationManager.NavigateTo("/error-page");
return Task.CompletedTask;
}
/// <summary>
/// 获得属性方法
/// </summary>

View File

@@ -7,34 +7,102 @@
<PackageTips Name="BootstrapBlazor.ImageCropper" />
<DemoBlock Title="@Localizer["ImageCropperNormalText"]" Introduction="@Localizer["ImageCropperNormalIntro"]" Name="Normal">
<ImageCropper @ref="_cropper" Url="@_images[0]"></ImageCropper>
<ImageCropper @ref="_cropper" Url="@_images[0]" Options="_roundOptions1" OnCropChangedAsync="OnCropChangedAsync"></ImageCropper>
<section ignore>
<BootstrapInputGroup>
<Button Text="OK" OnClick="Crop" />
<Button Text="@Localizer["ImageCropperResetText"]" OnClick="_cropper.Reset" />
<Button Text="@Localizer["ImageCropperReplaceText"]" OnClick="OnClickReplace" />
<Button Text="@Localizer["ImageCropperRotateText"]" OnClick="Rotate" />
<Button Text="@Localizer["ImageCropperEnableText"]" OnClick="_cropper.Enable" />
<Button Text="@Localizer["ImageCropperDisabledText"]" OnClick="_cropper.Disable" />
<Button Text="@Localizer["ImageCropperClearText"]" OnClick="_cropper.Clear" />
<Button Text="OK" OnClick="Crop"></Button>
<Button Text="@Localizer["ImageCropperResetText"]" OnClick="_cropper.Reset"></Button>
<Button Text="@Localizer["ImageCropperReplaceText"]" OnClick="OnClickReplace"></Button>
<Button Text="@Localizer["ImageCropperRotateText"]" OnClick="Rotate"></Button>
<Button Text="@Localizer["ImageCropperEnableText"]" OnClick="_cropper.Enable"></Button>
<Button Text="@Localizer["ImageCropperDisabledText"]" OnClick="_cropper.Disable"></Button>
<Button Text="@Localizer["ImageCropperClearText"]" OnClick="_cropper.Clear"></Button>
</BootstrapInputGroup>
<div class="d-flex mt-3" style="gap: 0.5rem;">
<div class="bb-cropper-preview bb-cropper-preview1 bb-cropper-preview-lg"></div>
<div class="bb-cropper-preview bb-cropper-preview1 bb-cropper-preview-md"></div>
<div class="bb-cropper-preview bb-cropper-preview1 bb-cropper-preview-sm"></div>
<div class="bb-cropper-preview bb-cropper-preview1 bb-cropper-preview-xs"></div>
</div>
<div class="row g-3 mt-3">
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="X"></BootstrapInputGroupLabel>
<Display Value="_data.X"></Display>
<BootstrapInputGroupLabel DisplayText="px"></BootstrapInputGroupLabel>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Y"></BootstrapInputGroupLabel>
<Display Value="_data.Y"></Display>
<BootstrapInputGroupLabel DisplayText="px"></BootstrapInputGroupLabel>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Width"></BootstrapInputGroupLabel>
<Display Value="_data.Width"></Display>
<BootstrapInputGroupLabel DisplayText="px"></BootstrapInputGroupLabel>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Height"></BootstrapInputGroupLabel>
<Display Value="_data.Height"></Display>
<BootstrapInputGroupLabel DisplayText="px"></BootstrapInputGroupLabel>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Rotate"></BootstrapInputGroupLabel>
<Display Value="_data.Rotate"></Display>
<BootstrapInputGroupLabel DisplayText="deg"></BootstrapInputGroupLabel>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 d-none d-sm-flex">
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ScaleX"></BootstrapInputGroupLabel>
<Display Value="_data.ScaleX"></Display>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ScaleY"></BootstrapInputGroupLabel>
<Display Value="_data.ScaleY"></Display>
</BootstrapInputGroup>
</div>
</div>
@if (!string.IsNullOrEmpty(_base64String))
{
<img src="@_base64String" style="width: 240px;" />
<Textarea Value="@_base64String" rows="3" class="mt-3" />
<img src="@_base64String" style="width: 240px; margin-top: 1rem;" />
<Textarea Value="@_base64String" rows="3" class="mt-3"></Textarea>
}
</section>
</DemoBlock>
<DemoBlock Title="@Localizer["ImageCropperNormalText"]" Introduction="@Localizer["ImageCropperNormalIntro"]" Name="Normal">
<ImageCropper @ref="_roundCropper" Url="@_images[0]" CropperShape="ImageCropperShape.Round" Options="_roundOptions" />
<DemoBlock Title="@Localizer["ImageCropperRoundText"]" Introduction="@Localizer["ImageCropperRoundIntro"]" Name="Round">
<ImageCropper @ref="_roundCropper" Url="@_images[0]" Options="_roundOptions2"></ImageCropper>
<section ignore>
<BootstrapInputGroup>
<Button Text="OK" OnClick="RoundCrop" />
<Button Text="OK" OnClick="RoundCrop"></Button>
</BootstrapInputGroup>
<div class="d-flex mt-3" style="gap: 0.5rem;">
<div class="bb-cropper-preview bb-cropper-preview-round bb-cropper-preview-lg"></div>
<div class="bb-cropper-preview bb-cropper-preview-round bb-cropper-preview-md"></div>
<div class="bb-cropper-preview bb-cropper-preview-round bb-cropper-preview-sm"></div>
<div class="bb-cropper-preview bb-cropper-preview-round bb-cropper-preview-xs"></div>
</div>
@if (!string.IsNullOrEmpty(_base64String2))
{
<img src="@_base64String2" style="width: 240px;" />
<img src="@_base64String2" style="width: 240px; margin-top: 1rem;" />
}
</section>
</DemoBlock>

View File

@@ -22,7 +22,9 @@ public partial class ImageCroppers
private string? _base64String2;
private readonly ImageCropperOption _roundOptions = new() { IsRound = true, Radius = "50%" };
private readonly ImageCropperOption _roundOptions1 = new() { AspectRatio = 16 / 9f, Preview = ".bb-cropper-preview1" };
private readonly ImageCropperOption _roundOptions2 = new() { IsRound = true, Preview = ".bb-cropper-preview-round" };
/// <summary>
/// <inheritdoc/>
@@ -56,11 +58,15 @@ public partial class ImageCroppers
private Task Rotate() => _cropper.Rotate(90);
/// <summary>
/// GetAttributes
/// </summary>
/// <returns></returns>
protected AttributeItem[] GetAttributes() =>
private ImageCropperData _data = new();
private Task OnCropChangedAsync(ImageCropperData data)
{
_data = data;
StateHasChanged();
return Task.CompletedTask;
}
private AttributeItem[] GetAttributes() =>
[
new()
{

View File

@@ -116,7 +116,7 @@
Name="PreviewList">
<div class="images img-ph mt-3">
<div class="images-item">
<ImageViewer Url="@WebsiteOption.CurrentValue.GetAssetUrl("images/bird.jpeg")" PreviewList="PreviewList" />
<ImageViewer Url="@WebsiteOption.CurrentValue.GetAssetUrl("images/bird.jpeg")" PreviewList="PreviewList" ZoomSpeed="0.5" />
</div>
</div>
</DemoBlock>

View File

@@ -17,6 +17,7 @@ private IIpLocatorFactory? IpLocatorFactory { get; set; }
</Tips>
<Pre>Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)</Pre>
<p><b>@Localizer["LocatorsNormalExtendDescription"]</b></p>
<p><b>@Localizer["LocatorsNormalExtend1"]</b></p>
<Pre>private class CustomerLocatorProvider : DefaultIpLocatorProvider
{
@@ -25,9 +26,15 @@ private IIpLocatorFactory? IpLocatorFactory { get; set; }
throw new NotImplementedException();
}
}</Pre>
<p><b>@Localizer["LocatorsNormalExtend2"]</b></p>
<Pre>services.AddSingleton&lt;IIpLocatorProvider, CustomerLocatorProvider&gt;();</Pre>
<p>@((MarkupString)Localizer["LocatorsNormalCustomerLocator"].Value)</p>
<p><b>@Localizer["LocatorsNormalExtend3"]</b></p>
<Pre>var provider = IpLocatorFactory.Create(ProviderName);
Location = await provider.Locate(Ip);</Pre>
<p>@Localizer["LocatorsNormalIpTitle"]</p>
<p><code>112.224.74.239</code> @Localizer["LocatorsNormalTips3"]</p>
<p><code>183.160.236.53</code> @Localizer["LocatorsNormalTips4"]</p>

View File

@@ -0,0 +1,24 @@
@page "/meet"
<h3>JitsiMeet会议</h3>
<h4>JitsiMeet创建会议</h4>
<PackageTips Name="BootstrapBlazor.JitsiMeet" />
<Tips class="mt-3">
<p>JitsiMeet是一个开源的WebRTC会议程序使25MAUJitsiMeet的客户端程序</p>
<p>5</p>
</Tips>
<DemoBlock Title="使用JitsiMeet创建会议室" Introduction="使用JitsiMeet创建会议室支持执行命令支持OnLoad回调meet.jit.si不会触发回调也不会响应命令请使用8x8.vc或子托管域名测试。例子中隐藏了内置的邀请程序无法在会议中找到邀请链接。" Name="Normal">
<section class="row form-inline g-3">
<div class="col-12 col-sm-6">
<Display Value="_domain" DisplayText="服务器地址" ShowLabel="true"></Display>
</div>
<div class="col-12 col-sm-6">
<Button OnClick="RunCommand"></Button>
</div>
</section>
<Meet @ref="@_meet" Option="@_option" Domain="@_domain" OnLoad="OnLoad"></Meet>
</DemoBlock>

View File

@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// Meet 视频会议组件示例
/// </summary>
public partial class Meets : ComponentBase
{
private MeetOption? _option;
private Meet? _meet;
private readonly string _domain = "meet.jit.si";
[Inject, NotNull]
private ToastService? ToastService { get; set; }
/// <summary>
/// <inheritdoc />
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
_option = new MeetOption
{
RoomName = "BootstrapBlazor",
Width = "100%",
Height = 700,
ConfigOverwrite = new
{
Lobby = new { EnableChat = false },
HiddenPremeetingButtons = new string[] { "invite" },
DisableInviteFunctions = true,
ButtonsWithNotifyClick = new[] { new { key = "invite", preventExecution = true } }
},
UserInfo = new UserInfo() { DisplayName = "BootstrapBlazor", Email = "a@blazor.zone" }
};
}
private void OnLoad()
{
ToastService.Information("Meet 示例", "会议室加载完成");
}
private async Task RunCommand()
{
if (_meet != null)
{
await _meet.ExecuteCommand("toggleChat");
}
}
}

View File

@@ -351,6 +351,14 @@ private enum MultiSelectEnumFoo
</div>
</DemoBlock>
<DemoBlock Title="@Localizer["MultiSelectGenericTitle"]" Introduction="@Localizer["MultiSelectGenericIntro"]" Name="Generic">
<div class="row">
<div class="col-12">
<MultiSelectGeneric Items="@FooItems" @bind-Value="_genericValue" ShowSearch="true" IsPopover="true"></MultiSelectGeneric>
</div>
</div>
</DemoBlock>
<AttributeTable Items="@GetAttributes()"></AttributeTable>
<EventTable Items="@GetEvents()"></EventTable>

View File

@@ -125,6 +125,11 @@ public partial class MultiSelects
private bool _showToolbar = true;
private bool _showSearch = true;
[NotNull]
private List<SelectedItem<Foo>>? FooItems { get; set; }
private List<Foo>? _genericValue = null;
private async Task<SelectedItem> OnEditCallback(string value)
{
await Task.Delay(100);
@@ -188,6 +193,7 @@ public partial class MultiSelects
Items8 = GenerateItems();
TemplateItems = GenerateItems();
EditableItems = GenerateItems();
FooItems = [.. Foo.GenerateFoo(LocalizerFoo).Select(i => new SelectedItem<Foo>(i, i.Name!))];
// 初始化数据
DataSource =

View File

@@ -0,0 +1,23 @@
@page "/network-monitor"
@inject IStringLocalizer<NetworkMonitors> Localizer
<h3>@Localizer["NetworkMonitorTitle"]</h3>
<h4>@((MarkupString)Localizer["NetworkMonitorDescription"].Value)</h4>
<DemoBlock Title="@Localizer["NormalTitle"]"
Introduction="@Localizer["NormalIntro"]"
Name="Normal">
<section ignore>
<ul class="ul-demo">
<li><div class="demo-indicator demo-indicator-4g"></div> @Localizer["IndicatorLi1"]</li>
<li><div class="demo-indicator demo-indicator-3g"></div> @Localizer["IndicatorLi2"]</li>
<li><div class="demo-indicator demo-indicator-2g"></div> @Localizer["IndicatorLi3"]</li>
<li><div class="demo-indicator demo-indicator-offline"></div> @Localizer["IndicatorLi4"]</li>
</ul>
</section>
<NetworkMonitorIndicator></NetworkMonitorIndicator>
<section ignore>
<ConsoleLogger @ref="_logger"></ConsoleLogger>
</section>
</DemoBlock>

View File

@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// NetworkMonitor Sample code
/// </summary>
public partial class NetworkMonitors : IDisposable
{
[Inject, NotNull]
private INetworkMonitorService? NetworkMonitorService { get; set; }
private ConsoleLogger? _logger;
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await NetworkMonitorService.RegisterStateChangedCallback(this, OnNetworkStateChanged);
}
private Task OnNetworkStateChanged(NetworkMonitorState state)
{
_logger?.Log($"Online: NetworkType: {state.NetworkType} Downlink: {state.Downlink} RTT: {state.RTT}");
return Task.CompletedTask;
}
private void Dispose(bool disposing)
{
if (disposing)
{
NetworkMonitorService.UnregisterStateChangedCallback(this);
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,29 @@
.ul-demo {
list-style: none;
padding-left: 0;
}
.demo-indicator {
cursor: pointer;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
display: inline-block;
margin-inline-end: .5rem;
}
.demo-indicator-4g {
background-color: var(--bs-primary);
}
.demo-indicator-3g {
background-color: var(--bs-warning);
}
.demo-indicator-2g {
background-color: var(--bs-danger);
}
.demo-indicator-offline {
background-color: var(--bs-secondary);
}

View File

@@ -0,0 +1,15 @@
@page "/office-viewer"
@inject IStringLocalizer<OfficeViewers> Localizer
<h3>@Localizer["OfficeViewerTitle"]</h3>
<h4>@Localizer["OfficeViewerDescription"]</h4>
<PackageTips Name="BootstrapBlazor.OfficeViewer" />
<DemoBlock Title="@Localizer["OfficeViewerNormalTitle"]" Introduction="@Localizer["OfficeViewerNormalIntro"]" Name="Normal">
<section ignore>
<Select @bind-Value="@_doc" Items="_docs"></Select>
</section>
<OfficeViewer Url="@_doc" Height="620px" OnLoaded="OnLoaded"></OfficeViewer>
</DemoBlock>

View File

@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// PdfViewers
/// </summary>
public partial class OfficeViewers
{
[Inject, NotNull]
private ToastService? ToastService { get; set; }
private readonly List<SelectedItem> _docs =
[
new SelectedItem("https://www.blazor.zone/samples/sample.docx", "sample.docx"),
new SelectedItem("https://www.blazor.zone/samples/sample.xlsx", "sample.xlsx"),
new SelectedItem("https://www.blazor.zone/samples/sample.pptx", "sample.pptx"),
];
private string _doc = "https://www.blazor.zone/samples/sample.docx";
private Task OnLoaded() => ToastService.Success("Office Documentation Viewer", Localizer["OfficeViewerToastSuccessfulContent"]);
}

View File

@@ -10,6 +10,11 @@
.otp-input-demo {
text-align: center;
}
.otp-input-demo .bb-otp-input {
--bb-otp-item-width: 32px;
--bb-otp-font-size: 1.2em;
}
</style>
</HeadContent>

View File

@@ -0,0 +1,24 @@
@page "/pdf-viewer"
@inject IStringLocalizer<PdfViewers> Localizer
<h3>@Localizer["PdfViewerTitle"]</h3>
<h4>@Localizer["PdfViewerDescription"]</h4>
<PackageTips Name="BootstrapBlazor.PdfViewer" />
<DemoBlock Title="@Localizer["PdfViewerNormalTitle"]" Introduction="@Localizer["PdfViewerNormalIntro"]" Name="Normal">
<section ignore>
<div class="row g-3">
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="UseGoogleDocs"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_useGoogleDocs"></Switch>
</BootstrapInputGroup>
</div>
</div>
</section>
<PdfViewer Url="./samples/sample.pdf" Height="620px"
NotSupportCallback="NotSupportCallback" OnLoaded="OnLoaded"
UseGoogleDocs="@_useGoogleDocs"></PdfViewer>
</DemoBlock>

View File

@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.ComponentModel;
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// PdfViewers
/// </summary>
public partial class PdfViewers
{
[Inject, NotNull]
private ToastService? ToastService { get; set; }
private bool _useGoogleDocs = false;
private Task OnLoaded() => ToastService.Success("Pdf Viewer", Localizer["PdfViewerToastSuccessfulContent"]);
private Task NotSupportCallback() => ToastService.Error("PdfViewer", Localizer["PdfViewerToastNotSupportContent"]);
}

View File

@@ -29,7 +29,7 @@
<DemoBlock Title="@Localizer["ProgressDisplayValueTitle"]"
Introduction="@Localizer["ProgressDisplayValueIntro"]"
Name="DisplayValue">
<BootstrapBlazor.Components.Progress Value="25" IsShowValue="true"></BootstrapBlazor.Components.Progress>
<BootstrapBlazor.Components.Progress Value="25.5" IsShowValue="true"></BootstrapBlazor.Components.Progress>
</DemoBlock>
<DemoBlock Title="@Localizer["ProgressHeightTitle"]"

View File

@@ -428,7 +428,8 @@
<section ignore>@((MarkupString)Localizer["SelectsGenericDesc"].Value)</section>
<div class="row">
<div class="col-12 col-sm-6">
<SelectGeneric Items="_genericItems" @bind-Value="_selectedFoo" IsEditable="true"></SelectGeneric>
<SelectGeneric Items="_genericItems" @bind-Value="_selectedFoo"
IsEditable="true" TextConvertToValueCallback="TextConvertToValueCallback"></SelectGeneric>
</div>
<div class="col-12 col-sm-6">
<Display Value="_selectedFoo.Address"></Display>

View File

@@ -251,6 +251,27 @@ public sealed partial class SelectGenerics
private Foo _selectedFoo = new();
private async Task<Foo> TextConvertToValueCallback(string v)
{
// 模拟异步通讯切换线程
await Task.Delay(10);
Foo? foo = null;
var item = _genericItems.Find(i => i.Text == v);
if (item == null)
{
var id = _genericItems.Count + 1;
foo = new Foo() { Id = id, Address = $"New Address - {id}" };
var fooItem = new SelectedItem<Foo> { Text = v, Value = foo };
_genericItems.Add(fooItem);
}
else
{
foo = item.Value;
}
return foo!;
}
/// <summary>
/// 获得事件方法
/// </summary>

View File

@@ -364,9 +364,15 @@
<DemoBlock Title="@Localizer["SelectsConfirmSelectTitle"]"
Introduction="@Localizer["SelectsConfirmSelectIntro"]"
Name="ConfirmSelect">
<section ignore>
<ul class="ul-demo">
<li>@((MarkupString)Localizer["SelectConfifrmSelectDesc1"].Value)</li>
<li>@((MarkupString)Localizer["SelectConfifrmSelectDesc2"].Value)</li>
</ul>
</section>
<div class="row">
<div class="col-12 col-sm-6">
<Select TValue="string" Items="Items" OnBeforeSelectedItemChange="@OnBeforeSelectedItemChange"
<Select TValue="string" Items="Items" ShowSwal="true"
SwalTitle="@Localizer["SwalTitle"]" SwalContent="@Localizer["SwalContent"]" SwalFooter="@Localizer["SwalFooter"]" />
</div>
</div>

View File

@@ -0,0 +1,85 @@
@page "/socket-factory"
@inject IStringLocalizer<SocketFactories> Localizer
<h3>Tcp <code>ITcpSocketFactory</code></h3>
<h4> Socket </h4>
<p class="code-label">1. </p>
<Pre>services.AddBootstrapBlazorTcpSocketFactory();</Pre>
<p class="code-label">2. 使</p>
<p> <code>TcpSocketFactory</code> <code>GetOrCreate</code> <code>ITcpSocketClient</code> <code>ITcpSocketClient</code> </p>
<Pre>[Inject]
[NotNull]
private ITcpSocketFactory? TcpSocketFactory { get; set; }</Pre>
<Pre>var client = TcpSocketFactory.GetOrCreate("bb", options =>
{
options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
});</Pre>
<p class="code-label">3. 使</p>
<ul class="ul-demo">
<li> <code>ITcpSocketClient</code> <code>ConnectAsync</code> </li>
<li> <code>ITcpSocketClient</code> <code>SendAsync</code> </li>
<li> <code>ITcpSocketClient</code> <code>Close</code> </li>
<li> <code>ITcpSocketClient</code> <code>SetDataHandler</code> </li>
<li> <code>ITcpSocketClient</code> <code>ReceivedCallBack</code> </li>
</ul>
<p class="code-label">4. </p>
<p></p>
<p> <b>4</b> </p>
<ul class="ul-demo">
<li><b></b> <b>1234</b> <b>123412</b> <b>12</b> 4 <b></b></li>
<li><b></b> <b>1234</b> <b>12</b> <b>34</b><b></b></li>
</ul>
<p> <code>IDataPackageHandler</code> <code>DataPackageHandlerBase</code> <b></b> <b></b> </p>
<p>使</p>
<Pre>[Inject]
[NotNull]
private ITcpSocketFactory? TcpSocketFactory { get; set; }
private async Task CreateClient()
{
// 创建 ITcpSocketClient 实例
var client = TcpSocketFactory.GetOrCreate("localhost", 0);
// 设置 FixLengthDataPackageHandler 数据处理器处理数据定长 4 的数据
client.SetDataHandler(new FixLengthDataPackageHandler(4)
{
// 设置接收数据回调方法
ReceivedCallBack = buffer =>
{
// 内部自动处理粘包分包问题,这里参数 buffer 一定是定长为 4 的业务数据
receivedBuffer = buffer;
return Task.CompletedTask;
}
});
// 连接远端节点 连接成功后自动开始接收数据
var connected = await client.ConnectAsync("192.168.10.100", 6688);
}
</Pre>
<p></p>
<ul class="ul-demo">
<li><code>FixLengthDataPackageHandler</code> <b></b> </li>
<li><code>DelimiterDataPackageHandler</code> <b></b> </li>
</ul>
<p class="code-label">5. </p>
<p> <code>Class</code> <code>Struct</code></p>
<p></p>

View File

@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// ISocketFactory 服务说明文档
/// </summary>
public partial class SocketFactories
{
}

View File

@@ -0,0 +1,75 @@
@page "/socket/adapter"
@inject IStringLocalizer<Adapters> Localizer
<HeadContent>
<style>
:root {
--bb-row-label-width: 180px;
}
</style>
</HeadContent>
<h3>@Localizer["AdaptersTitle"]</h3>
<h4>@Localizer["AdaptersDescription"]</h4>
<Notice></Notice>
<DemoBlock Title="@Localizer["NormalTitle"]"
Introduction="@Localizer["NormalIntro"]"
Name="Normal" ShowCode="false">
<p></p>
<p> <code>SocketClientOptions</code> <code>IsAutoReceive="false"</code></p>
<Pre>_client = TcpSocketFactory.GetOrCreate("demo-adapter", options =>
{
options.IsAutoReceive = false;
options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
});</Pre>
<ul class="ul-demo">
<li> <b></b> <code>ITcpSocketFactory</code> <code>ITcpSocketClient</code> <code>TcpServer</code></li>
<li> <b></b> <code>CloseAsync</code> Socket </li>
<li> <b></b> <code>SendAsync</code> </li>
</ul>
<p class="code-label"></p>
<p></p>
<ul class="ul-demo">
<li> <code>Header+ Body</code> 12 </li>
<li> 4 8 </li>
<li></li>
<li> <code>Header+ Body</code> 12 </li>
<li> 4 8 </li>
<li></li>
</ul>
<p> <b></b> <b>使</b> </p>
<ul class="ul-demo">
<li>使 <b></b> </li>
<li>使 <b></b> </li>
</ul>
<Pre>private readonly DataPackageAdapter _dataAdapter = new()
{
// 数据适配器内部使用固定长度数据处理器
DataPackageHandler = new FixLengthDataPackageHandler(12)
};
_dataAdapter.ReceivedCallBack += async Data =>
{
// 此处接收到的数据 Data 为完整响应数据
};</Pre>
<div class="row form-inline g-3">
<div class="col-12">
<Switch ShowLabel="true" DisplayText="是否使用数据适配器" @bind-Value="_useDataAdapter"></Switch>
</div>
<div class="col-12 col-sm-6">
<Button Text="连接" Icon="fa-solid fa-play"
OnClick="OnConnectAsync" IsDisabled="@_client.IsConnected"></Button>
<Button Text="断开" Icon="fa-solid fa-stop" class="ms-2"
OnClick="OnCloseAsync" IsDisabled="@(!_client.IsConnected)"></Button>
<Button Text="发送" Icon="fa-solid fa-paper-plane" class="ms-2" IsAsync="true"
OnClick="OnSendAsync" IsDisabled="@(!_client.IsConnected)"></Button>
</div>
<div class="col-12">
<Console Items="@_items" Height="496" HeaderText="模拟通讯示例"
ShowAutoScroll="true" OnClear="@OnClear"></Console>
</div>
</div>
</DemoBlock>

View File

@@ -0,0 +1,172 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.Net;
using System.Text;
namespace BootstrapBlazor.Server.Components.Samples.Sockets;
/// <summary>
/// 数据适配器示例
/// </summary>
public partial class Adapters : IDisposable
{
[Inject, NotNull]
private ITcpSocketFactory? TcpSocketFactory { get; set; }
private ITcpSocketClient _client = null!;
private List<ConsoleMessageItem> _items = [];
private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8900);
private readonly CancellationTokenSource _connectTokenSource = new();
private readonly CancellationTokenSource _sendTokenSource = new();
private readonly CancellationTokenSource _receiveTokenSource = new();
private readonly DataPackageAdapter _dataAdapter = new()
{
DataPackageHandler = new FixLengthDataPackageHandler(12)
};
private bool _useDataAdapter = true;
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
// 从服务中获取 ITcpSocketClient 实例
_client = TcpSocketFactory.GetOrCreate("demo-adapter", options =>
{
// 关闭自动接收功能
options.IsAutoReceive = true;
// 设置本地使用的 IP地址与端口
options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
});
_client.ReceivedCallBack += OnReceivedAsync;
_dataAdapter.ReceivedCallBack += async Data =>
{
// 直接处理接收的数据
await UpdateReceiveLog(Data);
};
}
private async Task OnConnectAsync()
{
if (_client is { IsConnected: false })
{
await _client.ConnectAsync(_serverEndPoint, _connectTokenSource.Token);
var state = _client.IsConnected ? "成功" : "失败";
_items.Add(new ConsoleMessageItem()
{
Message = $"{DateTime.Now}: 连接 {_client.LocalEndPoint} - {_serverEndPoint} {state}",
Color = _client.IsConnected ? Color.Success : Color.Danger
});
}
}
private async Task OnSendAsync()
{
if (_client is { IsConnected: true })
{
// 准备通讯数据
var data = new byte[12];
"2025"u8.CopyTo(data);
Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")).CopyTo(data, 4);
var result = await _client.SendAsync(data, _sendTokenSource.Token);
var state = result ? "成功" : "失败";
// 记录日志
_items.Add(new ConsoleMessageItem()
{
Message = $"{DateTime.Now}: 发送数据 {_client.LocalEndPoint} - {_serverEndPoint} Data: {BitConverter.ToString(data)} {state}"
});
}
}
private async ValueTask OnReceivedAsync(ReadOnlyMemory<byte> data)
{
if (_useDataAdapter)
{
// 使用数据适配器处理接收的数据
await _dataAdapter.HandlerAsync(data, _receiveTokenSource.Token);
}
else
{
// 直接处理接收的数据
await UpdateReceiveLog(data);
}
}
private async Task UpdateReceiveLog(ReadOnlyMemory<byte> data)
{
var payload = System.Text.Encoding.UTF8.GetString(data.Span);
var body = BitConverter.ToString(data.ToArray());
_items.Add(new ConsoleMessageItem
{
Message = $"{DateTime.Now}: 接收数据 {_client.LocalEndPoint} - {_serverEndPoint} Data: {payload} HEX: {body}",
Color = Color.Success
});
// 保持队列中最大数量为 50
if (_items.Count > 50)
{
_items.RemoveAt(0);
}
await InvokeAsync(StateHasChanged);
}
private async Task OnCloseAsync()
{
if (_client is { IsConnected: true })
{
await _client.CloseAsync();
var state = _client.IsConnected ? "失败" : "成功";
_items.Add(new ConsoleMessageItem()
{
Message = $"{DateTime.Now}: 关闭 {_client.LocalEndPoint} - {_serverEndPoint} {state}",
Color = _client.IsConnected ? Color.Danger : Color.Success
});
}
}
private Task OnClear()
{
_items = [];
return Task.CompletedTask;
}
private void Dispose(bool disposing)
{
if (disposing)
{
_client.ReceivedCallBack -= OnReceivedAsync;
// 释放连接令牌资源
_connectTokenSource.Cancel();
_connectTokenSource.Dispose();
// 释放发送令牌资源
_sendTokenSource.Cancel();
_sendTokenSource.Dispose();
// 释放接收令牌资源
_receiveTokenSource.Cancel();
_receiveTokenSource.Dispose();
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,44 @@
@page "/socket/auto-receive"
@inject IStringLocalizer<AutoReceives> Localizer
<h3>@Localizer["ReceivesTitle"]</h3>
<h4>@Localizer["ReceivesDescription"]</h4>
<Notice></Notice>
<DemoBlock Title="@Localizer["NormalTitle"]"
Introduction="@Localizer["NormalIntro"]"
Name="Normal" ShowCode="false">
<p> 10s </p>
<ul class="ul-demo">
<li> <b></b> <code>ITcpSocketFactory</code> <code>ITcpSocketClient</code> <code>TcpServer</code></li>
<li> <b></b> <code>CloseAsync</code> Socket </li>
</ul>
<p>使 <code>ReceivedCallBack</code> <code>+=</code> </p>
<Pre>_client.ReceivedCallBack += OnReceivedAsync;</Pre>
<p> <code>IDisposable</code> <code>IAsyncDisposable</code> <code>Dispose</code> <code>DisposeAsync</code> </p>
<Pre>private void Dispose(bool disposing)
{
if (disposing)
{
if (_client is { IsConnected: true })
{
_client.ReceivedCallBack -= OnReceivedAsync;
}
}
}</Pre>
<div class="row form-inline g-3">
<div class="col-12 col-sm-6">
<Button Text="连接" Icon="fa-solid fa-play"
OnClick="OnConnectAsync" IsDisabled="@_client.IsConnected"></Button>
<Button Text="断开" Icon="fa-solid fa-stop" class="ms-2"
OnClick="OnCloseAsync" IsDisabled="@(!_client.IsConnected)"></Button>
</div>
<div class="col-12">
<Console Items="@_items" Height="496" HeaderText="接收数据(间隔 10 秒)"
ShowAutoScroll="true" OnClear="@OnClear"></Console>
</div>
</div>
</DemoBlock>

View File

@@ -0,0 +1,97 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.Net;
namespace BootstrapBlazor.Server.Components.Samples.Sockets;
/// <summary>
/// 接收电文示例
/// </summary>
public partial class AutoReceives : IDisposable
{
[Inject, NotNull]
private ITcpSocketFactory? TcpSocketFactory { get; set; }
private ITcpSocketClient _client = null!;
private List<ConsoleMessageItem> _items = [];
private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8800);
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
// 从服务中获取 Socket 实例
_client = TcpSocketFactory.GetOrCreate("demo-auto-receive", options =>
{
options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
});
_client.ReceivedCallBack += OnReceivedAsync;
}
private async Task OnConnectAsync()
{
if (_client is { IsConnected: false })
{
await _client.ConnectAsync(_serverEndPoint, CancellationToken.None);
}
}
private async Task OnCloseAsync()
{
if (_client is { IsConnected: true })
{
await _client.CloseAsync();
}
}
private Task OnClear()
{
_items = [];
return Task.CompletedTask;
}
private async ValueTask OnReceivedAsync(ReadOnlyMemory<byte> data)
{
// 将数据显示为十六进制字符串
var payload = System.Text.Encoding.UTF8.GetString(data.Span);
_items.Add(new ConsoleMessageItem
{
Message = $"接收到来自站点的数据为 {payload}"
});
// 保持队列中最大数量为 50
if (_items.Count > 50)
{
_items.RemoveAt(0);
}
await InvokeAsync(StateHasChanged);
}
private void Dispose(bool disposing)
{
if (disposing)
{
if (_client is { IsConnected: true })
{
_client.ReceivedCallBack -= OnReceivedAsync;
}
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,39 @@
@page "/socket/auto-connect"
@inject IStringLocalizer<AutoReconnects> Localizer
<h3>@Localizer["AutoReconnectsTitle"]</h3>
<h4>@Localizer["AutoReconnectsDescription"]</h4>
<Notice></Notice>
<DemoBlock Title="@Localizer["NormalTitle"]"
Introduction="@Localizer["NormalIntro"]"
Name="Normal" ShowCode="false">
<p></p>
<p></p>
<p> <code>SocketClientOptions</code> </p>
<Pre>var client = factory.GetOrCreate("demo-reconnect", op =>
{
op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0);
options.IsAutoReconnect = true;
options.ReconnectInterval = 5000;
});</Pre>
<p></p>
<ul class="ul-demo">
<li><code>IsAutoReconnect</code> </li>
<li><code>ReconnectInterval</code> 5000 </li>
</ul>
<p> <b></b> <code></code> </p>
<div class="row form-inline g-3">
<div class="col-12 col-sm-6">
<Button Text="连接" Icon="fa-solid fa-play"
OnClick="OnConnectAsync" IsDisabled="@_client.IsConnected"></Button>
<Button Text="断开" Icon="fa-solid fa-stop" class="ms-2"
OnClick="OnCloseAsync" IsDisabled="@(!_client.IsConnected)"></Button>
</div>
<div class="col-12">
<Console Items="@_items" Height="496" HeaderText="接收数据(间隔 10 秒)"
ShowAutoScroll="true" OnClear="@OnClear"></Console>
</div>
</div>
</DemoBlock>

View File

@@ -0,0 +1,109 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.Net;
namespace BootstrapBlazor.Server.Components.Samples.Sockets;
/// <summary>
/// 自动重连示例组件
/// </summary>
public partial class AutoReconnects : IDisposable
{
[Inject, NotNull]
private ITcpSocketFactory? TcpSocketFactory { get; set; }
private ITcpSocketClient _client = null!;
private List<ConsoleMessageItem> _items = [];
private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8901);
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
// 从服务中获取 Socket 实例
_client = TcpSocketFactory.GetOrCreate("demo-auto-connect", options =>
{
options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
options.IsAutoReconnect = true;
options.ReconnectInterval = 5000;
});
_client.ReceivedCallBack += OnReceivedAsync;
_client.OnConnecting = async () =>
{
_items.Add(new ConsoleMessageItem { Message = $"{DateTime.Now} 正在连接到 {_serverEndPoint},请稍候..." });
await InvokeAsync(StateHasChanged);
};
_client.OnConnected = async () =>
{
_items.Add(new ConsoleMessageItem { Message = $"{DateTime.Now} 已连接到 {_serverEndPoint},等待接收数据", Color = Color.Success });
await InvokeAsync(StateHasChanged);
};
}
private async Task OnConnectAsync()
{
if (_client is { IsConnected: false })
{
await _client.ConnectAsync(_serverEndPoint, CancellationToken.None);
}
}
private async Task OnCloseAsync()
{
if (_client is { IsConnected: true })
{
await _client.CloseAsync();
}
}
private Task OnClear()
{
_items = [];
return Task.CompletedTask;
}
private async ValueTask OnReceivedAsync(ReadOnlyMemory<byte> data)
{
// 将数据显示为十六进制字符串
var payload = System.Text.Encoding.UTF8.GetString(data.Span);
_items.Add(data.IsEmpty
? new ConsoleMessageItem { Message = $"{DateTime.Now} 当前连接已关闭5s 后自动重连", Color = Color.Danger }
: new ConsoleMessageItem { Message = $"{DateTime.Now} 接收到来自站点的数据为 {payload}" });
// 保持队列中最大数量为 50
while (_items.Count > 50)
{
_items.RemoveAt(0);
}
await InvokeAsync(StateHasChanged);
}
private void Dispose(bool disposing)
{
if (disposing)
{
if (_client is { IsConnected: true })
{
_client.ReceivedCallBack -= OnReceivedAsync;
}
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,34 @@
@page "/socket/manual-receive"
@inject IStringLocalizer<ManualReceives> Localizer
<h3>@Localizer["ReceivesTitle"]</h3>
<h4>@Localizer["ReceivesDescription"]</h4>
<Notice></Notice>
<DemoBlock Title="@Localizer["NormalTitle"]"
Introduction="@Localizer["NormalIntro"]"
Name="Normal" ShowCode="false">
<p></p>
<ul class="ul-demo">
<li> <b></b> <code>ITcpSocketFactory</code> <code>ITcpSocketClient</code> <code>TcpServer</code></li>
<li> <b></b> <code>CloseAsync</code> Socket </li>
<li> <b></b> <code>SendAsync</code> </li>
</ul>
<p>使 <code>ReceiveAsync</code> </p>
<div class="row form-inline g-3">
<div class="col-12 col-sm-6">
<Button Text="连接" Icon="fa-solid fa-play"
OnClick="OnConnectAsync" IsDisabled="@_client.IsConnected"></Button>
<Button Text="断开" Icon="fa-solid fa-stop" class="ms-2"
OnClick="OnCloseAsync" IsDisabled="@(!_client.IsConnected)"></Button>
<Button Text="发送" Icon="fa-solid fa-paper-plane" class="ms-2" IsAsync="true"
OnClick="OnSendAsync" IsDisabled="@(!_client.IsConnected)"></Button>
</div>
<div class="col-12">
<Console Items="@_items" Height="496" HeaderText="接收数据(间隔 10 秒)"
ShowAutoScroll="true" OnClear="@OnClear"></Console>
</div>
</div>
</DemoBlock>

View File

@@ -0,0 +1,89 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.Net;
using System.Text;
namespace BootstrapBlazor.Server.Components.Samples.Sockets;
/// <summary>
/// 接收电文示例
/// </summary>
public partial class ManualReceives
{
[Inject, NotNull]
private ITcpSocketFactory? TcpSocketFactory { get; set; }
private ITcpSocketClient _client = null!;
private List<ConsoleMessageItem> _items = [];
private readonly IPEndPoint _serverEndPoint = new(IPAddress.Loopback, 8810);
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
// 从服务中获取 Socket 实例
_client = TcpSocketFactory.GetOrCreate("demo-manual-receive", options =>
{
options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
options.IsAutoReceive = false;
});
}
private async Task OnConnectAsync()
{
if (_client is { IsConnected: false })
{
await _client.ConnectAsync(_serverEndPoint, CancellationToken.None);
}
}
private async Task OnCloseAsync()
{
if (_client is { IsConnected: true })
{
await _client.CloseAsync();
}
}
private Task OnClear()
{
_items = [];
return Task.CompletedTask;
}
private async Task OnSendAsync()
{
if (_client is { IsConnected: true })
{
// 准备通讯数据
var data = new byte[2] { 0x01, 0x02 };
var result = await _client.SendAsync(data, CancellationToken.None);
var state = result ? "成功" : "失败";
// 记录日志
_items.Add(new ConsoleMessageItem()
{
Message = $"{DateTime.Now}: 发送数据 {_client.LocalEndPoint} - {_serverEndPoint} Data: {BitConverter.ToString(data)} {state}"
});
if (result)
{
var buffer = await _client.ReceiveAsync(CancellationToken.None);
var payload = buffer.ToArray();
_items.Add(new ConsoleMessageItem()
{
Message = $"{DateTime.Now}: 接收数据 {_client.LocalEndPoint} - {_serverEndPoint} Data {Encoding.UTF8.GetString(payload)} HEX: {BitConverter.ToString(payload)} 成功",
Color = Color.Success
});
}
}
}
}

View File

@@ -0,0 +1,3 @@
<Tips>
<p><code>ITcpSocketFactory</code> <code>Server</code> </p>
</Tips>

View File

@@ -6,7 +6,7 @@
margin-top: 1rem;
}
::deep .bb_stack {
::deep .bb-stack {
height: 100%;
}
@@ -14,15 +14,15 @@
width: 120px;
}
::deep .bb_stack_item:nth-child(1) {
::deep .bb-stack-item:nth-child(1) {
background-color: var(--bs-primary);
}
::deep .bb_stack_item:nth-child(2) {
::deep .bb-stack-item:nth-child(2) {
background-color: var(--bs-success);
}
::deep .bb_stack_item:nth-child(3) {
::deep .bb-stack-item:nth-child(3) {
background-color: var(--bs-danger);
}

View File

@@ -64,13 +64,7 @@ public partial class TablesFilter
isSorted = true;
}
// 此段代码可不写,组件内部自行处理
if (options.SortName == nameof(Foo.DateTime))
{
items = items.Sort(options.SortList);
isSorted = true;
}
else if (!string.IsNullOrEmpty(options.SortName))
if (!string.IsNullOrEmpty(options.SortName))
{
// 外部未进行排序,内部自动进行排序处理
items = items.Sort(options.SortName, options.SortOrder);

View File

@@ -52,6 +52,14 @@
<ConsoleLogger @ref="Logger2"></ConsoleLogger>
</DemoBlock>
<DemoBlock Title="@Localizer["TreeViewDraggableTitle"]"
Introduction="@Localizer["TreeViewDraggableIntro"]"
Name="TreeDraggable">
<section ignore>@((MarkupString)Localizer["TreeViewDraggableDescription"].Value)</section>
<TreeView Items="@DraggableItems" AllowDrag="true" OnDragItemEndAsync="OnDragItemEndAsync">
</TreeView>
</DemoBlock>
<DemoBlock Title="@Localizer["TreeViewTreeDisableTitle"]"
Introduction="@Localizer["TreeViewTreeDisableIntro"]"
Name="TreeDisable">

View File

@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using DocumentFormat.OpenXml.Spreadsheet;
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
@@ -33,6 +35,8 @@ public sealed partial class TreeViews
private bool AutoCheckParent { get; set; }
private List<TreeViewItem<TreeFoo>> DraggableItems { get; set; } = [];
private List<TreeViewItem<TreeFoo>> DisabledItems { get; } = GetDisabledItems();
private List<TreeViewItem<TreeFoo>>? AccordionItems { get; } = TreeFoo.GetAccordionItems();
@@ -77,12 +81,61 @@ public sealed partial class TreeViews
private string? _selectedValue;
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
var items = GetDraggableItems();
DraggableItems = TreeFoo.CascadingTree(items);
DraggableItems[0].IsExpand = true;
if (DraggableItems.Count > 1)
{
DraggableItems[1].IsExpand = true;
}
if (DraggableItems.Count > 2)
{
DraggableItems[2].IsExpand = true;
}
}
private Task OnTreeItemClick(TreeViewItem<TreeFoo> item)
{
Logger1.Log($"TreeItem: {item.Text} clicked");
return Task.CompletedTask;
}
private Task OnDragItemEndAsync(TreeViewDragContext<TreeFoo> context)
{
// 本例是使用静态数据模拟数据库操作的,实战中应该是更新节点的父级 Id 可能还需要更改排序字段等信息,然后重构 TreeView 数据源即可
// 根据 context 处理原始数据
var items = GetDraggableItems();
var source = items.Find(i => i.Id == context.Source.Value.Id);
if (source != null)
{
var target = items.Find(i => i.Id == context.Target.Value.Id);
if (target != null)
{
source.ParentId = context.IsChildren ? target.Id : target.ParentId;
}
}
DraggableItems = TreeFoo.CascadingTree(items);
DraggableItems[0].IsExpand = true;
if (DraggableItems.Count > 1)
{
DraggableItems[1].IsExpand = true;
}
if (DraggableItems.Count > 2)
{
DraggableItems[2].IsExpand = true;
}
StateHasChanged();
return Task.CompletedTask;
}
private Task OnTreeItemKeyboardClick(TreeViewItem<TreeFoo> item)
{
_selectedValue = item.Value.Text;
@@ -122,6 +175,28 @@ public sealed partial class TreeViews
return Task.CompletedTask;
}
private static List<TreeFoo>? _dragItems = null;
private static List<TreeFoo> GetDraggableItems()
{
_dragItems ??=
[
new() { Text = "Item A", Id = "1", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item D", Id = "4", ParentId = "1", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item E", Id = "5", ParentId = "1", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item B (Drop inside blocked)", Id = "2", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item F", Id = "6", ParentId = "2", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item G (Can not move out)", Id = "9", ParentId = "2", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item C", Id = "3", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item H", Id = "7", ParentId = "3", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item I", Id = "8", ParentId = "3", Icon = "fa-solid fa-font-awesome" },
];
return _dragItems;
}
private static List<TreeViewItem<TreeFoo>> GetDisabledItems()
{
var ret = TreeFoo.GetTreeItems();

View File

@@ -0,0 +1,92 @@
@page "/tutorials/template5"
@layout TutorialsLayout
<HeadContent>
<style>
.main:has(.background-image) {
height: calc(100vh - 56px - 175px);
}
@@media (min-width: 768px) {
.main:has(.background-image) {
height: calc(100vh - 50px);
}
.login-box {
width: 350px;
}
}
</style>
</HeadContent>
<div class="background-image">
<div class="login-container">
<div class="login-box animate-fade-in">
<div class="header-row">
@if (isEmailEntered)
{
<button @onclick="GoBack" aria-label="返回" class="back-button">
<span>
<i class="fa-solid fa-arrow-left"></i>
</span>
</button>
}
<div class="logo-container">
<h1 class="blazor-text">BootstrapBlazor</h1>
</div>
</div>
@if (!isEmailEntered)
{
<h2></h2>
<p class="subtitle">使 BootstrapBlazor </p>
<BootstrapInput type="email" class="input" placeholder="电子邮件或电话号码" @bind-Value="email" />
<div class="error" hidden="@(!showEmailError)"></div>
<Button class="button" Color="Color.Primary" OnClick="OnEmailSubmit"></Button>
<div class="links">
<a href="#"></a>
</div>
<div class="small">
BootstrapBlazor<a href="/"></a>
</div>
}
else
{
<h2></h2>
<p class="email-display">@email</p>
<BootstrapInput type="password" class="input" placeholder="密码" @bind-Value="password" />
<div class="links">
<a href="#"></a>
</div>
<Button class="button" Color="Color.Primary"></Button>
<div class="links">
<a href="#"></a>
</div>
}
</div>
</div>
</div>
@code {
private bool isEmailEntered = false;
private string email = "";
private string password = "";
private bool showEmailError = false;
private void OnEmailSubmit()
{
if (string.IsNullOrWhiteSpace(email))
{
showEmailError = true;
}
else
{
showEmailError = false;
isEmailEntered = true;
}
}
private void GoBack()
{
isEmailEntered = false;
}
}

View File

@@ -0,0 +1,167 @@
.background-image {
background-image: url('https://logincdn.msauth.net/shared/5/js/../images/fluent_web_light_57fee22710b04cebe1d5.svg');
background-repeat: no-repeat;
background-size: cover;
background-position: center;
width: 100%;
height: 100%;
}
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.login-box {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
width: auto;
font-family: "Segoe UI", sans-serif;
text-align: left;
position: relative;
box-sizing: content-box !important;
}
::deep .input {
width: 100%;
padding: 10px;
margin: 12px 0;
border: 1px solid #ccc;
border-radius: 4px;
}
.header-row {
display: flex;
align-items: center;
position: relative;
height: 40px;
margin-bottom: 20px;
}
.back-button {
position: absolute;
left: 0;
background: transparent;
border: none;
padding: 0;
cursor: pointer;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.blazor-text {
font-family: Arial, sans-serif;
font-size: 1.8rem;
font-weight: bold;
text-align: center;
background: linear-gradient(to right, #8e44ad, #e84393);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin: 0;
}
.logo-container {
flex: 1;
display: flex;
justify-content: center;
margin-left: 40px;
margin-right: 40px;
}
.logo {
height: 28px;
}
::deep .button {
width: 100%;
padding: 10px;
background-color: #0078d4;
color: white;
border: none;
border-radius: 4px;
font-weight: bold;
margin-top: 10px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.button:hover {
background-color: #005a9e;
}
.links {
margin-top: 10px;
font-size: 14px;
}
.links a {
color: #0066cc;
text-decoration: none;
}
.links a:hover {
text-decoration: underline;
}
.small {
font-size: 12px;
color: #666;
margin-top: 10px;
}
.email-display {
font-size: 14px;
color: #333;
margin-bottom: 10px;
}
.error {
color: red;
font-size: 13px;
}
.lang-switch {
position: absolute;
top: 10px;
right: 10px;
}
.animate-fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-out {
animation: fadeOut 0.5s ease-in-out forwards;
}
@keyframes fadeOut {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-20px);
}
}

View File

@@ -36,7 +36,7 @@
font-weight: 400;
}
::deep .bb-opt-input .bb-opt-item {
--bb-opt-item-width: 50px;
--bb-opt-font-size: 2em;
::deep .bb-otp-input .bb-otp-item {
--bb-otp-item-width: 50px;
--bb-otp-font-size: 2em;
}

View File

@@ -77,8 +77,6 @@ public partial class OnlineSheet : IDisposable
ForceDelay = true
});
DispatchService.UnSubscribe(Dispatch);
await _sheetExcel.PushDataAsync(entry.Entry.Data);
}
}

View File

@@ -0,0 +1,79 @@
@page "/upload-avatar"
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
@inject IStringLocalizer<UploadAvatars> Localizer
@inject ToastService ToastService
<h3>@Localizer["UploadsTitle"]</h3>
<h4>@Localizer["UploadsSubTitle"]</h4>
<p>@((MarkupString)Localizer["UploadsNote"].Value)</p>
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
<DemoBlock Title="@Localizer["AvatarUploadTitle"]"
Introduction="@Localizer["AvatarUploadIntro"]"
Name="Normal">
<section ignore>
<div class="row g-3">
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsDisabled"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isDisabled"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsMultiple"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isMultiple"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsUploadButtonAtFirst"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isUploadButtonAtFirst"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsCircle"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isCircle"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="BorderRadius"></BootstrapInputGroupLabel>
<Slider @bind-Value="@_radius" Min="0" Max="49" UseInputEvent="true" IsDisabled="@(_isCircle == false)"></Slider>
</BootstrapInputGroup>
</div>
</div>
</section>
<AvatarUpload TValue="string" IsMultiple="@_isMultiple" IsCircle="@_isCircle" BorderRadius="@RadiusString"
IsDisabled="@_isDisabled"></AvatarUpload>
</DemoBlock>
<DemoBlock Title="@Localizer["AvatarUploadAcceptTitle"]"
Introduction="@Localizer["AvatarUploadAcceptIntro"]"
Name="Accept">
<AvatarUpload TValue="string" Accept="image/*"></AvatarUpload>
</DemoBlock>
<DemoBlock Title="@Localizer["AvatarUploadValidateTitle"]"
Introduction="@Localizer["AvatarUploadValidateIntro"]"
Name="ValidateForm">
<ValidateForm Model="@_foo" OnValidSubmit="OnAvatarValidSubmit" OnInValidSubmit="OnAvatarInValidSubmit">
<div class="row g-3">
<div class="col-12">
<BootstrapInput @bind-Value="@_foo.Name"></BootstrapInput>
</div>
<div class="col-12">
<AvatarUpload @bind-Value="@_foo.Picture" IsMultiple="true" MaxFileCount="3"></AvatarUpload>
</div>
<div class="col-12">
<Button ButtonType="@ButtonType.Submit" Text="@Localizer["AvatarUploadButtonText"]"></Button>
</div>
</div>
</ValidateForm>
</DemoBlock>
<AttributeTable Items="@GetAttributes()"></AttributeTable>

View File

@@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using Microsoft.AspNetCore.Components.Forms;
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// AvatarUpload sample code
/// </summary>
public partial class UploadAvatars
{
private readonly List<UploadFile> _previewFileList = [];
private readonly Person _foo = new();
private bool _isUploadButtonAtFirst;
private bool _isCircle;
private int _radius = 49;
private bool _isMultiple = true;
private bool _isDisabled = false;
private string? RadiusString => $"{_radius}px";
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
_previewFileList.AddRange(
[
new UploadFile { PrevUrl = $"{WebsiteOption.CurrentValue.AssetRootPath}images/Argo.png" }
]);
}
private Task OnAvatarValidSubmit(EditContext context)
{
return ToastService.Success(Localizer["UploadsValidateFormTitle"], Localizer["UploadsValidateFormValidContent"]);
}
private Task OnAvatarInValidSubmit(EditContext context)
{
return ToastService.Error(Localizer["UploadsValidateFormTitle"], Localizer["UploadsValidateFormInValidContent"]);
}
private List<AttributeItem> GetAttributes() =>
[
new()
{
Name = "Width",
Description = Localizer["UploadsWidth"],
Type = "int",
ValueList = " — ",
DefaultValue = "0"
},
new()
{
Name = "Height",
Description = Localizer["UploadsHeight"],
Type = "int",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "IsCircle",
Description = Localizer["UploadsIsCircle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = "BorderRadius",
Description = Localizer["UploadsBorderRadius"],
Type = "string?",
ValueList = " — ",
DefaultValue = " — "
}
];
class Person
{
[Required]
[StringLength(20, MinimumLength = 2)]
public string Name { get; set; } = "Blazor";
[Required]
[FileValidation(Extensions = [".png", ".jpg", ".jpeg"], FileSize = 5 * 1024 * 1024)]
public List<IBrowserFile>? Picture { get; set; }
}
}

View File

@@ -0,0 +1,61 @@
@page "/upload-button"
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
@inject IStringLocalizer<UploadButtons> Localizer
@inject ToastService ToastService
<h3>@Localizer["UploadsTitle"]</h3>
<h4>@Localizer["UploadsSubTitle"]</h4>
<p>@((MarkupString)Localizer["UploadsNote"].Value)</p>
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
<DemoBlock Title="@Localizer["ButtonUploadTitle"]"
Introduction="@Localizer["ButtonUploadIntro"]"
Name="Normal">
<section ignore>
<div class="row g-3">
<div class="col-12 col-sm-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsDisabled"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isDisabled"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsMultiple"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isMultiple"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsDirectory"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isDirectory"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowProgress"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showProgress"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowUploadFileList"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showUploadFileList"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-4">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowDownloadButton"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showDownloadButton"></Switch>
</BootstrapInputGroup>
</div>
</div>
</section>
<ButtonUpload TValue="string" IsMultiple="@_isMultiple" IsDirectory="@_isDirectory" IsDisabled="@_isDisabled"
ShowProgress="@_showProgress"
ShowUploadFileList="@_showUploadFileList" ShowDownloadButton="@_showDownloadButton"
OnChange="@OnClickToUpload" OnDownload="OnDownload" OnDelete="OnDelete"></ButtonUpload>
</DemoBlock>

View File

@@ -0,0 +1,102 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// ButtonUpload sample code
/// </summary>
public partial class UploadButtons : IDisposable
{
private static readonly Random Random = new();
private static readonly long MaxFileLength = 5 * 1024 * 1024;
private CancellationTokenSource? _token;
private bool _isMultiple = true;
private bool _showProgress = true;
private bool _showUploadFileList = true;
private bool _showDownloadButton = true;
private bool _isDirectory = false;
private bool _isDisabled = false;
private async Task OnClickToUpload(UploadFile file)
{
// 示例代码,模拟 80% 几率保存成功
var error = Random.Next(1, 100) > 80;
if (error)
{
file.Code = 1;
file.Error = Localizer["UploadsError"];
}
else
{
await SaveToFile(file);
}
}
private async Task OnDownload(UploadFile item)
{
await ToastService.Success("文件下载", $"下载 {item.FileName} 成功");
}
private async Task<bool> OnDelete(UploadFile item)
{
await ToastService.Success("文件操作", $"删除文件 {item.FileName} 成功");
return true;
}
private async Task SaveToFile(UploadFile file)
{
// Server Side 使用
// Web Assembly 模式下必须使用 WebApi 方式去保存文件到服务器或者数据库中
// 生成写入文件名称
if (!string.IsNullOrEmpty(WebsiteOption.CurrentValue.WebRootPath))
{
var uploaderFolder = Path.Combine(WebsiteOption.CurrentValue.WebRootPath, "images", "uploader");
file.FileName = $"{Path.GetFileNameWithoutExtension(file.OriginFileName)}-{DateTimeOffset.Now:yyyyMMddHHmmss}{Path.GetExtension(file.OriginFileName)}";
var fileName = Path.Combine(uploaderFolder, file.FileName);
_token ??= new CancellationTokenSource();
try
{
var ret = await file.SaveToFileAsync(fileName, MaxFileLength, token: _token.Token);
if (ret)
{
// 保存成功
file.PrevUrl = $"{WebsiteOption.CurrentValue.AssetRootPath}images/uploader/{file.FileName}";
}
else
{
var errorMessage = $"{Localizer["UploadsSaveFileError"]} {file.OriginFileName}";
file.Code = 1;
file.Error = errorMessage;
await ToastService.Error(Localizer["UploadFile"], errorMessage);
}
}
catch (OperationCanceledException)
{
}
}
else
{
file.Code = 1;
file.Error = Localizer["UploadsWasmError"];
await ToastService.Information(Localizer["UploadsSaveFile"], Localizer["UploadsSaveFileMsg"]);
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
_token?.Cancel();
_token?.Dispose();
_token = null;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,101 @@
@page "/upload-card"
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
@inject IStringLocalizer<UploadCards> Localizer
@inject ToastService ToastService
<h3>@Localizer["UploadsTitle"]</h3>
<h4>@Localizer["UploadsSubTitle"]</h4>
<p>@((MarkupString)Localizer["UploadsNote"].Value)</p>
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
<DemoBlock Title="@Localizer["ButtonUploadTitle"]"
Introduction="@Localizer["ButtonUploadIntro"]"
Name="Normal">
<section ignore>
<div class="row g-3">
<div class="col-12 col-sm-6 col-xl-3">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsDisabled"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isDisabled"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsMultiple"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isMultiple"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsDirectory"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isDirectory"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsUploadButtonAtFirst"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isUploadButtonAtFirst"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowProgress"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showProgress"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowDeleteButton"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showDeleteButton"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowZoomButton"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showZoomButton"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowDownloadButton"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showDownloadButton"></Switch>
</BootstrapInputGroup>
</div>
</div>
</section>
<CardUpload TValue="string" IsMultiple="@_isMultiple" IsDirectory="@_isDirectory"
IsDisabled="@_isDisabled" IsUploadButtonAtFirst="@_isUploadButtonAtFirst"
ShowProgress="@_showProgress" ShowDeleteButton="@_showDeleteButton"
ShowDownloadButton="@_showDownloadButton" ShowZoomButton="@_showZoomButton" OnChange="@OnCardUpload"></CardUpload>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadFileIconTitle"]"
Introduction="@Localizer["UploadFileIconIntro"]"
Name="FileIcon">
<CardUpload TValue="string" IsMultiple="true" ShowDownloadButton="true" DefaultFileList="@DefaultFormatFileList"
OnChange="@OnCardUpload"></CardUpload>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadFileIconTemplateTitle"]"
Introduction="@Localizer["UploadFileIconTemplateIntro"]"
Name="IconTemplate">
<CardUpload TValue="string" IsMultiple="true" ShowDownloadButton="true" DefaultFileList="@DefaultFormatFileList"
OnChange="@OnCardUpload">
<IconTemplate>
<FileIcon Extension="@context.GetExtension()">
<BackgroundTemplate>
<i class="fa-regular fa-clipboard fa-4x"></i>
</BackgroundTemplate>
</FileIcon>
</IconTemplate>
</CardUpload>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadBase64Title"]"
Introduction="@Localizer["UploadBase64Intro"]"
Name="Base64">
<CardUpload TValue="string" DefaultFileList="@Base64FormatFileList" ShowDownloadButton="false" ShowDeleteButton="false"></CardUpload>
</DemoBlock>

View File

@@ -0,0 +1,120 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// CardUpload sample code
/// </summary>
public partial class UploadCards : IDisposable
{
private bool _isMultiple = true;
private bool _isDirectory = false;
private bool _isDisabled = false;
private bool _isUploadButtonAtFirst = false;
private bool _showProgress = true;
private bool _showZoomButton = true;
private bool _showDeleteButton = true;
private bool _showDownloadButton = true;
private List<UploadFile> DefaultFormatFileList { get; } =
[
new() { FileName = "Test.xls" },
new() { FileName = "Test.doc" },
new() { FileName = "Test.ppt" },
new() { FileName = "Test.mp3" },
new() { FileName = "Test.mp4" },
new() { FileName = "Test.pdf" },
new() { FileName = "Test.cs" },
new() { FileName = "Test.zip" },
new() { FileName = "Test.txt" },
new() { FileName = "Test.dat" }
];
private List<UploadFile> Base64FormatFileList { get; } =
[
new() { FileName = "Test", PrevUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAkCAYAAAD/yagrAAAE60lEQVR4AWJwL/AB9GIWvI0jURz/L1NomZmZP8p9kWNmhnIbKPdomZmZGcJgO7QsWMbce55Yl11t47FbVdKTYpjOr/+H4y4Z/MpYeJVV8KVvokFRylq9osKfVtGQ/NHyPl2CbNVGwau2oOlmgUDJtDJGzxtvvIA3vRH+7IgeA0VtYhQBtKPlToFgC6RY58aggfwLNKhr0Zry2NnPrpIM2YGW2+UBG1IEmdGVJEVXE+hQXt8joKhLjdGVZHd7TSD9DHmT3J1dheroSF4fhnvUVQwbZx9UOnHSzQjkTN0tIG+8hFdbxet4PQOG4WqJwN1hFVb+xUBmCblxvxRkIE+Q+Yf0T31NSrp4/RV4FhHgoSTchQRZBK4D1+Fe2q2g8CYXU6buQyNDaiZKZhn0IYXHVwbkRQydHyawOAGGisa/o3DvI/jF3QKKiuAy+LKs5CvaXMLdeXb35wbkKTjmReDZHysCBosWJqN7r0jZ/VfgXtIlUNQnVpK7D4nNJSC5BHGi1d108Ppr8MxlF0cJqBSyFJaUfUnvHLyO4cttgaI+NhGB7HE0MaSUks/gU5vwe1gv5tfhmUmAhxME8jbIUtiEDus+fhmDJlgCRY0ylZRZhybKWtNinmYlH9NvL5puO3m9UHOkgzb3xuF5HCkDyhYiSwrYVfQPTpYChTc+E35tG9VJBjGFFNmtVlNMDnjzbx0EBpKq1eTeh2awbArBRuHadBWu6WVB4U/NITfuobYoCZl7QL//wDtr+nTmsgzQh2Kwgtz7QAZWFeGw/RI8s94Kigp1PvzZg2i9x11FDtKn/oZCoZdZxhaAXmG4/ohJKHudLE1Gyu66DOfs10BRR5CU3TKQIrtzj9BAkF991dsMslTZEClLsI+jFmANZYFAdAJl9QG03jWH9GrcFh9QTP4Of6GfJGSpsv1J2aoEKRuWCANNJNqOFAaPAW36rVRMCjVf6ZCtqYF2p6Asxg5mWK6tQamY9RCs8z1wF+FxTR5UqYT/3GC7oCkMHcjxKguq6cnl+Egf2whip3C9Yu56jk+GXXOtv1XIa8L1f3AFkHe9az83AmNanwV/bhfa5JKJ3n1slCUL8dk7CNfvVFMfySRThoxbK7fh18tTZXI2QeySLk88IRGsbHkqQj6wUJ72G5CloCXKZrdbLPgVMgVfQMq5m97bfQWOOeVbaK02nSA2ofn2y662UJE47horLZTe38oDjdxQUhmawocxa0OJ5hXjnZE4w1y0uc/iULKGfk+xNuZxI/BnjhFs+THPq4phmTsbeYPXRjFsJJ+NSMlnInHkxrwzGDjR3uBcG1/B/b/TwZkhubb6tP2oVfTAD2G4kw9vYiA2h+zy4GwYxd7S4qGOgd6oqVn6re1DTXiO4e4wPF+yQlF4ZCBfheSPIjJn+ciS/w93qjA6aZYqeQ5D3dTqvuE6GZNTkov5vqsYvLh7j8u1sWWkLIdBQR+q+XdtaKGhJCWBDpkwgSw9gRpKyoNKw6rjCLCDP4yVflSgWFwt3C0HSUo2BTFYX28fVAaWPpDx7wtwjOSSUszawnUT0KTudlfrFQwZ3WNf867CNZQhk/C8kIFUhJLt/O3Jzn62IYNwrUvA8zws4W61CGlDSfugXMz5pJgiSFYyXMYiRXdH4GmyqaR9UHLxzxG41KAwpZxRPN4kRf85Z5I4MvYfFUFGfemJG40AAAAASUVORK5CYII=" },
];
private static long MaxFileLength => 5 * 1024 * 1024;
private CancellationTokenSource? _token;
private async Task OnCardUpload(UploadFile file)
{
if (file is { File: not null })
{
// 服务器端验证当文件大于 5MB 时提示文件太大信息
if (file.Size > MaxFileLength)
{
await ToastService.Information(Localizer["UploadsFileTitle"], Localizer["UploadsFileError"]);
file.Code = 1;
file.Error = Localizer["UploadsFileError"];
}
else
{
// 模拟保存成功
await Task.Delay(100);
await SaveToFile(file);
await ToastService.Success(Localizer["UploadsFileTitle"], $"{file.File!.Name} {Localizer["UploadsSuccess"]}");
}
}
}
private async Task SaveToFile(UploadFile file)
{
// Server Side 使用
// Web Assembly 模式下必须使用 WebApi 方式去保存文件到服务器或者数据库中
// 生成写入文件名称
if (!string.IsNullOrEmpty(WebsiteOption.CurrentValue.WebRootPath))
{
var uploaderFolder = Path.Combine(WebsiteOption.CurrentValue.WebRootPath, "images", "uploader");
file.FileName = $"{Path.GetFileNameWithoutExtension(file.OriginFileName)}-{DateTimeOffset.Now:yyyyMMddHHmmss}{Path.GetExtension(file.OriginFileName)}";
var fileName = Path.Combine(uploaderFolder, file.FileName);
_token ??= new CancellationTokenSource();
try
{
var ret = await file.SaveToFileAsync(fileName, MaxFileLength, token: _token.Token);
if (ret)
{
// 保存成功
file.PrevUrl = $"{WebsiteOption.CurrentValue.AssetRootPath}images/uploader/{file.FileName}";
}
else
{
var errorMessage = Localizer["UploadsSaveFileError"];
file.Code = 1;
file.Error = errorMessage;
await ToastService.Error(Localizer["UploadsFileTitle"], errorMessage);
}
}
catch (OperationCanceledException)
{
}
}
else
{
file.Code = 1;
file.Error = Localizer["UploadsWasmError"];
await ToastService.Error(Localizer["UploadsFileTitle"], Localizer["UploadsWasmError"]);
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
if (_token != null)
{
_token.Cancel();
_token.Dispose();
_token = null;
}
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,60 @@
@page "/upload-drop"
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
@inject IStringLocalizer<UploadDrops> Localizer
@inject ToastService ToastService
<h3>@Localizer["UploadsTitle"]</h3>
<h4>@Localizer["UploadsSubTitle"]</h4>
<p>@((MarkupString)Localizer["UploadsNote"].Value)</p>
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
<DemoBlock Title="@Localizer["DropUploadTitle"]" Introduction="@Localizer["DropUploadIntro"]" Name="Normal">
<section ignore>
<div class="row g-3">
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsDisabled"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isDisabled"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="IsMultiple"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_isMultiple"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowUploadFileList"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showUploadFileList"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowDownloadButton"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showDownloadButton"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowProgress"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showProgress"></Switch>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="ShowFooter"></BootstrapInputGroupLabel>
<Switch @bind-Value="@_showFooter"></Switch>
</BootstrapInputGroup>
</div>
</div>
</section>
<DropUpload OnChange="@OnDropUpload" FooterText="@Localizer["DropUploadFooterText"]"
IsDisabled="@_isDisabled" IsMultiple="@_isMultiple"
ShowProgress="@_showProgress" ShowUploadFileList="@_showUploadFileList" ShowFooter="@_showFooter"></DropUpload>
</DemoBlock>
<AttributeTable Items="@GetAttributes()"></AttributeTable>

View File

@@ -0,0 +1,98 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// DropUpload sample code
/// </summary>
public partial class UploadDrops
{
private bool _isMultiple = true;
private bool _isDisabled = false;
private bool _showProgress = true;
private bool _showFooter = true;
private bool _showUploadFileList = true;
private bool _showDownloadButton = true;
private Task OnDropUpload(UploadFile file)
{
// 模拟保存文件等处理
if (file.File is { Size: > 5 * 1024 * 1024 })
{
file.Code = 1004;
ToastService.Information("Error", Localizer["DropUploadFooterText"]);
}
return Task.CompletedTask;
}
private List<AttributeItem> GetAttributes() =>
[
new()
{
Name = "BodyTemplate",
Description = Localizer["UploadsBodyTemplate"],
Type = "RenderFragment",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "IconTemplate",
Description = Localizer["UploadsIconTemplate"],
Type = "RenderFragment",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "TextTemplate",
Description = Localizer["UploadsTextTemplate"],
Type = "RenderFragment",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "UploadIcon",
Description = Localizer["UploadsUploadIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "UploadText",
Description = Localizer["UploadsUploadText"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "ShowFooter",
Description = Localizer["UploadsShowFooter"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = "FooterTemplate",
Description = Localizer["UploadsFooterTemplate"],
Type = "RenderFragment",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "FooterText",
Description = Localizer["UploadsFooterText"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
}
];
}

View File

@@ -0,0 +1,49 @@
@page "/upload-input"
@inject IStringLocalizer<UploadInputs> Localizer
@inject ToastService ToastService
<h3>@Localizer["UploadsTitle"]</h3>
<h4>@Localizer["UploadsSubTitle"]</h4>
<p>@((MarkupString)Localizer["UploadsNote"].Value)</p>
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
<DemoBlock Title="@Localizer["UploadNormalTitle"]"
Introduction="@Localizer["UploadNormalIntro"]"
Name="Normal">
<div class="row g-3">
<div class="col-12">
<InputUpload TValue="string" ShowDeleteButton="true" IsMultiple="true"
ShowLabel="true" DisplayText="@Localizer["UploadNormalLabelPhoto"]"
OnChange="@OnFileChange"></InputUpload>
</div>
</div>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadFormSettingsTitle"]"
Introduction="@Localizer["UploadFormSettingsIntro"]"
Name="FormSettings">
<section ignore>
<ul class="ul-demo">
<li>@((MarkupString)Localizer["UploadFormSettingsLi1"].Value)</li>
<li>@((MarkupString)Localizer["UploadFormSettingsLi2"].Value)</li>
</ul>
</section>
<ValidateForm Model="Foo1" OnValidSubmit="OnSubmit">
<div class="row g-3">
<div class="col-12">
<BootstrapInput @bind-Value="@Foo1.Name"></BootstrapInput>
</div>
<div class="col-12">
<InputUpload @bind-Value="@Foo1.Picture" ShowDeleteButton="true"></InputUpload>
</div>
<div class="col-12">
<Button ButtonType="@ButtonType.Submit" Text="@Localizer["UploadFormSettingsButtonText"]"></Button>
</div>
</div>
</ValidateForm>
</DemoBlock>
<AttributeTable Items="@GetAttributes()"></AttributeTable>

View File

@@ -0,0 +1,158 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using Microsoft.AspNetCore.Components.Forms;
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// InputUpload sample code
/// </summary>
public partial class UploadInputs
{
private Person Foo1 { get; set; } = new Person();
private async Task OnFileChange(UploadFile file)
{
// 未真正保存文件
// file.SaveToFile()
await Task.Delay(200);
await ToastService.Information(Localizer["UploadsSaveFile"], $"{file.File!.Name} {Localizer["UploadsSuccess"]}");
}
private static Task OnSubmit(EditContext context)
{
// 示例代码请根据业务情况自行更改
// var fileName = Foo.Picture?.Name;
return Task.CompletedTask;
}
private List<AttributeItem> GetAttributes() =>
[
new()
{
Name = "IsDirectory",
Description = Localizer["UploadsIsDirectory"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = "IsMultiple",
Description = Localizer["UploadsIsMultiple"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = "ShowDeleteButton",
Description = Localizer["UploadsShowDeleteButton"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = "IsDisabled",
Description = Localizer["UploadsIsDisabled"],
Type = "boolean",
ValueList = "true / false",
DefaultValue = "false"
},
new()
{
Name = "PlaceHolder",
Description = Localizer["UploadsPlaceHolder"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "Accept",
Description = Localizer["UploadsAccept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "BrowserButtonClass",
Description = Localizer["UploadsBrowserButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-primary"
},
new()
{
Name = "BrowserButtonIcon",
Description = Localizer["UploadsBrowserButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa-regular fa-folder-open"
},
new()
{
Name = "BrowserButtonText",
Description = Localizer["UploadsBrowserButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = ""
},
new()
{
Name = "DeleteButtonClass",
Description = Localizer["UploadsDeleteButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-danger"
},
new()
{
Name = "DeleteButtonIcon",
Description = Localizer["UploadsDeleteButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa-regular fa-trash"
},
new()
{
Name = "DeleteButtonText",
Description = Localizer["UploadsDeleteButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = Localizer["UploadsDeleteButtonTextDefaultValue"]
},
new()
{
Name = "OnDelete",
Description = Localizer["UploadsOnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "OnChange",
Description = Localizer["UploadsOnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
}
];
class Person
{
[Required]
[StringLength(20, MinimumLength = 2)]
public string Name { get; set; } = "Blazor";
[Required]
[FileValidation(Extensions = [".png", ".jpg", ".jpeg"], FileSize = 5 * 1024 * 1024)]
public IBrowserFile? Picture { get; set; }
}
}

View File

@@ -1,196 +0,0 @@
@page "/upload"
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
@inject IStringLocalizer<Uploads> Localizer
@inject ToastService ToastService
@implements IDisposable
<h3>@Localizer["UploadsTitle"]</h3>
<h4>@Localizer["UploadsSubTitle"]</h4>
<p>@((MarkupString)Localizer["UploadsNote"].Value)</p>
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
<DemoBlock Title="@Localizer["UploadNormalTitle"]"
Introduction="@Localizer["UploadNormalIntro"]"
Name="Normal">
<div class="row g-3">
<div class="col-12 col-sm-6">
<label for="text1" class="form-label">@Localizer["UploadNormalLabelName"]</label>
<input id="text1" class="form-control">
</div>
<div class="col-12 col-sm-6">
<label for="text2" class="form-label">@Localizer["UploadNormalLabelAddress"]</label>
<input id="text2" class="form-control">
</div>
<div class="col-12">
<label for="text3" class="form-label">@Localizer["UploadNormalLabelPhoto"]</label>
<InputUpload TValue="string" ShowDeleteButton="true" OnChange="@OnFileChange" OnDelete="@OnFileDelete"></InputUpload>
</div>
</div>
<ConsoleLogger @ref="Logger1" />
</DemoBlock>
<DemoBlock Title="@Localizer["UploadFormSettingsTitle"]"
Introduction="@Localizer["UploadFormSettingsIntro"]"
Name="FormSettings">
<section ignore>
<ul class="ul-demo mb-3">
<li>@((MarkupString)Localizer["UploadFormSettingsLi1"].Value)</li>
<li>@((MarkupString)Localizer["UploadFormSettingsLi2"].Value)</li>
</ul>
</section>
<ValidateForm Model="Foo1" OnValidSubmit="OnSubmit">
<div class="row g-3">
<div class="col-12">
<BootstrapInput @bind-Value="@Foo1.Name" />
</div>
<div class="col-12">
<InputUpload @bind-Value="@Foo1.Picture" />
</div>
<div class="col-12">
<Button ButtonType="@ButtonType.Submit" Text="@Localizer["UploadFormSettingsButtonText"]"></Button>
</div>
</div>
</ValidateForm>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadClickUploadTitle"]"
Introduction="@Localizer["UploadClickUploadIntro"]"
Name="ClickUpload">
<div class="row g-3">
<div class="col-12 col-sm-6">
<p>@((MarkupString)Localizer["UploadClickUploadTips1"].Value)</p>
<ButtonUpload TValue="string" IsMultiple="true" ShowProgress="true" OnChange="@OnClickToUpload" OnDelete="@(fileName => Task.FromResult(true))"></ButtonUpload>
</div>
</div>
<section ignore>
<p class="mt-3">@((MarkupString)Localizer["UploadClickUploadTips2"].Value)</p>
<ButtonUpload TValue="string" IsSingle="true" OnChange="@OnClickToUpload" OnDelete="@(fileName => Task.FromResult(true))" class="mt-3"></ButtonUpload>
<p class="mt-3">@((MarkupString)Localizer["UploadClickUploadTips3ShowUploadList"].Value)</p>
<ButtonUpload TValue="string" OnChange="@OnClickToUploadNoUploadList" ShowUploadFileList="false" BrowserButtonText="Upload" BrowserButtonIcon="fa-solid fa-cloud-arrow-up" class="mt-3"></ButtonUpload>
</section>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadedFilesTitle"]"
Introduction="@Localizer["UploadedFilesIntro"]"
Name="UploadedFiles">
<div class="row g-3">
<div class="col-12 col-sm-6">
<ButtonUpload TValue="string" ShowDownloadButton="true" OnDownload="OnDownload" OnDelete="@(fileName => Task.FromResult(true))" DefaultFileList="@DefaultFormatFileList"></ButtonUpload>
</div>
</div>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadFolderTitle"]"
Introduction="@Localizer["UploadFolderIntro"]"
Name="UploadFolder">
<div class="row g-3">
<div class="col-12 col-sm-6">
<ButtonUpload TValue="string" IsDirectory="true" OnChange="@OnUploadFolder" OnDelete="@(fileName => Task.FromResult(true))"></ButtonUpload>
</div>
</div>
</DemoBlock>
<DemoBlock Title="@Localizer["AvatarUploadTitle"]"
Introduction="@Localizer["AvatarUploadIntro"]"
Name="AvatarUpload">
<div class="row g-3">
<div class="col-12">
<p>@Localizer["AvatarUploadTips1"]</p>
<AvatarUpload TValue="string" Accept="image/*" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
</div>
<div class="col-12">
<p>@Localizer["AvatarUploadTips2"]</p>
<AvatarUpload TValue="string" Accept="image/*" ShowProgress="true" IsCircle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
</div>
<div class="col-12">
<p>@((MarkupString)Localizer["AvatarUploadTips3"].Value)</p>
<AvatarUpload class="mb-3" TValue="string" IsSingle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
<p>@((MarkupString)Localizer["AvatarUploadTips5"].Value)</p>
<AvatarUpload TValue="string" Accept="image/gif, image/jpeg" IsSingle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
</div>
<div class="col-12">
<p>@((MarkupString)Localizer["AvatarUploadTips6"].Value)</p>
<AvatarUpload TValue="string" Accept="image/gif, image/jpeg" IsSingle="true" DefaultFileList="@PreviewFileList"
OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))" />
</div>
<div class="col-12">
<p>@((MarkupString)Localizer["AvatarUploadTips7"].Value)</p>
<ValidateForm Model="Foo2" OnValidSubmit="OnAvatarValidSubmit">
<div class="row g-3">
<div class="col-12">
<BootstrapInput @bind-Value="@Foo2.Name" />
</div>
<div class="col-12">
<AvatarUpload @bind-Value="@Foo2.Picture" IsSingle="true" />
</div>
<div class="col-12">
<Button ButtonType="@ButtonType.Submit" Text="@Localizer["AvatarUploadButtonText"]"></Button>
</div>
</div>
</ValidateForm>
</div>
</div>
<ConsoleLogger @ref="Logger2" />
</DemoBlock>
<DemoBlock Title="@Localizer["UploadPreCardStyleTitle"]"
Introduction="@Localizer["UploadPreCardStyleIntro"]"
Name="PreCardStyle">
<section ignore>
<div>@((MarkupString)Localizer["UploadPreCardStyleSSR"].Value)</div>
<div>@((MarkupString)Localizer["UploadPreCardStyleServerSide"].Value)</div>
<div>@((MarkupString)Localizer["UploadPreCardStyleWasm"].Value)</div>
<div>@((MarkupString)Localizer["UploadPreCardStyleWasmSide"].Value)</div>
<div>@((MarkupString)Localizer["UploadPreCardStyleLink", WebsiteOption.CurrentValue.VideoLibUrl].Value)</div>
<div>@((MarkupString)Localizer["UploadPreCardStyleValidation"].Value)</div>
</section>
<div class="row g-3 mt-3">
<div class="col-12">
<p>@((MarkupString)Localizer["UploadPreCardStyleTips1"].Value)</p>
<CardUpload TValue="string" ShowProgress="true" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
</div>
<div class="col-12">
<p>@((MarkupString)Localizer["UploadPreCardStyleTips2"].Value)</p>
<CardUpload TValue="string" IsSingle="true" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
</div>
</div>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadFileIconTitle"]"
Introduction="@Localizer["UploadFileIconIntro"]"
Name="FileIcon">
<CardUpload TValue="string" ShowDownloadButton="true" DefaultFileList="@DefaultFormatFileList" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadFileIconTemplateTitle"]"
Introduction="@Localizer["UploadFileIconTemplateIntro"]"
Name="IconTemplate">
<CardUpload TValue="string" ShowDownloadButton="true" DefaultFileList="@DefaultFormatFileList" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))">
<IconTemplate>
<FileIcon Extension="@context.GetExtension()">
<BackgroundTemplate>
<i class="fa-regular fa-clipboard fa-4x" />
</BackgroundTemplate>
</FileIcon>
</IconTemplate>
</CardUpload>
</DemoBlock>
<DemoBlock Title="@Localizer["UploadBase64Title"]"
Introduction="@Localizer["UploadBase64Intro"]"
Name="Base64">
<CardUpload TValue="string" DefaultFileList="@Base64FormatFileList" IsSingle="true" />
</DemoBlock>
<DemoBlock Title="@Localizer["DropUploadTitle"]" Introduction="@Localizer["DropUploadIntro"]" Name="DropUpload">
<DropUpload OnChange="@OnCardUpload" ShowProgress="true" ShowFooter="true" FooterText="@Localizer["DropUploadFooterText"]"></DropUpload>
</DemoBlock>
<AttributeTable Items="@GetInputAttributes()" Title="InputUpload" />
<AttributeTable Items="@GetButtonAttributes()" Title="ButtonUpload/CardUpload" />
<AttributeTable Items="@GetAvatarAttributes()" Title="AvatarUpload" />

View File

@@ -1,492 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using Microsoft.AspNetCore.Components.Forms;
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// Uploads
/// </summary>
public sealed partial class Uploads
{
[NotNull]
private ConsoleLogger? Logger1 { get; set; }
[NotNull]
private ConsoleLogger? Logger2 { get; set; }
private static readonly Random random = new();
private CancellationTokenSource? ReadToken { get; set; }
private static long MaxFileLength => 5 * 1024 * 1024;
private Person Foo1 { get; set; } = new Person();
private Person Foo2 { get; set; } = new Person();
private List<UploadFile> PreviewFileList { get; } = [];
private CancellationTokenSource? ReadAvatarToken { get; set; }
private List<UploadFile> DefaultFormatFileList { get; } =
[
new UploadFile { FileName = "Test.xls" },
new UploadFile { FileName = "Test.doc" },
new UploadFile { FileName = "Test.ppt" },
new UploadFile { FileName = "Test.mp3" },
new UploadFile { FileName = "Test.mp4" },
new UploadFile { FileName = "Test.pdf" },
new UploadFile { FileName = "Test.cs" },
new UploadFile { FileName = "Test.zip" },
new UploadFile { FileName = "Test.txt" },
new UploadFile { FileName = "Test.dat" }
];
private List<UploadFile> Base64FormatFileList { get; } =
[
new UploadFile { FileName = "Test", PrevUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAkCAYAAAD/yagrAAAE60lEQVR4AWJwL/AB9GIWvI0jURz/L1NomZmZP8p9kWNmhnIbKPdomZmZGcJgO7QsWMbce55Yl11t47FbVdKTYpjOr/+H4y4Z/MpYeJVV8KVvokFRylq9osKfVtGQ/NHyPl2CbNVGwau2oOlmgUDJtDJGzxtvvIA3vRH+7IgeA0VtYhQBtKPlToFgC6RY58aggfwLNKhr0Zry2NnPrpIM2YGW2+UBG1IEmdGVJEVXE+hQXt8joKhLjdGVZHd7TSD9DHmT3J1dheroSF4fhnvUVQwbZx9UOnHSzQjkTN0tIG+8hFdbxet4PQOG4WqJwN1hFVb+xUBmCblxvxRkIE+Q+Yf0T31NSrp4/RV4FhHgoSTchQRZBK4D1+Fe2q2g8CYXU6buQyNDaiZKZhn0IYXHVwbkRQydHyawOAGGisa/o3DvI/jF3QKKiuAy+LKs5CvaXMLdeXb35wbkKTjmReDZHysCBosWJqN7r0jZ/VfgXtIlUNQnVpK7D4nNJSC5BHGi1d108Ppr8MxlF0cJqBSyFJaUfUnvHLyO4cttgaI+NhGB7HE0MaSUks/gU5vwe1gv5tfhmUmAhxME8jbIUtiEDus+fhmDJlgCRY0ylZRZhybKWtNinmYlH9NvL5puO3m9UHOkgzb3xuF5HCkDyhYiSwrYVfQPTpYChTc+E35tG9VJBjGFFNmtVlNMDnjzbx0EBpKq1eTeh2awbArBRuHadBWu6WVB4U/NITfuobYoCZl7QL//wDtr+nTmsgzQh2Kwgtz7QAZWFeGw/RI8s94Kigp1PvzZg2i9x11FDtKn/oZCoZdZxhaAXmG4/ohJKHudLE1Gyu66DOfs10BRR5CU3TKQIrtzj9BAkF991dsMslTZEClLsI+jFmANZYFAdAJl9QG03jWH9GrcFh9QTP4Of6GfJGSpsv1J2aoEKRuWCANNJNqOFAaPAW36rVRMCjVf6ZCtqYF2p6Asxg5mWK6tQamY9RCs8z1wF+FxTR5UqYT/3GC7oCkMHcjxKguq6cnl+Egf2whip3C9Yu56jk+GXXOtv1XIa8L1f3AFkHe9az83AmNanwV/bhfa5JKJ3n1slCUL8dk7CNfvVFMfySRThoxbK7fh18tTZXI2QeySLk88IRGsbHkqQj6wUJ72G5CloCXKZrdbLPgVMgVfQMq5m97bfQWOOeVbaK02nSA2ofn2y662UJE47horLZTe38oDjdxQUhmawocxa0OJ5hXjnZE4w1y0uc/iULKGfk+xNuZxI/BnjhFs+THPq4phmTsbeYPXRjFsJJ+NSMlnInHkxrwzGDjR3uBcG1/B/b/TwZkhubb6tP2oVfTAD2G4kw9vYiA2h+zy4GwYxd7S4qGOgd6oqVn6re1DTXiO4e4wPF+yQlF4ZCBfheSPIjJn+ciS/w93qjA6aZYqeQ5D3dTqvuE6GZNTkov5vqsYvLh7j8u1sWWkLIdBQR+q+XdtaKGhJCWBDpkwgSw9gRpKyoNKw6rjCLCDP4yVflSgWFwt3C0HSUo2BTFYX28fVAaWPpDx7wtwjOSSUszawnUT0KTudlfrFQwZ3WNf867CNZQhk/C8kIFUhJLt/O3Jzn62IYNwrUvA8zws4W61CGlDSfugXMz5pJgiSFYyXMYiRXdH4GmyqaR9UHLxzxG41KAwpZxRPN4kRf85Z5I4MvYfFUFGfemJG40AAAAASUVORK5CYII=" },
];
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
PreviewFileList.AddRange(new[]
{
new UploadFile { PrevUrl = $"{WebsiteOption.CurrentValue.AssetRootPath}images/Argo.png" }
});
}
private Task OnFileChange(UploadFile file)
{
// 未真正保存文件
// file.SaveToFile()
Logger1.Log($"{file.File!.Name} {Localizer["UploadsSuccess"]}");
return Task.CompletedTask;
}
private Task<bool> OnFileDelete(UploadFile item)
{
Logger1.Log($"{item.OriginFileName} {Localizer["UploadsRemoveMsg"]}");
return Task.FromResult(true);
}
private static Task OnSubmit(EditContext context)
{
// 示例代码请根据业务情况自行更改
// var fileName = Foo.Picture?.Name;
return Task.CompletedTask;
}
private async Task OnClickToUpload(UploadFile file)
{
// 示例代码,模拟 80% 几率保存成功
var error = random.Next(1, 100) > 80;
if (error)
{
file.Code = 1;
file.Error = Localizer["UploadsError"];
}
else
{
await SaveToFile(file);
}
}
private async Task OnClickToUploadNoUploadList(UploadFile file)
{
await ToastService.Success("Upload", $"{file.OriginFileName} uploaded success.");
}
private async Task<bool> SaveToFile(UploadFile file)
{
// Server Side 使用
// Web Assembly 模式下必须使用 WebApi 方式去保存文件到服务器或者数据库中
// 生成写入文件名称
var ret = false;
if (!string.IsNullOrEmpty(WebsiteOption.CurrentValue.WebRootPath))
{
var uploaderFolder = Path.Combine(WebsiteOption.CurrentValue.WebRootPath, $"images{Path.DirectorySeparatorChar}uploader");
file.FileName = $"{Path.GetFileNameWithoutExtension(file.OriginFileName)}-{DateTimeOffset.Now:yyyyMMddHHmmss}{Path.GetExtension(file.OriginFileName)}";
var fileName = Path.Combine(uploaderFolder, file.FileName);
ReadToken ??= new CancellationTokenSource();
ret = await file.SaveToFileAsync(fileName, MaxFileLength, ReadToken.Token);
if (ret)
{
// 保存成功
file.PrevUrl = $"{WebsiteOption.CurrentValue.AssetRootPath}images/uploader/{file.FileName}";
}
else
{
var errorMessage = $"{Localizer["UploadsSaveFileError"]} {file.OriginFileName}";
file.Code = 1;
file.Error = errorMessage;
await ToastService.Error(Localizer["UploadFile"], errorMessage);
}
}
else
{
file.Code = 1;
file.Error = Localizer["UploadsWasmError"];
await ToastService.Information(Localizer["UploadsSaveFile"], Localizer["UploadsSaveFileMsg"]);
}
return ret;
}
private async Task OnDownload(UploadFile item)
{
await ToastService.Success("文件下载", $"下载 {item.FileName} 成功");
}
private async Task OnUploadFolder(UploadFile file)
{
// 上传文件夹时会多次回调此方法
await SaveToFile(file);
}
private async Task OnAvatarUpload(UploadFile file)
{
// 示例代码,使用 base64 格式
if (file != null && file.File != null)
{
var format = file.File.ContentType;
if (CheckValidAvatarFormat(format))
{
ReadAvatarToken ??= new CancellationTokenSource();
if (ReadAvatarToken.IsCancellationRequested)
{
ReadAvatarToken.Dispose();
ReadAvatarToken = new CancellationTokenSource();
}
await file.RequestBase64ImageFileAsync(format, 640, 480, MaxFileLength, ReadAvatarToken.Token);
}
else
{
file.Code = 1;
file.Error = Localizer["UploadsFormatError"];
}
if (file.Code != 0)
{
await ToastService.Error(Localizer["UploadsAvatarMsg"], $"{file.Error} {format}");
}
}
}
private static bool CheckValidAvatarFormat(string format)
{
return "jpg;png;bmp;gif;jpeg".Split(';').Any(f => format.Contains(f, StringComparison.OrdinalIgnoreCase));
}
private Task OnAvatarValidSubmit(EditContext context)
{
Logger2.Log(Foo2.Picture?.Name ?? "");
return Task.CompletedTask;
}
private async Task OnCardUpload(UploadFile file)
{
if (file != null && file.File != null)
{
// 服务器端验证当文件大于 5MB 时提示文件太大信息
if (file.Size > MaxFileLength)
{
await ToastService.Information(Localizer["UploadsFileMsg"], Localizer["UploadsFileError"]);
file.Code = 1;
file.Error = Localizer["UploadsFileError"];
}
else
{
await SaveToFile(file);
}
}
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
ReadToken?.Cancel();
ReadAvatarToken?.Cancel();
GC.SuppressFinalize(this);
}
private List<AttributeItem> GetInputAttributes() =>
[
new() {
Name = "ShowDeleteButton",
Description = Localizer["UploadsShowDeleteButton"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new() {
Name = "IsDisabled",
Description = Localizer["UploadsIsDisabled"],
Type = "boolean",
ValueList = "true / false",
DefaultValue = "false"
},
new() {
Name = "PlaceHolder",
Description = Localizer["UploadsPlaceHolder"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "Accept",
Description = Localizer["UploadsAccept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "BrowserButtonClass",
Description = Localizer["UploadsBrowserButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-primary"
},
new() {
Name = "BrowserButtonIcon",
Description = Localizer["UploadsBrowserButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa-regular fa-folder-open"
},
new() {
Name = "BrowserButtonText",
Description = Localizer["UploadsBrowserButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = ""
},
new() {
Name = "DeleteButtonClass",
Description = Localizer["UploadsDeleteButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-danger"
},
new() {
Name = "DeleteButtonIcon",
Description = Localizer["UploadsDeleteButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa-regular fa-trash"
},
new() {
Name = "DeleteButtonText",
Description = Localizer["UploadsDeleteButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = Localizer["UploadsDeleteButtonTextDefaultValue"]
},
new() {
Name = "OnDelete",
Description = Localizer["UploadsOnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "OnChange",
Description = Localizer["UploadsOnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
}
];
private List<AttributeItem> GetButtonAttributes() =>
[
new() {
Name = "IsDirectory",
Description = Localizer["UploadsIsDirectory"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new() {
Name = "IsMultiple",
Description = Localizer["UploadsIsMultiple"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new() {
Name = "IsSingle",
Description = Localizer["UploadsIsSingle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new() {
Name = "ShowProgress",
Description = Localizer["UploadsShowProgress"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new() {
Name = "Accept",
Description = Localizer["UploadsAccept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "BrowserButtonClass",
Description = Localizer["UploadsBrowserButtonClass"],
Type = "string",
ValueList = " — ",
DefaultValue = "btn-primary"
},
new() {
Name = "BrowserButtonIcon",
Description = Localizer["UploadsBrowserButtonIcon"],
Type = "string",
ValueList = " — ",
DefaultValue = "fa-regular fa-folder-open"
},
new() {
Name = "BrowserButtonText",
Description = Localizer["UploadsBrowserButtonText"],
Type = "string",
ValueList = " — ",
DefaultValue = ""
},
new() {
Name = "DefaultFileList",
Description = Localizer["UploadsDefaultFileList"],
Type = "List<UploadFile>",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "OnGetFileFormat",
Description = Localizer["UploadsOnGetFileFormat"],
Type = "Func<string, string>",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "OnDelete",
Description = Localizer["UploadsOnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "OnChange",
Description = Localizer["UploadsOnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "OnDownload",
Description = Localizer["UploadsOnDownload"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "IconTemplate",
Description = Localizer["UploadsIconTemplate"],
Type = "RenderFragment<UploadFile>",
ValueList = " — ",
DefaultValue = " — "
}
];
private List<AttributeItem> GetAvatarAttributes() =>
[
new() {
Name = "Width",
Description = Localizer["UploadsWidth"],
Type = "int",
ValueList = " — ",
DefaultValue = "0"
},
new() {
Name = "Height",
Description = Localizer["UploadsHeight"],
Type = "int",
ValueList = "—",
DefaultValue = "0"
},
new() {
Name = "IsCircle",
Description = Localizer["UploadsIsCircle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new() {
Name = "IsSingle",
Description = Localizer["UploadsIsSingle"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new() {
Name = "ShowProgress",
Description = Localizer["UploadsShowProgress"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new() {
Name = "Accept",
Description = Localizer["UploadsAccept"],
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "DefaultFileList",
Description = Localizer["UploadsDefaultFileList"],
Type = "List<UploadFile>",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "OnDelete",
Description = Localizer["UploadsOnDelete"],
Type = "Func<string, Task<bool>>",
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = "OnChange",
Description = Localizer["UploadsOnChange"],
Type = "Func<UploadFile, Task>",
ValueList = " — ",
DefaultValue = " — "
}
];
private class Person
{
[Required]
[StringLength(20, MinimumLength = 2)]
public string Name { get; set; } = "Blazor";
[Required]
[FileValidation(Extensions = [".png", ".jpg", ".jpeg"], FileSize = 50 * 1024)]
public IBrowserFile? Picture { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
@page "/vditor"
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
<h3>@Localizer["VditorTitle"]</h3>
<h4>@Localizer["VditorSubTitle"]</h4>
<PackageTips Name="BootstrapBlazor.Vditor" />
<p>@((MarkupString)Localizer["MarkdownsNote"].Value)</p>
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
<DemoBlock Title="@Localizer["BaseUsageTitle"]" Introduction="@Localizer["BaseUsageIntro"]" Name="Normal">
<section ignore class="row g-3">
<div class="col-12">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Mode"></BootstrapInputGroupLabel>
<Segmented Value="_mode" OnValueChanged="OnModeChanged">
@foreach (var item in Enum.GetValues(typeof(VditorMode)).Cast<VditorMode>())
{
<SegmentedItem Value="@item" Text="@item.ToString()" />
}
</Segmented>
</BootstrapInputGroup>
</div>
<div class="col-12">
<Button Text="GetValue" OnClick="OnTriggerGetValueAsync" IsDisabled="_isDisabled"></Button>
<Button Text="InsertValue" OnClick="OnTriggerInsertValueAsync" IsDisabled="_isDisabled"></Button>
<Button Text="GetHtml" OnClick="OnTriggerGetHtmlAsync" IsDisabled="_isDisabled"></Button>
<Button Text="GetSelection" OnClick="OnTriggerGetSelectionAsync" IsDisabled="_isDisabled"></Button>
<Button Text="Enable" OnClick="OnTriggerEnableAsync" IsDisabled="!_isDisabled"></Button>
<Button Text="Disable" OnClick="OnTriggerDisableAsync" IsDisabled="_isDisabled"></Button>
<Button Text="Focus" OnClick="OnTriggerFocusAsync" IsDisabled="_isDisabled"></Button>
<Button Text="Blur" OnClick="OnTriggerBlurAsync" IsDisabled="_isDisabled"></Button>
</div>
</section>
<Vditor Value="@_vditorValueString" Options="_vditorOptions" @ref="_vditor"
OnRenderedAsync="OnRenderAsync"
OnFocusAsync="OnFocusAsync" OnBlurAsync="OnBlurAsync"
OnEscapeAsync="OnEscapeAsync" OnCtrlEnterAsync="OnCtrlEnterAsync"
OnSelectAsync="OnSelectAsync" OnInputAsync="OnInputAsync"></Vditor>
<section ignore class="mt-3">
<p>
<textarea class="form-control" rows="6" disabled="disabled">@_vditorValueString</textarea>
</p>
<p>
<textarea class="form-control" rows="6" disabled="disabled"> @_htmlString</textarea>
</p>
<ConsoleLogger @ref="_logger"></ConsoleLogger>
</section>
</DemoBlock>
<AttributeTable Items="GetAttributes()" />

View File

@@ -0,0 +1,195 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
namespace BootstrapBlazor.Server.Components.Samples;
/// <summary>
/// Vditors 组件示例代码
/// </summary>
public partial class Vditors
{
[Inject]
[NotNull]
private IStringLocalizer<Vditors>? Localizer { get; set; }
private VditorOptions _vditorOptions = new()
{
Height = "500px"
};
private Vditor _vditor = default!;
private string? _htmlString;
private string _vditorValueString = "## 所见即所得WYSIWYG\n所见即所得模式对不熟悉 Markdown 的用户较为友好,熟悉 Markdown 的话也可以无缝使用。";
private VditorMode _mode = VditorMode.WYSIWYG;
private ConsoleLogger _logger = default!;
private async Task OnModeChanged(VditorMode mode)
{
_mode = mode;
_vditorOptions.Mode = mode;
if (mode == VditorMode.WYSIWYG)
{
_vditorValueString = "## 所见即所得WYSIWYG\n所见即所得模式对不熟悉 Markdown 的用户较为友好,熟悉 Markdown 的话也可以无缝使用。";
}
else if (mode == VditorMode.IR)
{
_vditorValueString = "## 即时渲染IR\n即时渲染模式对熟悉 Typora 的用户应该不会感到陌生,理论上这是最优雅的 Markdown 编辑方式。";
}
else if (mode == VditorMode.SV)
{
_vditorValueString = "## 分屏预览SV\n传统的分屏预览模式适合大屏下的 Markdown 编辑。";
}
_htmlString = await _vditor.GetHtmlAsync();
StateHasChanged();
}
private Task OnRenderAsync()
{
_logger.Log($"Trigger OnRenderAsync");
return Task.CompletedTask;
}
private Task OnFocusAsync(string value)
{
_logger.Log($"Trigger OnFocusAsync");
return Task.CompletedTask;
}
private async Task OnBlurAsync(string value)
{
_vditorValueString = value;
_logger.Log($"Trigger OnBlurAsync");
_htmlString = await _vditor.GetHtmlAsync();
StateHasChanged();
}
private Task OnEscapeAsync(string value)
{
_logger.Log($"Trigger OnEscapeAsync");
return Task.CompletedTask;
}
private Task OnSelectAsync(string value)
{
_logger.Log($"Trigger OnSelectAsync");
return Task.CompletedTask;
}
private async Task OnInputAsync(string value)
{
_vditorValueString = value;
_htmlString = await _vditor.GetHtmlAsync();
_logger.Log($"Trigger OnInputAsync");
StateHasChanged();
}
private async Task OnCtrlEnterAsync(string value)
{
_vditorValueString = value;
_htmlString = await _vditor.GetHtmlAsync();
_logger.Log($"Trigger OnCtrlEnterAsync");
StateHasChanged();
}
private async Task OnTriggerGetValueAsync()
{
_vditorValueString = await _vditor.GetValueAsync() ?? "";
}
private async Task OnTriggerInsertValueAsync()
{
await _vditor.InsertValueAsync("光标处插入当前值");
}
private async Task OnTriggerGetHtmlAsync()
{
_htmlString = await _vditor.GetHtmlAsync();
}
private async Task OnTriggerGetSelectionAsync()
{
var selection = await _vditor.GetSelectionAsync() ?? "";
_logger.Log($"Trigger OnTriggerGetSelectionAsync: {selection}");
}
private bool _isDisabled = false;
private async Task OnTriggerEnableAsync()
{
await _vditor.EnableAsync();
_isDisabled = false;
}
private async Task OnTriggerDisableAsync()
{
await _vditor.DisableAsync();
_isDisabled = true;
}
private async Task OnTriggerFocusAsync()
{
await _vditor.FocusAsync();
}
private async Task OnTriggerBlurAsync()
{
await _vditor.BlurAsync();
}
private static AttributeItem[] GetAttributes() =>
[
new()
{
Name = "EditorSettings",
Description = "编辑器设置",
Type = "EditorSettings",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "ToolbarSettings",
Description = "工具栏设置",
Type = "ToolbarSettings",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "Value",
Description = "组件值",
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "Html",
Description = "组件 Html 代码",
Type = "string",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "OnFileUpload",
Description = "文件上传回调方法",
Type = "Func<CherryMarkdownUploadFile, Task<string>>",
ValueList = " — ",
DefaultValue = " — "
},
new()
{
Name = "IsViewer",
Description = "组件是否为浏览器模式",
Type = "bool",
ValueList = "true/false",
DefaultValue = "false"
}
];
}

View File

@@ -14,7 +14,7 @@ FROM build AS publish
RUN dotnet publish -c Release -o /app
FROM base AS final
ENV LANG zh_CN.utf8
ENV LANG=zh_CN.utf8
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "BootstrapBlazor.Server.dll"]

View File

@@ -13,15 +13,7 @@ internal static class MenusLocalizerExtensions
{
var menus = new List<MenuItem>();
// 快速入门
var item = new DemoMenuItem()
{
Text = Localizer["GetStarted"],
Icon = "fa-solid fa-fw fa-font-awesome"
};
AddQuickStar(item);
item = new DemoMenuItem()
{
Text = Localizer["LayoutComponents"],
Icon = "fa-fw fa-solid fa-desktop"
@@ -91,6 +83,13 @@ internal static class MenusLocalizerExtensions
};
AddSpeech(item);
item = new DemoMenuItem()
{
Text = Localizer["SocketComponents"],
Icon = "fa-fw fa-solid fa-satellite-dish text-danger"
};
AddSocket(item);
item = new DemoMenuItem()
{
Text = Localizer["Services"],
@@ -117,16 +116,15 @@ internal static class MenusLocalizerExtensions
Text = Localizer["Utility"],
Icon = "fa-fw fa-solid fa-code"
};
AddBootstrapBlazorUtility(item);
// 快速入门
item = new DemoMenuItem()
{
Text = Localizer["Components"],
Icon = "fa-solid fa-fw fa-heart fa-beat icon-summary",
Url = "components"
Text = Localizer["GetStarted"],
Icon = "fa-solid fa-fw fa-font-awesome"
};
AddSummary(item);
AddQuickStar(item);
return menus;
@@ -205,6 +203,38 @@ internal static class MenusLocalizerExtensions
AddBadge(item, count: 5);
}
void AddSocket(DemoMenuItem item)
{
item.Items = new List<DemoMenuItem>
{
new()
{
IsNew = true,
Text = Localizer["SocketManualReceive"],
Url = "socket/manual-receive"
},
new()
{
IsNew = true,
Text = Localizer["SocketAutoReceive"],
Url = "socket/auto-receive"
},
new()
{
IsNew = true,
Text = Localizer["DataPackageAdapter"],
Url = "socket/adapter"
},
new()
{
IsNew = true,
Text = Localizer["SocketAutoConnect"],
Url = "socket/auto-connect"
}
};
AddBadge(item, count: 1);
}
void AddQuickStar(DemoMenuItem item)
{
item.Items = new List<DemoMenuItem>
@@ -237,7 +267,6 @@ internal static class MenusLocalizerExtensions
},
new()
{
IsUpdate = true,
Text = Localizer["Labels"],
Url = "label"
},
@@ -293,7 +322,7 @@ internal static class MenusLocalizerExtensions
Url = "layout-page"
}
};
AddBadge(item, count: 0);
AddSummary(item);
}
void AddForm(DemoMenuItem item)
@@ -410,7 +439,6 @@ internal static class MenusLocalizerExtensions
},
new()
{
IsNew = true,
Text = Localizer["OtpInput"],
Url = "otp-input"
},
@@ -447,13 +475,11 @@ internal static class MenusLocalizerExtensions
},
new()
{
IsUpdate = true,
Text = Localizer["SelectTable"],
Url = "select-table"
},
new()
{
IsUpdate = true,
Text = Localizer["SelectTree"],
Url = "select-tree"
},
@@ -489,13 +515,38 @@ internal static class MenusLocalizerExtensions
},
new()
{
Text = Localizer["Upload"],
Url = "upload"
Text = Localizer["InputUpload"],
Url = "upload-input"
},
new()
{
Text = Localizer["ButtonUpload"],
Url = "upload-button"
},
new()
{
Text = Localizer["AvatarUpload"],
Url = "upload-avatar"
},
new()
{
Text = Localizer["CardUpload"],
Url = "upload-card"
},
new()
{
Text = Localizer["DropUpload"],
Url = "upload-drop"
},
new()
{
Text = Localizer["ValidateForm"],
Url = "validate-form"
},
new()
{
Text = Localizer["Vditor"],
Url = "vditor"
}
};
AddBadge(item);
@@ -681,11 +732,23 @@ internal static class MenusLocalizerExtensions
Url = "mermaid"
},
new()
{
IsNew = true,
Text = Localizer["OfficeViewer"],
Url = "office-viewer"
},
new()
{
Text = Localizer["PdfReader"],
Url = "pdf-reader"
},
new()
{
IsNew = true,
Text = Localizer["PdfViewer"],
Url = "pdf-viewer"
},
new()
{
Text = Localizer["Player"],
Url = "player"
@@ -772,13 +835,11 @@ internal static class MenusLocalizerExtensions
},
new()
{
IsNew = true,
Text = Localizer["Typed"],
Url = "typed"
},
new()
{
IsNew = true,
Text = Localizer["UniverSheet"],
Url = "univer-sheet"
},
@@ -1183,11 +1244,16 @@ internal static class MenusLocalizerExtensions
},
new()
{
IsNew = true,
Text = Localizer["FullScreenButton"],
Url = "fullscreen-button"
},
new()
{
IsNew = true,
Text = Localizer["NetworkMonitor"],
Url = "network-monitor"
},
new()
{
Text = Localizer["Light"],
Url = "light"
@@ -1208,6 +1274,11 @@ internal static class MenusLocalizerExtensions
Url = "message"
},
new()
{
Text = Localizer["Meet"],
Url = "meet"
},
new()
{
Text = Localizer["Modal"],
Url = "modal"
@@ -1504,7 +1575,6 @@ internal static class MenusLocalizerExtensions
},
new()
{
IsNew = true,
Text = Localizer["Html2Image"],
Url = "html2image"
},
@@ -1539,13 +1609,18 @@ internal static class MenusLocalizerExtensions
Url = "print-service"
},
new()
{
IsNew = true,
Text = Localizer["TcpSocketFactory"],
Url = "socket-factory"
},
new()
{
Text = Localizer["ThemeProvider"],
Url = "theme-provider"
},
new()
{
IsNew = true,
Text = Localizer["TotpService"],
Url = "otp-service"
},
@@ -1556,13 +1631,11 @@ internal static class MenusLocalizerExtensions
},
new()
{
IsNew = true,
Text = Localizer["AudioDevice"],
Url = "audio-device"
},
new()
{
IsNew = true,
Text = Localizer["VideoDevice"],
Url = "video-device"
},
@@ -1601,13 +1674,13 @@ internal static class MenusLocalizerExtensions
},
new()
{
Text = Localizer["AntDesignIcon"],
Url = "ant-design-icon"
Text = Localizer["OctIcon"],
Url = "oct-icon"
},
new()
{
Text = Localizer["ElementIcon"],
Url = "element-icon"
Text = Localizer["UniverIcon"],
Url = "univer-icon"
},
new()
{
@@ -1616,14 +1689,13 @@ internal static class MenusLocalizerExtensions
},
new()
{
Text = Localizer["OctIcon"],
Url = "oct-icon"
Text = Localizer["ElementIcon"],
Url = "element-icon"
},
new()
{
IsNew = true,
Text = Localizer["UniverIcon"],
Url = "univer-icon"
Text = Localizer["AntDesignIcon"],
Url = "ant-design-icon"
}
};
AddBadge(item);
@@ -1635,7 +1707,7 @@ internal static class MenusLocalizerExtensions
var count = 0;
count = menus.OfType<DemoMenuItem>().Sum(i => i.Count);
AddBadge(item, false, count);
menus.Insert(1, item);
menus.Insert(0, item);
}
void AddBadge(DemoMenuItem item, bool append = true, int? count = null)

View File

@@ -45,6 +45,10 @@ static class ServiceCollectionExtensions
services.AddTaskServices();
services.AddHostedService<ClearTempFilesService>();
services.AddHostedService<MockOnlineContributor>();
services.AddHostedService<MockReceiveSocketServerService>();
services.AddHostedService<MockSendReceiveSocketServerService>();
services.AddHostedService<MockCustomProtocolSocketServerService>();
services.AddHostedService<MockDisconnectServerService>();
// 增加通用服务
services.AddBootstrapBlazorServices();

View File

@@ -91,6 +91,9 @@ public static class ServiceCollectionSharedExtensions
// 增加 JuHe 定位服务
services.AddBootstrapBlazorJuHeIpLocatorService();
// 增加 ITcpSocketFactory 服务
services.AddBootstrapBlazorTcpSocketFactory();
// 增加 PetaPoco ORM 数据服务操作类
// 需要时打开下面代码
//services.AddPetaPoco(option =>

View File

@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using Microsoft.Extensions.FileProviders;
namespace Microsoft.AspNetCore.Builder;
static class WebApplicationExtensions
{
public static void UseUploaderStaticFiles(this WebApplication app)
{
var uploader = Path.Combine(app.Environment.WebRootPath, "images", "uploader");
Directory.CreateDirectory(uploader);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(uploader),
RequestPath = "/images/uploader"
});
}
}

View File

@@ -676,6 +676,9 @@
"TreeViewCheckboxCheckBoxDisplayText2": "Automatically select parent node",
"TreeViewCheckboxButtonText": "Refresh",
"TreeViewCheckboxAddButtonText": "Add",
"TreeViewDraggableTitle": "Draggable nodes",
"TreeViewDraggableIntro": "Allows nodes to be dragged and dropped in the tree control",
"TreeViewDraggableDescription": "By setting the <code>AllowDrag</code> property, you can drag and drop nodes in the tree control. Use the <code>OnDragItemEndAsync</code> callback delegate to handle the drop event.",
"TreeViewTreeDisableTitle": "Disabled state",
"TreeViewTreeDisableIntro": "Some nodes of the Tree can be set to disabled state",
"TreeViewTreeDisableDescription": "By setting the <code>Disabled</code> property of the data source <code>TreeViewItem</code> object, you can control whether this node can be checked or not. When set to <code>false</code>, it will not affect the node expansion. /shrink function",
@@ -744,7 +747,7 @@
"DownloadsTitle": "Download file download",
"DownloadsSubTitle": "For direct download of physical files",
"DownloadsTips1": "pay attention",
"DownloadsTips2": "<code>Download</code> use the <code>DotNetStreamReference</code> object which allows streaming file data to the client. This approach loads the entire file into the client's' memory, which can impair performance. To download relatively large files (<code>&gt;=250 MB</code>), it is recommended to follow the <code>MVC</code> download from <code>Url</code>. <b><code>NOT SUPPORT NET5</code></b>",
"DownloadsTips2": "<code>Download</code> use the <code>DotNetStreamReference</code> object which allows streaming file data to the client. This approach loads the entire file into the client's' memory, which can impair performance. To download relatively large files (<code>&gt;=250 MB</code>), it is recommended to follow the <code>MVC</code> download from <code>Url</code>.",
"DownloadsExample": "Example",
"DownloadsExampleButtonText": "download file",
"DownloadsExampleRazorCodeTitle": "<code>Razor</code> code",
@@ -1177,15 +1180,8 @@
"P1": "Please check <a href='https://learn.microsoft.com/zh-cn/aspnet/core/blazor/globalization-localization?WT.mc_id=DT-MVP-5004174' target='_blank'> before reading the following knowledge points Official document</a>"
},
"BootstrapBlazor.Server.Components.Components.InstallContent": {
"Heading": "Environmental preparation",
"P1": "Make sure the system is installed",
"P2": "or",
"P3": "or",
"P4": "Currently supported",
"P5": "Create a project using the BootstrapBlazor Project Template extension",
"P6": "you can pass",
"P7": "portal",
"P8": "After downloading and installing the extension, create a project through the extension",
"InstallByTemplate": "You can download and install the extension through <a href=\"template\">[port]</a>, and then create a project with the extension. <code>Highly recommended</code>",
"P9": "Create a project with Visual Studio",
"P10": "Step 1. Create a project",
"P11": "Open Visual Studio",
@@ -1209,9 +1205,7 @@
"P29": "Add the following to",
"P30": "file so that",
"P31": "Components are recognized in the file",
"P32": "Increase",
"P33": "component to",
"P34": "in file (Replace original content with follow code)",
"AddRootText": "Add <code>BootstrapBlazorRoot</code> component into file (Replace original content with follow code)",
"P35": "under development",
"P36": "Step 3. Use components in the page",
"P37": "The last step is to use in the page",
@@ -1222,7 +1216,7 @@
"P42": "exist",
"P43": "middle",
"P44": "run the application",
"Tips2": "If you use the <code>Blazor App</code> template to create a project, please remove the default <code>Bootstrap</code> style link <code>&lt;link rel='stylesheet' href='css/bootstrap/bootstrap.min.css' /&gt;</code>. Install <b>FontAwesome</b> related resources by yourself or get the nuget package <code>BootstrapBlazor.FontAwesome</code>"
"Tips2": "If you use the blazor default template to create a project, please remove the default <code>Bootstrap</code> style link <code>&lt;link rel='stylesheet' href='css/bootstrap/bootstrap.min.css' /&gt;</code>. Install <b>FontAwesome</b> related resources by yourself or get the nuget package <code>BootstrapBlazor.FontAwesome</code>"
},
"BootstrapBlazor.Server.Components.Pages.Install_Server": {
"Title": "Server-Side Blazor Installation Tutorial",
@@ -1273,9 +1267,7 @@
"P24": "Add the following to",
"P25": "file so that",
"P26": "Components are recognized in the file",
"P27": "7. Increase",
"P28": "component to",
"P29": "in file",
"AddRootText": "7. Add <code>BootstrapBlazorRoot</code> component into <code>Routes.razor</code> file",
"P30": "under development",
"P31": "Step 3. Use components in the page",
"P32": "The last step is to use in the page <code>BootstrapBlazor</code> component and run it in the browser. E.g",
@@ -1546,137 +1538,6 @@
"SkeletonsTreeTitle": "Tree skeleton",
"SkeletonsTreeIntro": "Display when tree component is loaded"
},
"BootstrapBlazor.Server.Components.Pages.Coms": {
"Search": "Search for the desired component",
"Text1": "Layout",
"DividerText": "Divider",
"LayoutText": "Layout",
"FooterText": "Footer",
"RowText": "Row",
"ScrollText": "Scroll",
"SkeletonText": "Skeleton",
"SplitText": "Split",
"Text2": "Navigation",
"AnchorText": "Anchor",
"AnchorLinkText": "AnchorLink",
"BreadcrumbText": "Breadcrumb",
"MenuText": "Menu",
"NavText": "Nav",
"DropdownText": "Dropdown",
"FullScreenText": "FullScreen",
"GoTopText": "GoTop",
"LogoutText": "Logout",
"PaginationText": "Pagination",
"StepsText": "Step",
"TabText": "Tab",
"Text3": "Form",
"EditorFormText": "EditorForm",
"ValidateFormText": "ValidateForm",
"AutoCompleteText": "AutoComplete",
"AutoFillText": "AutoFill",
"ButtonText": "Button",
"CascaderText": "CascadingSelection",
"CheckboxText": "Checkbox",
"CheckboxListText": "CheckboxList",
"ColorPickerText": "ColorPicker",
"DateTimePickerText": "DateTimePicker",
"DateTimeRangeText": "DateTimeRange",
"EditorText": "Editor",
"InputText": "Input",
"InputNumberText": "InputNumber",
"InputGroupText": "InputGroup",
"MarkdownText": "Markdown",
"FloatingLabelText": "FloatingLabel",
"RadioText": "Radio",
"RateText": "Rate",
"SelectText": "Select",
"MultiSelectText": "MultiSelect",
"SliderText": "Slider",
"SwitchText": "Switch",
"TextareaText": "Textarea",
"ToggleText": "Toggle",
"TransferText": "Transfer",
"UploadText": "Upload",
"Text4": "Data",
"AvatarText": "Avatar",
"BadgeText": "Badge",
"CardText": "Card",
"CalendarText": "Calendar",
"CaptchaText": "Captcha",
"CarouselText": "Carousel",
"CircleText": "Circle",
"ClientText": "Client",
"DisplayText": "Display",
"EmptyText": "Empty",
"LocatorText": "IpLocator",
"ImageViewerText": "ImageViewer",
"IpText": "IpAddress",
"PrintText": "Print",
"TitleText": "Title",
"DownloadText": "Download",
"TransitionText": "Transition",
"CollapseText": "Collapse",
"DropdownWidgetText": "DropdownWidget",
"GroupBoxText": "GroupBox",
"LinkButtonText": "LinkButton",
"ListViewText": "ListView",
"PopoverText": "Popover",
"QRCodeText": "QRCode",
"SearchText": "Search",
"RecognizerText": "Recognizer",
"SpeechWaveText": "SpeechWave",
"SwitchButtonText": "Switch Button",
"TableText": "Table",
"TagText": "Tag",
"TimelineText": "Timeline",
"TooltipText": "Tooltip",
"TreeViewText": "TreeView",
"BarcodeReaderText": "BarcodeReader",
"BlockText": "Block",
"CameraText": "Camera",
"HandwrittenPageText": "Handwritten",
"Text5": "Notification",
"AlertText": "Alert",
"ConsoleText": "Console",
"DialogText": "Dialog",
"DrawerText": "Drawer",
"EditDialogText": "EditDialog",
"MessageText": "Message",
"ModalText": "Modal",
"LightText": "Light",
"PopoverConfirmText": "PopConfirm",
"ProgressText": "Progress",
"ReconnectorText": "Reconnector",
"ResponsiveText": "Responsive",
"SpinnerText": "Spinner",
"SweetAlertText": "SweetAlert",
"SearchDialogText": "SearchDialog",
"ToastText": "Toast",
"TimerText": "Timer",
"Text6": "Chart",
"ChartText": "Chart",
"ChartSummaryText": "Summary",
"ChartLineText": "Line",
"ChartBarText": "Bar",
"ChartPieText": "Pie",
"ChartDoughnutText": "Doughnut",
"ChartBubbleText": "Bubble",
"DispatchText": "Dispatch",
"GeolocationText": "Geolocation",
"OnScreenKeyboardText": "OnScreenKeyboard",
"NotificationsText": "Notification",
"SignaturePadText": "SignaturePad",
"BluetoothText": "Bluetooth Service",
"PdfReaderText": "PDF Reader",
"VideoPlayerText": "VideoPlayer",
"FileViewerText": "FileViewer",
"WebSerialText": "SerialService",
"MindMapText": "MindMap",
"MermaidText": "Mermaid",
"WebSpeechText": "WebSpeech",
"ImageCropperText": "ImageCropper",
"BarcodeGeneratorText": "BarcodeGenerator"
},
"BootstrapBlazor.Server.Components.Pages.Breakpoints": {
"Heading": "Breakpoints",
"Heading1": "Breakpoints are customizable widths that determine how responsive layouts behave in Bootstrap across devices or viewport sizes",
@@ -1725,7 +1586,9 @@
"Block2Intro": "Set custom exception handling logic by setting <code>OnErrorHandleAsync</code> callback method",
"DialogTitle": "In Dialog",
"DialogIntro": "Click the button to pop up a pop-up window. The button in the pop-up window triggers an exception and the error is displayed in the pop-up window",
"DialogText": "Popup"
"DialogText": "Popup",
"PageErrorTitle": "Page",
"PageErrorIntro": "Click the button to navigate to the page where the error occurred during the page life cycle. The error is displayed on the current page and does not affect the menu and the overall page layout."
},
"BootstrapBlazor.Server.Components.Pages.GlobalOption": {
"Title": "Global exception",
@@ -3088,7 +2951,9 @@
"MultiSelectVirtualizeDescription": "Component virtual scrolling supports two ways of providing data through <code>Items</code> or <code>OnQueryAsync</code> callback methods",
"MultiSelectsAttribute_ShowSearch": "Whether to display the search box",
"MultiSelectsAttribute_IsVirtualize": "Wether to enable virtualize",
"MultiSelectsAttribute_DefaultVirtualizeItemText": "The text string corresponding to the first load value when virtual scrolling is turned on is separated by commas"
"MultiSelectsAttribute_DefaultVirtualizeItemText": "The text string corresponding to the first load value when virtual scrolling is turned on is separated by commas",
"MultiSelectGenericTitle": "Generic",
"MultiSelectGenericIntro": "Data source <code>Items</code> supports generics when using <code>SelectedItem&lt;TValue&gt;</code>"
},
"BootstrapBlazor.Server.Components.Samples.Radios": {
"RadiosTitle": "Radio",
@@ -3210,7 +3075,7 @@
"SelectsEnumTitle": "Enumerate the data",
"SelectsEnumIntro": "an example of a type enumerated a <code>select</code> component binding",
"SelectsEnumDescription1": "Enumeration When the binding value is an empty enumeration type, the component automatically adds preferences internally through the <code>PlaceHolder</code> value, and when the <code>PlaceHolder</code> value is not set, <b>please select ... </b>as a preference, and this example binds <code>the EnumEducation</code> enumeration type data",
"SelectsEnumDescription2": "Setting <code>PalceHolder</code> is not valid when the binding value is an enumerated type",
"SelectsEnumDescription2": "Setting <code>PlaceHolder</code> is not valid when the binding value is an enumerated type",
"SelectsEnumSelectText1": "Can be empty",
"SelectsEnumSelectText2": "Not empty",
"SelectsEnumSelectText3": "Using enum integer values as Items",
@@ -3226,7 +3091,9 @@
"SelectsShowSearchTitle": "Drop-down box with search box",
"SelectsShowSearchIntro": "Controls whether the search box is displayed by setting the <code>ShowSearch</code> property, which is not displayed by default <b>false</b>. You can set the <code>IsAutoClearSearchTextWhenCollapsed</code> parameter to control whether the text in the search box is automatically cleared after the drop-down box is collapsed. The default value is <b>false</b>.",
"SelectsConfirmSelectTitle": "Drop-down box with confirmation",
"SelectsConfirmSelectIntro": "Block changes to the current value by setting the <code>OnBeforeSelectedItemChange</code> delegate.",
"SelectsConfirmSelectIntro": "Prevent the current value from changing by setting the <code>OnBeforeSelectedItemChange</code> delegate or setting the <code>ShowSwal</code> parameter to <code>true</code>.",
"SelectConfifrmSelectDesc1": "Set the <code>OnBeforeSelectedItemChange</code> callback method, and pop up a window in the callback method to confirm whether to change the value. If it returns <code>true</code>, the value will be changed, otherwise it will not be changed.",
"SelectConfifrmSelectDesc2": "Set <code>ShowSwal=\"true\"</code> and then confirm the value of the <code>SwalTitle</code> <code>SwalContent</code> parameter using the built-in popup window. In the callback method, you can confirm whether to change the value.",
"SelectsTimeZoneTitle": "Timezone",
"SelectsTimeZoneIntro": "Display data of Timezone",
"SwalTitle": "The drop-down box value changes",
@@ -3262,7 +3129,7 @@
"SelectsVirtualizeDescription": "Component virtual scrolling supports two ways of providing data through <code>Items</code> or <code>OnQueryAsync</code> callback methods",
"SelectsGenericTitle": "Generic",
"SelectsGenericIntro": "Data source <code>Items</code> supports generics when using <code>SelectedItem&lt;TValue&gt;</code>",
"SelectsGenericDesc": "<p>Please refer to <a href=\"https://github.com/dotnetcore/BootstrapBlazor/issues/4497?wt.mc_id=DT-MVP-5004174\" target=\"_blank\">Design Ideas</a> to understand this feature. In this example, by selecting the drop-down box option, the value obtained is the <code>Foo</code> instance, and the value displayed in the text box on the right is the <code>Address</code> value of the <code>Foo</code> attribute</p><p>In this example, the <code>ValueEqualityComparer</code> and <code>CustomKeyAttribute</code> parameters are not set, and the <code>[Key]</code> tag of the <code>Id</code> attribute of <code>Foo</code> is used for equality judgment</p>",
"SelectsGenericDesc": "<p>Please refer to <a href=\"https://github.com/dotnetcore/BootstrapBlazor/issues/4497?wt.mc_id=DT-MVP-5004174\" target=\"_blank\">Design Ideas</a> to understand this feature. In this example, by selecting the drop-down box option, the value obtained is the <code>Foo</code> instance, and the value displayed in the text box on the right is the <code>Address</code> value of the <code>Foo</code> attribute</p><p>In this example, <code>IsEditable=\"true\"</code> and <code>TextConvertToValueCallback</code> parameters are set. When a <code>Foo</code> that does not exist in the original data source is entered, a new <code>Foo</code> instance is added to the data source in the <code></code> callback method</p>",
"SelectsOnInputChangedCallback": "Callback method for converting input text into corresponding Value in edit mode",
"TextConvertToValueCallback": "Callback method when input text changes in edit mode",
"SelectsIsEditable": "Whether editable",
@@ -3456,60 +3323,50 @@
"RightHeaderTemplate": "the right panel Header template",
"RightItemTemplate": "the right panel Item template"
},
"BootstrapBlazor.Server.Components.Samples.Uploads": {
"UploadsTitle": "Upload",
"UploadsSubTitle": "Upload the file by clicking",
"BootstrapBlazor.Server.Components.Samples.UploadAvatars": {
"UploadsTitle": "AvatarUpload",
"UploadsSubTitle": "Click to upload a file, usually used to upload a preview of one or a group of avatar-like images",
"UploadsNote": "If you edit too much content, <code>signalR</code> communication interruption may be triggered. Please adjust the <code>HubOptions</code> configuration.",
"UploadNormalTitle": "Basic usage",
"UploadNormalIntro": "The <code>InputUpload</code> component is used with other form components to display the file name, select the file and upload it by clicking the <b>browse button</b>, and by setting the <code>ShowRemoveButton</code> parameter, display the <b>delete button</b>, click the delete button to call back <code>onDelete</code> delegate method",
"UploadNormalLabelName": "Name:",
"UploadNormalLabelAddress": "Address:",
"UploadNormalLabelPhoto": "Photo:",
"UploadFormSettingsTitle": "FormSettings",
"UploadFormSettingsIntro": "Use the file upload component to constrain the file format within the form",
"UploadFormSettingsLi1": "Using the <code>ValidateForm</code> form component, custom validation is set by setting the <code>fileValidation</code> label of the model properties to support file <b>extension</b><b>size</b>validation, in this case with the extension <code>.png .jpg .jpeg</code> and the file size limit to <code>50K</code>",
"UploadFormSettingsLi2": "After selecting the file and not starting to upload the file, click the <code>submit</code> button to verify that the data is legitimate, and then upload the file <code>OnSubmit</code> callback delegate, noting that the <b>Picture</b>property type is <code>IBrowserFile</code>",
"UploadFormSettingsButtonText": "Submit",
"UploadClickUploadTitle": "Click upload",
"UploadClickUploadIntro": "The <code>ButtonUpload</code> components, classic styles, user click button to pop up the file selection box.",
"UploadClickUploadTips1": "Click on the <code>browse button</code> select file upload, in this case set <code>IsMultiple-true</code> multiple-selectable file can be uploaded",
"UploadClickUploadTips2": "When you set up <code>IsSingle</code>, you can upload only one image or file",
"UploadClickUploadTips3ShowUploadList": "Set <code>ShowUploadFileList</code> value to <b>false</b> as normal button",
"UploadedFilesTitle": "A list of files has been uploaded",
"UploadedFilesIntro": "Use <code>DefaultFileList</code> to set up uploaded content",
"UploadFolderTitle": "Upload a folder",
"UploadFolderIntro": "Use <code>DefaultFileList</code> to set up uploaded content",
"AvatarUploadTitle": "User profile picture upload",
"AvatarUploadIntro": "<code>AvatarUpload</code> component, using the <code>OnChange</code> to limit the format and size of images uploaded by users. In this example, only <code>jpg/png/bmp/jpeg/gif</code> five picture formats are allowed",
"AvatarUploadTips1": "Card form avatar box",
"AvatarUploadTips2": "Round avatar frame",
"AvatarUploadTitle": "Basic usage",
"AvatarUploadIntro": "<code>AvatarUpload</code> component, using the <code>OnChange</code> to limit the format and size of images uploaded by users. In this example, only <code>jpg/png/bmp/jpeg/gif</code> five picture formats are allowed. The component processes user uploaded avatars by setting the <code>OnChange</code> callback function. If this callback is not provided, the component will use the built-in method to try to read the uploaded file and generate the preview data in <code>base64</code> format.",
"AvatarUploadTips3": "When you set up <code>IsSingle</code>, you can upload only one image or file",
"AvatarUploadTips4": " <div>The component provides <code>Accept</code> property for upload file filtering, in this case the circular avatar box accepts both GIF and JPEG images, sets the <code>Accept='image/gif, image/jpeg'</code> and can be written as: <code>Accept='image/*'</code> if you don't restrict the format of the image. Whether this property is not secure or should be to file format validation using the <b>server-side authentication </b></div>",
"AvatarUploadTips5": "RELATED: <a href='https://www.runoob.com/tags/att-input-accept.html' target='_blank'>[Accept]</a> <a href='https://www.iana.org/assignments/media-types/media-types.xhtml' target='_blank'>[Media Types]</a>",
"AvatarUploadTips6": "Set the preview address <code>PrevUrl</code> with the <code>DefaultFileList</code> property",
"AvatarUploadTips7": "Verify that an example of using a picture box is used in the form",
"AvatarUploadButtonText": "Submit",
"UploadPreCardStyleTitle": "Preview the card style",
"UploadPreCardStyleIntro": "<code>CardUpload</code> components and rendered in card-style band preview mode",
"UploadPreCardStyleSSR": "<b>SSR mode </b>",
"UploadPreCardStyleServerSide": "<code>Server Side</code> mode, you can use the <code>IWebHostEnvironment</code> injection service to get to the <code>wwwroot</code> directory and save the file to the <code>images/uploader</code>, which does not require a direct call the controller secondary of <b> MVC</b><code>SaveToFile</code> method",
"UploadPreCardStyleWasm": "<b>Wasm mode </b>",
"UploadPreCardStyleWasmSide": "It wasn't available in <code>wasm</code> mode, <code>IWebHostEnvironment</code> needed to save files to the server side by calling <code>webapi</code> interface, and so on",
"UploadPreCardStyleLink": "Interested students can view their knowledge of <code>Upload</code> components through the <code>wiki</code> in the open source repository related resources of the <a href='{0}' target='_blank'>[The portal]</a>",
"UploadPreCardStyleValidation": "In this example, server-side verification prompts the file for too much prompt when the file size exceeds <code>5MB</code>",
"UploadPreCardStyleTips1": "In this example, the <code>ShowProgress=true</code> display upload progress bar",
"UploadPreCardStyleTips2": "When you set up <code>IsSingle</code>, you can upload only one image or file",
"UploadFileIconTitle": "The file icon",
"UploadFileIconIntro": "Icons are displayed in different file formats",
"UploadFileIconTemplateTitle": "Custom file icon",
"UploadFileIconTemplateIntro": "By setting the <code>IconTemplate</code> parameter and using the <code>FileIcon</code> component, you can further customize the file icon <a href=\"/file-icon\">[FileIcon example]</a>",
"UploadBase64Title": "Base64 format",
"UploadBase64Intro": "use <code>data:image/xxx;base64,xxx</code> format data string as PrevUrl value",
"AvatarUploadValidateTitle": "ValidateForm",
"AvatarUploadValidateIntro": "Place it in <code>ValidateForm</code> to integrate automatic data validation function. For details, please refer to <code>ValidateForm</code> component. In this example, the uploaded file extension is limited to <code>.png, .jpg, .jpeg</code>. An error message will be displayed when uploading other formats. The file size limit is <code>5M</code>. An error message will also be displayed when it exceeds",
"AvatarUploadAcceptTitle": "Accept",
"AvatarUploadAcceptIntro": "The component provides <code>Accept</code> property for upload file filtering, in this case the circular avatar box accepts both GIF and JPEG images, sets the <code>Accept='image/gif, image/jpeg'</code> and can be written as: <code>Accept='image/*'</code> if you don't restrict the format of the image. Whether this property is not secure or should be to file format validation using the <b>server-side authentication</b>",
"UploadsWidth": "The width of the preview box",
"UploadsHeight": "The height of the preview box",
"UploadsIsCircle": "Whether it is circular avatar mode",
"UploadsBorderRadius": "Border radius",
"UploadsValidateFormTitle": "ValidateForm",
"UploadsValidateFormValidContent": "Saved successfully",
"UploadsValidateFormInValidContent": "Please correct it and submit the form again",
"UploadsFormatError": "The file format is incorrect",
"UploadsAvatarMsg": "Avatar upload"
},
"BootstrapBlazor.Server.Components.Samples.UploadInputs": {
"UploadsTitle": "InputUpload",
"UploadsSubTitle": "Select one or more files to upload by clicking the Browse button.",
"UploadsNote": "If you edit too much content, <code>signalR</code> communication interruption may be triggered. Please adjust the <code>HubOptions</code> configuration.",
"UploadNormalTitle": "Basic usage",
"UploadNormalIntro": "You can show the <b>Delete</b> button by setting <code>ShowDeleteButton=\"true\"</code>",
"UploadNormalLabelPhoto": "Select one or more files",
"UploadFormSettingsTitle": "ValidateForm",
"UploadFormSettingsIntro": "Use the file upload component to constrain the file format within the form",
"UploadFormSettingsLi1": "Using the <code>ValidateForm</code> form component, custom validation is set by setting the <code>fileValidation</code> label of the model properties to support file <b>extension</b><b>size</b>validation, in this case with the extension <code>.png .jpg .jpeg</code> and the file size limit to <code>5M</code>",
"UploadFormSettingsLi2": "After selecting the file and not starting to upload the file, click the <code>submit</code> button to verify that the data is legitimate, and then upload the file <code>OnSubmit</code> callback delegate, noting that the <b>Picture</b>property type is <code>IBrowserFile</code>",
"UploadFormSettingsButtonText": "Submit",
"UploadsIsDirectory": "Whether to upload the entire directory",
"UploadsIsMultiple": "Whether to allow multiple file uploads",
"UploadsShowProgress": "Whether to display the upload progress",
"UploadsDefaultFileList": "The collection of files has been uploaded",
"UploadsShowDeleteButton": "Whether to display the Delete button",
"UploadsShowDownloadButton": "Whether to display the Download button",
"UploadsIsDisabled": "Whether to disable it",
"UploadsPlaceHolder": "The place-in string",
"UploadsAccept": "Upload the received file format",
"UploadsBrowserButtonClass": "Upload button style",
"UploadsBrowserButtonIcon": "Browse the button icon",
"UploadsBrowserButtonText": "The browse button displays text",
@@ -3517,34 +3374,52 @@
"UploadsDeleteButtonIcon": "Remove the button icon",
"UploadsDeleteButtonText": "Delete the button text",
"UploadsDeleteButtonTextDefaultValue": "Delete",
"UploadsAccept": "Upload the received file format",
"UploadsOnDelete": "Call back this method when you click the Delete button",
"UploadsOnChange": "Call back this method when you click the Browse button",
"UploadsOnDownload": "Call back this method when you click the Download button",
"UploadsIsDirectory": "Whether to upload the entire directory",
"UploadsIsMultiple": "Whether to allow multiple file uploads",
"UploadsIsSingle": "Whether to upload only once",
"UploadsShowProgress": "Whether to display the upload progress",
"UploadsDefaultFileList": "The collection of files has been uploaded",
"UploadsOnGetFileFormat": "Set the file format icon to call back the delegate",
"UploadsWidth": "The width of the preview box",
"UploadsHeight": "The height of the preview box",
"UploadsIsCircle": "Whether it is circular avatar mode",
"UploadsSuccess": "The upload was successful",
"UploadsError": "The simulated upload failed",
"UploadsFormatError": "The file format is incorrect",
"UploadsAvatarMsg": "Avatar upload",
"UploadsFileMsg": "Upload the file",
"UploadsFileError": "The file size is greater than 5MB",
"UploadsSaveFileError": "Failed to save the file",
"UploadFile": "Upload the file",
"UploadsWasmError": "Wasm mode does not implement saving code",
"UploadsSaveFile": "Save the file",
"UploadsSaveFileMsg": "The current mode is WebAssembly, call Webapi mode to save files to the server side or database",
"UploadsRemoveMsg": "The removal was successful",
"UploadsIconTemplate": "The template of file icon",
"DropUploadTitle": "Drop to upload",
"DropUploadIntro": "Drag and drop files into the specified area to upload",
"DropUploadFooterText": "file size less than 5Mb"
"UploadsOnChange": "Call back this method when you click the Browse button"
},
"BootstrapBlazor.Server.Components.Samples.UploadButtons": {
"UploadsTitle": "ButtonUpload",
"UploadsSubTitle": "Upload files by clicking on them, usually used to upload file attachments",
"UploadsNote": "If you edit too much content, <code>signalR</code> communication interruption may be triggered. Please adjust the <code>HubOptions</code> configuration.",
"ButtonUploadTitle": "Basic usage",
"ButtonUploadIntro": "By setting <code>ShowUploadFileList=\"true\"</code> you can display the uploaded file list, and setting <code>ShowDeleteButton=\"true\"</code> you can display the <b>Delete</b> button"
},
"BootstrapBlazor.Server.Components.Samples.UploadCards": {
"UploadsTitle": "CardUpload",
"UploadsSubTitle": "Click the button to pop up the file selection box to select one or more files, which will be presented in a card-style preview mode.",
"UploadsNote": "If you edit too much content, <code>signalR</code> communication interruption may be triggered. Please adjust the <code>HubOptions</code> configuration.",
"ButtonUploadTitle": "Basic usage",
"ButtonUploadIntro": "Use <code>DefaultFileList</code> to set up uploaded content",
"UploadPreCardStyleTitle": "Preview the card style",
"UploadPreCardStyleIntro": "<code>CardUpload</code> components and rendered in card-style band preview mode",
"UploadFileIconTitle": "The file icon",
"UploadFileIconIntro": "Icons are displayed in different file formats",
"UploadFileIconTemplateTitle": "Custom file icon",
"UploadFileIconTemplateIntro": "By setting the <code>IconTemplate</code> parameter and using the <code>FileIcon</code> component, you can further customize the file icon <a href=\"/file-icon\">[FileIcon example]</a>",
"UploadBase64Title": "Base64 format",
"UploadBase64Intro": "By setting the <code>PrevUrl</code> parameter value of the <code>UploadFile</code> instance, use the image content string in the <code>data:image/xxx;base64,XXXXX</code> format as the preview file path",
"UploadsFileTitle": "Upload",
"UploadsFileError": "The file is larger than 5M. Please reselect the file to upload.",
"UploadsSuccess": "File saved successfully",
"UploadsSaveFileError": "File save failed",
"UploadsWasmError": "In wasm mode, please call the api to save"
},
"BootstrapBlazor.Server.Components.Samples.UploadDrops": {
"UploadsTitle": "DropUpload",
"UploadsSubTitle": "Upload one or more files by clicking on the component or by dragging or pasting",
"UploadsNote": "If the uploaded file is too large, it may trigger <code>signalR</code> communication interruption. Please adjust the <code>HubOptions</code> configuration yourself.",
"DropUploadTitle": "Basic usage",
"DropUploadIntro": "Handle all uploaded files via the <code>OnChange</code> callback",
"DropUploadFooterText": "File size should not exceed 5Mb",
"UploadsBodyTemplate": "Body Template",
"UploadsIconTemplate": "Icon Template",
"UploadsTextTemplate": "Text Template",
"UploadsUploadIcon": "Icon",
"UploadsUploadText": "Text",
"UploadsShowFooter": "Whether to display Footer",
"UploadsFooterTemplate": "Footer Text Template",
"UploadsFooterText": "Footer text"
},
"BootstrapBlazor.Server.Components.Samples.ValidateForms": {
"ChangeButtonText": "Change",
@@ -3801,7 +3676,8 @@
"CollapsibleTitle": "Collapsible",
"CollapsibleHeaderTemplate": "Header Template",
"CollapsibleBody": "Click card hader for collapse/expand card body",
"ShadowBody": "Shadow effect sample"
"ShadowBody": "Shadow effect sample",
"HeaderPaddingY": "Header top and bottom padding"
},
"BootstrapBlazor.Server.Components.Samples.Calendars": {
"Title": "Calendar",
@@ -3933,6 +3809,11 @@
"BasicUsageP2": "1. The <code>UseBootstrapBlazor</code> middleware in the <b>Startup.cs</b> file that client information collection is performed.",
"BasicUsageTips": "<code>app.UseBootstrapBlazor</code> Middleware is located assembly <code>BootstrapBlazor.Middleware</code>, please refer to this package yourself for proper use",
"BasicUsageP3": "2. The component uses the injection service <code>WebClientService</code> to call the <code>GetClientInfo</code> method.",
"BasicUsageP4": "3. Turn on IP geolocation",
"LocatorsProviderOptions": "<code>BootstrapBlazorOptions</code> section <code>WebClientOptions</code> By default it is <code>false</code>, which means the IP address location function is not enabled. Please change it to <code>true</code> in the configuration file or code.",
"LocatorsProviderDesc1": "Update the <code>appsetting.json</code> project configuration file",
"LocatorsProviderDesc2": "Or use the code to open",
"LocatorsProviderDesc3": "Or enable this function through configuration",
"GroupBoxTitle": "Connection information",
"IpLocatorFactoryDesc": "This service has built-in IP geolocation function. For detailed configuration and documentation, please refer to",
"Id": "Connection ID",
@@ -4219,6 +4100,7 @@
"LocatorsNormalExtendDescription": "Extend the custom geo-location query interface",
"LocatorsNormalExtend1": "1. Implement a custom locator",
"LocatorsNormalExtend2": "2. Configure a custom locator",
"LocatorsNormalExtend3": "3. Use locator",
"LocatorsNormalCustomerLocator": "Add <code>CustomerLocatorProvider</code> to the service container using the <code>AddSingleton</code> method",
"LocatorsNormalIpTitle": "IP test data",
"LocatorsNormalTips3": "Shandong, China Unicom",
@@ -4633,9 +4515,9 @@
"BootstrapBlazor.Server.Components.Components.Header": {
"DownloadText": "Download",
"HomeText": "Home",
"ComponentsText": "Components",
"IntroductionText": "Documents",
"TutorialsText": "Tutorials"
"TutorialsText": "Tutorials",
"FullScreenTooltipText": "Full Screen"
},
"BootstrapBlazor.Server.Components.Layout.BaseLayout": {
"SiteTitle": "Bootstrap Blazor enterprise-level UI component library",
@@ -4848,6 +4730,7 @@
"PulseButton": "PulseButton",
"Bluetooth": "IBluetooth",
"PdfReader": "PDF Reader",
"PdfViewer": "PDF Viewer",
"VideoPlayer": "VideoPlayer",
"FileViewer": "FileViewer",
"FlipClock": "FlipClock",
@@ -4943,7 +4826,22 @@
"TotpService": "ITotpService",
"VideoDevice": "IVideoDevice",
"AudioDevice": "IAudioDevice",
"FullScreenButton": "FullScreenButton"
"FullScreenButton": "FullScreenButton",
"Meet": "Meet",
"InputUpload": "InputUpload",
"ButtonUpload": "ButtonUpload",
"AvatarUpload": "AvatarUpload",
"CardUpload": "CardUpload",
"DropUpload": "DropUpload",
"Vditor": "Vditor Markdown",
"TcpSocketFactory": "ITcpSocketFactory",
"OfficeViewer": "Office Viewer",
"SocketComponents": "ITcpSocketFactory",
"SocketAutoReceive": "Auto Receive",
"SocketManualReceive": "Manual Receive",
"DataPackageAdapter": "DataPackageAdapter",
"SocketAutoConnect": "Reconnect",
"NetworkMonitor": "Network Monitor"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
"TablesHeaderTitle": "Header grouping function",
@@ -5796,7 +5694,7 @@
"SetFilterInCodeIntro": "Example shows how to set filters through code",
"SetFilterInCodeButtonText1": "Name contains 01",
"SetFilterInCodeButtonText2": "Reset All Filter",
"TablesFilterTemplateDescription": "<code>The FilterTemplate</code> type is <code>RenderFragment</code> <span>its value is a custom component, and the component must inherit</span> <code>the filterBase</code> In this case, the last column in this case<b>, the Quantity column</b>, uses the <code>custom component by filtering the template CustomerFilter</code> <a href=\"{0}\" target=\"_blank\">[portal] CustomerFilter component source code</a>"
"TablesFilterTemplateDescription": "<p><code>The FilterTemplate</code> type is <code>RenderFragment</code> <span>its value is a custom component, and the component must inherit</span> <code>the filterBase</code> In this case, the last column in this case<b>, the Quantity column</b>, uses the <code>custom component by filtering the template CustomerFilter</code> <a href=\"{0}\" target=\"_blank\">[portal] CustomerFilter component source code</a></p><p class=\"code-label\">Notes:</p><ul class=\"ul-demo\"><li>Custom filter components are wrapped with <code>FilterProvider</code>, and <code>FilterProvider</code> must be under the <code>FilterTemplate</code> node</li><li>Filters can be fine-tuned through the parameters of the <code>FilterProvider</code> component; for example, by setting <code>ShowMoreButton</code> to control whether the <b>+ -</b> symbol is displayed</li></ul-demo>"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesFixedHeader": {
"TablesFixedHeaderTitle": "Fixed header function",
@@ -5908,7 +5806,7 @@
"ImageViewerErrorTemplateTitle": "Load failed",
"ImageViewerErrorTemplateIntro": "Can be set up by the <code>Error Template</code> open Error Template function, Url parameter <code>Url</code> unable to load images displayed when the content of the Template",
"ImageViewerPreviewListTitle": "A larger preview",
"ImageViewerPreviewListIntro": "Can be set up by the <code>Preview List</code> opens a larger Preview of the function",
"ImageViewerPreviewListIntro": "Can be set up by the <code>Preview List</code> opens a larger Preview of the function, set <code>ZoomSpeed</code> to control the speed of scrolling and scaling",
"ImageViewersAttrUrl": "Picture Url",
"ImageViewersAttrAlt": "Native Alt attribute",
"ImageViewersAttrShowPlaceHolder": "Whether display placeholder for large images added",
@@ -6142,6 +6040,14 @@
"PdfReaderCompatibilityModeTips": "- Chrome < 97 automatically uses version 2.4.456<br/>- Chrome < 109 automatically uses version 2.6.347<br/>- Note: ReadOnly and Watermark cannot be used in these two compatibility modes",
"LocalFileName": "local PDF file path"
},
"BootstrapBlazor.Server.Components.Samples.PdfViewers": {
"PdfViewerTitle": "PDFViewer",
"PdfViewerDescription": "Open the PDF file in the component to read its contents",
"PdfViewerNormalTitle": "Basic usage",
"PdfViewerNormalIntro": "Load a PDF file by setting the <code>Url</code> parameter. Set <code>UseGoogleDocs</code> to use docs.google.com preview",
"PdfViewerToastSuccessfulContent": "PDF document loaded successfully.",
"PdfViewerToastNotSupportContent": "The browser does not support inline viewing of PDF files."
},
"BootstrapBlazor.Server.Components.Samples.VideoPlayers": {
"VideoPlayersTitle": "VideoPlayer",
"VideoPlayersNormalTitle": "Basic usage",
@@ -6656,7 +6562,9 @@
"BootstrapBlazor.Server.Components.Samples.ImageCroppers": {
"Title": "Image cropper",
"ImageCropperNormalText": "Basic usage",
"ImageCropperNormalIntro": "Url parameter setting image",
"ImageCropperNormalIntro": "The URL of the image can be set by setting the <code>Url</code> parameter. The cropping ratio can be set by setting <code>ImageCropperOption.AspectRatio=16/9f</code>. <code>1</code> is a square.",
"ImageCropperRoundText": "Round",
"ImageCropperRoundIntro": "Set the cropping mode to circular by setting the <code>IsRound</code> parameter",
"ImageCropperResetText": "Reset",
"ImageCropperReplaceText": "Replace",
"ImageCropperRotateText": "Rotate",
@@ -6785,12 +6693,6 @@
"BootstrapBlazor.Server.Components.Samples.FlipClocks": {
"FlipClocksTitle": "FlipClock",
"FlipClocksDescription": "Used for website timing or countdown",
"BaseUsageText": "Basic usage",
"BaseUsageIntro": "Synchronize display of current time",
"ShowMinuteText": "not display hours and minutes",
"ShowMinuteIntro": "By setting <code>ShowHour=\"false\"</code> <code>ShowMinute=\"false\"</code> to not display hour and minute information",
"ShowSecondText": "not display seconds",
"ShowSecondIntro": "By setting <code>ShowSecond=\"false\"</code> to display hour and minute information",
"CountText": "Counter",
"CountIntro": "By setting <code>ViewMode=\"FlipClockViewMode.Count\"</code> to enable the timer function, the start time can be set using <code>StartValue</code>",
"IsCountDownText": "CountDown",
@@ -6803,7 +6705,26 @@
"CardHeight": "Card Height",
"CardWidth": "Card Width",
"CardMargin": "Card Margin",
"CardGroupMargin": "Card Group Margin"
"CardGroupMargin": "Card Group Margin",
"ShowDay_Description": "Show day",
"ShowHour_Description": "Show hour",
"ShowMinute_Description": "Show minute",
"ShowMonth_Description": "Show month",
"ShowYear_Description": "Show year",
"ViewMode_Description": "View mode",
"StartValue_Description": "Start time",
"OnCompletedAsync_Description": "Callback when timer completed",
"Height_Description": "Height",
"BackgroundColor_Description": "Background color",
"FontSize_Description": "Font size",
"CardWidth_Description": "Card width",
"CardHeight_Description": "Card height",
"CardColor_Description": "Card font color",
"CardBackgroundColor_Description": "Card background color",
"CardDividerHeight_Description": "Card divider height",
"CardDividerColor_Description": "Card divider color",
"CardMargin_Description": "Card margin",
"CardGroupMargin_Description": "Card group margin"
},
"BootstrapBlazor.Server.Components.Samples.Icons.BootstrapIcons": {
"Title": "Bootstrap Icons",
@@ -7162,5 +7083,52 @@
"AudioDevicePauseText": "Pause",
"AudioDeviceResumeText": "Resume",
"AudioDeviceDownloadText": "Download"
},
"BootstrapBlazor.Server.Components.Samples.Vditors": {
"VditorTitle": "Vditor Markdown",
"VditorSubTitle": "Vditor is a browser-based Markdown editor that supports WYSIWYG, instant rendering (similar to Typora), and split-screen preview mode.",
"BaseUsageTitle": "Basic usage",
"BaseUsageIntro": "Set the content displayed by the component by setting the <code>Value</code> value, and set the component configuration information by setting the <code>Options</code> parameter"
},
"BootstrapBlazor.Server.Components.Samples.OfficeViewers": {
"OfficeViewerTitle": "Office Document Viewer",
"OfficeViewerDescription": "This component previews Office documents using Microsoft's online document preview feature",
"OfficeViewerNormalTitle": "Basic Usage",
"OfficeViewerNormalIntro": "Set the document URL for preview by configuring the <code>Url</code> value",
"OfficeViewerToastSuccessfulContent": "Office document loaded successfully"
},
"BootstrapBlazor.Server.Components.Samples.Sockets.ManualReceives": {
"ReceivesTitle": "Manual Receive",
"ReceivesDescription": "Receive data through call ReceiveAsync and display it",
"NormalTitle": "Basic usage",
"NormalIntro": "After the connection is established, the data sent by the server is received through the <code>ReceiveAsync</code> callback method. The data problems of sticking and splitting packets need to be handled by the client."
},
"BootstrapBlazor.Server.Components.Samples.Sockets.AutoReceives": {
"ReceivesTitle": "Socket Receive",
"ReceivesDescription": "Receive data through ReceivedCallBack and display it",
"NormalTitle": "Basic usage",
"NormalIntro": "After connecting, the timestamp data sent by the server is automatically received through the <code>ReceivedCallBack</code> callback method"
},
"BootstrapBlazor.Server.Components.Samples.Sockets.Adapters": {
"AdaptersTitle": "DataPackageAdapter",
"AdaptersDescription": "Receive data through the data adapter and display",
"NormalTitle": "Basic usage",
"NormalIntro": "After the connection is established, the timestamp data sent by the server is received through the <code>ReceivedCallBack</code> callback method of the <code>DataPackageAdapter</code> data adapter."
},
"BootstrapBlazor.Server.Components.Samples.Sockets.AutoReconnects": {
"AutoReconnectsTitle": "DataPackageAdapter",
"AutoReconnectsDescription": "Receive data through the data adapter and display",
"NormalTitle": "Basic usage",
"NormalIntro": "Enable automatic reconnection by setting <code>IsAutoReconnect</code>"
},
"BootstrapBlazor.Server.Components.Samples.NetworkMonitors": {
"NetworkMonitorTitle": "NetworkMonitor",
"NetworkMonitorDescription": "Use the browser native API <code>navigator.connection</code> to display the current network status in real time",
"NormalTitle": "Basic usage",
"NormalIntro": "Use the component <code>NetworkMonitorIndicator</code> to display indicator lights of different colors when the network status changes, and display the network status details when the mouse moves over it",
"IndicatorLi1": "Green: Very good network (4G)",
"IndicatorLi2": "Yellow: Average network (3G)",
"IndicatorLi3": "Red: Poor network (2G)",
"IndicatorLi4": "Gray: Offline"
}
}

View File

@@ -676,6 +676,9 @@
"TreeViewCheckboxCheckBoxDisplayText2": "自动选中父节点",
"TreeViewCheckboxButtonText": "刷新",
"TreeViewCheckboxAddButtonText": "追加节点",
"TreeViewDraggableTitle": "可拖拽节点",
"TreeViewDraggableIntro": "使树中的节点可以进行跨层级拖拽操作",
"TreeViewDraggableDescription": "通过设置 <code>AllowDrag</code> 属性开启节点拖拽功能,使用 <code>OnDragItemEndAsync</code> 回调委托方法响应拖拽节点放置事件",
"TreeViewTreeDisableTitle": "禁用状态",
"TreeViewTreeDisableIntro": "可将 Tree 的某些节点设置为禁用状态",
"TreeViewTreeDisableDescription": "通过设置数据源 <code>TreeViewItem</code> 对象的 <code>Disabled</code> 属性,来控制此节点是否可以进行勾选动作,设置为 <code>false</code> 时不影响节点展开/收缩功能",
@@ -744,7 +747,7 @@
"DownloadsTitle": "Download 文件下载",
"DownloadsSubTitle": "用于直接下载物理文件",
"DownloadsTips1": "特别注意",
"DownloadsTips2": "<code>Download</code> 组件底层使用了 <code>DotNetStreamReference</code> 对象,这允许将文件数据流式传输到客户端,此方法会将整个文件加载到客户端内存中,若要下载相对较大的文件 (<code>&gt;= 250 MB</code>),建议遵循 <code>MVC</code> 从 <code>Url</code> 下载<b><code>本组件不支持 NET5</code></b>",
"DownloadsTips2": "<code>Download</code> 组件底层使用了 <code>DotNetStreamReference</code> 对象,这允许将文件数据流式传输到客户端,此方法会将整个文件加载到客户端内存中,若要下载相对较大的文件 (<code>&gt;= 250 MB</code>),建议遵循 <code>MVC</code> 从 <code>Url</code> 下载",
"DownloadsExample": "示例",
"DownloadsExampleButtonText": "下载文件",
"DownloadsExampleRazorCodeTitle": "<code>Razor</code> 代码",
@@ -1177,16 +1180,9 @@
"P1": "阅读以下知识点前请先查看 <a href='https://learn.microsoft.com/zh-cn/aspnet/core/blazor/globalization-localization?WT.mc_id=DT-MVP-5004174' target='_blank'>微软官方文档</a>"
},
"BootstrapBlazor.Server.Components.Components.InstallContent": {
"Heading": "环境准备",
"P1": "确保系统安装",
"P2": "或者",
"P3": "或者",
"P4": "目前支持",
"P5": "使用 BootstrapBlazor Project Template 扩展创建项目",
"P6": "您可以通过",
"P7": "传送门",
"P8": "下载安装扩展后,通过扩展创建项目",
"P9": "使用 Visual Studio 创建项目 1",
"InstallByTemplate": "您可以通过 <a href=\"template\">[传送门]</a> 下载安装扩展后,通过扩展创建项目, <code>强烈推荐</code>",
"P9": "使用 Visual Studio 创建项目",
"P10": "步骤一、创建项目",
"P11": "打开 Visual Studio",
"P12": "创建一个新项目",
@@ -1209,9 +1205,7 @@
"P29": "将以下内容添加到",
"P30": "文件中,以便",
"P31": "文件中能识别组件",
"P32": "增加",
"P33": "组件到",
"P34": "文件中 (用下面代码替换原内容即可)",
"AddRootText": "增加 <code>BootstrapBlazorRoot</code> 组件到 <code>App.razor</code> 文件中 (用下面代码替换原内容即可)",
"P35": "正在玩命开发中",
"P36": "步骤三、页面中使用组件",
"P37": "最后一步是在页面中使用",
@@ -1222,7 +1216,7 @@
"P42": "在",
"P43": "中",
"P44": "运行应用程序",
"Tips2": "如果使用微软自带 <code>Blazor App</code> 模板创建工程,请移除 <code>_Layout.cshtml/index.html</code> 文件中的默认 <code>Bootstrap</code> 样式表 <code>&lt;link rel='stylesheet' href='css/bootstrap/bootstrap.min.css' /&gt;</code>。如果使用 <code>FontAwesome</code> 图标库可安装扩展包 <code>BootstrapBlazor.FontAwesome</code>"
"Tips2": "如果使用微软自带模板创建工程,请移除默认 <code>Bootstrap</code> 样式表 <code>&lt;link rel='stylesheet' href='css/bootstrap/bootstrap.min.css' /&gt;</code>。如果使用 <code>FontAwesome</code> 图标库可安装扩展包 <code>BootstrapBlazor.FontAwesome</code>"
},
"BootstrapBlazor.Server.Components.Pages.Install_Server": {
"Title": "服务器端 Blazor 安装教程",
@@ -1273,9 +1267,7 @@
"P24": "将以下内容添加到",
"P25": "文件中,以便",
"P26": "文件中能识别组件",
"P27": "7. 增加",
"P28": "组件到",
"P29": "文件中",
"AddRootText": "7. 增加 <code>BootstrapBlazorRoot</code> 组件到 <code>Routes.razor</code> 文件中",
"P30": "正在玩命开发中",
"P31": "步骤三、页面中使用组件",
"P32": "最后一步是在页面中使用 <code>BootstrapBlazor</code> 组件并在浏览器中运行它。例如:",
@@ -1546,137 +1538,6 @@
"SkeletonsTreeTitle": "树骨架屏",
"SkeletonsTreeIntro": "适用于树组件加载时显示"
},
"BootstrapBlazor.Server.Components.Pages.Coms": {
"Search": "搜索想要的组件",
"Text1": "布局组件",
"DividerText": "分割线 Divider",
"LayoutText": "布局组件 Layout",
"FooterText": "页脚组件 Footer",
"RowText": "行组件 Row",
"ScrollText": "滚动条 Scroll",
"SkeletonText": "骨架屏 Skeleton",
"SplitText": "分割面板 Split",
"Text2": "导航组件",
"AnchorText": "锚点 Anchor",
"AnchorLinkText": "锚点链接 AnchorLink",
"BreadcrumbText": "面包屑 Breadcrumb",
"MenuText": "菜单 Menu",
"NavText": "导航栏 Nav",
"DropdownText": "下拉菜单 Dropdown",
"FullScreenText": "全屏组件 FullScreen",
"GoTopText": "跳转组件 GoTop",
"LogoutText": "登出组件 Logout",
"PaginationText": "分页 Pagination",
"StepsText": "步骤条 Step",
"TabText": "标签页 Tab",
"Text3": "表单组件",
"EditorFormText": "表单组件 EditorForm",
"ValidateFormText": "表单组件 ValidateForm",
"AutoCompleteText": "自动完成 AutoComplete",
"AutoFillText": "自动填充 AutoFill",
"ButtonText": "按钮 Button",
"CascaderText": "Cascader 级联选择",
"CheckboxText": "多选框 Checkbox",
"CheckboxListText": "多选框组 CheckboxList",
"ColorPickerText": "颜色拾取器 ColorPicker",
"DateTimePickerText": "时间框 DateTimePicker",
"DateTimeRangeText": "时间范围框 DateTimeRange",
"EditorText": "富文本框 Editor",
"InputText": "输入框 Input",
"InputNumberText": "数字框 InputNumber",
"InputGroupText": "输入组 InputGroup",
"MarkdownText": "富文本框 Markdown",
"FloatingLabelText": "悬浮标签 FloatingLabel",
"RadioText": "单选框 Radio",
"RateText": "评分 Rate",
"SelectText": "选择器 Select",
"MultiSelectText": "多项选择器 MultiSelect",
"SliderText": "滑块 Slider",
"SwitchText": "开关 Switch",
"TextareaText": "多行文本框 Textarea",
"ToggleText": "开关 Toggle",
"TransferText": "穿梭框 Transfer",
"UploadText": "上传组件 Upload",
"Text4": "数据组件",
"AvatarText": "头像框 Avatar",
"BadgeText": "徽章 Badge",
"CardText": "卡片 Card",
"CalendarText": "日历框 Calendar",
"CaptchaText": "验证码 Captcha",
"CarouselText": "走马灯 Carousel",
"CircleText": "进度环 Circle",
"ClientText": "客户信息服务 Client",
"DisplayText": "数据显示 Display",
"EmptyText": "空状态 Empty",
"LocatorText": "地理位置定位 IpLocatorFactory ",
"ImageViewerText": "图片 ImageViewer",
"IpText": "IP 地址 IpAddress",
"PrintText": "打印按钮 Print",
"TitleText": "网站标题 Title",
"DownloadText": "文件下载 Download",
"TransitionText": "过渡效果 Transition",
"CollapseText": "折叠 Collapse",
"DropdownWidgetText": "挂件 DropdownWidget",
"GroupBoxText": "集合 GroupBox",
"LinkButtonText": "链接按钮组件 LinkButton",
"ListViewText": "列表组件 ListView",
"PopoverText": "弹出框 Popover",
"QRCodeText": "二维码 QRCode",
"SearchText": "搜索框 Search",
"RecognizerText": "语音识别 Recognizer",
"SpeechWaveText": "语音波形图 SpeechWave",
"SwitchButtonText": "状态切换按钮 SwitchButton",
"TableText": "表格 Table",
"TagText": "标签 Tag",
"TimelineText": "时间线 Timeline",
"TooltipText": "工具条 Tooltip",
"TreeViewText": "树形控件 TreeView",
"BarcodeReaderText": "条码扫描 BarcodeReader",
"BlockText": "条件块 Block",
"CameraText": "摄像头组件 Camera",
"HandwrittenPageText": "手写签名 Handwritten",
"Text5": "通知组件",
"AlertText": "警告框 Alert",
"ConsoleText": "控制台 Console",
"DialogText": "对话框 Dialog",
"DrawerText": "抽屉 Drawer",
"EditDialogText": "编辑弹窗 EditDialog",
"MessageText": "消息框 Message",
"ModalText": "模态框 Modal",
"LightText": "指示灯 Light",
"PopoverConfirmText": "确认框 PopConfirm",
"ProgressText": "进度条 Progress",
"ReconnectorText": "重连组件 Reconnector",
"ResponsiveText": "断点通知 Responsive",
"SpinnerText": "旋转图标 Spinner",
"SweetAlertText": "模态弹窗 SweetAlert",
"SearchDialogText": "搜索弹窗 SearchDialog",
"ToastText": "轻量弹窗 Toast",
"TimerText": "计时器 Timer",
"Text6": "图表 Chart",
"ChartText": "图表 Chart",
"ChartSummaryText": "图表简介",
"ChartLineText": "折线图 Line",
"ChartBarText": "柱状图 Bar",
"ChartPieText": "饼图 Pie",
"ChartDoughnutText": "圆环图 Doughnut",
"ChartBubbleText": "气泡图 Bubble",
"DispatchText": "消息分发服务 Dispatch",
"GeolocationText": "地理定位组件 Geolocation",
"NotificationsText": "浏览器通知 Notification",
"OnScreenKeyboardText": "屏幕键盘 OnScreenKeyboard",
"SignaturePadText": "手写签名 SignaturePad",
"BluetoothText": "蓝牙服务 IBluetoothService",
"PdfReaderText": "PDF阅读器 PDF Reader",
"VideoPlayerText": "视频播放器 VideoPlayer",
"FileViewerText": "文件预览器 FileViewer",
"WebSerialText": "串口服务 ISerialService",
"MindMapText": "思维导图 Mind Map",
"MermaidText": "图表工具 Mermaid",
"WebSpeechText": "语音识别/合成 WebSpeech",
"ImageCropperText": "图像裁剪 ImageCropper",
"BarcodeGeneratorText": "条码生成器 BarcodeGenerator"
},
"BootstrapBlazor.Server.Components.Pages.Breakpoints": {
"Heading": "断点",
"Heading1": "断点是可自定义的宽度,它决定了响应式布局在 Bootstrap 中跨设备或视口大小的行为方式",
@@ -1725,7 +1586,9 @@
"Block2Intro": "通过设置 <code>OnErrorHandleAsync</code> 回调方法,设置自定义异常处理逻辑",
"DialogTitle": "弹窗中异常捕获",
"DialogIntro": "点击按钮弹出弹窗,弹窗内按钮触发异常,错误显示在弹窗内",
"DialogText": "弹窗"
"DialogText": "弹窗",
"PageErrorTitle": "页面异常捕获",
"PageErrorIntro": "点击按钮导航到页面生命周期内出错的页面,错误显示在当前页面,不影响菜单以及整体页面布局"
},
"BootstrapBlazor.Server.Components.Pages.GlobalOption": {
"Title": "全局配置",
@@ -3088,7 +2951,9 @@
"MultiSelectVirtualizeDescription": "组件虚拟滚动支持两种形式通过 <code>Items</code> 或者 <code>OnQueryAsync</code> 回调方法提供数据",
"MultiSelectsAttribute_ShowSearch": "是否显示搜索框",
"MultiSelectsAttribute_IsVirtualize": "是否开启虚拟滚动",
"MultiSelectsAttribute_DefaultVirtualizeItemText": "开启虚拟滚动时首次加载 Value 对应的文本字符串用逗号分割"
"MultiSelectsAttribute_DefaultVirtualizeItemText": "开启虚拟滚动时首次加载 Value 对应的文本字符串用逗号分割",
"MultiSelectGenericTitle": "泛型支持",
"MultiSelectGenericIntro": "数据源 <code>Items</code> 使用 <code>SelectedItem&lt;TValue&gt;</code> 时即可支持泛型"
},
"BootstrapBlazor.Server.Components.Samples.Radios": {
"RadiosTitle": "Radio 单选框",
@@ -3210,7 +3075,7 @@
"SelectsEnumTitle": "枚举数据",
"SelectsEnumIntro": "<code>Select</code> 组件绑定枚举类型示例",
"SelectsEnumDescription1": "当绑定值为可为空枚举类型时,组件内部自动通过 <code>PlaceHolder</code> 值添加首选项,未设置 <code>PlaceHolder</code> 值时,使用资源文件中的 <b>请选择 ...</b> 作为首选项,本示例绑定 <code>EnumEducation</code> 枚举类型",
"SelectsEnumDescription2": "绑定值为枚举类型时,设置 <code>PalceHolder</code> 无效",
"SelectsEnumDescription2": "绑定值为枚举类型时,设置 <code>PlaceHolder</code> 无效",
"SelectsEnumSelectText1": "可为空",
"SelectsEnumSelectText2": "不为空",
"SelectsEnumSelectText3": "使用枚举整形值作为集合",
@@ -3226,7 +3091,9 @@
"SelectsShowSearchTitle": "带搜索框的下拉框",
"SelectsShowSearchIntro": "通过设置 <code>ShowSearch</code> 属性控制是否显示搜索框,默认为 <b>false</b> 不显示搜索框,可以通过设置 <code>IsAutoClearSearchTextWhenCollapsed</code> 参数控制下拉框收起后是否自动清空搜索框内文字,默认值为 <b>false</b> 不清空",
"SelectsConfirmSelectTitle": "带确认的下拉框",
"SelectsConfirmSelectIntro": "通过设置 <code>OnBeforeSelectedItemChange</code> 委托,阻止当前值的改变",
"SelectsConfirmSelectIntro": "通过设置 <code>OnBeforeSelectedItemChange</code> 委托或者设置 <code>ShowSwal</code> 参数值为 <code>true</code>,阻止当前值的改变",
"SelectConfifrmSelectDesc1": "设置 <code>OnBeforeSelectedItemChange</code> 回调方法,在回调方法内自己弹窗确认是否更改值,返回 <code>true</code> 时更改,否则不更改",
"SelectConfifrmSelectDesc2": "设置 <code>ShowSwal=\"true\"</code> 然后通过设置 <code>SwalTitle</code> <code>SwalContent</code> 参数值使用内置弹窗进行确认即可,在回调方法内自己弹窗确认是否更改值",
"SelectsTimeZoneTitle": "时区下拉框",
"SelectsTimeZoneIntro": "下拉框展现时区数据",
"SwalTitle": "下拉框值变更",
@@ -3262,7 +3129,7 @@
"SelectsVirtualizeDescription": "组件虚拟滚动支持两种形式通过 <code>Items</code> 或者 <code>OnQueryAsync</code> 回调方法提供数据",
"SelectsGenericTitle": "泛型支持",
"SelectsGenericIntro": "数据源 <code>Items</code> 使用 <code>SelectedItem&lt;TValue&gt;</code> 时即可支持泛型",
"SelectsGenericDesc": "<p>请参考 <a href=\"https://github.com/dotnetcore/BootstrapBlazor/issues/4497?wt.mc_id=DT-MVP-5004174\" target=\"_blank\">设计思路</a> 理解此功能。本例中通过选择下拉框选项,得到的值为 <code>Foo</code> 实例,右侧文本框内显示值为 <code>Foo</code> 属性 <code>Address</code> 值</p><p>本例中设置 <code>ValueEqualityComparer</code> 以及 <code>CustomKeyAttribute</code> 参数,使用 <code>Foo</code> 属性 <code>Id</code> <code>[Key]</code> 标签进行相等判定</p>",
"SelectsGenericDesc": "<p>请参考 <a href=\"https://github.com/dotnetcore/BootstrapBlazor/issues/4497?wt.mc_id=DT-MVP-5004174\" target=\"_blank\">设计思路</a> 理解此功能。本例中通过选择下拉框选项,得到的值为 <code>Foo</code> 实例,右侧文本框内显示值为 <code>Foo</code> 属性 <code>Address</code> 值</p><p>本例中设置 <code>IsEditable=\"true\"</code> 以及 <code>TextConvertToValueCallback</code> 参数,录入原数据源中不存在的 <code>Foo</code> 时,在 <code></code> 回调方法中添加新 <code>Foo</code> 实例到数据源中</p>",
"SelectsOnInputChangedCallback": "编辑模式下输入文本转换为对应 Value 回调方法",
"TextConvertToValueCallback": "编辑模式下输入文本变化时回调方法",
"SelectsIsEditable": "是否可编辑",
@@ -3456,60 +3323,50 @@
"RightHeaderTemplate": "右侧数据 Header 模板",
"RightItemTemplate": "右侧数据项模板"
},
"BootstrapBlazor.Server.Components.Samples.Uploads": {
"UploadsTitle": "Upload 上传",
"UploadsSubTitle": "通过点击上传文件",
"BootstrapBlazor.Server.Components.Samples.UploadAvatars": {
"UploadsTitle": "AvatarUpload 头像上传组件",
"UploadsSubTitle": "通过点击上传文件,通常用作上传预览一个或者一组类似头像的图片",
"UploadsNote": "如果上传文件过大,可能会触发 <code>signalR</code> 通讯中断问题,请自行调整 <code>HubOptions</code> 配置即可。",
"UploadNormalTitle": "基用法",
"UploadNormalIntro": "<code>InputUpload</code> 组件与其他表单组件一起使用,显示文件名称,点击 <b>浏览</b> 按钮后选择文件并上传;通过设置 <code>ShowRemoveButton</code> 参数,显示 <b>删除</b> 按钮,点击删除按钮时回调 <code>OnDelete</code> 委托方法",
"UploadNormalLabelName": "姓名:",
"UploadNormalLabelAddress": "地址:",
"UploadNormalLabelPhoto": "照片:",
"UploadFormSettingsTitle": "表单应用",
"UploadFormSettingsIntro": "在表单内使用文件上传组件对文件格式进行约束",
"UploadFormSettingsLi1": "使用 <code>ValidateForm</code> 表单组件,通过设置模型属性的 <code>FileValidation</code> 标签设置自定义验证,支持文件 <b>扩展名</b> <b>大小</b> 验证,本例中设置扩展名为 <code>.png .jpg .jpeg</code>,文件大小限制为 <code>50K</code>",
"UploadFormSettingsLi2": "选择文件后并未开始上传文件,点击 <code>提交</code> 按钮数据验证合法后,再 <code>OnSubmit</code> 回调委托中进行上传文件操作,注意 <b>Picture</b> 属性类型为 <code>IBrowserFile</code>",
"UploadFormSettingsButtonText": "提交",
"UploadClickUploadTitle": "点击上传",
"UploadClickUploadIntro": "<code>ButtonUpload</code> 组件,经典款式,用户点击按钮弹出文件选择框。",
"UploadClickUploadTips1": "点击 <code>浏览按钮</code> 选择文件上传,本例中设置 <code>IsMultiple=true</code> 可多选文件进行上传",
"UploadClickUploadTips2": "设置 <code>IsSingle</code> 时,仅可以上传一张图片或者文件",
"UploadClickUploadTips3ShowUploadList": "设置 <code>ShowUploadFileList</code> 值为 <b>false</b> 组件即与普通按钮一样,可自行处理上传文件逻辑",
"UploadedFilesTitle": "已上传文件列表",
"UploadedFilesIntro": "使用 <code>DefaultFileList</code> 设置已上传的内容",
"UploadFolderTitle": "上传文件夹",
"UploadFolderIntro": "使用 <code>DefaultFileList</code> 设置已上传的内容",
"AvatarUploadTitle": "用户头像上传",
"AvatarUploadIntro": "<code>AvatarUpload</code> 组件,使用 <code>OnChange</code> 限制用户上传的图片格式和大小。本例中仅允许上传 <code>jpg/png/bmp/jpeg/gif</code> 五种图片格式",
"AvatarUploadTips1": "卡片形式头像框",
"AvatarUploadTips2": "圆形头像框",
"AvatarUploadTitle": "基用法",
"AvatarUploadIntro": "通过设置 <code>IsMultiple</code> 控制是否允许多文件上传,通过设置 <code>IsCircle</code> 控制是否为圆角,通过设置 <code>BorderRadius</code> 控制圆角曲率。组件通过设置 <code>OnChange</code> 回调函数处理用户上传头像,如果未提供此回调时,将使用内置方法尝试读取上传文件生成 <code>base64</code> 格式预览数据",
"AvatarUploadTips3": "设置 <code>IsSingle</code> 时,仅可以上传一张图片或者文件",
"AvatarUploadTips4": " <div>组件提供了 <code>Accept</code> 属性用于设置上传文件过滤功能,本例中圆形头像框接受 GIF 和 JPEG 两种图像,设置 <code>Accept='image/gif, image/jpeg'</code>,如果不限制图像的格式,可以写为:<code>Accept='image/*'</code>,该属性并不安全还是应该是使用 <b>服务器端验证</b> 进行文件格式验证</div>",
"AvatarUploadTips5": "相关文档:<a href='https://www.runoob.com/tags/att-input-accept.html' target='_blank'>[Accept 属性详解]</a> <a href='https://www.iana.org/assignments/media-types/media-types.xhtml' target='_blank'>[Media Types 详细列表]</a>",
"AvatarUploadTips6": "通过 <code>DefaultFileList</code> 属性设置预览地址 <code>PrevUrl</code> 即可",
"AvatarUploadTips7": "验证表单内使用头像框示例",
"AvatarUploadButtonText": "提交",
"UploadPreCardStyleTitle": "预览卡片式",
"UploadPreCardStyleIntro": "<code>CardUpload</code> 组件,呈现为卡片式带预览模式",
"UploadPreCardStyleSSR": "<b>SSR 模式</b>",
"UploadPreCardStyleServerSide": "<code>Server Side</code> 模式中可以使用 <code>IWebHostEnvironment</code> 注入服务获取到 <code>wwwwroot</code> 目录,保存文件到 <code>images\\uploader</code> 中,此功能无需 <b>MVC</b> 的控制器辅助进行文件的保存,直接调用 <code>SaveToFile</code> 方法即可",
"UploadPreCardStyleWasm": "<b>Wasm 模式</b>",
"UploadPreCardStyleWasmSide": "<code>wasm</code> 模式中无法使用 <code>IWebHostEnvironment</code> 需要调用 <code>webapi</code> 接口等形式将文件保存到服务器端",
"UploadPreCardStyleLink": "有兴趣的同学可以通过开源仓库中的 <code>wiki</code> 文档中相关资源查看关于 <code>Upload</code> 组件的相关知识技巧 <a href='{0}' target='_blank'>[传送门]</a>",
"UploadPreCardStyleValidation": "本例中通过服务器端验证当文件大小超过 <code>5MB</code> 时,提示文件太大提示信息",
"UploadPreCardStyleTips1": "本例中设置 <code>ShowProgress=true</code> 显示上传进度条",
"UploadPreCardStyleTips2": "设置 <code>IsSingle</code> 时,仅可以上传一张图片或者文件",
"UploadFileIconTitle": "文件图标",
"UploadFileIconIntro": "不同文件格式显示的图标不同",
"UploadFileIconTemplateTitle": "自定义文件图标",
"UploadFileIconTemplateIntro": "通过设置 <code>IconTemplate</code> 参数,使用 <code>FileIcon</code> 组件可以对文件图标进行进一步自定义 <a href=\"/file-icon\">[FileIcon 示例]</a>",
"UploadBase64Title": "Base64 格式文件",
"UploadBase64Intro": "使用 <code>data:image/xxx;base64,XXXXX</code> 格式图片内容字符串作为预览文件路径",
"AvatarUploadValidateTitle": "ValidateForm",
"AvatarUploadValidateIntro": "放置到 <code>ValidateForm</code> 内集成自动数据验证功能,详情可以参考 <code>ValidateForm</code> 组件,本例中上传文件扩展名仅限制为 <code>.png, .jpg, .jpeg</code>,上传其他格式时会有错误提示,文件大小限制为 <code>5M</code> 超过时也会有错误提示显示",
"AvatarUploadAcceptTitle": "Accept",
"AvatarUploadAcceptIntro": "组件提供了 <code>Accept</code> 属性用于设置上传文件过滤功能,本例中圆形头像框接受 GIF 和 JPEG 两种图像,设置 <code>Accept='image/gif, image/jpeg'</code>,如果不限制图像的格式,可以写为:<code>Accept='image/*'</code>,该属性并不安全还是应该是使用 <b>服务器端验证</b> 进行文件格式验证",
"UploadsWidth": "预览框宽度",
"UploadsHeight": "预览框高度",
"UploadsIsCircle": "是否为圆形头像模式",
"UploadsBorderRadius": "预览框圆角曲率",
"UploadsValidateFormTitle": "表单应用",
"UploadsValidateFormValidContent": "数据合规,保存成功",
"UploadsValidateFormInValidContent": "数据不合规,请更正后再提交表单",
"UploadsFormatError": "文件格式不正确",
"UploadsAvatarMsg": "头像上传"
},
"BootstrapBlazor.Server.Components.Samples.UploadInputs": {
"UploadsTitle": "InputUpload 上传组件",
"UploadsSubTitle": "通过点击浏览按钮弹出选择文件框选择一个或者多个文件进行上传",
"UploadsNote": "如果上传文件过大,可能会触发 <code>signalR</code> 通讯中断问题,请自行调整 <code>HubOptions</code> 配置即可。",
"UploadNormalTitle": "基础用法",
"UploadNormalIntro": "可以通过设置 <code>ShowDeleteButton=\"true\"</code> 显示 <b>删除</b> 按钮",
"UploadNormalLabelPhoto": "选择一个或者多个文件",
"UploadFormSettingsTitle": "表单应用",
"UploadFormSettingsIntro": "放置到 <code>ValidateForm</code> 内集成自动数据验证功能,详情可以参考 <code>ValidateForm</code> 组件",
"UploadFormSettingsLi1": "使用 <code>ValidateForm</code> 表单组件,通过设置模型属性的 <code>FileValidation</code> 标签设置自定义验证,支持文件 <b>扩展名</b> <b>大小</b> 验证,本例中设置扩展名为 <code>.png .jpg .jpeg</code>,文件大小限制为 <code>5M</code>",
"UploadFormSettingsLi2": "选择文件后并未开始上传文件,点击 <code>提交</code> 按钮数据验证合法后,再 <code>OnSubmit</code> 回调委托中进行上传文件操作,注意 <b>Picture</b> 属性类型为 <code>IBrowserFile</code>",
"UploadFormSettingsButtonText": "提交",
"UploadsIsDirectory": "是否上传整个目录",
"UploadsIsMultiple": "是否允许多文件上传",
"UploadsShowProgress": "是否显示上传进度",
"UploadsDefaultFileList": "已上传文件集合",
"UploadsShowDeleteButton": "是否显示删除按钮",
"UploadsShowDownloadButton": "是否显示下载按钮",
"UploadsIsDisabled": "是否禁用",
"UploadsPlaceHolder": "占位字符串",
"UploadsAccept": "上传接收的文件格式",
"UploadsBrowserButtonClass": "上传按钮样式",
"UploadsBrowserButtonIcon": "浏览按钮图标",
"UploadsBrowserButtonText": "浏览按钮显示文字",
@@ -3517,34 +3374,52 @@
"UploadsDeleteButtonIcon": "删除按钮图标",
"UploadsDeleteButtonText": "删除按钮文字",
"UploadsDeleteButtonTextDefaultValue": "删除",
"UploadsAccept": "上传接收的文件格式",
"UploadsOnDelete": "点击删除按钮时回调此方法",
"UploadsOnChange": "点击浏览按钮时回调此方法",
"UploadsOnDownload": "点击下载按钮时回调此方法",
"UploadsIsDirectory": "是否上传整个目录",
"UploadsIsMultiple": "是否允许多文件上传",
"UploadsIsSingle": "是否仅上传一次",
"UploadsShowProgress": "是否显示上传进度",
"UploadsDefaultFileList": "已上传文件集合",
"UploadsOnGetFileFormat": "设置文件格式图标回调委托",
"UploadsWidth": "预览框宽度",
"UploadsHeight": "预览框高度",
"UploadsIsCircle": "是否为圆形头像模式",
"UploadsSuccess": "上传成功",
"UploadsError": "模拟上传失败",
"UploadsFormatError": "文件格式不正确",
"UploadsAvatarMsg": "头像上传",
"UploadsFileMsg": "上传文件",
"UploadsFileError": "文件大小超过 5MB",
"UploadsSaveFileError": "保存文件失败",
"UploadFile": "上传文件",
"UploadsWasmError": "Wasm 模式未实现保存代码",
"UploadsSaveFile": "保存文件",
"UploadsSaveFileMsg": "当前模式为 WebAssembly 模式,请调用 Webapi 模式保存文件到服务器端或数据库中",
"UploadsRemoveMsg": "成功移除",
"UploadsIconTemplate": "文件图标模板",
"DropUploadTitle": "拖拽上传",
"DropUploadIntro": "文件拖拽到特定区域以进行上传",
"DropUploadFooterText": "文件大小不超过 5Mb"
"UploadsOnChange": "点击浏览按钮时回调此方法"
},
"BootstrapBlazor.Server.Components.Samples.UploadButtons": {
"UploadsTitle": "ButtonUpload 按钮上传组件",
"UploadsSubTitle": "通过点击按钮弹出选择文件框选择一个或者多个文件,通常用作上传文件附件",
"UploadsNote": "如果上传文件过大,可能会触发 <code>signalR</code> 通讯中断问题,请自行调整 <code>HubOptions</code> 配置即可。",
"ButtonUploadTitle": "基础用法",
"ButtonUploadIntro": "通过设置 <code>ShowUploadFileList=\"true\"</code> 可以显示上传文件列表,设置 <code>ShowDeleteButton=\"true\"</code> 显示 <b>删除</b> 按钮"
},
"BootstrapBlazor.Server.Components.Samples.UploadCards": {
"UploadsTitle": "CardUpload 卡片上传组件",
"UploadsSubTitle": "通过点击按钮弹出选择文件框选择一个或者多个文件,呈现为卡片式带预览模式",
"UploadsNote": "如果上传文件过大,可能会触发 <code>signalR</code> 通讯中断问题,请自行调整 <code>HubOptions</code> 配置即可。",
"ButtonUploadTitle": "基础用法",
"ButtonUploadIntro": "使用 <code>DefaultFileList</code> 设置已上传的内容",
"UploadPreCardStyleTitle": "预览卡片式",
"UploadPreCardStyleIntro": "<code>CardUpload</code> 组件,呈现为卡片式带预览模式",
"UploadFileIconTitle": "文件图标",
"UploadFileIconIntro": "不同文件格式显示的图标不同",
"UploadFileIconTemplateTitle": "自定义文件图标",
"UploadFileIconTemplateIntro": "通过设置 <code>IconTemplate</code> 参数,使用 <code>FileIcon</code> 组件可以对文件图标进行进一步自定义 <a href=\"/file-icon\">[FileIcon 示例]</a>",
"UploadBase64Title": "Base64 格式文件",
"UploadBase64Intro": "通过设置 <code>UploadFile</code> 实例的 <code>PrevUrl</code> 参数值使用 <code>data:image/xxx;base64,XXXXX</code> 格式图片内容字符串作为预览文件路径",
"UploadsFileTitle": "文件上传",
"UploadsFileError": "文件大于 5M 请重新选择文件上传",
"UploadsSuccess": "文件保存成功",
"UploadsSaveFileError": "文件保存失败",
"UploadsWasmError": "wasm 模式请调用 api 进行保存"
},
"BootstrapBlazor.Server.Components.Samples.UploadDrops": {
"UploadsTitle": "DropUpload 拖拽上传组件",
"UploadsSubTitle": "通过点击组件或者拖拽或者粘贴上传一个或者多个文件",
"UploadsNote": "如果上传文件过大,可能会触发 <code>signalR</code> 通讯中断问题,请自行调整 <code>HubOptions</code> 配置即可。",
"DropUploadTitle": "基础用法",
"DropUploadIntro": "通过 <code>OnChange</code> 回调处理所有上传文件",
"DropUploadFooterText": "文件大小不超过 5Mb",
"UploadsBodyTemplate": "Body 模板",
"UploadsIconTemplate": "图标模板",
"UploadsTextTemplate": "文字模板",
"UploadsUploadIcon": "图标",
"UploadsUploadText": "上传文字",
"UploadsShowFooter": "是否显示 Footer",
"UploadsFooterTemplate": "Footer 字符串模板",
"UploadsFooterText": "Footer 字符串信息"
},
"BootstrapBlazor.Server.Components.Samples.ValidateForms": {
"ChangeButtonText": "更改组件",
@@ -3801,7 +3676,8 @@
"CollapsibleTitle": "可以收缩展开的卡片",
"CollapsibleBody": "点击 Header 收缩/展开",
"CollapsibleHeaderTemplate": "这里是模板",
"ShadowBody": "阴影特效示例"
"ShadowBody": "阴影特效示例",
"HeaderPaddingY": "Header 上下内边距"
},
"BootstrapBlazor.Server.Components.Samples.Calendars": {
"Title": "Calendar 日历框",
@@ -3933,6 +3809,11 @@
"BasicUsageP2": "1. <b>Startup.cs</b> 文件中使用 <code>UseBootstrapBlazor</code> 中间件进行客户端信息收集",
"BasicUsageTips": "<code>app.UseBootstrapBlazor</code> 中间件位于程序集 <code>BootstrapBlazor.Middleware</code>,请自行引用此包才能正常使用",
"BasicUsageP3": "2. 组件中使用注入服务 <code>WebClientService</code> 调用 <code>GetClientInfo</code> 方法",
"BasicUsageP4": "3. 开启 IP 地理位置定位功能",
"LocatorsProviderOptions": "全局配置定位器选项 <code>WebClientOptions</code> 默认 <code>false</code> 没有启用 IP 地址定位功能,请在配置文件中或者代码中更改为 <code>true</code>",
"LocatorsProviderDesc1": "更新 <code>appsetting.json</code> 项目配置文件",
"LocatorsProviderDesc2": "或者使用代码开启",
"LocatorsProviderDesc3": "或者通过配置开启本功能",
"GroupBoxTitle": "您的连接信息",
"IpLocatorFactoryDesc": "本服务已内置 IP 地理位置定位功能,详细配置与文档请参考",
"Id": "连接 ID",
@@ -4219,6 +4100,7 @@
"LocatorsNormalExtendDescription": "扩展自定义地理位置查询接口",
"LocatorsNormalExtend1": "1. 实现自定义定位器",
"LocatorsNormalExtend2": "2. 配置自定义定位器",
"LocatorsNormalExtend3": "3. 通过定位器定位",
"LocatorsNormalCustomerLocator": "通过 <code>AddSingleton</code> 方法将自定义定位器 <code>CustomerLocatorProvider</code> 添加到服务容器中",
"LocatorsNormalIpTitle": "IP 测试数据",
"LocatorsNormalTips3": "山东省 中国联通",
@@ -4633,9 +4515,9 @@
"BootstrapBlazor.Server.Components.Components.Header": {
"DownloadText": "Download",
"HomeText": "首页",
"ComponentsText": "组件",
"IntroductionText": "文档",
"TutorialsText": "实战"
"TutorialsText": "实战",
"FullScreenTooltipText": "点击切换全屏模式"
},
"BootstrapBlazor.Server.Components.Layout.BaseLayout": {
"SiteTitle": "Bootstrap Blazor - 组件库",
@@ -4848,6 +4730,7 @@
"PulseButton": "心跳按钮 PulseButton",
"Bluetooth": "蓝牙服务 IBluetoothService",
"PdfReader": "PDF阅读器 PDF Reader",
"PdfViewer": "PDF阅读器 PDF Viewer",
"VideoPlayer": "视频播放器 VideoPlayer",
"FileViewer": "文件预览器 FileViewer",
"FlipClock": "卡片翻转时钟 FlipClock",
@@ -4943,7 +4826,22 @@
"TotpService": "时间密码验证服务 ITotpService",
"VideoDevice": "视频设备服务 IVideoDevice",
"AudioDevice": "音频设备服务 IAudioDevice",
"FullScreenButton": "全屏按钮 FullScreenButton"
"FullScreenButton": "全屏按钮 FullScreenButton",
"Meet": "视频会议组件 Meet",
"InputUpload": "上传组件 InputUpload",
"ButtonUpload": "按钮上传组件 ButtonUpload",
"AvatarUpload": "头像上传组件 AvatarUpload",
"CardUpload": "卡片上传组件 CardUpload",
"DropUpload": "拖动上传组件 DropUpload",
"Vditor": "富文本框 Vditor Markdown",
"TcpSocketFactory": "套接字服务 ITcpSocketFactory",
"OfficeViewer": "Office 文档预览组件",
"SocketComponents": "Socket 服务",
"SocketAutoReceive": "自动接收数据",
"SocketManualReceive": "手动接收数据",
"DataPackageAdapter": "数据处理器",
"SocketAutoConnect": "自动重连",
"NetworkMonitor": "网络状态 NetworkMonitor"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
"TablesHeaderTitle": "表头分组功能",
@@ -5796,7 +5694,7 @@
"SetFilterInCodeIntro": "示例展示如何通过代码设置过滤条件",
"SetFilterInCodeButtonText1": "名称包含01",
"SetFilterInCodeButtonText2": "重置条件",
"TablesFilterTemplateDescription": "<code>FilterTemplate</code> 类型为 <code>RenderFragment</code> <span>其值为自定义组件,组件必须继承</span> <code>FilterBase</code> 本例中最后一列 <b>数量列</b> 通过筛选模板使用自定义组件 <code>CustomerFilter</code> <a href=\"{0}\" target=\"_blank\">[传送门] CustomerFilter 组件源码</a>"
"TablesFilterTemplateDescription": "<p><code>FilterTemplate</code> 类型为 <code>RenderFragment</code> <span>其值为自定义组件,组件必须继承</span> <code>FilterBase</code> 本例中最后一列 <b>数量列</b> 通过筛选模板使用自定义组件 <code>CustomerFilter</code> <a href=\"{0}\" target=\"_blank\">[传送门] CustomerFilter 组件源码</a></p><p class=\"code-label\">注意事项:</p><ul class=\"ul-demo\"><li>自定义过滤组件使用 <code>FilterProvider</code> 包裹,<code>FilterProvider</code>必须在 <code>FilterTemplate</code> 节点下</li><li>通过 <code>FilterProvider</code> 组件的参数可微调过滤器;例如通过设置 <code>ShowMoreButton</code> 控制是否显示 <b>+ -</b> 符号</li></ul-demo>"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesFixedHeader": {
"TablesFixedHeaderTitle": "固定表头功能",
@@ -5908,7 +5806,7 @@
"ImageViewerErrorTemplateTitle": "加载失败",
"ImageViewerErrorTemplateIntro": "可通过设置 <code>ErrorTemplate</code> 开启错误模板功能,参数 <code>Url</code> 无法加载图片时显示此模板内容",
"ImageViewerPreviewListTitle": "大图预览",
"ImageViewerPreviewListIntro": "可通过设置 <code>PreviewList</code> 开启预览大图的功能",
"ImageViewerPreviewListIntro": "可通过设置 <code>PreviewList</code> 开启预览大图的功能,设置 <code>ZoomSpeed</code> 控制滚动缩放时的速度",
"ImageViewersAttrUrl": "图片 Url",
"ImageViewersAttrAlt": "原生 alt 属性",
"ImageViewersAttrShowPlaceHolder": "是否显示占位符 适用于大图片加载",
@@ -6142,6 +6040,14 @@
"PdfReaderCompatibilityModeTips": "- Chrome < 97 自动使用 2.4.456 版本<br/>- Chrome < 109 自动使用 2.6.347 版本<br/>- 注:ReadOnly 和 Watermark 在这两种兼容模式下不能使用",
"LocalFileName": "PDF本地文件路径"
},
"BootstrapBlazor.Server.Components.Samples.PdfViewers": {
"PdfViewerTitle": "PDFViewer PDF 阅读器",
"PdfViewerDescription": "在组件中打开 Pdf 文件阅读其内容",
"PdfViewerNormalTitle": "基础用法",
"PdfViewerNormalIntro": "通过设置 <code>Url</code> 参数加载 Pdf 文件,设置 <code>UseGoogleDocs</code> 使用 docs.google.com 预览",
"PdfViewerToastSuccessfulContent": "PDF 文档加载成功",
"PdfViewerToastNotSupportContent": "当前浏览器不支持 Pdf 文档预览功能"
},
"BootstrapBlazor.Server.Components.Samples.VideoPlayers": {
"VideoPlayersTitle": "VideoPlayer 视频播放器",
"VideoPlayersNormalTitle": "基础用法",
@@ -6656,7 +6562,9 @@
"BootstrapBlazor.Server.Components.Samples.ImageCroppers": {
"Title": "ImageCropper 图像裁剪",
"ImageCropperNormalText": "基础用法",
"ImageCropperNormalIntro": "Url 参数设置图片",
"ImageCropperNormalIntro": "通过设置 <code>Url</code> 参数设置图片地址,可通过设置 <code>ImageCropperOption.AspectRatio=16/9f</code> 设置裁剪框比例,<code>1</code> 时为正方形",
"ImageCropperRoundText": "圆角",
"ImageCropperRoundIntro": "通过设置 <code>IsRound</code> 参数设置裁剪方式为圆形",
"ImageCropperResetText": "复位",
"ImageCropperReplaceText": "替换",
"ImageCropperRotateText": "旋转",
@@ -6785,12 +6693,6 @@
"BootstrapBlazor.Server.Components.Samples.FlipClocks": {
"FlipClocksTitle": "FlipClock 卡片翻转时钟",
"FlipClocksDescription": "用于网站计时,或者倒计时使用",
"BaseUsageText": "基本功能",
"BaseUsageIntro": "同步显示当前时间",
"ShowMinuteText": "不显示时、分",
"ShowMinuteIntro": "通过设置 <code>ShowHour=\"false\"</code> <code>ShowMinute=\"false\"</code> 不显示小时、分钟信息",
"ShowSecondText": "不显示秒",
"ShowSecondIntro": "通过设置 <code>ShowSecond=\"false\"</code> 不显示秒信息",
"CountText": "计时器",
"CountIntro": "通过设置 <code>ViewMode=\"FlipClockViewMode.Count\"</code> 开启计时功能,开始时间使用 <code>StartValue</code> 设置",
"IsCountDownText": "倒计时",
@@ -6803,7 +6705,26 @@
"CardHeight": "卡片高度",
"CardWidth": "卡片宽度",
"CardMargin": "卡片边距",
"CardGroupMargin": "卡片组边距"
"CardGroupMargin": "卡片组边距",
"ShowDay_Description": "是否显示日",
"ShowHour_Description": "是否显示小时",
"ShowMinute_Description": "是否显示分钟",
"ShowMonth_Description": "是否显示月",
"ShowYear_Description": "是否显示年",
"ViewMode_Description": "显示模式",
"StartValue_Description": "开始时间",
"OnCompletedAsync_Description": "计时结束回调方法",
"Height_Description": "高度",
"BackgroundColor_Description": "背景色",
"FontSize_Description": "字体大小",
"CardWidth_Description": "卡片宽度",
"CardHeight_Description": "卡片高度",
"CardColor_Description": "卡片字体颜色",
"CardBackgroundColor_Description": "卡片背景颜色",
"CardDividerHeight_Description": "卡片分割线高度",
"CardDividerColor_Description": "卡片分割线颜色",
"CardMargin_Description": "卡片间隔",
"CardGroupMargin_Description": "卡片组间隔"
},
"BootstrapBlazor.Server.Components.Samples.Icons.BootstrapIcons": {
"Title": "Bootstrap Icons",
@@ -7162,5 +7083,52 @@
"AudioDevicePauseText": "暂停",
"AudioDeviceResumeText": "恢复",
"AudioDeviceDownloadText": "下载"
},
"BootstrapBlazor.Server.Components.Samples.Vditors": {
"VditorTitle": "Vditor Markdown 富文本编辑框",
"VditorSubTitle": "Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora和分屏预览模式",
"BaseUsageTitle": "基本用法",
"BaseUsageIntro": "通过设置 <code>Value</code> 值设置组件显示的内容,通过 <code>Options</code> 参数设置组件配置信息"
},
"BootstrapBlazor.Server.Components.Samples.OfficeViewers": {
"OfficeViewerTitle": "Office 文档预览器",
"OfficeViewerDescription": "本组件通过使用微软在线文档预览功能预览 Office 文档内容",
"OfficeViewerNormalTitle": "基本用法",
"OfficeViewerNormalIntro": "通过设置 <code>Url</code> 值设置预览文档地址",
"OfficeViewerToastSuccessfulContent": "Office 文档加载成功"
},
"BootstrapBlazor.Server.Components.Samples.Sockets.ManualReceives": {
"ReceivesTitle": "手动接收示例",
"ReceivesDescription": "通过调用 ReceiveAsync 接收数据并且显示",
"NormalTitle": "基本用法",
"NormalIntro": "连接后通过 <code>ReceiveAsync</code> 回调方法接收服务端发送来的数据,需要自行处理粘包分包的数据问题"
},
"BootstrapBlazor.Server.Components.Samples.Sockets.AutoReceives": {
"ReceivesTitle": "自动接收示例",
"ReceivesDescription": "通过 ReceiveCallback 接收数据并且显示",
"NormalTitle": "基本用法",
"NormalIntro": "连接后通过 <code>ReceivedCallBack</code> 回调方法自动接收服务端发送来的时间戳数据"
},
"BootstrapBlazor.Server.Components.Samples.Sockets.Adapters": {
"AdaptersTitle": "Socket 数据适配器示例",
"AdaptersDescription": "通过数据适配器接收数据并且显示",
"NormalTitle": "基本用法",
"NormalIntro": "连接后通过 <code>DataPackageAdapter</code> 数据适配器的 <code>ReceivedCallBack</code> 回调方法接收服务端发送来的时间戳数据"
},
"BootstrapBlazor.Server.Components.Samples.Sockets.AutoReconnects": {
"AutoReconnectsTitle": "Socket 自动重连示例",
"AutoReconnectsDescription": "链路断开后自动重连示例",
"NormalTitle": "基本用法",
"NormalIntro": "通过设置 <code>IsAutoReconnect</code> 开启自动重连机制"
},
"BootstrapBlazor.Server.Components.Samples.NetworkMonitors": {
"NetworkMonitorTitle": "NetworkMonitor 网络状态",
"NetworkMonitorDescription": "使用浏览器原生 api <code>navigator.connection</code> 实时显示当前网络状态",
"NormalTitle": "基本用法",
"NormalIntro": "使用组件 <code>NetworkMonitorIndicator</code> 当网络状态变化时,显示不同颜色的指示灯,鼠标移动到上面时显示网络状态明细",
"IndicatorLi1": "绿色:网络非常好 (4G)",
"IndicatorLi2": "黄色:网络一般 (3G)",
"IndicatorLi3": "红色:网络差 (2G)",
"IndicatorLi4": "灰色:离线状态"
}
}

View File

@@ -35,6 +35,9 @@ if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/Error", createScopeForErrors: true);
}
// 增加上传目录静态资源文件
app.UseUploaderStaticFiles();
app.UseAntiforgery();
app.UseBootstrapBlazor();

View File

@@ -0,0 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Longbow.Tasks.Services;
/// <summary>
/// 模拟 Socket 服务端服务类
/// </summary>
internal class MockCustomProtocolSocketServerService(ILogger<MockCustomProtocolSocketServerService> logger) : BackgroundService
{
/// <summary>
/// 运行任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var server = new TcpListener(IPAddress.Loopback, 8900);
server.Start();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
var client = await server.AcceptTcpClientAsync(stoppingToken);
_ = Task.Run(() => OnDataHandlerAsync(client, stoppingToken), stoppingToken);
}
catch { }
}
}
private async Task OnDataHandlerAsync(TcpClient client, CancellationToken stoppingToken)
{
// 方法目的:
// 收到消息后发送自定义通讯协议的响应数据
// 响应头 + 响应体
await using var stream = client.GetStream();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
// 接收数据
var len = await stream.ReadAsync(new byte[1024], stoppingToken);
if (len == 0)
{
// 断开连接
break;
}
// 实际应用中需要解析接收到的数据进行处理,本示例中仅模拟接收数据后发送响应数据
// 发送响应数据
// 响应头: 4 字节表示响应体长度 [0x32, 0x30, 0x32, 0x35]
// 响应体: 8 字节当前时间戳字符串
// 此处模拟分包操作故意分 2 次写入数据,导致客户端接收 2 次才能得到完整数据
await stream.WriteAsync("2025"u8.ToArray(), stoppingToken);
// 模拟延时
await Task.Delay(40, stoppingToken);
await stream.WriteAsync(Encoding.UTF8.GetBytes(DateTime.Now.ToString("ddHHmmss")), stoppingToken);
}
catch (OperationCanceledException) { break; }
catch (IOException) { break; }
catch (SocketException) { break; }
catch (Exception ex)
{
logger.LogError(ex, "MockCustomProtocolSocketServerService encountered an error while sending data.");
break;
}
}
}
}

View File

@@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Longbow.Tasks.Services;
/// <summary>
/// 模拟 Socket 自动断开服务端服务类
/// </summary>
internal class MockDisconnectServerService(ILogger<MockDisconnectServerService> logger) : BackgroundService
{
/// <summary>
/// 运行任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var server = new TcpListener(IPAddress.Loopback, 8901);
server.Start();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
var client = await server.AcceptTcpClientAsync(stoppingToken);
_ = Task.Run(() => OnDataHandlerAsync(client, stoppingToken), stoppingToken);
}
catch { }
}
}
private async Task OnDataHandlerAsync(TcpClient client, CancellationToken stoppingToken)
{
// 方法目的:
// 收到消息后发送自定义通讯协议的响应数据
// 响应头 + 响应体
await using var stream = client.GetStream();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
// 发送数据
await stream.WriteAsync(Encoding.UTF8.GetBytes(DateTime.Now.ToString("yyyyMMddHHmmss")), stoppingToken);
await Task.Delay(2000, stoppingToken);
// 主动关闭连接
client.Close();
}
catch (OperationCanceledException) { break; }
catch (IOException) { break; }
catch (SocketException) { break; }
catch (Exception ex)
{
logger.LogError(ex, "MockDisconnectServerService encountered an error while sending data.");
break;
}
}
}
}

View File

@@ -0,0 +1,60 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.Net;
using System.Net.Sockets;
namespace Longbow.Tasks.Services;
/// <summary>
/// 模拟 Socket 服务端服务类
/// </summary>
class MockReceiveSocketServerService(ILogger<MockReceiveSocketServerService> logger) : BackgroundService
{
/// <summary>
/// 运行任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var server = new TcpListener(IPAddress.Loopback, 8800);
server.Start();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
var client = await server.AcceptTcpClientAsync(stoppingToken);
_ = Task.Run(() => MockSendAsync(client, stoppingToken), stoppingToken);
}
catch { }
}
}
private async Task MockSendAsync(TcpClient client, CancellationToken stoppingToken)
{
// 方法目的:
// 1. 模拟服务器间隔 10秒 发送当前时间戳数据包到客户端
await using var stream = client.GetStream();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
var data = System.Text.Encoding.UTF8.GetBytes(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await stream.WriteAsync(data, stoppingToken);
await Task.Delay(10 * 1000, stoppingToken);
}
catch (OperationCanceledException) { break; }
catch (IOException) { break; }
catch (SocketException) { break; }
catch (Exception ex)
{
logger.LogError(ex, "MockReceiveSocketServerService encountered an error while sending data.");
break;
}
}
}
}

View File

@@ -0,0 +1,67 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using System.Net;
using System.Net.Sockets;
namespace Longbow.Tasks.Services;
/// <summary>
/// 模拟 Socket 服务端服务类
/// </summary>
class MockSendReceiveSocketServerService(ILogger<MockReceiveSocketServerService> logger) : BackgroundService
{
/// <summary>
/// 运行任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var server = new TcpListener(IPAddress.Loopback, 8810);
server.Start();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
var client = await server.AcceptTcpClientAsync(stoppingToken);
_ = Task.Run(() => MockSendAsync(client, stoppingToken), stoppingToken);
}
catch { }
}
}
private async Task MockSendAsync(TcpClient client, CancellationToken stoppingToken)
{
// 方法目的:
// 接收到数据后会送当前时间戳数据包到客户端
await using var stream = client.GetStream();
while (stoppingToken is { IsCancellationRequested: false })
{
try
{
// 接收数据
var len = await stream.ReadAsync(new byte[1024], stoppingToken);
if (len == 0)
{
// 断开连接
break;
}
// 模拟服务,对接收到的消息未做处理
// 模拟一发一收的通讯方法
var data = System.Text.Encoding.UTF8.GetBytes(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await stream.WriteAsync(data, stoppingToken);
}
catch (OperationCanceledException) { break; }
catch (IOException) { break; }
catch (SocketException) { break; }
catch (Exception ex)
{
logger.LogError(ex, "MockSendReceiveSocketServerService encountered an error while sending data.");
break;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More