mirror of
https://github.com/dotnetcore/BootstrapBlazor.git
synced 2025-12-20 10:26:41 +08:00
Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fffebd10b | ||
|
|
c47c61beca | ||
|
|
a1b8cbf08e | ||
|
|
9465cae520 | ||
|
|
56f936e2a0 | ||
|
|
cd6c8fd1f5 | ||
|
|
8daec76686 | ||
|
|
61ef8f7409 | ||
|
|
7441314758 | ||
|
|
146778f0f0 | ||
|
|
2f59a8db16 | ||
|
|
c70eca956f | ||
|
|
5d2ed311fb | ||
|
|
39578edde6 | ||
|
|
c8e95e9fa0 | ||
|
|
b0245db885 | ||
|
|
694361509d | ||
|
|
b84dd4cda9 | ||
|
|
b442af2a57 | ||
|
|
1ae85795a8 | ||
|
|
e3d081edd9 | ||
|
|
84764e3a02 | ||
|
|
7e2f1f1567 | ||
|
|
786b2c15aa | ||
|
|
20a64443f0 | ||
|
|
fb36b0468a | ||
|
|
c3caf7d7c7 | ||
|
|
48f8936027 | ||
|
|
6aa2bd6e7b | ||
|
|
1afd090438 | ||
|
|
e755e92239 | ||
|
|
ad6f782c58 | ||
|
|
1dd1e19644 | ||
|
|
37db88ab9f | ||
|
|
889410f642 | ||
|
|
a408e98676 | ||
|
|
f8d6acccb7 | ||
|
|
1bcf624cc9 | ||
|
|
daf9af9b38 | ||
|
|
8775218a3b | ||
|
|
ef8270975d | ||
|
|
618b956c9c | ||
|
|
f43449cffb | ||
|
|
5d1457b33f | ||
|
|
2abfb68a85 | ||
|
|
a8c8dc7339 | ||
|
|
45c99798c7 | ||
|
|
342ff517fc | ||
|
|
5779d48485 | ||
|
|
db3423995b | ||
|
|
560bb21327 | ||
|
|
29d02df022 | ||
|
|
10fc77cce5 | ||
|
|
2dc0cfffef | ||
|
|
79b91d90a9 | ||
|
|
de03bba9a0 | ||
|
|
72186d342d | ||
|
|
efa9089f21 | ||
|
|
3a3b8cc5ea | ||
|
|
547980d7a9 | ||
|
|
ed4a2a4043 | ||
|
|
d4d76e49ad | ||
|
|
0df17f52cf | ||
|
|
7826f2281d | ||
|
|
556fcf3abe | ||
|
|
0caed5aa3a | ||
|
|
4fdc480909 | ||
|
|
ea1c142496 | ||
|
|
4ce223e19b | ||
|
|
31a2a958b6 | ||
|
|
c2343f5e38 | ||
|
|
62ed6e6ebc | ||
|
|
9bd5946050 | ||
|
|
e3971e7af3 | ||
|
|
07d63755b1 | ||
|
|
48a7777fd9 | ||
|
|
1469626de4 | ||
|
|
e6c8012704 | ||
|
|
863a2089a3 | ||
|
|
b7e67a8690 | ||
|
|
d97672f33d | ||
|
|
bed5200193 | ||
|
|
21048883aa | ||
|
|
9b51e50374 | ||
|
|
6850f1a89b | ||
|
|
c926f58725 | ||
|
|
a3f422712e | ||
|
|
98384ca8de | ||
|
|
6f84d88f76 | ||
|
|
48289a4f4e | ||
|
|
1c0abc46ab | ||
|
|
280219afb9 | ||
|
|
c0e88780bd | ||
|
|
f120ef67ad | ||
|
|
e4203dd7a2 | ||
|
|
80ef545dce | ||
|
|
d5cd54c2ce | ||
|
|
f94e3c0491 | ||
|
|
a828f38b7f | ||
|
|
5cdf89afeb | ||
|
|
75553b846c | ||
|
|
7ba4e74ac7 | ||
|
|
6549430f2e | ||
|
|
684400448c | ||
|
|
d3a9ef4188 | ||
|
|
015b93de24 | ||
|
|
f201bff143 | ||
|
|
8738cafff4 | ||
|
|
0ad43d2884 | ||
|
|
fdbfbc8cf9 | ||
|
|
a6e1b04dad | ||
|
|
9a88fa62f3 | ||
|
|
2e6e63ceee | ||
|
|
4353628cb7 | ||
|
|
c507f68b5c | ||
|
|
16b02fe94a | ||
|
|
d139e8cf9c | ||
|
|
c3330247b3 | ||
|
|
30110267d2 | ||
|
|
406eb19923 | ||
|
|
16cc7d49a9 | ||
|
|
7e580f94cb | ||
|
|
e8d8aca4f0 | ||
|
|
c234b76292 | ||
|
|
8b477838ee | ||
|
|
4640cae828 | ||
|
|
3167b47a42 | ||
|
|
2df033c050 | ||
|
|
5acba45e55 | ||
|
|
7336b9ef9f | ||
|
|
b63ed8adb7 | ||
|
|
1f5e494b14 | ||
|
|
450937c59f | ||
|
|
f81d4008a8 | ||
|
|
6f83053df3 | ||
|
|
16731b9e58 | ||
|
|
5fdaee0d23 | ||
|
|
0289df5047 | ||
|
|
dae37ad10c | ||
|
|
b80e875f0a | ||
|
|
c06050cd3e | ||
|
|
7065ff2df1 | ||
|
|
a6167cd7cb | ||
|
|
a0834cda42 | ||
|
|
4ad4ea4969 | ||
|
|
a7d575b8c1 | ||
|
|
89d90772f2 | ||
|
|
2ba3fc8e08 | ||
|
|
b331fc3db0 | ||
|
|
ac20831cd0 | ||
|
|
5246cce79b | ||
|
|
ea34fd6aec | ||
|
|
de715633c0 | ||
|
|
a86514f247 | ||
|
|
2b7554a228 | ||
|
|
c5be0c75cb | ||
|
|
863017cdc2 | ||
|
|
03e52bda1c |
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -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
3
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -114,3 +114,7 @@ inputmode
|
||||
Totp
|
||||
otpauth
|
||||
Hotp
|
||||
univer
|
||||
rdkit
|
||||
webkitdirectory
|
||||
dotx
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
<link href="BlazorApp1.styles.css" rel="stylesheet">
|
||||
</head></Pre>
|
||||
|
||||
<p class="code-label">4. @Localizer["P25"]</p>
|
||||
<p class="code-label">4. @((MarkupString)Localizer["P25"].Value)</p>
|
||||
@ScriptsTemplate
|
||||
<Pre><body>
|
||||
...
|
||||
@@ -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><BootstrapBlazorRoot>
|
||||
<Router AppAssembly="@@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<PageTitle>Title</PageTitle>
|
||||
<RouteView RouteData="@@routeData" DefaultLayout="@@typeof(MainLayout)" />
|
||||
<FocusOnNavigate RouteData="@@routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@@typeof(MainLayout)">
|
||||
<p> @Localizer["P35"] ...</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</BootstrapBlazorRoot></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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pre-code > pre {
|
||||
color: #e83e8c;
|
||||
margin-bottom: 0;
|
||||
max-height: 260px;
|
||||
}
|
||||
|
||||
::deep .btn-group {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" }
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@page "/error-page"
|
||||
|
||||
<h3>ErrorPage</h3>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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><BootstrapBlazorRoot>
|
||||
<Router AppAssembly="@@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
|
||||
@@ -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
|
||||
<BootstrapBlazorRoot>
|
||||
<Router><Router>
|
||||
</BootstrapBlazorRoot>
|
||||
</Pre>
|
||||
|
||||
<Pre>// NET8/NET9
|
||||
<BootstrapBlazorRoot>
|
||||
<@@Body
|
||||
</BootstrapBlazorRoot></Pre>
|
||||
</RootTemplate>
|
||||
</InstallContent>
|
||||
|
||||
@@ -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 包
|
||||
<link rel="stylesheet" href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" />
|
||||
// 组件样式已集成 Bootstrap 最新版
|
||||
<link rel="stylesheet" href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" />
|
||||
// Motronic 样式可不添加
|
||||
// Motronic 主题可选建议添加
|
||||
<link rel="stylesheet" href="_content/BootstrapBlazor/css/motronic.min.css" /></Pre>
|
||||
|
||||
<h5>4. 添加脚本</h5>
|
||||
<p><code>App.razor</code> 文件在 <code>body</code> 结尾位置添加如下内容它应与默认 blazor 脚本位于同一位置。</p>
|
||||
<Pre class="mb-3">// 添加 BootstrapBlazor 脚本
|
||||
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
|
||||
<script src="_framework/blazor.web.js"></script></Pre>
|
||||
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script></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"><BootstrapBlazorRoot>
|
||||
<Header></Header>
|
||||
|
||||
@@ -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
|
||||
<BootstrapBlazorRoot>
|
||||
<Router><Router>
|
||||
</BootstrapBlazorRoot>
|
||||
</Pre>
|
||||
</RootTemplate>
|
||||
</InstallContent>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/docs"
|
||||
@page "/introduction"
|
||||
@page "/components"
|
||||
|
||||
<h3>@Localizer["Title"]</h3>
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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<HubOptions>(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>
|
||||
|
||||
@@ -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<BootstrapBlazorOptions>(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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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()" />
|
||||
|
||||
@@ -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 = " — "
|
||||
|
||||
@@ -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()" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<IIpLocatorProvider, CustomerLocatorProvider>();</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>
|
||||
|
||||
24
src/BootstrapBlazor.Server/Components/Samples/Meets.razor
Normal file
24
src/BootstrapBlazor.Server/Components/Samples/Meets.razor
Normal file
@@ -0,0 +1,24 @@
|
||||
@page "/meet"
|
||||
|
||||
<h3>JitsiMeet会议</h3>
|
||||
|
||||
<h4>通过JitsiMeet创建会议</h4>
|
||||
|
||||
<PackageTips Name="BootstrapBlazor.JitsiMeet" />
|
||||
|
||||
<Tips class="mt-3">
|
||||
<p>JitsiMeet是一个开源的WebRTC会议程序,可以自托管安装也可以使用官方的托管服务(免费计划为25MAU),此组件仅为JitsiMeet的客户端程序,不含服务端。</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>
|
||||
56
src/BootstrapBlazor.Server/Components/Samples/Meets.razor.cs
Normal file
56
src/BootstrapBlazor.Server/Components/Samples/Meets.razor.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"]);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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"]);
|
||||
}
|
||||
@@ -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"]"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<Tips>
|
||||
<p><code>ITcpSocketFactory</code> 服务仅在 <code>Server</code> 模式下可用</p>
|
||||
</Tips>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -77,8 +77,6 @@ public partial class OnlineSheet : IDisposable
|
||||
ForceDelay = true
|
||||
});
|
||||
|
||||
DispatchService.UnSubscribe(Dispatch);
|
||||
|
||||
await _sheetExcel.PushDataAsync(entry.Entry.Data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HubOptions>(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>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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<HubOptions>(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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
101
src/BootstrapBlazor.Server/Components/Samples/UploadCards.razor
Normal file
101
src/BootstrapBlazor.Server/Components/Samples/UploadCards.razor
Normal 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<HubOptions>(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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<HubOptions>(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>
|
||||
@@ -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 = " — "
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -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<HubOptions>(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>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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<HubOptions>(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" />
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
54
src/BootstrapBlazor.Server/Components/Samples/Vditors.razor
Normal file
54
src/BootstrapBlazor.Server/Components/Samples/Vditors.razor
Normal 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<HubOptions>(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()" />
|
||||
195
src/BootstrapBlazor.Server/Components/Samples/Vditors.razor.cs
Normal file
195
src/BootstrapBlazor.Server/Components/Samples/Vditors.razor.cs
Normal 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"
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -91,6 +91,9 @@ public static class ServiceCollectionSharedExtensions
|
||||
// 增加 JuHe 定位服务
|
||||
services.AddBootstrapBlazorJuHeIpLocatorService();
|
||||
|
||||
// 增加 ITcpSocketFactory 服务
|
||||
services.AddBootstrapBlazorTcpSocketFactory();
|
||||
|
||||
// 增加 PetaPoco ORM 数据服务操作类
|
||||
// 需要时打开下面代码
|
||||
//services.AddPetaPoco(option =>
|
||||
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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>>=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>>=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><link rel='stylesheet' href='css/bootstrap/bootstrap.min.css' /></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><link rel='stylesheet' href='css/bootstrap/bootstrap.min.css' /></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<TValue></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<TValue></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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>>= 250 MB</code>),建议遵循 <code>MVC</code> 从 <code>Url</code> 下载,<b><code>本组件不支持 NET5</code></b>",
|
||||
"DownloadsTips2": "<code>Download</code> 组件底层使用了 <code>DotNetStreamReference</code> 对象,这允许将文件数据流式传输到客户端,此方法会将整个文件加载到客户端内存中,若要下载相对较大的文件 (<code>>= 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><link rel='stylesheet' href='css/bootstrap/bootstrap.min.css' /></code>。如果使用 <code>FontAwesome</code> 图标库可安装扩展包 <code>BootstrapBlazor.FontAwesome</code>"
|
||||
"Tips2": "如果使用微软自带模板创建工程,请移除默认 <code>Bootstrap</code> 样式表 <code><link rel='stylesheet' href='css/bootstrap/bootstrap.min.css' /></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<TValue></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<TValue></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": "灰色:离线状态"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ if (!app.Environment.IsDevelopment())
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
}
|
||||
|
||||
// 增加上传目录静态资源文件
|
||||
app.UseUploaderStaticFiles();
|
||||
|
||||
app.UseAntiforgery();
|
||||
app.UseBootstrapBlazor();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/BootstrapBlazor.Server/Services/MockDisconnectService.cs
Normal file
64
src/BootstrapBlazor.Server/Services/MockDisconnectService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user