From ee2daaaf7961c227f23ad67244ac033c5b507f19 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 25 Nov 2025 10:08:39 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(PdfReader):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=A1=B5=E7=A0=81=E5=9B=9E=E8=B0=83=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BootstrapBlazor.PdfReader/PdfReader.razor | 51 ++++++----- .../PdfReader.razor.cs | 87 ++++++++++++------ .../css/pdf.css => PdfReader.razor.css} | 6 +- .../PdfReader.razor.js | 90 +++++++++++-------- .../PdfReaderOptions.cs | 41 +++++++++ 5 files changed, 185 insertions(+), 90 deletions(-) rename src/components/BootstrapBlazor.PdfReader/{wwwroot/css/pdf.css => PdfReader.razor.css} (96%) create mode 100644 src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor index 08d84cba..936b3d4d 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor @@ -3,31 +3,34 @@ @inherits BootstrapModuleComponentBase
-
-
-
- @_docTitle + @if (Options.ShowToolbar) + { +
+
+
+ @_docTitle +
+
+ /
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- /
14
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ }
diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs index 7fa50e64..f7e54e5f 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs @@ -3,6 +3,7 @@ // Website: https://www.blazor.zone or https://argozhang.github.io/ using Microsoft.AspNetCore.Components; +using System.Globalization; namespace BootstrapBlazor.Components; @@ -13,38 +14,43 @@ namespace BootstrapBlazor.Components; public partial class PdfReader { /// - /// 获得/设置 PDF 文档路径 + /// 获得/设置 配置项实例 /// [Parameter] - public string? Url { get; set; } - - /// - /// 获得/设置 PDF 组件高度 默认 600px - /// - [Parameter] - public string? ViewHeight { get; set; } - - /// - /// 获得/设置 是否适配当前页面宽度 默认 false - /// - [Parameter] - public bool IsFitToPage { get; set; } + [NotNull] + public PdfReaderOptions? Options { get; set; } private string? ClassString => CssBuilder.Default("bb-pdf-reader") .AddClassFromAttributes(AdditionalAttributes) .Build(); private string? StyleString => CssBuilder.Default() - .AddClass($"--bb-pdf-view-height: {ViewHeight};", !string.IsNullOrEmpty(ViewHeight)) + .AddClass($"--bb-pdf-view-height: {Options.ViewHeight};", !string.IsNullOrEmpty(Options.ViewHeight)) .AddClassFromAttributes(AdditionalAttributes) .Build(); private string? ViewBodyString => CssBuilder.Default("bb-view-body") - .AddClass("fit-page", IsFitToPage) + .AddClass("fit-page", Options.IsFitToPage) .Build(); private string? _docTitle; private bool _isFitToPage; + private uint _currentPage; + private string? _url; + + private string CurrentPageString + { + get => Options.CurrentPage.ToString(CultureInfo.InvariantCulture); + set => SetCurrentPage(value); + } + + private void SetCurrentPage(string value) + { + if (uint.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num)) + { + Options.CurrentPage = num; + } + } /// /// @@ -53,7 +59,13 @@ protected override void OnParametersSet() { base.OnParametersSet(); - _docTitle = Path.GetFileName(Url); + Options ??= new PdfReaderOptions(); + + if (Options.CurrentPage == 0) + { + Options.CurrentPage = 1; + } + _docTitle = Path.GetFileName(Options.Url); } /// @@ -67,13 +79,26 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { - _isFitToPage = IsFitToPage; + _isFitToPage = Options.IsFitToPage; + _currentPage = Options.CurrentPage; + _url = Options.Url; + } + + if (_url != Options.Url) + { + _url = Options.Url; + await InvokeInitAsync(); } - if (_isFitToPage != IsFitToPage) + if (_isFitToPage != Options.IsFitToPage) + { + _isFitToPage = Options.IsFitToPage; + await TriggerFit(_isFitToPage ? "fitToPage" : "fitToWidth"); + } + if (_currentPage != Options.CurrentPage) { - _isFitToPage = IsFitToPage; - await TriggerFit(IsFitToPage ? "fitToPage" : "fitToWidth"); + _currentPage = Options.CurrentPage; + await NavigateToPageAsync(_currentPage); } } @@ -81,24 +106,29 @@ protected override async Task OnAfterRenderAsync(bool firstRender) /// /// /// - protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Url, IsFitToPage }); + protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new + { + Options.Url, + Options.IsFitToPage, + TriggerPagesInit = Options.OnInitAsync != null + }); /// /// 跳转到指定页码方法 /// /// /// - public Task NavigateToPageAsync(int pageNumber) => InvokeVoidAsync("navigateToPage", Id, pageNumber); + public Task NavigateToPageAsync(uint pageNumber) => InvokeVoidAsync("navigateToPage", Id, pageNumber); /// /// 适应页面宽度 /// - public void FitToPage() => IsFitToPage = true; + public void FitToPage() => Options.IsFitToPage = true; /// /// 适应文档宽度 /// - public void FitToWidth() => IsFitToPage = false; + public void FitToWidth() => Options.IsFitToPage = false; /// /// 旋转页面方法 @@ -125,9 +155,12 @@ public async Task RotateRight() /// /// [JSInvokable] - public Task PagesInit() + public async Task PagesInit(int pagesCount) { - return Task.CompletedTask; + if (Options.OnInitAsync != null) + { + await Options.OnInitAsync(pagesCount); + } } /// diff --git a/src/components/BootstrapBlazor.PdfReader/wwwroot/css/pdf.css b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css similarity index 96% rename from src/components/BootstrapBlazor.PdfReader/wwwroot/css/pdf.css rename to src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css index 25a0a262..c15865c9 100644 --- a/src/components/BootstrapBlazor.PdfReader/wwwroot/css/pdf.css +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css @@ -1,5 +1,3 @@ -@import url('pdf_viewer.css'); - .bb-pdf-reader { --bb-pdf-view-height: 600px; --bb-pdf-toolbar-height: 60px; @@ -16,6 +14,10 @@ color: #fff; } + .bb-view-toolbar.init > div { + visibility: hidden; + } + .bb-view-title { display: flex; align-items: center; diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js index 4154fbfb..8f2c2dad 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js @@ -8,25 +8,28 @@ if (pdfjsLib != null) { } export async function init(id, invoke, options) { + await addLink('./_content/BootstrapBlazor.PdfReader/css/pdf_viewer.css'); + const el = document.getElementById(id); if (el === null) { return; } - await addLink('./_content/BootstrapBlazor.PdfReader/css/pdf.css'); + if (options.url === null) { + return; + } const loadingTask = pdfjsLib.getDocument(options); loadingTask.onProgress = function (progressData) { console.log(progressData.loaded, progressData.total); }; - // handle password only when required (optional password support) loadingTask.onPassword = function (updatePassword, reason) { if (reason === pdfjsLib.PasswordResponses.NEED_PASSWORD) { - // only prompt if PDF actually requires password const password = prompt("This PDF is password protected. Enter password:"); updatePassword(password); - } else if (reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) { + } + else if (reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) { const password = prompt("Incorrect password. Please try again:"); updatePassword(password); } @@ -39,43 +42,11 @@ export async function init(id, invoke, options) { eventBus }); + addEventListener(pdfViewer, eventBus, invoke, options); - eventBus.on("pagesinit", function () { - if (options.isFitToPage) { - pdfViewer.currentScaleValue = "page-width"; - } - else { - pdfViewer.currentScaleValue = 1.0; - } - }); - - // handle the promise const pdfDocument = await loadingTask.promise; pdfViewer.setDocument(pdfDocument); - // pdfDocument.then(function (doc) { - // pdf.pdfDoc = doc; - // pdf.pagesCount = doc.numPages; - // renderPage(pdf, pdf.pageNum); - - // // notify .NET side that document is loaded - // invoke.invokeMethodAsync('DocumentLoaded', { - // pagesCount: pdf.pagesCount, - // pageNumber: pdf.pageNum - // }); - // }) - // .catch(function (error) { - // console.error("PDF loading error:", error); - - // // handle password exceptions specifically - // if (error.name === "PasswordException") { - // console.error("Password required but not provided"); - // } - - // // notify .NET side that document loading failed - // invoke.invokeMethodAsync('DocumentLoadError', error.message); - // }); - Data.set(id, pdfViewer); } @@ -106,6 +77,51 @@ export function dispose(id) { Data.get(id); } +const addEventListener = (pdfViewer, eventBus, invoke, options) => { + eventBus.on("pagesinit", async () => { + if (options.isFitToPage) { + pdfViewer.currentScaleValue = "page-width"; + } + else { + pdfViewer.currentScaleValue = 1.0; + } + + const el = pdfViewer.container.parentElement; + const numPages = pdfViewer.pagesCount; + const countEl = el.querySelector(".bb-view-pagesCount"); + if (countEl) { + countEl.innerHTML = numPages; + } + + const toolbarEl = el.querySelector(".bb-view-toolbar"); + if (toolbarEl) { + toolbarEl.classList.remove("init"); + } + + if (options.triggerPagesInit === true) { + await invoke.invokeMethodAsync("pagesInit", numPages); + } + }); + + eventBus.on("documentloaded", e => { + console.log(e); + }); + + eventBus.on( + "pagechanging", + function (evt) { + const page = evt.pageNumber; + console.log(page); + //const numPages = PDFViewerApplication.pagesCount; + + //document.getElementById("pageNumber").value = page; + //document.getElementById("previous").disabled = page <= 1; + //document.getElementById("next").disabled = page >= numPages; + }, + true + ); +} + function getCanvas(item) { if (isDomSupported() && typeof item === 'string') { item = document.getElementById(item); diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs b/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs new file mode 100644 index 00000000..d1d37d12 --- /dev/null +++ b/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs @@ -0,0 +1,41 @@ +// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Components; + +/// +/// PdfReader 组件配置类 +/// +public class PdfReaderOptions +{ + /// + /// 获得/设置 是否显示工具栏 默认 true 显示 + /// + public bool ShowToolbar { get; set; } = true; + + /// + /// 获得/设置 PDF 文档路径 + /// + public string? Url { get; set; } + + /// + /// 获得/设置 PDF 组件高度 默认 600px + /// + public string? ViewHeight { get; set; } + + /// + /// 获得/设置 当前页码 + /// + public uint CurrentPage { get; set; } + + /// + /// 获得/设置 是否适配当前页面宽度 默认 false + /// + public bool IsFitToPage { get; set; } + + /// + /// 页面初始化回调方法 + /// + public Func? OnInitAsync { get; set; } +} From 181fe95e9df82d3e98a20e2e05ea94331149b6e4 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 25 Nov 2025 10:22:47 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20OnPageChangedA?= =?UTF-8?q?sync=20=E5=9B=9E=E8=B0=83=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PdfReader.razor.cs | 13 ++++++--- .../PdfReader.razor.js | 27 ++++++++----------- .../PdfReaderOptions.cs | 5 ++++ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs index f7e54e5f..46db80c6 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs @@ -110,7 +110,8 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { Options.Url, Options.IsFitToPage, - TriggerPagesInit = Options.OnInitAsync != null + TriggerPagesInit = Options.OnInitAsync != null, + TriggerPageChanged = Options.OnPageChangedAsync != null }); /// @@ -168,8 +169,14 @@ public async Task PagesInit(int pagesCount) /// /// [JSInvokable] - public Task PageChanging() + public async Task PageChanged(uint pageIndex) { - return Task.CompletedTask; + _currentPage = pageIndex; + Options.CurrentPage = pageIndex; + + if (Options.OnPageChangedAsync != null) + { + await Options.OnPageChangedAsync(pageIndex); + } } } diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js index 8f2c2dad..07a451d3 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js @@ -103,23 +103,18 @@ const addEventListener = (pdfViewer, eventBus, invoke, options) => { } }); - eventBus.on("documentloaded", e => { - console.log(e); - }); + eventBus.on("pagechanging", async evt => { + const page = evt.pageNumber; + const el = evt.source.container.parentElement; + const pageNumberEl = el.querySelector(".bb-view-num"); + if (pageNumberEl) { + pageNumberEl.value = page; + } - eventBus.on( - "pagechanging", - function (evt) { - const page = evt.pageNumber; - console.log(page); - //const numPages = PDFViewerApplication.pagesCount; - - //document.getElementById("pageNumber").value = page; - //document.getElementById("previous").disabled = page <= 1; - //document.getElementById("next").disabled = page >= numPages; - }, - true - ); + if (options.triggerPageChanged === true) { + await invoke.invokeMethodAsync("pageChanged", page); + } + }, true); } function getCanvas(item) { diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs b/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs index d1d37d12..cc688f00 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs +++ b/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs @@ -38,4 +38,9 @@ public class PdfReaderOptions /// 页面初始化回调方法 /// public Func? OnInitAsync { get; set; } + + /// + /// 页面初始化回调方法 + /// + public Func? OnPageChangedAsync { get; set; } }