Skip to content

Latest commit

 

History

History
522 lines (382 loc) · 16.1 KB

D3.BarcodeScanner.md

File metadata and controls

522 lines (382 loc) · 16.1 KB

Blazor组件自做三 : 使用JS隔离封装ZXing扫码

本文基础步骤参考前两篇文章

Blazor组件自做一 : 使用JS隔离封装viewerjs库

Blazor组件自做二 : 使用JS隔离制作手写签名组件

1. 在文件夹wwwroot/lib,添加zxing子文件夹,里面下载库文件(文件文末源码里可复制) qrcode.min.js和zxing.min.js复制到此文件夹. 最终版本参考如下

+zxing
  |-qrcode.min.js
  |-zxing.min.js

zxing

2. 添加zxingjs.js文件

+zxing
  |-zxingjs.js
zxingjs.js代码
import '/lib/zxing/zxing.min.js';
var codeReader = null;
export function init(autostop, wrapper, options) {
    console.log('autostop' + autostop);

    let selectedDeviceId;
    //const codeReader = new ZXing.BrowserBarcodeReader()
    codeReader = new ZXing.BrowserMultiFormatReader()
    console.log('ZXing code reader initialized')
    codeReader.getVideoInputDevices()
        .then((videoInputDevices) => {
            const sourceSelect = document.getElementById('sourceSelect')
            selectedDeviceId = videoInputDevices[0].deviceId
            console.log('videoInputDevices:' + videoInputDevices.length);
            if (videoInputDevices.length > 1) {
                videoInputDevices.forEach((element) => {
                    const sourceOption = document.createElement('option')
                    sourceOption.text = element.label
                    sourceOption.value = element.deviceId
                    sourceSelect.appendChild(sourceOption)
                    selectedDeviceId = element.deviceId;
                })

                sourceSelect.onchange = () => {
                    selectedDeviceId = sourceSelect.value;
                    codeReader.reset();
                    StartScan();
                }

                const sourceSelectPanel = document.getElementById('sourceSelectPanel')
                sourceSelectPanel.style.display = 'block'
            }

            StartScan(autostop);

            document.getElementById('startButton').addEventListener('click', () => {
                StartScan();
            })

            function StartScan(autostop) {
                codeReader.decodeOnceFromVideoDevice(selectedDeviceId, 'video').then((result) => {
                    console.log(result)
                    document.getElementById('result').textContent = result.text

                    var supportsVibrate = "vibrate" in navigator;
                    if (supportsVibrate) navigator.vibrate(1000);

                    if (autostop) {
                        console.log('autostop');
                        codeReader.reset();
                        return wrapper.invokeMethodAsync("invokeFromJS", result.text);
                    } else {
                        console.log('None-stop');
                        codeReader.reset();
                        wrapper.invokeMethodAsync("invokeFromJS", result.text);
                    }

                }).catch((err) => {
                    console.error(err)
                    document.getElementById('result').textContent = err
                })
                console.log(`Started continous decode from camera with id ${selectedDeviceId}`)
            }

            document.getElementById('resetButton').addEventListener('click', () => {
                document.getElementById('result').textContent = '';
                codeReader.reset();
                console.log('Reset.')
            })

            document.getElementById('closeButton').addEventListener('click', () => {
                document.getElementById('result').textContent = '';
                codeReader.reset();
                console.log('closeButton.')
                wrapper.invokeMethodAsync("invokeFromJSClose");
            })

        })
        .catch((err) => {
            console.error(err)
        })
}
export function destroy(options) {
    if (undefined !== codeReader && null !== codeReader && options.id == codeReader.element.id) {
        codeReader.destroy();
        console.log(codeReader.element.id, 'destroy');
    }
}

3. 前面两篇文章主要在于快速入手建立组件, 基本没有解释代码. 现在开始穿插一点Blazor和JS交互的相关知识.

3.1 打开Day1的Pages/ViewerPage.razor文件,顶端有一句 @page "/viewer" , 意思是此页面的路由地址为viewer,请求此路径就会加载到此页面/组件.

参考阅读:ASP.NET Core Blazor 路由和导航

同理,Pages/HandwrittenPage.razor文件的 @page "/handwritten" 也是一样作用

QQ截图20220320173304

3.2 调用组件演示页面代码逐行说明
<Viewerjs Images="imagesList" /> 直接调用Viewerjs组件,Images是组件的参数,打开文件Components/Viewerjs.razor可以查看定义
    /// <summary>
    /// 图片列表
    /// </summary>
    [Parameter] public List<string> Images { get; set; } = new List<string>();


