背景
最先采用的是 Maui + Blazor 开发,使用社区热度比较高的 Blazor UI 框架。
可是开发进行过程中, Maui 巨多坑,Blazor UI 框架也是巨多坑,使用 Blazor UI 写的页面和样式,过不了设计师和产品经理的是法眼。
最终决定使用原生前端结合,生成静态内容放到 Maui 中,然后将两者结合起来打包发布。
先搞前端
对于前端来说,按照正常的开发模式就行,不对对前端的代码产生污染。
可以使用 VS 创建前端项目,将其放到解决方案中,也可以单独创建一个目录,将前端代码放到里面。
创建 MAUI Blazor 项目
创建 MAUI Blazor 项目,然后解决方案如下所示:
首先将 wwwroot/css/app.css
文件移出来,放到 wwwroot
中,然后新建一个 app.js
也是放到 wwwroot
中。
将 app.css
文件中的内容删除与 Blazor 无关的内容,只保留以下内容:
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.status-bar-safe-area {
display: none;
}
@supports (-webkit-touch-callout: none) {
.status-bar-safe-area {
display: flex;
position: sticky;
top: 0;
height: env(safe-area-inset-top);
background-color: #f7f7f7;
width: 100%;
z-index: 1;
}
.flex-column, .navbar-brand {
padding-left: env(safe-area-inset-left);
}
}
接着,将以下代码放到 app.js
中,用于动态导入前端生成的 css 文件。
function InitializeCss(name) {
document.getElementById("app-css").innerHTML = '<link rel="stylesheet" href="' + name + '">';
}
然后删除 js
、css
目录。
剩下的文件如图所示:
然后修改 index.html
,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>MauiApp3</title>
<base href="/" />
<link href="/app.css" rel="stylesheet" />
</head>
<body>
<div id="app-css"></div>
<div class="status-bar-safe-area"></div>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="app.js"></script>
<script src="_framework/blazor.webview.js" autostart="false"></script>
</body>
</html>
增加的 <div id="app-css"></div>
,用于动态加载 css 文件。
其它内容基本不变。
打开 MainLayout.razor
,这里负责动态加载前端文件,其内容如下:
@using MauiApp3.Data
@inherits LayoutComponentBase
@code {
#region static fields
private static readonly string[] css;
private static readonly string[] js;
#endregion
#region instance fields
[Inject]
private IJSRuntime JS { get; set; }
#endregion
static MainLayout()
{
var path = Windows.ApplicationModel.Package.Current.InstalledLocation.Path;
if (Directory.Exists(Path.Combine(path, "wwwroot", "css")))
{
var cssList = Directory.GetFiles(Path.Combine(path, "wwwroot", "css"))
.Where(x => x.EndsWith(".css"))
.Select(x => Path.GetFileName(x)).ToArray();
css = cssList;
}
else css = Array.Empty<string>();
if (Directory.Exists(Path.Combine(path, "wwwroot", "js")))
{
var jsList = Directory.GetFiles(Path.Combine(path, "wwwroot", "js"))
.Where(x => x.EndsWith(".js"))
.Select(x => Path.GetFileName(x)).ToArray();
js = jsList;
}
else js = Array.Empty<string>();
}
protected override async Task OnInitializedAsync()
{
await Task.CompletedTask;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
foreach (var item in css)
{
await JS.InvokeVoidAsync("InitializeCss", $"css/{item}");
}
foreach (var item in js)
{
await JS.InvokeAsync<IJSObjectReference>("import", $"/js/{item}");
}
}
}
}
然后为了能够将前端生成的内容自动复制到 Maui 中,可以设置脚本,在 Maui 的 .csproj
中,增加以下内容:
<Target Name="ClientBuild" BeforeTargets="BeforeBuild">
<Exec WorkingDirectory="../vueproject1" Command="npm install" />
<Exec WorkingDirectory="../vueproject1" Command="npm run build" />
<Exec WorkingDirectory="../vueproject1" Command="DEL "dist\index.html"" />
<Exec WorkingDirectory="./" Command="RMDIR /s/q "css"" />
<Exec WorkingDirectory="./" Command="RMDIR /s/q "js"" />
<Exec WorkingDirectory="../vueproject1" Command="Xcopy "dist" "../MauiApp3/wwwroot" /E/C/I/Y" />
<Exec WorkingDirectory="./" Command="RMDIR /s/q "$(LayoutDir)""/>
</Target>
这样当启动 Maui 项目时,会编译前端项目,然后将生成的文件(不包括 index.html) 复制到 wwwroot 目录中。
启动后:
文章评论