- 【笔记】横向滑动列表的渲染
前言 今天在学习列表渲染的时候,尝试实现了支持横向滑动的列表,但是遇到了很多问题,做一个小小的总结。 组件scroll-view scroll-view是一种视图容器,指定可滚动视图区域。通过设置属性scroll-y=true并给给scroll-view一个固定高度height,可以实现竖向滚动;通过设置属性scroll-x=true可以实现横向滚动,其他的属性可以参考官方文档。 列表渲染 列表渲染是一种很基础的渲染方法,在组件上使用 wx:for 绑定一个数组,即可使用数组中各项的数据重复渲染该组件。在组件中,使用 wx:for-item 可以指定数组当前元素的变量名,默认为item;使用 wx:for-index 可以指定数组当前下标的变量名,默认为index;使用 wx:key 来指定列表中项目的唯一的标识符,可以提高渲染效率(没有特殊需求的话可以直接用index指定)。 例如,在.js中声明一个含有六个元素的数组list,可以用以下代码循环地渲染list中的所有元素 [代码]<!-- index.wxml 列表渲染 --> <view wx:for="{{list}}" wx:key="{{index}}" class="view-parent"> <view class="view-item">{{item.txt}}</view> </view> [代码] 显示效果如下: [图片] 横向滑动列表 将组件scroll-view和列表渲染结合,就可以实现横向滑动列表了,样例代码如下: [代码]<!-- index.wxml 横向滑动列表 --> <text style="margin-left: 40%;">横向滑动列表</text> <scroll-view scroll-x="true" class="scroll-x-list"> <view wx:for="{{list}}" wx:key="{{index}}" class="view-parent"> <view class="view-item">{{item.txt}}</view> </view> </scroll-view> /* index.wxss */ .scroll-x-list{ height:150px; } .view-item{ width:100px; height:100px; background:#1bf891; margin:10px; } [代码] 显示效果如下: [图片] 可以看到结果和预期差很多,不仅没有实现横向滑动,还没有显示出所有的元素。原因是代码虽然在组件中设置了需要的属性,但是在样式上没有做对应的调整,我们必须在wxss中设置布局才可以达到预期效果。最容易想到的就是我们常用的flex布局,关于flex布局的内容比较多,这里就不展开了,推荐看官方文档学习。在进行下一步修改前,先声明几个必须要知道的小细节: 组件scroll-view是不支持flex布局的,要想在scroll-view中使用flex布局,必须嵌套一个其他的支持flex布局的容器,如view。 scroll-view 中的需要滑动的元素不可以用 float 浮动。 scroll-view 中在需要装载滑动元素的父容器中开启flex布局是没有作用的,应该使用dislay:inline-block来进行元素的横向编排。 第一种方法,由于要实现的是横向滑动列表,那么容器中的元素一定是不允许换行的,刚刚提到,scroll-view是不支持flex布局的,所以开启flex布局并设置flex-wrap=nowrap是无效的行为。我们选择在类scroll-x-list中设置white-space: nowrap来处理元素中的空白,让容器内的换行无效。同时,还应设置装载滑动元素的父容器——view-parent的dislay为inline-block,代码如下: [代码]/* index.wxss */ .scroll-x-list{ height:150px; white-space: nowrap; } .view-parent{ display:inline-block; } [代码] 显示效果如下,已经可以横向滚动列表了。 [图片] 第二种方法,可以在scroll-view中嵌套一个view,在这个view中开启flex布局并设置flex-wrap=nowrap来阻止换行,代码如下: [代码]<!-- index.wxml 另一种横向滑动列表 --> <text style="margin-left: 40%;">横向滑动列表</text> <scroll-view scroll-x="true" class="scroll-x-list"> <view class='flex-view'> <view wx:for="{{list}}" wx:key="{{index}}" class="view-parent"> <view class="view-item">{{item.txt}}</view> </view> </view> </scroll-view> /* index.wxss */ .scroll-x-list{ height:150px; } .flex-view{ display:flex; flex-wrap: nowrap; } .view-parent{ display:inline-block; } [代码] 显示效果与刚刚相同: [图片] 一些改进 在第一种方法中,我们没有使用到flex布局,就很容易遇到一些对齐的问题,假设我们设置数组中第三个元素为空,就会出现下面的情况: [图片] 原因很简单,inline-block的属性中在某个元素没有内容的情况下,它的基线对齐方式是基于这个元素的底边的,解决方式是设置一个垂直的对齐方式: [代码]/* index.wxss */ .view-parent{ display:inline-block; vertical-align: top; } [代码] 显示效果如下: [图片] 同时,你会发现第二种开启flex布局方法的横向列表不会有这种对齐的问题,我们还可以在装载滑动元素的容器中开启flex布局来让内容更加美观: [代码].view-item{ width:100px; height:100px; background:#1bf891; margin-right: 20px; align-items:center; display:flex; justify-content:center; } [代码] 显示效果如下: [图片] 总结 scroll-view是一个十分常见实用的组件,但是使用时也有一些需要注意的问题,比如不支持直接使用flex布局。总体来看,比起设置inline-block的布局,更推荐在scroll-view中嵌套一层view再开启flex布局的方法,可以更灵活的摆放控制滑动元素。
2021-11-15 - 【笔记】WXSS中定位属性position的使用总结
前言 WXSS (WeiXin Style Sheets)是微信开发的样式语言,用于描述 WXML 的组件样式,它具有CSS 的大部分特性。关于在定位时常用到的属性position,文档中没有对应的描述,所以我自己总结了position的一些基础知识点。 定位 定位的目的是为了更好控制和摆放box,从而实现需要的网页布局。与flex布局的容器、坐标轴等概念不同,定位布局可以轻松任意指定box的摆放位置,定位的属性分为两种,一是边的偏移,二是定位的方式。 边偏移 边偏移有四个属性,分别是top,left,right和bottom,对应于基于顶部,左边,右边和底部的偏移。注意,如果position是默认值,也就是静态定位static时,指定边偏移是无效的行为。例如下面的代码,对于static-view类选择器,我们没有指定position的值,因此position设为默认值static,此时指定属性left是无效的行为: [代码]/* index.wxss */ .static-view{ height: 100px; width: 100px; background-color:red; left:50px; /* position: static; */ } [代码] 显示效果如下,红色正方形仍然贴着屏幕左端: [图片] 定位方式 定位的方式即position的值,常用的有五种,分别是静态定位static(默认值), 相对定位relative, 绝对定位absolute, 固定定位fixed和粘性定位sticky。接下来分别介绍一下各种定位方式的区别。 静态定位 static 静态定位是所有元素的默认定位方式,当你没有为元素指定position的值时,默认值就是static,举个例子,在.wxml中渲染三个不同颜色的view,它们的position都为默认的static: [代码]<!-- index.wxml --> <view class='view-red'></view> <view class='view-blue'></view> <view class='view-green'></view> /* index.wxss */ .view-red{ height: 100px; width: 100px; background-color:red; /* position: static; */ } [代码] 显示效果如下,三个view按照标准流的方式排列: [图片] 注意,在静态定位状态下,无法通过边偏移属性(top、bottom、left或right)来改变元素的位置。 相对定位 relative 相对定位是将元素相对于它的正常位置进行定位的,所谓的正常位置就是position为static时元素在标准文档流中的位置。相对定位的position值为relative,我们修改刚刚红色正方形的position为relative,并设置left为50px: [代码]/* index.wxss */ .view-red{ height: 100px; width: 100px; background-color:red; left: 50px; position: relative; } [代码] 显示效果如下,可以看到红色正方形相对于刚刚的位置向右偏移了50px: [图片] 绝对定位 absolute 绝对定位比较特殊,因为它脱离了标准文档流,以其父容器或父容器中最近的非static(其他四种定位方式)元素的位置为参考。如果没有找到这样的非static元素,就以page作为参考。绝对定位的position值为absolute,我们修改蓝色正方形的position为absolute,并设置left为100px,top为100px;: [代码]/* index.wxss */ .view-blue{ height: 100px; width: 100px; background-color:blue; left:100px; top:100px; position: absolute; } [代码] 显示效果如下,由于没有非static的父容器,view-blue以page为参考位置,left和top指定了它的偏移。还可以看到,绿色正方形在原来的位置向上移动了,原因是蓝色正方形使用了绝对定位,脱离了标准文档流,就为绿色正方形“腾出”了它原来的位置。 [图片] 我们换个例子,让红色正方形作为蓝色正方形的父容器,代码如下: [代码]<!-- index.wxml --> <view class='view-red'> <view class='view-blue'></view> </view> /* index.wxss */ .view-red{ height: 300px; width: 300px; background-color:red; left: 50px; position: relative; } .view-blue{ height: 100px; width: 100px; background-color:blue; left:200px; top:100px; position: absolute; } [代码] 显示效果如下,可以看出此时蓝色正方形以红色正方形的位置作为参考: [图片] 固定定位 fixed 固定定位也脱离了标准文档流,可以把它理解为以page为参考的绝对定位,固定定位的position值为fixed,我们常常会在顶部或底部导航栏中使用固定定位。以自定义tabBar组件为例: [代码].tab-bar { position: fixed; bottom: 0; left: 0; right: 0; height: 48px; background: white; display: flex; padding-bottom: env(safe-area-inset-bottom); } [代码] 显示效果如下,无论如何滚动,tabBar都会固定在屏幕底部。 [图片] 关于自定义tabbar组件,可以查看该代码片段:自定义tabBar组件 粘性定位 sticky 粘性定位的元素依赖于用户的滚动,在相对定位和固定定位之间切换。一般情况下,sticky等同于relative,当页面滚动超出目标区域时,它便切换为fixed,固定在目标位置。目标位置通过指定top, left, right和bottom来设置。修改代码如下: [代码]<!-- index.wxml --> <view style="height:100px"></view> <view class='view-blue'></view> <view class='view-green'></view> <view style="height: 3000px;"></view> /* index.wxss */ .view-blue{ height: 100px; width: 100px; background-color:blue; position: relative; } .view-green{ height: 100px; width: 100px; background-color:green; position: sticky; top:0px; } [代码] 显示如下,此时绿色正方形是相对定位,在标准文档流中: [图片] 下拉滚动条100px,可以看出绿色正方形仍为相对定位: [图片] 继续下拉滚动条,可以发现蓝色正方形消失,但绿色正方形一直处于屏幕顶端,此时已经切换为固定定位: [图片] 覆盖问题 在开发中,我们常常使用子元素绝对定位,父元素相对定位的方法来控制布局,但是仍会遇到不同元素的覆盖现象,可以通过设置属性z-index来解决。z-index的默认值是0,取值越大,定位元素在层叠次序中就越优先;如果取值相同,则以后渲染的优先。 总结 定位布局是各自布局方法中较为简单的一种,它的属性一是边偏移,二是定位方式,两者配合使用才可以构造出美观的布局。
2021-11-10 - 使用微信云托管快速部署一个.Net Core项目(一)
前几天微信将小程序开发者工具内的云托管升级为微信云托管,新增了很多诸如OpenApi、MySql数据库、流水线构建、web控制台等能力。看文档还是蛮激动的,对开发者来说确实是个好消息,因为之前的一些业务逻辑大都写在云函数或者部署在服务器里面,要管理好几套。看了这次发布的微信云托管有点动心要做一个迁移,微信云托管相比其它模式来说还是很有优势和前景的。关于微信云托管和云函数以及服务器、Kubernetes的对比大家看这个链接就够了:https://developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/basic/intro.html 这次先拿一个小demo练手。话不多说,出于好奇心,今天带大家来体验一把: Ø 第一步:开通环境 首先登陆微信云托管创建环境,微信云托管的地址为:https://cloud.weixin.qq.com/ 首先需要创建一个环境,创建环境分为系统创建和私人网络,选择私人网络会罗列出该小程序对应的腾讯云账号之前创建过的环境,在这里我选择了私人网络里面和我目前小程序使用的相同环境。 这次咱们也看看mysql,微信云托管相比之前增加了MySql服务,开通也是非常方便。如下图所示简单几步就可以开通成功了并且支持自动暂停: [图片] 开通之后是这样滴,支持内外网访问数据库,并且提供自动暂停服务,闲置的时候就帮你暂停了。 [图片] 由于项目中需要使用到“云调用”获取小程序码的服务,所以这里安装一下微信云托管提供的OpenApi,这里一定要注意如果要使用“云调用”服务,微信令牌权限设置这里一定是要把要使用的接口先添加到白名单的。 [图片] 小tips:大家在使用OpenApi接口的时候测试开发中可以把公网域名访问打开,线上环境启动内网访问就可以,这样相对比较安全。因为你调用OpenApi的接口不再像之前需要换取Access_Token啦,公网暴露风险大。 [图片] [图片] Ø 第二步:新建服务 流水线发布选择流水线发布的话第一步需要新建流水线,选择流水线发布的代码中必须要包含container.config.json文件,关于写法根据文档自己定义即可:https://developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/basic/guide.html [图片] [图片] GitHub授权访问之后并在仓库中包含container.config.json文件,然后根据自己实际情况勾选其它配置以后这样一条流水线就定义好了,当代码推送触发main分支的时候就会自动构建镜像,构建好之后别忘了最后还要把最新版本部署发布,流水线不会自动帮你去将最新版本发布上去的。 版本发布版本发布也是很方便的,定义好Dockerfile文件之后可以在腾讯云个人仓库构建配置那里配置从GitHub拉取或者本地构建好docker镜像后推送到腾讯云。我这里选择的是微信云托管代码拉取: [图片] 在版本列表里面选择新建版本,从代码库拉取,把我们写好的程序拉取下来: [图片] 代码拉取之后会在微信云托管自动帮助我们构建镜像,点击查看日志就可以看到详细的构建过程,又是熟悉的操作。 [图片] 等版本构建完毕之后,之后就是发布,无论是选择流水线发布还是版本发布,最后一步一定要选择发布上线。 [图片] Ø 第三步:开发 这里我创建一个.Net Core项目,选择WebApp模版。 [图片] [图片] 接下来编辑Dockerfile文件: #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base WORKDIR /app EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build WORKDIR /src COPY ["HtArtGoWebApp.csproj", "."] RUN dotnet restore "./HtArtGoWebApp.csproj" COPY . . WORKDIR "/src/." RUN dotnet build "HtArtGoWebApp.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "HtArtGoWebApp.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "HtArtGoWebApp.dll"] 然后安装数据库驱动 <ItemGroup> <PackageReference Include="FreeSql" Version="2.5.200" /> <PackageReference Include="FreeSql.Provider.MySql" Version="2.5.200" /> </ItemGroup> 注入FreeSql以及定义实体,先定义实体,我在项目中新建了Models文件夹,实体都放在Models文件夹中,首先定义一个基础类BaseEntity.cs public class BaseEntity where TKey : IEquatable { [Column(IsPrimary = true, IsIdentity = true)] public TKey Id { get; set; } [Column(ServerTime = DateTimeKind.Utc, CanUpdate = false)] public DateTime CreateTime { get; set; } [Column(ServerTime = DateTimeKind.Utc)] public DateTime UpdateTime { get; set; } public string OperatorId { get; set; } public bool IsDelete { get; set; } public bool Status { get; set; } } 之后定义一个用于测试的类Exhibitions.cs类,让它继承BaseEntity,主键可以自定义类型: public class Exhbitions:BaseEntity { public string Title { get; set; } } 接下来就是FreeSql的配置以及注入,在Stratup.cs文件里面进行注入: public Startup(IConfiguration configuration) { Configuration = configuration; //FreeSql配置 fsql = new FreeSql.FreeSqlBuilder() //链接字符串 .UseConnectionString(FreeSql.DataType.MySql, Configuration.GetConnectionString("MySql")) //自动同步实体打开 .UseAutoSyncStructure(true) //SQL日志也打开 .UseMonitorCommand(cmd => Console.WriteLine(cmd.CommandText)) .Build(); } public IConfiguration Configuration { get; } public IFreeSql fsql; 最后在ConfigureServices里面进行注入一下,注入方式为单例模式:services.AddSingleton<IFreeSql>(fsql) 这样数据库部分就配置注入成功了,然后就是调用数据库查询数据了,选一个Index.cshtml页面进行数据查询展示: public class IndexModel : PageModel { private readonly ILogger _logger; private IFreeSql _freeSql; //前端要展示的数据定义为属性 public IList ExhbitionListList { get; set; } public IndexModel(ILogger logger,IFreeSql freeSql) { _logger = logger; _freeSql = freeSql; } //页面初始化的时候查询数据 public async Task OnGetAsync() { var data = await _freeSql.Select().ToListAsync(); //赋值 ExhbitionListList = data; } } 在前端Index.cshtml用一个table展示: <table style="margin-left: 30%"> <thead> <tr> <th>ID</th> <th>名称</th> <th>创建日期</th> </tr> </thead> <tbody> @foreach (var item in this.Model.ExhbitionListList) { <tr> <td>@item.Id</td> <td>@item.Title</td> <td>@item.CreateTime</td> </tr> } </tbody> </table> 以上是数据库部分,接下来咱们使用微信云托管的OpenApi进行调用,关于OpenApi的部分放在RestService文件夹里面的OpenApiService类中 [图片] 这个文件夹里面我只写了一个获取小程序码的接口用于测试,代码如下: public class OpenApiService { private HttpClient _client; public OpenApiService(HttpClient client) { client.BaseAddress =new Uri("http://替换成大家自己的"); _client = client; } public async Task GetgetUnlimited() { var body = new StringContent(JsonSerializer.Serialize(new { scene = "index", page = "pages/index/index" }), Encoding.UTF8, "application/json"); var response = await _client.PostAsync("/wxa/getwxacodeunlimit", body); if (response.IsSuccessStatusCode) { MemoryStream ms = new MemoryStream(); await response.Content.CopyToAsync(ms); return ms; } return null; } } 然后在ConfigureService中注入一下:services.AddHttpClient<OpenApiService>(); 调用该服务我们写一个Controller接口供前端调用,这里要在Startup.cs里面配置两处: [图片] [图片] 配置好之后在控制器里面调用即可: [ApiController] [Route("api/[controller]")] public class WxController : Controller { private OpenApiService _openApiService; public WxController(OpenApiService openApiService) { _openApiService = openApiService; } [HttpGet] public async Task GetgetUnlimited() { var data = await _openApiService.GetgetUnlimited(); return new FileContentResult(data.ToArray(), "image/jpeg"); } } 这里别忘了在微信云托管将获取小程序码的接口添加白名单: [图片] 好啦,到这里代码部分就结束了。先在本地调试一遍没问题就发布啦: [图片] 获取小程序码正常 [图片] 数据库访问正常 页面看起来有点丑,用Vue和ElementUI 优化一下首页,在_Layout.cshtml中引入Vue和ElementUI的相关库,然后在前端Index.cshtml.cs中做如下修改: public class IndexModel : PageModel { private readonly ILogger _logger; private IFreeSql _freeSql; public IList ExhbitionListList { get; set; } public IndexModel(ILogger logger,IFreeSql freeSql) { _logger = logger; _freeSql = freeSql; } // public async Task OnGetAsync() // { // var data = await _freeSql.Select().ToListAsync(); // ExhbitionListList = data; // } public async Task OnGetList() { var data = await _freeSql.Select().ToListAsync(); return new JsonResult(data); } } Html页面中用element的Table组件优化一下: <div id="app"> <el-table v-bind:data="list" border style="width: 100%"> <el-table-column fixed prop="id" label="id"> </el-table-column> <el-table-column prop="title" label="标题"> </el-table-column> <el-table-column prop="createTime" label="创建时间"> </el-table-column> <el-table-column fixed="right" label="操作" width="100"> <template slot-scope="scope"> <el-button v-on:click="handleClick(scope.row)" type="text" size="small">查看</el-button> <el-button type="text" size="small">编辑</el-button> </template> </el-table-column> </el-table> </div> @section Scripts { <script type="text/javascript"> var app=new Vue({ el:'#app', data:{ title:'主页', list:[] }, async created(){ const list= await this.loadData(); this.list = list }, methods:{ loadData(){ return new Promise(((resolve, reject) => $.ajax({ url:'?handler=List', success:(res)=>{ resolve(res) },fail:(err)=>{ reject(err) } }) )) }, handleClick(row) { console.log(row); }} }) </script> } 呈现出来的页面如下所示,以后再把CURD相关操作添加上: [图片] 然后添加一个API管理工具Swagger,在这里为了演示添加一个最简易配置的的Swagger,添加Swagger首先需要在Nuget上面安装Swagger的包Swashbuckle.AspNetCore。之后在ConfigureServices方法中注入services.AddSwaggerGen();,在Configure里面注册Swagger中间件:app.UseSwagger(); app.UseSwaggerUI(c=>{c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");}); 然后访问localhost:5000//swagger/index.html即可 [图片] 最后发布上线之后也一切正常,感觉未来可期,日志查询也非常方便,这个必须赞: [图片] Ø 总结 总结起来微信云托管这个方向很好,很适合快速开发验证业务模型,希望后期尽快支持环境共享、自定义域名等,后续还有第二篇将如何将CMS接入项目中来,大家敬请期待~
2021-07-01 - 【解决方法】域名被拦截?网站被限制访问?可以参考一下这些方法
最近社区中有很多关于域名被拦截的提问,我总结了一些解决方法提供参考。 一. 腾讯网页安全中心进行申诉 网站地址:https://urlsec.qq.com/check.html ,腾讯网页安全中心提供了网站安全检查、网站拦截申诉、可疑网站举报等功能,如果你的域名被限制访问,建议先进行网站安全检查,如果检测结果显示存在非法内容,请先清除网站中的不规范内容,请参考《微信外部链接内容管理规范》:https://weixin.qq.com/cgi-bin/readtemplate?t=weixin_external_links_content_management_specification [图片] 如果检测结果正常,但是域名仍被限制访问,可能原因是之前因为违规内容而被拦截。进入网站拦截申诉,可以看到提示存在潜在的【恶意内容】: [图片][图片] 在确认网站内没有违规内容的情况下,可以点击进行申诉,填写必要的信息后,官方会在3个工作日内进行答复。 [图片] 二、在微信内申请恢复访问 在微信中访问违规网站时会被拦截在以下页面,可以点击申请恢复访问进行申诉。 [图片] 三、联系微信官方邮箱 参考社区技术运营专员的一个回复,确认网站不存在违规内容的话,可以通过发送电子邮件进行申诉,邮箱地址:moment@tencent.com [图片] 四、求助运营人员 如果以上方法都无法解决问题,还可以在社区中提问,社区中的技术运营专员一定会帮到你的。 以上只是我个人总结的一些解决方法,若有错误欢迎各位指出,如果能帮助到你的话,不妨帮我点一个赞吧!
2021-11-09 - 微信小程序路由实战
欢迎来到我博客阅读:BlueSun - 微信小程序路由实战 0. 目录 1. 前言 2. 智能路由跳转 — Navigator 模块 3. 虚拟路由策略 — Router 模块 4. 落地中转策略 — LandTransfer 模块 4.1. 对于要解决的第一个问题:统一的落地页 4.2. 对于第二个要解决的问题:短链参数 4.3. LandTransfer 模块设计 5. 更好的开发体验 5.1. Typescript + Router 5.2. 智能生成路由配置 5.3. 自定义组件跳转 6. 整体架构图 7. 最后的最后 1. 前言 在微信小程序由一个 [代码]App()[代码]实例,和众多[代码]Page()[代码]组成。而在小程序中所有页面的路由全部由框架进行管理,框架以栈的形式维护了所有页面,然后提供了以下 API 来进行路由之间的跳转: [代码]wx.navigateTo[代码] [代码]wx.redirectTo[代码] [代码]wx.navigateBack[代码] [代码]wx.switchTab[代码] [代码]wx.reLaunch[代码] 但是,对于一个企业应用,把这些问题留给了开发者: 原生 API 使用了 [代码]Callback[代码] 的函数实现形式,与我们现代普遍的 [代码]Promise[代码] 和 [代码]async/await[代码] 存在 gap。 基于小程序路由的设计,暴露给外部的是真实路由(如扫码,公众号链接等方式),对后续项目重构留下历史包袱。 小程序页面栈最多十层, 在超过十层后 [代码]wx.navigateTo[代码] 失效,需要开发者判断使用 [代码]wx.redirectTo[代码] 或其他API 小程序页面栈存在一种特殊的页面:Tab 页面,需要使用 [代码]wx.switchTab[代码] 才能跳转。需要开发者主动判断,不方便后期改动 Tab 页面属性。 额外的,对于小程序码,要使用无数量限制 API wxacode.getUnlimited ,存在参数长度限制32位以内。需要开发者自行解决。 而本文,期望能对这若干问题,逐个提供解决方案。 2. 智能路由跳转 — Navigator 模块 在这里我们一起解决: 原生 API 非 Promsie 页面栈突破十层时特殊处理 特殊页面 Tab 的跳转处理 我们的思路是,希望能设计一种逻辑,根据场景来自动判断使用哪个微信路由 API,然后对外只提供一个函数,例如: [代码]gotoPage('/pages/goods/index') [代码] 具体逻辑如下: 当跳转的路由为小程序 tab 页面时,则使用 [代码]wx.switchTab[代码]。 当页面栈达到 10 层之后,如果要跳转的页面在页面栈中,使用 [代码]wx.navigateBack({ delta: X })[代码] 出栈到目标页面。 当页面栈达到 10 层之后,目标页面不存在页面栈中,使用 [代码]wx.redirectTo[代码] 替换栈顶页面。 其他情况使用 [代码]wx.navigateTo[代码] 顺带的,我们把这个函数以 Promise 形式实现,以及支持参数作为 [代码]object[代码]传入,例如: [代码]gotoPage('/pages/goods/index', { name: 'jc' }).then(...).catch(...); [代码] 大部分场景下,只要使用[代码]gotoPage[代码]就能满足。 那肯定也会有特定的情况,需要显式的指定使用 [代码]navigateTo/switchTab/redirectTo/navigateBack[代码]的哪一个。 那么我们也按照类似的实现,满足相同模式的 API [代码]navigateTo('/pages/goods/index', { name: 'jc' }).then(...).catch(...); switchTab('/pages/goods/index', { name: 'jc' }).then(...).catch(...); redirectTo('/pages/goods/index', { name: 'jc' }).then(...).catch(...); navigateBack('/pages/goods/index', { name: 'jc' }).then(...).catch(...); [代码] 这些函数都可以内聚到同一个模块,我们称其为:Navigator [代码]const navigator = new Navigator(); navigator.gotoPage(...); navigator.navigateTo(...); navigator.switchTab(...); navigator.redirectTo(...); navigator.navigateBack(...); [代码] 模块设计: [图片] 3. 虚拟路由策略 — Router 模块 在这里,我们解决: 对外暴露了真实路由,导致历史包袱沉重的问题。 在许多应用开发中,我们经常需要把某种模式匹配到的所有路由,全都映射到同个页面中去。 例如,我们有一个 Goods 页面,对于所有 ID 各不相同的商品,都要使用这个页面来承载。 [图片] 那么在代码层面上,期望能实现这样的调用方式: [代码]// 创建路由实例 const router = new Router(); // 注册路由 router.register({ path: '/goods/:id', // 虚拟路由 route: '/pages/goods/index', // 真实路由 }); // 跳转到 /pages/goods/index,参数: onLoad(options) 的 options = { id: '123' } router.gotoPage('/goods/123'); // 跳转到 /pages/goods/index,参数: onLoad(options) 的 options = { id: '456' } router.gotoPage('/goods/456'); [代码] Class Router 的核心逻辑是完成: 路由的注册,完成「虚拟路径」和「真实路径」关系的存储。 满足「虚拟路径」到「真实路径」的转换,并且识别「动态路径参数」(dynamic segment)。 路由跳转。 对于「路由的注册」,我们在其内部存储一个 map 就能完成。 而对于「路径的转换」, [代码]vue-router[代码] 有类似的实现,通过其源码发现,内部是使用 path-to-regexp 作为路径匹配引擎,我们可以拿来用之。 然后对于「路由的跳转」,我们可以直接复用上面提到的 Navigator 模块,通过输入真实路径,来完成路由的跳转。 模块设计: [图片] 其中: RouteMatcher:提供动态路由参数匹配功能,内部使用 path-to-regexp 作为路径匹配引擎。 Route: 为每个路径创建路由器,存储每个路由的虚拟路径和真实路由的关系。 Router:整合内部各模块,对外提供统一且优雅的调用方式。 4. 落地中转策略 — LandTransfer 模块 在这里,我们解决: 小程序扫码、公众号链接等场景下的落地页统一。 小程序码,对于无限量API wxacode.getUnlimited ,突破参数32位长度限制。 4.1. 对于要解决的第一个问题:统一的落地页 我们把如:扫小程序码、公众号菜单、公众号文章等方式打开小程序某个页面的路径称为「外部路由」。 根据小程序的设计,暴露给外部的连接是真实的页面路径,如:[代码]/pages/home/index[代码],该设计在实践中存在的弊端:各个落地页分散,后期修改真实文件路径难度大。 在 「中长生命周期」 产品中,随着产品的迭代,我们难免会遇到项目的重构。如果分发出去的都是没经过处理的真实路径的话,我们重构时就会束手束脚,要做很多的兼容操作。因为你不知道,分发出去的小程序二维码, 有多少被打印到实体物料中。 那么,「虚拟路由」+「落地中转」 的策略就显得基本且重要了。 「虚拟路由」的功能,**Router **模块给我们提供了支持了,我们还需要对外提供一个统一的落地页面,让它来完成对内部路由的中转。 基本逻辑: 分发出去的真实路由,指向到唯一的落地页面,如:[代码]$LAND_PAGE: /pages/land-page/index[代码] 由这个落地页面,进行内部路由的重定向转发,通过接收 参数,如:[代码]path=/user&name=jc&age=18[代码] [图片] 在代码层面上,我们希望能实现这样的使用: [代码]// /pages/land-page/index.ts const landTransfer = new LandTransfer(landTransferOptions); Page({ onLoad(options) { landTransfer .run(options) .then(() => {...}) .catch(() => {...}); } }); [代码] 然后针对 TS,我们还可以使用装饰器版本,更加简便: [代码]import { landTransferDecorator } from 'wxapp-router'; Page({ @landTransferDecorator(landTransferOptions) onLoad(options) { // ... }, }); [代码] 4.2. 对于第二个要解决的问题:短链参数 微信小程序主要提供了两个接口去生成小程序码: wxacode.get: 获取小程序码,适用于需要的码数量较少的业务场景。通过该接口生成的小程序码,永久有效,数量限制为 100,000 个 wxacode.getUnlimited: 获取小程序码,适用于需要的码数量极多的业务场景。通过该接口生成的小程序码,永久有效,数量暂无限制。 第一种方式,[代码]wxacode.get[代码] 数量限制为 10w 个,虽然量很大了,绝大多数的小程序可能用不到这个量。 但如果我们运营的是一个中大型电商小程序的话,假如:1w 种商品 x 10 种商品规格,那就会超过这个数量。到时候再进行改造,就困难了。 所以,如果抱着是运营一个 「中长生命周期」 的产品的话,我们会使用第二种方式:[代码]wxacode.getUnlimited[代码] 不尽人意的是,虽然它没有数量限制,但是对参数会有 32 个字符的限制,显然是不够用的(一个 uuid 就 32 字符了)。 对于这种情况,我们可以使用「短链参数」的形式解决,由于wxacode.getUnlimited 会通过 [代码]scene[代码]字段作为 query 参数传递给小程序的,那么我们可以通过 [代码]scene[代码]参数来实现短链服务,这需要后端配合。 前后端交互如下: [图片] 当小程序需要生成小程序码的时候,请求后端提供的接口,例如:[代码]/api/encodeShortParams[代码] 后端把内容转换为 32 字符内的字符串,存储到数据库中。 后端通过 wxacode.getUnlimited 接口,以短链字符串作为 [代码]scene[代码]的值,以商定好的统一落地页 [代码]$LAND_PAGE[代码]作为 [代码]page[代码]值,生成小程序码。 当通过小程序码进入小程序,小程序获取到 [代码]scene[代码]参数,请求后端提供的接口,例如:[代码]/api/decodeShrotParams[代码] 小程序理解内容,跳转到目标页面中去。 而前端对于统一落地页的逻辑处理,我们只需要在第一个问题的基础上,增加一个转换短链参数内容的逻辑就行了: [图片] 代码层面上,我们我们只需要多定义转换短链参数的方式:[代码]convertScenePrams[代码] [代码]// in /pages/land-page/index.js import { landTransferDecorator } from 'wxapp-router'; const landTransferOptions = { // 此处接收 onLoad(options) 中的 options.scene convertSceneParams: (sceneParams) => { return API.convertScene({ sceneParams }).then((content) => { // 假如后端存的是 JSON 字符串,前端decode // 要求 content = { path: '/home', a: 1, b:2 } return JSON.parse(content); }); }, }; Page({ @landTransferDecorator(landTransferOptions) onLoad(options) { // ... }, }); [代码] 而其中的 [代码]API.convertScene[代码] 就对接服务端提供 HTTP 接口服务来完成。 4.3. LandTransfer 模块设计 [图片] 5. 更好的开发体验 5.1. Typescript + Router 对于小程序内部的路由跳转,我们除了指定一个字符串的路由,我们是否也可以通过链式调用,像调用函数那样去跳转页面呢?类似这样; [代码]routes.pages.user.go({ name: 'jc' }); [代码] 这样做的好处是: 更自然的调用方式。 能结合 TS,来做到类型提示和联想。 由于事先 [代码]wxapp-router[代码] 并不知道开发者需要注册的路由是什么样的,所以路由的 TS 声明文件,需要开发者来定义。 例如,我们在项目中维护一份路由文件: [代码]// config/routes.ts // 创建路由实例 const router = new Router(); const routesConfig = [{ path: '/user', route: '/pages/user/index', }, { path: '/goods', route: '/pages/goods/index', }]; type RoutesType { paegs: { user: Route<{name: string}>, goods: Route, } } // 注册路由 router.batchRegister(routesConfig); // 获取 routes const routes: RoutesType = router.getRoutes(); export default routes; [代码] 然后在别的地方使用它: [代码]import routes from './routes.ts'; routes.pages.user.go({ name: 'jc' }); [代码] 5.2. 智能生成路由配置 如果路由变多的时候,我们还需要对每个路由手动去编写 [代码]RoutesType[代码] 的话,就有点难受了。 在小程序中,我们把正式路由都配置到 [代码]app.json[代码] ,那么在遵循既定的项目结构情况下,我们可以通过自动构建,完成大部分工作,例如: 智能注册路由 智能识别页面入参声明 5.3. 自定义组件跳转 以上都是脚本层面的使用,小程序中还有 [代码]wxml[代码], 我们希望能在有个组件快速使用: [代码]<Router path="/pageA" query="{{pageAQuery}}"></Router> <Router path="/pageB" query="{{pageBQuery}}" type="redirectTo"></Router> <Router path="/pageC/katy"></Router> [代码] 那么,实现一个自定义组件,然后把 Router模块包装一下,问题就不大了。 示例代码: [代码]// components/router.wxml <view class="wxapp-router" bind:tap="gotoPage"> <slot /> </view> [代码] [代码]// components/router.ts Component({ properties: { path: String, type: { type: String, value: 'gotoPage' }, route: String, query: Object, delta: Number, setData: Object, }, methods: { gotoPage(event) { const router = getApp().router; const { path, route, type, query} = this.data; const toPath = route || path; if (['gotoPage', 'navigateTo', 'switchTab', 'redirectTo'].includes(type)) { (router as any)[type](toPath, query); } if (type === 'navigateBack') { const { delta, setData } = this.data; router.navigateBack({ delta }, { setData }) } } } }) [代码] 6. 整体架构图 最后,我们来整体回顾一下各模块的设计 [图片] Navigator:封装微信原生路由 API,提供智能跳转策略。 LandTransfer:提供落地页中转策略。 RouteMatcher:提供动态路由参数匹配功能。 Route: 为每个路径创建路由器。 Router:整合内部各模块,对外提供优雅的调用方式。 Logger:内部日志器。 Path-to-regexp: 开源社区的路由匹配引擎。 7. 最后的最后 鉴于写过很多的实战类的文章,会有不少同学想要到整体的示例代码,这次我就索性写了一个工具,Enjoy it! wxapp-router: 🛵 The router for Wechat Miniprogram
2021-03-31 - MiniTest小程序云测插件邀请开发者体验
MiniTest 是一套由微信测试团队自主研发的、为小程序提供自动化测试的服务,可以帮助开发者简单快捷地实现对微信小程序进行UI自动化、性能和Monkey测试 ,后续将陆续开放录制回放和UI自动化测试能力。目前插件特色功能如下: 1. 零代码接入智能Monkey测试 MiniTest为用户提供自动跑查 开发版、体验版、线上版小程序Monkey测试功能, 在跑测同时,平台还会自动检测 黑白屏,JsError,Crash 异常情况。当发现这些异常情况时,测试结果将标记为失败,并在报告中提供相关信息帮助用户排查问题。 普通Monkey测试是采用 随机点击 的方式来测试小程序的稳定性(如发现JsError,黑白屏问题)。MiniTest插件后台采用 自研智能Monkey策略,利用深度学习,智能识别当前可点击元素,大大提升Monkey测试效率。 当用户第一次跑测时Monkey覆盖率可能较低,随着用户跑测次数增加,后台会智能学习历史经验,提升覆盖率 2. 完善的性能分析 在平台跑查任意测试时,会自动获取跑查过程中的性能数据,并和现网的平均性能数据进行对比,方便开发者掌握小程序的性能情况。 [图片] 如何体验 MiniTest云测插件已开始灰度开放,开发者可以下载 最新nightly版本微信开发者工具 安装体验。安装完成后,开发者登录微信开发者工具,打开小程序源码后,在上方导航栏,点击“设置”=>“扩展设置”,打开下图所示的窗口。在“其他插件”中,选择“云测”插件安装。安装完成后,可以通过点击开发者工具右上方“云测”按钮进入MiniTest云测平台开始体验。 如未被灰度的开发者希望提前体验插件,可点击填写 MiniTest小程序云测报名表 申请体验。申请成功后三个工作日内会开通体验资格,开发者也可以通过查看 开通公示文档 查看是否开通。 [图片] 如在体验过程中,有任何反馈及建议,欢迎在微信开放社区发帖 反馈,或识别以下企业微信二维码加入云测官方企业微信群,将有技术专员和大家深度互动交流。 [图片] 识别二维码加入云测官方群 [图片]
2021-11-03