@page "/viewer"  //页面的路由地址

<Viewerjs Images="imagesList" /> //调用Viewerjs组件,指定组件图片列表数据来源

@code{
    List<string>? imagesList;

    protected override void OnInitialized() //组件初始化 , [参考阅读:组件生命周期](https://docs.microsoft.com/zh-cn/aspnet/core/blazor/components/lifecycle?view=aspnetcore-6.0)
    {
        //生成演示图片数据

        imagesList = new List<string>();
        if (!imagesList.Any())
        {
            for (int i = 1; i <= 9; i++)
            {
                imagesList.Add($"https://fengyuanchen.github.io/viewerjs/images/thumbnails/tibet-{i}.jpg");
            }
        }
    }

}

3.3 组件生命周期

参考阅读:组件生命周期

QQ截图20220320175545

3.4 调整演示工程外观

现在Pages/Index.razor已经直接放置了两个占用空间比较大的组件,再加入今天的组件势必很难看,我们将对首页以及左侧导航菜单做一些调整,以便条理更加清晰.

删除Pages/Index.razor文件中以下代码

<ViewerPage />
 
<hr />

<HandwrittenPage />

QQ截图20220320172953

打开Shared/NavMenu.razor添加相关导航,NavLink组件是Blazor默认自带导航组件, 参考阅读:NavLink 类

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="viewer">
                <span class="oi oi-plus" aria-hidden="true"></span> 图片浏览
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="handwritten">
                <span class="oi oi-plus" aria-hidden="true"></span> 手写签名
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="barcodescanner">
                <span class="oi oi-plus" aria-hidden="true"></span> 条码扫描
            </NavLink>
        </div>

QQ截图20220320173050

5. 打开Components文件夹 , 新建BarcodeScanner.razor组件

参考阅读:Blazor JS 互操作

参考阅读:JS 模块中的 JS 隔离

5.1 C#组件实例封装
DotNetObjectReference.Create(this)
5.2 C#标记JS调用的函数[JSInvokable("invokeFromJS")]
    [JSInvokable("invokeFromJS")] 
    public async Task ChangeValue(string val)
    {
        Result = val;
        StateHasChanged();
        await ScanResult.InvokeAsync(val);
    }
5.3 zxingjs.js文件调用C#
import '/lib/zxing/zxing.min.js' //模块的方式加载zxing库

//定义一个函数init供blazor组件调用

export function init(autostop, wrapper, options) {}  //wrapper为blazor组件的实例 

//扫码结果通过blazor组件的实例调用DotNet.invokeMethodAsync实现

wrapper.invokeMethodAsync("invokeFromJS", result.text);

5.4 C#动态载入JS模块
private IJSObjectReference? module;

module = await JS.InvokeAsync<IJSObjectReference>("import", "./lib/zxing/zxingjs.js");

5.5 C#调用JS函数
module.InvokeVoidAsync("init", true, DotNetObjectReference.Create(this), null);
5.6 完整代码
BarcodeScanner.razor代码
@implements IAsyncDisposable
@namespace Blazor100.Components
@inject IJSRuntime JS

<div class="modal alert-popup" tabindex="-1" style="display:block" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">
            <!-- Edit form for the current item -->
            <div class="modal-body">

                <button class="btn btn-primary p-2 m-1 w-25" id="startButton">@ScanBtnTitle</button>
                <button class="btn btn-secondary p-2 m-1 w-25" id="resetButton">@ResetBtnTitle</button>
                <button type="button" class="btn btn-info p-2 m-1 w-25" id="closeButton">@CloseBtnTitle</button>

                <div id="sourceSelectPanel" style="display:none">
                    <label for="sourceSelect">@SelectDeviceBtnTitle:</label><span class="text-dark" id="result"></span>
                    <select id="sourceSelect" style="max-width:100%" class="form-control">
                    </select>
                </div>
                <div>
                    <video id="video" style="min-height:150px;max-height:60%; max-width: 100%;border: 1px solid gray"></video>
                </div>

            </div>
        </div>
    </div>
</div>
@Result


@code {
    /// <summary>
    /// BarcodeScanner 条码扫描
    /// </summary>

    /// <summary>
    /// 扫码按钮文本/Scan button title
    /// </summary>
    [Parameter]
    public string ScanBtnTitle { get; set; } = "扫码";

    /// <summary>
    /// 复位按钮文本/Reset button title
    /// </summary>
    [Parameter]
    public string ResetBtnTitle { get; set; } = "复位";

    /// <summary>
    /// 关闭按钮文本/Close button title
    /// </summary>
    [Parameter]
    public string CloseBtnTitle { get; set; } = "关闭";

    /// <summary>
    /// 选择设备按钮文本/Select device button title
    /// </summary>
    [Parameter]
    public string SelectDeviceBtnTitle { get; set; } = "选择设备";


    /// <summary>
    /// 扫码结果回调方法/Scan result callback method
    /// </summary>
    [Parameter]
    public EventCallback<string> ScanResult { get; set; }

    /// <summary>
    /// 关闭扫码框回调方法/Close scan code callback method
    /// </summary>
    [Parameter]
    public EventCallback Close { get; set; }


    /// <summary>
    /// 扫码结果/Scan result
    /// </summary>
    [Parameter]
    public string? Result { get; set; }

    /// <summary>
    /// 显示扫码框/Show scan box
    /// </summary>
    [Parameter]
    public bool ShowScanBarcode { get; set; }

    private IJSObjectReference? module;

    // To prevent making JavaScript interop calls during prerendering
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        try
        {
            if (!firstRender) return;
            module = await JS.InvokeAsync<IJSObjectReference>("import", "./lib/zxing/zxingjs.js");
            await module.InvokeVoidAsync("init", true, DotNetObjectReference.Create(this), null); //组件实例封装:DotNetObjectReference.Create(this)
        }
        catch (Exception e)
        {
            if (OnError != null) await OnError.Invoke(e.Message);
        }

    }

    [JSInvokable("invokeFromJS")]
    public async Task ChangeValue(string val)
    {
        Result = val;
        StateHasChanged();
        await ScanResult.InvokeAsync(val);
    }

    [JSInvokable("invokeFromJSClose")]
    public async Task CloseScan()
    {
        await Close.InvokeAsync(null);
    }

    /// <summary>
    /// 获得/设置 错误回调方法
    /// </summary>
    [Parameter]
    public Func<string, Task>? OnError { get; set; }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            //await module.InvokeVoidAsync("destroy", Options);
            await module.DisposeAsync();
        }
    }

}

6. Pages文件夹添加BarcodeScannerPage.razor文件,用于演示组件调用.
BarcodeScannerPage.razor代码
@page "/barcodescanner"

<h3>条码扫描 BarcodeReader</h3>

<h4>扫描条码/QR码。</h4>

<button class="btn btn-primary"
        type="button"
        @onclick="(() => ShowScanBarcode = !ShowScanBarcode)">
    扫码
</button>
<input type="text" class="form-control" style="min-width: 100px;"
       @bind-value="BarCode"
       placeholder="条码" />
@if (ShowScanBarcode)
{

    <BarcodeScanner ScanResult="((e) => { BarCode=e; ShowScanBarcode = !ShowScanBarcode; })"
                    ShowScanBarcode="ShowScanBarcode"
                    Close="(()=>ShowScanBarcode=!ShowScanBarcode)" />

}

<p>@message</p>

@code{

    /// <summary>
    /// 显示扫码界面
    /// </summary>
    bool ShowScanBarcode { get; set; } = false;

    /// <summary>
    /// 条码
    /// </summary>
    public string? BarCode { get; set; }

    private string? message;

    private Task OnError(string message)
    {
        this.message = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

} 

7. _Imports.razor加入一行引用组件的命名空间,已经有这行就不需要再重复写了.

@using Blazor100.Components

8. 首页引用组件演示页 <BarcodeScannerPage />或者Shared/NavMenu.razor添加导航

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="barcodescanner">
                <span class="oi oi-plus" aria-hidden="true"></span> 条码扫描
            </NavLink>
        </div>

QQ截图20220320173125

9. F5运行程序

QQ截图20220320183738

QQ截图20220320183904

至此,使用JS隔离封装ZXing扫码组件大功告成! Happy coding!

Blazor组件自做系列

Blazor组件自做一 : 使用JS隔离封装viewerjs库

Blazor组件自做二 : 使用JS隔离制作手写签名组件

Blazor组件自做三 : 使用JS隔离封装ZXing扫码

Blazor组件自做四: 使用JS隔离封装signature_pad签名组件

Blazor组件自做五: 使用JS隔离封装Google地图

Blazor组件自做六: 使用JS隔离封装Baidu地图

Blazor组件自做七: 使用JS隔离制作定位/持续定位组件

Blazor组件自做八: 使用JS隔离封装屏幕键盘kioskboard.js组件

cnblogs

Blazor组件自做一 : 使用JS隔离封装viewerjs库

Blazor组件自做二 : 使用JS隔离制作手写签名组件

项目源码 Github | Gitee