2023年2月

有一小段时间没有持续升级ABP框架了,最近就因应客户的需要,把ABP框架进行全面的更新,由于我们应用的ABP框架,基础部分还是会使用官方的内容,因此升级的时候需要把官方基础ABP的DLL进行全面的更新,以及对应的引用DLL也同步更新才行。不过在升级过程中还是很多奇奇怪怪的问题,本篇随笔针对出现的情况进行一系列的总结,以便后面有一个对照参考吧。

1、最新案例源码和NugGet程序包更新

ABP官方的基础模块更新速度还是很快的,一段时间过去,就跳过了几个版本号,我是在旧版本的基础上进行手动的NugGet更新,但是基于VS的Nugget总是更新卡顿,不知不觉就没有反应了,严重影响开发的效率。因此先从官方下载的Demo案例中把相关部分源码进行更新。

官方的案例源码下载地址是:
https://aspnetboilerplate.com/Templates

1)最新案例源码结构和部分内容调整

我们从其中下载对应的源码,然后根据项目结构中的对应源码文件,使用Beyond Compare对比文件进行文件逐一对比,原则上除了个人扩展的部分,都以官方的源码做法为准即可。

目前ABP官方最新的DLL版本是5.3.0,可以下载的Demo版本是5.2.0,它们应该差别不大。下载下来的Aspnet-core部分的源码结构如下所示。

而我们的ABP框架是在这个基础上进行一定的结构优化,以更加方便快速的开发,以及结合代码生成工具进行快速的使用。

我们的VS项目结构 如下所示。

以上是VS里面解决方案的项目结构,我根据项目之间的关系,整理了一个架构的图形,如下所示。

上图中,其中橘红色部分就是我们为各个层添加的类或者接口,分层上的序号是我们需要逐步处理的内容。

应用服务层是整个ABP框架的灵魂所在,对内协同仓储对象实现数据的处理,对外配合Web.Core、Web.Host项目提供Web API的服务,而Web.Core、Web.Host项目几乎不需要进行修改,因此应用服务层就是一个非常关键的部分,需要考虑对用户登录的验证、接口权限的认证、以及对审计日志的记录处理,以及异常的跟踪和传递,基本上应用服务层就是一个大内总管的角色,重要性不言而喻。

回顾了解一下我们改造过的ABP开发框架的结构后,我们返回到版本升级的主体上来介绍。

目前我把VS的版本升级到最新,其.net framework支持4.8, 并单独安装了dotnetcore最新版本3.1,因此环境是最新的,而基础的ABP 5.3.0也是采用了.net core3.1。

对比源码,我们可以发现,Web.Host和Web.Core项目里面已经有所差异,IHostingEnvironment已经被抛弃使用,而采用dotnetcore最新对象IWebHostEnvironment来替代了。

替换最新源码为

因此Web.Host项目中的Module类也进行了调整。

相对应的Web.Core项目里面的Module也同时进行调整了。

2)NugGet程序包更新

Nugget程序包的更新,原则上可以选择单个项目进行更新,或者选择整个解决方案进行程序包的更新,前者可能相应速度快一些,后者由于解决方案项目数量问题,可能会较慢。

我早期的基础ABP版本是4.9,因此想一次性整个的解决方案的程序包进行更新,不过尝试多次,花了几个小时,都无法顺利进行项目的全部更新,于是单个项目进行更新,也非常慢。

于是也通过推荐采用Nugget最新地址进行更新,如下设置更新源。

在程序包源中添加:
https://api.nuget.org/v3/index.json

响应速度相对快了一些,没有不经常的出问题了。

我们如果需要单独更新某个项目的程序包,那么需要选择项目,选择【管理Nugget程序包】进入界面更新即可。如下界面所示。

一般情况下,我们推荐对整个解决方案进行全面的程序包更新,如下选择解决方案,然后进入对应Nugget程序包管理界面更新即可。

这样的全部更新解决方案的程序包,如果能够顺利完成,那是皆大欢喜,不过可能会稍微慢一些。如果不行,只有逐个更新程序包了。

我之前选择这样的方式更新的时候,总是有一两个程序包更新出错,因此只有使用npm 控制台进行单独的升级了。

2、ABP框架基类封装接口命名调整

在更新ABP基础模块的时候,发现ABP的基础接口全部调整了命名,如原来的Get变为GetAsync,GetAll变为 GetAllAsync,Delete变为了DeleteAsync,Update变为了UpdateAsync,Create变为了CreateAsync。

也就是说,他们全部采用了异步名称的命名规则,在异步方法后面全部加上了Async作为标识。

我在之前模块介绍过ABP框架的基础接口。IAsyncCrudAppService定义了几个通用的创建、更新、删除、获取单个对象和获取所有对象列表的接口,接口定义如下所示。

namespaceAbp.Application.Services
{
public interface IAsyncCrudAppService<TEntityDto, TPrimaryKey, in TGetAllInput, in TCreateInput, in TUpdateInput, in TGetInput, in TDeleteInput>: IApplicationService, ITransientDependencywhere TEntityDto : IEntityDto<TPrimaryKey> where TUpdateInput : IEntityDto<TPrimaryKey> where TGetInput : IEntityDto<TPrimaryKey> where TDeleteInput : IEntityDto<TPrimaryKey>{
Task
<TEntityDto>Create(TCreateInput input);
Task Delete(TDeleteInput input);
Task
<TEntityDto>Get(TGetInput input);
Task
<PagedResultDto<TEntityDto>>GetAll(TGetAllInput input);
Task
<TEntityDto>Update(TUpdateInput input);
}
}

现在这些接口全部调整如下所示了。

namespaceAbp.Application.Services
{
public interface IAsyncCrudAppService<TEntityDto, TPrimaryKey, in TGetAllInput, in TCreateInput, in TUpdateInput, in TGetInput, in TDeleteInput>: IApplicationService, ITransientDependencywhere TEntityDto : IEntityDto<TPrimaryKey> where TUpdateInput : IEntityDto<TPrimaryKey> where TGetInput : IEntityDto<TPrimaryKey> where TDeleteInput : IEntityDto<TPrimaryKey>{
Task
<TEntityDto>CreateAsync(TCreateInput input);
Task DeleteAsync(TDeleteInput input);
Task
<PagedResultDto<TEntityDto>>GetAllAsync(TGetAllInput input);
Task
<TEntityDto>GetAsync(TGetInput input);
Task
<TEntityDto>UpdateAsync(TUpdateInput input);
}
}

那么这些我们都必须随着ABP框架的调整也同时进行接口和对应类实现的调整了。

例如对应的异步ApplicationService服务的基类封装,我们也需要调整对应的异步接口实现。

针对远程调用的ApiCaller接口和实现,我们也需要进行命名方面的统一调整,这样才能顺利进行异步接口的调用

我在之前随笔《
ABP开发框架前后端开发系列---(10)Web API调用类的简化处理
》有针对API调用层的简化处理做了说明,我们所有的API调用,基本都是通过统一的一个函数进行调用的。

统一调用处理的方法名称是DoActionAsync。

虽然由于前面介绍了应用服务层的接口很多接口增加了Async的后缀字符,但是客户端通过URL调用,这个Async是不需要的,也就是需要移除,我们之前组装的URL地址是根据函数名称,那么函数名称需要移除这个后缀的Async字样了。

这样系统就顺利跑起来了。服务端界面如下所示。

我们启动Winform客户端,界面如下所示。

登陆启动后主体界面如下所示

在微信开发H5页面的时候,往往借助于WeUI或者Jquery WeUI等基础上进行界面效果的开发,由于本人喜欢在Asp.net的Web界面上使用JQuery,因此比较倾向于使用 jQuery WeUI,本篇随笔结合官方案例和自己的项目实际开发过程的经验总结,对在H5页面开发过程中设计到的界面控件进行逐一的分析和总结,以期能够给大家在H5页面开发过程中提供有用的参考。

1、资源参考

WeUI :
https://github.com/Tencent/weui

WeUI for work:
WeUI for Work (企业微信版)

jQuery WeUI:
http://jqweui.com/

一般情况下,我们使用后者Jquery WeUI,在结合一些用于页面的图标和H5页面模板就可以快速开发相关的页面效果了。



2、界面控件的使用

1)文本控件的使用

文本控件是我们实际项目使用最多的界面控件了,文本控件一般包括常规的Input单文本框类型和TextArea多文本框类型。

文本框的录入,一般可以把提示标题放在左侧,如有后缀信息放在右侧,如下所示。

它的定义HTML代码,主要就是设置好布局和样式即可。

<inputid="Title"name="Title"class="weui-input"type="text"placeholder="流程标题"required>

完整加上布局的HTML代码如下所示。

<divclass="weui-cells weui-cells_form">
    <divclass="weui-cell">
        <divclass="weui-cell__hd"><labelclass="weui-label">流程标题</label></div>
        <divclass="weui-cell__bd">
            <inputid="Title"name="Title"class="weui-input"type="text"placeholder="流程标题"required>
        </div>
    </div>
    <divclass="weui-cell">
        <divclass="weui-cell__hd"><labelclass="weui-label">付款事由</label></div>
        <divclass="weui-cell__bd">
            <inputid="Reason"name="Reason"class="weui-input"type="text"placeholder="付款事由"required>
        </div>
    </div>
    <divclass="weui-cell weui-cell_vcode">
        <divclass="weui-cell__hd"><labelclass="weui-label">付款金额</label></div>
        <divclass="weui-cell__bd">
            <inputid="PayAmount"name="PayAmount"type="number"class="weui-input"placeholder="付款金额"required/>
        </div>
        <divclass="weui-cell__ft"><spanclass="weui-vcode-btn"></span></div>
    </div>

其中 weui-cells 是定义单元块的开始,而且 weui-cell 则是一行的开始,而weui-cell__ft 样式则是定义输入信息的后缀部分,如元、岁等单位可以放到后面。其他控件的布局也是遵循这样的方式处理。

使用控件最基本,也是最重要的就是如何通过JS获取和设置对应控件的值了。

文本框是比较简单的,也遵循通用的做法,获取值的JS代码如下所示:

var Title = $("#Title").val();

如果我们需要判断值是否为空,来在保存数据之前提示用户,那么可以利用如下JS代码。

            var Title = $("#Title").val();if (!Title || Title == '') {
$.toast(
'表单标题不能为空', "forbidden");
$(
"#Title").focus();return false;
}

我们如果需要统一对相关控件进行校验,可以编写一个函数来进行统一的校验提示处理。

        //检查界面的输入是否符合要求
        functionCheckData() {var Title = $("#Title").val();if (!Title || Title == '') {
$.toast(
'表单标题不能为空', "forbidden");
$(
"#Title").focus();return false;
}
var Reason = $("#Reason").val();if (!Reason || Reason == '') {
$.toast(
'付款事由不能为空', "forbidden");
$(
"#Reason").focus();return false;
}
var PayAmount = $("#PayAmount").val();if (!PayAmount || PayAmount == '') {
$.toast(
'付款金额不能为空', "forbidden");
$(
"#PayAmount").focus();return false;
}
return true;
}
//保存表单 functionSave() {if (!CheckData()) {return;
}
var flowUser = $("#FlowUser").val();if (!flowUser || flowUser == '') {
$.toast(
'请选定流程处理人', "forbidden");
$(
"#FlowUser").focus();return;
}
//提交信息和上传图片 submitCallback();
}

如果我们需要通过JS进行控件的赋值如下所示。

$("#Title").val(info.Title);//申请单标题

如果我们可以通过Ajax获取对应数据后,把数据赋值到控件显示在界面上的操作如下所示。

//如果是从已有申请单过来,则加载信息
$.getJSON("/Qyh5Flow/FindApplyById?id=" + applyid, function(info) {if (info != null) {
$(
"#Title").val(info.Title);//申请单标题 $("#Apply_ID").val(info.ID);//修改为已有ID }
});

这个就是我们常规对控件的取值和赋值操作,大多数控件处理和这个类似,因此本小节会着重介绍常规的处理方式。

2、多文本框控件和
文本框计数处理

对于多文本控件,一般在界面上,我们通过如下代码定义。和常规单文本的样式不同,这里使用 weui-textarea 样式。

<textareaid="Note"name="Note"class="weui-textarea"placeholder="备注信息"rows="3"style="height:100px"></textarea>

文本框的赋值、取值和普通单文本控件一样,如下所示。

var note = $("#Note").val(); //获取文本框的值

或者赋值处理

$("#Note").val(info.Note);//对多文本控件进行赋值

对于多文本框控件,我们一般会在文本框的基础上增加一个字符计数的处理,如下界面所示。

这部分的HTML定义代码如下所示。

<divclass="weui-cells__title">备注信息</div>
<divclass="weui-cells">
    <divclass="weui-cell">
        <divclass="weui-cell__bd">
            <textareaid="Note"name="Note"class="weui-textarea"placeholder="备注信息"rows="3"style="height:100px"></textarea>
            <divclass="weui-textarea-counter"><span>0</span>/250</div>
        </div>
    </div>
</div>

我们注意,在其下面增加了一个样式为 weui-textarea-counter 的层,这个我们没有定义任何ID,我们准备通过这个样式来定位并显示多文本框的字符信息的。

这个weui-textarea-counter DIV层是在文本框控件的父DIV层下的一个 class="weui-textarea-counter" 的DIV,因此我们可以通过DOM的查询查找进行处理,避免通过ID定位的方式。

在页面初始化的时候,我们绑定一个对这个文本框的 input propertychange 事件,以便在文本框内容变化的时候,实时进行文本框的计数处理,如下JS代码所示。

//绑定字数动态显示
$('#Note').bind('input propertychange', function() {var fizeNum = $(this).val().length;//获取输入字数
    if (fizeNum > 250) {//如果大于250
        var char = $(this).val();char = char.substr(0, 250);//截取字符串(前200)
        $(this).val(char);
fizeNum
= 250;
}
var countText = `${fizeNum}/250`; $(this).parent().find('.weui-textarea-counter').html(countText);
});

文本框内容变化的时候,我们通过$(this).parent().find('.weui-textarea-counter') 这样的DOM元素查找的方式就可以找到对应的文本框计数的层,然后设置它的HTML或者文本进行显示即可。

对于通过样式来定位某个控件,是我们常规一种较为灵活的做法,后面也有很多这样的处理。

如我们需要寻找某个DIV层下面的某个对应样式的元素,我们也可以用同样的方式,如下代码所示。

$("#DispatchReadAction .promotion-sku .sku-default").addClass("active");
$(
"#DispatchReadAction .promotion-sku li").unbind("click");//解除上次的绑定操作 $("#DispatchReadAction .promotion-sku li").click(function() {//单击事件处理代码........... });

3)下拉列表控件(单选和多选)

下拉列表控件,一般用于绑定多个选项,以便从中选择一个或者多个值。这些列表值,有固定的,也有动态从数据库里面获取的,动态的一般是常规的处理。

单选和多选的下拉列表的处理都差不多,只是设置属性不同罢了。

我们先来看看默认的单选的下拉列表控件的效果,官方提供的案例如下所示。

初始化界面的HTML和常规的文本框样式一样。

      <divclass="weui-cell">
        <divclass="weui-cell__hd"><labelfor="name"class="weui-label">职业</label></div>
        <divclass="weui-cell__bd">
          <inputclass="weui-input"id="job"type="text"value="">
        </div>
      </div>

不过JS代码就不一样,JS初始化下拉列表的代码如下所示。

$("#job").select({
title:
"选择职业",
items: [
"法官", "医生", "猎人", "学生", "记者", "其他"]
});

以上是固定的列表,一般情况下,我们需要根据数据库信息,动态进行绑定下拉列表的。

例如对于付款方式,如果我们使用固定列表的方式,那么JS初始化代码如下所示。

var payType = ["银行卡", "现金", "微信"];
$(
"#PayType").select({
title:
"选择支付方式",
items: payType,
//onChange: onChangeDept, //onClose: onClose, //onOpen: onOpen });

如果是采用数据库字典的方式,我们可以通过一个自定义函数进行绑定即可。

BindDictItem("PayType", "付款方式");//绑定字典

这个BindDictItem是我们定义的一个通用的字典获取方式,通过字典类型绑定下拉列表的值,这里主要使用了Ajax方式请求数据,然后遍历数据进行添加到集合里面,进行界面控件的绑定即可。

如果我们想更灵活的处理,也可以自定义一个函数进行处理,如下代码所示,一样的效果。

//选择流程处理人
var flowUsers =[];var url = '/qyh5flow/GetCorpUser';
$.getJSON(url,
function(data) {
$.each(data,
function(i, item) {
flowUsers.push({
title: item.name, value: item.userid
});
});
$(
"#FlowUser").select({
title:
"选择流程处理人",
items: flowUsers,
});
});

我们注意到,绑定的列表里面,每项都有一个title和value的定义,这个是文本和值的两个属性,我们一般在界面上显示的是文本属性,如果需要获取后面的值(一般为某个内容的ID),那么我们需要如何处理才能获取到呢。

如果我们通过常规的获取内容方式获取,如下所示。

var flowUser = $("#FlowUser").val(); //获取列表的文本

那么你会发现获取的是列表的文本值,而不是我们需要获取到的ID值,那么如果我们需要获取ID值,我们可以用以下属性获取即可。

var flowuserId = $("#FlowUser").attr("data-values");//获取列表的值

默认情况下,列表的值是单选的,如果我们需要设置多选,只需要初始化列表控件的时候,指定为multi为true即可。

例如,我们在一个流程中,如果需要会签,那么需要选定多个会签的流程处理人,如下所示。

那么就需要设置可以多选人员了

//流程用户
var flowUsers =[];var url = '/qyh5flow/GetCorpUser';
$.getJSON(url,
function(data) {
$.each(data,
function(i, item) {
flowUsers.push({
title: item.name, value: item.userid
});
});
$(
"#txtSelectSignUser").select({
title:
"选择会签人员",
items: flowUsers,
multi:
true,//min: 2, //max: 3, });
});

4)日期控件

日期控件也是非常常见的一个信息录入的控件,一般我们处理某些表单都需要默认一个日期,可以让用户进行修改设置的。

JQuery WeUI提供了一个Calender和datetimePicker两个不同的控件,两者针对不同的场合,不过一般来说,我倾向于使用datetimePicker,这个选择日期起来更加方便。

我们先来看看Calender的日期控件。

它的定义和常规的处理差不多,如HTML代码如下所示。

      <divclass="weui-cell">
        <divclass="weui-cell__hd"><labelfor="date"class="weui-label">日期</label></div>
        <divclass="weui-cell__bd">
          <inputclass="weui-input"id="date"type="text">
        </div>
      </div>

它的JS初始化代码如下所示。

      $("#date").calendar({
onChange:
function(p, values, displayValues) {
console.log(values, displayValues);
}
});
var currentDate = '2020-03-22';
console.log(currentDate);

$(
"#date2").calendar({
value: [currentDate],
dateFormat:
'yyyy年mm月dd日' //自定义格式的时候,不能通过 input 的value属性赋值 '2016年12月12日' 来定义初始值,这样会导致无法解析初始值而报错。只能通过js中设置 value 的形式来赋值,并且需要按标准形式赋值(yyyy-mm-dd 或者时间戳) });
$(
"#date2").val(currentDate);

而对于datetimePicker控件,这也是提供了多种显示的效果。

例如对于我们常用的审批单,有时候也需要选择默认当前的日期,让用户进行修改调整的。

datetimePicker控件的赋值操作和Calender不太一样,除了设置通过val()函数来设置值,还需要设置各个列的值,否则显示不正常的,datetimePicker控件控件说到底还是一个picker的选择列表控件。

例如对于上面界面的付款日期,我们的初始化代码如下所示。

//付款日期设置
$("#PayDate").datetimePicker({
title:
'付款日期',
times:
function () { return[]; },
parse:
function(str) {return str.split("-");
},
onChange:
function(picker, values, displayValues) {
console.log(values);
}
});
var payDate = GetCurrentDate();//获取当前日期 $("#PayDate").val(payDate);//设置显示值//拆开日期年月日进行设置日期的各个列的显示 var b = payDate.split("-");
$(
"#PayDate").picker("setValue", [b[0], b[1], b[2]]);

其中 GetCurrentDate 函数是我们常用来获取当然日期时间的一个函数,贴出来供参考吧。

//获取日期获取日期+时间的字符串
functionGetCurrentDate(hasTime) {var d = newDate();var year =d.getFullYear();var month = change(d.getMonth() + 1);var day =change(d.getDate());var hour =change(d.getHours());var minute =change(d.getMinutes());var second =change(d.getSeconds());functionchange(t) {if (t < 10) {return "0" +t;
}
else{returnt;
}
}
var time = year + '-' + month + '-' +day;if(hasTime) {
time
+= ' ' + hour + ':' + minute + ':' +second;
}
returntime;
}

5)
省市区级联处理

省市区级联操作,严格意义上来说也是一个类似日期datetimePicker控件的选择列表,也是提供了多列操作。

地址选择器需要引入额外的JS文件:

<scripttype="text/javascript"src="js/city-picker.js"charset="utf-8"></script>

省市区的地址选择控件,可以通过设置属性
showDistrict 来开启是否显示区的列。

HTML代码定义代码和其他控件类似。

<divclass="weui-cell">
    <divclass="weui-cell__hd"><labelclass="weui-label">籍贯</label></div>
    <divclass="weui-cell__bd">
        <inputclass="weui-input"id="NativePlace"type="text"placeholder="籍贯">
    </div>
</div>

JS的初始化代码如下所示。

$("#NativePlace").cityPicker({
title:
"选择籍贯",
showDistrict:
false,
onChange:
function(picker, values, displayValues) {//console.log(values, displayValues); }
});

6)
单选框和复选框

单选框和复选框都是常规的选择出来,在WeUI里面也定义了这两个控件的基本样式,一般情况下,参考使用即可。

以上定义代码如下所示。

    <divclass="weui-cells__title">单选列表项</div>
    <divclass="weui-cells weui-cells_radio">
      <labelclass="weui-cell weui-check__label"for="x11">
        <divclass="weui-cell__bd">
          <p>cell standard</p>
        </div>
        <divclass="weui-cell__ft">
          <inputtype="radio"class="weui-check"name="radio1"id="x11">
          <spanclass="weui-icon-checked"></span>
        </div>
      </label>
      <labelclass="weui-cell weui-check__label"for="x12">

        <divclass="weui-cell__bd">
          <p>cell standard</p>
        </div>
        <divclass="weui-cell__ft">
          <inputtype="radio"name="radio1"class="weui-check"id="x12"checked="checked">
          <spanclass="weui-icon-checked"></span>
        </div>
      </label>
      <ahref="javascript:void(0);"class="weui-cell weui-cell_link">
        <divclass="weui-cell__bd">添加更多</div>
      </a>
    </div>
    <divclass="weui-cells__title">复选列表项</div>
    <divclass="weui-cells weui-cells_checkbox">
      <labelclass="weui-cell weui-check__label"for="s11">
        <divclass="weui-cell__hd">
          <inputtype="checkbox"class="weui-check"name="checkbox1"id="s11"checked="checked">
          <iclass="weui-icon-checked"></i>
        </div>
        <divclass="weui-cell__bd">
          <p>standard is dealt for u.</p>
        </div>
      </label>
      <labelclass="weui-cell weui-check__label"for="s12">
        <divclass="weui-cell__hd">
          <inputtype="checkbox"name="checkbox1"class="weui-check"id="s12">
          <iclass="weui-icon-checked"></i>
        </div>
        <divclass="weui-cell__bd">
          <p>standard is dealicient for u.</p>
        </div>
      </label>
      <ahref="javascript:void(0);"class="weui-cell weui-cell_link">
        <divclass="weui-cell__bd">添加更多</div>
      </a>
    </div>

单选框和复选框,主要就是在块上定义的样式不同,weui-cells_radio 和 weui-cells_checkbox的差异。

或者使用类似开关的界面效果。

这个定义代码如下所示。

    <divclass="weui-cells__title">开关</div>
    <divclass="weui-cells weui-cells_form">
      <divclass="weui-cell weui-cell_switch">
        <divclass="weui-cell__bd">标题文字</div>
        <divclass="weui-cell__ft">
          <inputclass="weui-switch"type="checkbox">
        </div>
      </div>
      <divclass="weui-cell weui-cell_switch">
        <divclass="weui-cell__bd">兼容IE Edge的版本</div>
        <divclass="weui-cell__ft">
          <labelfor="switchCP"class="weui-switch-cp">
            <inputid="switchCP"class="weui-switch-cp__input"type="checkbox"checked="checked">
            <divclass="weui-switch-cp__box"></div>
          </label>
        </div>
      </div>
    </div>

这个样式weui-cell_switch 和weui-switch也就是定义开关的样式的。

除了上面标准的这两个,我们在购物或者流程申请单需要选择选项的时候,都涉及到多个选择选择其一的情景,WeUI里面的单选框占用界面太多内容,不是很理想,应该采用一种更为紧凑的方式进行选择。

类似下面的效果,才是我们实际经常使用到的选择场景。

仿照这个,我们在流程申请单的时候,选择审批意见的时候,会更加美观。

那么以上的代码应该如何处理呢,我们先看看HTML定义代码

<divclass="wy-media-box2 weui-media-box_text"style="margin:0;">
    <divclass="weui-media-box_appmsg">
        <divclass="wy-media-box__title"><spanclass="promotion-label-tit">【审批意见】</span></div>
        <divclass="weui-media-box__bd">
            <divclass="promotion-sku clear">
                <ul>
                    <liclass="sku-default"><ahref="javascript:;">批准申请</a></li>
                    <li><ahref="javascript:;">退回拟稿人重新处理</a></li>
                    <li><ahref="javascript:;">退回上一步处理</a></li>
                </ul>
            </div>
        </div>
    </div>
</div>

这里我们为了操作灵活,采用了样式定义的方式进行处理,这个我在多文本的计数器的时候就介绍过,在这个样式单击的时候,我们进行状态的切换即可实现。

我们看看对应 li 元素单击的时候,触发的JS代码如下所示(由于我们是在DIV层弹出的时候才处理,避免重复绑定,使用前需解除绑定事件:unbind("click") )。

//选项变化
$("#DispatchReadAction .promotion-sku .sku-default").addClass("active");
$(
"#DispatchReadAction .promotion-sku li").unbind("click");//解除上次的绑定操作 $("#DispatchReadAction .promotion-sku li").click(function() {
$(
this).addClass("active").siblings("li").removeClass("active");var action = $(this).text();
console.log(action);
//测试 //console.log($("#DispatchSignAction .promotion-sku .active").text());//测试选中值 //................ });

其中 #DispatchReadAction 是DIV层的定位方式 .promotion-sku .sku-default 和 .promotion-sku li 是样式的过滤选择,以方便选中对应的元素进行操作;而siblings 的操作是遍历处理,类似$.each的处理方式。

这个操作,主要就是移除所有选中的(样式active)的状态,然后重新设置当前选中的元素为选中(样式active)。

这个active样式的选中颜色、以及效果,我们通过样式进行设置好即可。

这样就可以实现之前说的效果了

而我们如果需要获取选中的选项的内容,那么通过样式的选择器就可以获得的了

var action = $("#DispatchSignAction .promotion-sku .active").text();

6)
图片上传和预览

在实际项目上传表单的时候,往往都需要使用附件上传,一般情况下是上传图片文件较多,而图片往往也需要预览进行管理,如果是维护已有记录的编辑界面,还应该可以实现对已有图片的删除操作。

那么就这里涉及到几个问题:图片上传、图片预览、图片删除的几个操作。

例如我们在流程表单里面,需要上传附件图片的界面如下所示。

单击+符号,可以继续上传多个图片,而选择图片就马上进行本地的图片缩略图预览操作,而单击图片缩略图可以实现图片的大图预览,如下所示。

下角有垃圾箱的图片,是用来删除图片的(如果已经上传的,需要删除数据库,如果是刚添加的,则移除本地集合即可)。

那么这样的处理效果,是如何实现的呢,这些同样分为界面的定义和JS代码的处理操作,上传文件还涉及到服务端后台代码对附件的保存处理操作。

图片预览层我们在HTML放入如下定义。

    <!--图片预览层-->
    <divclass="weui-gallery"id="gallery">
        <spanclass="weui-gallery__img"id="galleryImg"style="width:auto"></span>
        <divclass="weui-gallery__opr">
            <ahref="javascript:"class="weui-gallery__del">
                <iclass="weui-icon-delete weui-icon_gallery-delete"></i>
            </a>
        </div>
    </div>

常规的图片附件界面HTML代码如下所示。

<divclass="weui-cells__title">附件</div>
<divclass="weui-cells weui-cells_form">
    <divclass="weui-cell">
        <divclass="weui-cell__bd weui-cell_primary">
            <divclass="weui-uploader">
                <!--预览图片的列表-->
                <ulclass="weui-uploader__files"id="imgAttach">
                </ul>
                <divclass="weui-uploader__input-box">
                    <!--上传图片附件控件-->
                    <inputid="uploaderAttach"class="weui-uploader__input"type="file"accept="image/*"multiple="">
                </div>
            </div>
        </div>
    </div>
</div>

我们在上传图片的附近,放置了一个ul的界面元素,用来承载图片文件缩略图显示的,定义ID为 imgAttach。

下面的JS代码就是常规的图片上传展示缩略图、单击缩略图预览图片、删除图片处理操作等内容,一般情况下,我们直接复制到HTML界面里面即可。

    <script>$(function() {//初始化
initImage();//初始化图片删除操作
});//存放文件图片的集合
        varfileAttach= newArray();functioninitImage() {//预览缩略图模板
            vartmpl= '<li class="weui-uploader__file" style="background-image:url(#url#)"></li>',
$gallery
=$("#gallery"),
$galleryImg
=$("#galleryImg"),

$uploaderAttach
=$("#uploaderAttach"),
$imgAttach
=$("#imgAttach");//图片上传,加入缩略图标 $uploaderAttach.on("change",function(e) {varsrc, url=window.URL||window.webkitURL||window.mozURL,
files
=e.target.files;for(vari= 0, len=files.length; i<len;++i) {varfile=files[i];
fileAttach.push(file);
//加入集合 if(url) {
src
=url.createObjectURL(file);
}
else{
src
=e.target.result;
}
$imgAttach.append($(tmpl.replace(
'#url#', src)));
}
});
varindex;//第几张图片 varcategory;//那个类别 varimgid;//图片ID //图片缩略图元素单击,展示图片预览视图 $imgAttach.on("click","li",function() {
index
=$(this).index();
category
= "payment";
imgid
=$(this).attr("id");
$galleryImg.attr(
"style",this.getAttribute("style"));
$gallery.fadeIn(
100);
});
//预览界面单击退出 $gallery.on("click",function() {
$gallery.fadeOut(
100);
});
//删除图片(根据类别和序号处理) $(".weui-gallery__del").click(function() {
console.log(index
+ ',' +category+ ',' +imgid);//记录显示 //如果是在服务端的图片,确认后移除 if(imgid!=undefined&&imgid!= '') {
$.confirm(
"您确定要永久删除该图片吗?","永久删除?",function() {varurl= "/QYH5/DeleteAttachment";varpostData={
id: imgid.replace(
/img_/,'')//控件id去掉前缀为真正附件ID };

$.post(url, postData,
function(json) {//转义JSON为对象 vardata=$.parseJSON(json);if(data.Success) {
$.toptip(
"删除成功!",'success');//在界面上找到对应控件ID,移除控件 RemoveImg();
}
else{
$.toast(
"操作失败:" +data.ErrorMessage,"forbidden");
}
});
});
}
else{
RemoveImg();
//普通图片快速移除 };
});
//移除对应类别的图片 functionRemoveImg() {if(category== "payment") {
$imgAttach.find(
"li").eq(index).remove();
fileSick.splice(index,
1);
}
else{
console.log(
"没有对应类型");//$imgPres.find("li").eq(index).remove(); //filePres.splice(index, 1); }
};
};
</script>

而上面代码主要处理新增内容的情况下的图片处理,一般情况下,还需要包括编辑现有记录的情况下图片的预览情况,对于现有的图片列表,我们从数据库里面列出,然后展示在缩略图即可。

如下JS代码就是处理编辑情况下的图片缩略图绑定,主要注意,我们这里定义了一个id的规则,和附件在后台数据库里面对应的ID保持一定的规则即可。

//动态处理图片绑定
$.getJSON("/Qyh5Flow/GetImageList2?attachGuid=" + info.AttachGUID, function(data) {
$.each(data,
function(i, item) {
$(
"#imgAttach").append(`<img class='weui-uploader__file' src='${item.Text}' id='img_${item.Value}'/>`); });
});

这个ID,在删除后台附件的时候,我们需要解析出对应的ID,如下是对ID的处理。

var postData ={
id: imgid.replace(
/img_/, '') //控件id去掉前缀为真正附件ID };

我们的图片附件,一般情况下随着表单其他内容一并提交即可,但是由于常规的文本内容和附件内容一并处理,那么我们需要引入FormData来存储复杂对象,从而可以在后台能够接收到各种参数和附件文件信息。

//提交信息和上传图片
functionsubmitCallback() {var formData = new FormData();//构建一个FormData存储复杂对象
    formData.append("ID", $("#ID").val());
formData.append(
"Reason", $("#Reason").val());//.........省略其他内容 //加入附件图片 for (var i = 0; i < fileAttach.length; i++){
formData.append(
"attach_" +i, fileAttach[i]);
};

其中的 fileAttach 的文件集合就是我们加入的文件集合。

有了这些内容,我们就统一处理保存所有的界面录入文本和附件信息了。

//提交表单数据和文件
var url = "/qyH5Flow/PaymentSave?userid=@ViewBag.userid";
$.ajax({
url: url,
type:
'post',
processData:
false,
contentType:
false,
data: formData,
success:
function(json) {//转义JSON为对象 var data =$.parseJSON(json);if(data.Success) {
$.toast(
"保存成功,稍后请到列表界面查看。");
}
else{
$.toast(
"保存失败:" + data.ErrorMessage, "forbidden");
}
}
});

由于使用了混合复杂对象的FormData,我们异步提交的时候,需要设置 processData: false 来处理。

以上就是前端HTML+JS的界面代码部分,要完成附件的上传,还需要后台代码进行附件的处理。

附件保存我们统一采用一个独立的函数处理。

其实后台就是只需要通过Request.Files 的对象即可获得,然后通过我们数据库和文件的处理,就可以实现附件的上传处理了,详细的我们这里就不再赘述。

由于篇幅原因,我们下一篇随笔继续介绍微信开发H5页面控件的各种操作。

在微信开发H5页面的时候,往往借助于WeUI或者Jquery WeUI等基础上进行界面效果的开发,由于本人喜欢在Asp.net的Web界面上使用JQuery,因此比较倾向于使用 jQuery WeUI,本篇随笔结合官方案例和自己的项目实际开发过程的经验总结,对在H5页面开发过程中设计到的界面控件进行逐一的分析和总结,以期能够给大家在H5页面开发过程中提供有用的参考。

本篇随笔继续上篇随笔《
基于Jquery WeUI的微信开发H5页面控件的经验总结(1)
》进行介绍其他部分的内容。

7)
条码、二维码生成

在我们做一些扫码操作的时候,我们可能需要根据一些参数生成一些URL,然后生成一个二维码的方式,方便手机扫码直接查看,或者给一些条码设备进行条码的读取,那么这两种情况结合起来就是二维码和条码的处理场景。

例如下面的处方信息展示里面,就需要这样的场景。

二维码QRCode使用的地址是:
https://github.com/davidshimjs/qrcodejs

条码JsBarcode使用的地址是:
https://github.com/lindell/JsBarcode

使用前,我们引入所需要的qrcodejs和JsBarcode的JS库文件。

<scriptsrc="~/Content/JQueryTools/qrcodejs/qrcode.min.js"></script>
<scriptsrc="~/Content/JQueryTools/JsBarcode/dist/JsBarcode.all.min.js"></script>

二维码和条码的处理脚本很简单,如下JS代码所示。

//条码
JsBarcode("#barcode", "@ViewBag.Info.PrescriptionNo", {
format:
"CODE128",
lineColor:
"#0aa",
height:
50,
displayValue:
false});//二维码 var url = '@ViewBag.WebsiteDomain/h5/PrescriptionDetail?id=@ViewBag.Info.ID';var qrcode = new QRCode(document.getElementById("qrcode"), {
text: url,
//"@ViewBag.Info.PrescriptionNo", width: 128,
height:
128,
colorDark :
"#000000",
colorLight :
"#ffffff",
correctLevel : QRCode.CorrectLevel.H
});

使用脚本处理的方式非常不错。

8)
JQuery的Ajax/Post/Get等相关处理

在我们前面很多案例代码里面,都采用了JQuery的Ajax/Post/Get/getJSON等函数,它们之间很多时候可以相互替代,差异只是很少的部分。

$.get()方法使用GET方式来进行异步请求,它的语法结构为:$.get( url [, data] [, callback] )。如下代码所示。

$.getJSON()是专门为ajax获取json数据而设置的,并且
支持跨域调用
,其语法的格式为:getJSON(url,[data],[callback])。

var flowUsers =[];var url = '/qyh5flow/GetCorpUser';
$.getJSON(url,
function(data) {
$.each(data,
function(i, item) {
flowUsers.push({
title: item.name, value: item.userid
});
});
$(
"#txtSelectSignUser").select({
title:
"选择会签人员",
items: flowUsers,
multi:
true,//min: 2, //max: 3, });
});

$.get()和$.getJSON()两种方法,后者比前者多了一个对返回数据进行JSON转换对象的处理,$.get()方法需要使用下面代码进行JSON的转换。

 var data = $.parseJSON(json);

$.post()方法使用POST方式来进行异步请求,它的语法结构为:$.post(url,[data],[callback],[type]),这个相对于$.get()的操作,使用POST方式提交。

其中的type:
type为请求的数据类型
,可以是html,xml,json等类型,如果我们设置这个参数为:json,那么返回的格式则是json格式的,如果没有设置,就和$.get()返回的格式一样,都是字符串的。

如下代码是我们使用$.post()的函数进行数据的提交,注意我们也需要使用$.parseJSON进行返回值JSON转换对象的处理。

$.post(url, postData, function(json) {//转义JSON为对象
    var data = $.parseJSON(json);if(data.Success) {
$.toptip(
"删除成功!", 'success');//在界面上找到对应控件ID,移除控件 RemoveImg();
}
else{
$.toast(
"操作失败:" + data.ErrorMessage, "forbidden");
}
});

$.ajax()是jquery中通用的一个ajax封装,其语法的格式为:$.ajax(options),其中options是一个object类型,它指明了本次ajax调用的具体参数。

一般在使用FormData对象的时候,我倾向于使用$.ajax进行处理,以便更多的弹性化设置。

//提交表单数据和文件
var url = "/qyH5Flow/PaymentSave?userid=@ViewBag.userid";
$.ajax({
url: url,
type:
'post',
processData:
false,
contentType:
false,
data: formData,
success:
function(json) {//转义JSON为对象 var data =$.parseJSON(json);if(data.Success) {
$.toast(
"保存成功,稍后请到列表界面查看。");
}
else{
$.toast(
"保存失败:" + data.ErrorMessage, "forbidden");
}
}
});

以上就我进行在案例里面使用的各种操作,有时候可以相互替换,根据需要选择不同的操作方式即可。

9)JS脚本的数组对象处理

上面我们使用了各种异步的操作,如JQuery的Ajax/Post/Get/getJSON等函数操作,经常会涉及对数组的遍历处理或者插入处理。

JS数组是一个很的强大的数组对象,支持很多复杂的操作,对数组的操作也是我们经常处理的方式之一。

例如对于返回的数据,我们一般需要进行数组的遍历,然后逐一追加到界面显示,这里就需要使用$.each()函数。

//动态处理图片绑定
$.getJSON("/Qyh5Flow/GetImageList2?attachGuid=" + info.AttachGUID, function(data) {
$.each(data,
function(i, item) {
$(
"#imgAttach").append(`<img class='weui-uploader__file' src='${item.Text}' id='img_${item.Value}'/>`); });
});

$.each()函数另一个经常使用的场景就是根据JQuery选择器进行元素集合的遍历处理。

var itemCount = 0;//计算单选框的选中数量
$("input[type='radio']:checked").each(function(){
itemCount
+= 1;
});
//复选框选中算一个 var chkName =[]
$(
"input[type='checkbox']:checked").each(function() {var name = $(this).attr('name');if (chkName.indexOf(name) < 0) {
itemCount
+= 1;
chkName.push(name);
}
});

我们在需要加入对象的时候,就需要使用push()函数操作了,有时候往往和$.each()函数配合使用。

var items =[];
$.each(data,
function(i, item) {
items.push({
title: item.Text, value: item.Value
});
});

或者进行元素遍历查询的时候,如下代码所示。

//构建选中的列表内容
var list =[];//计算单选框
$("input[type='radio']:checked").each(function() {
list.push($(
this).val());
});
//计算复选框 $("input[type='checkbox']:checked").each(function() {
list.push($(
this).val());
});

如果我们需要判断某个对象是否在集合里面,我们就会用到$.inArray() 函数进行判断,如下代码所示。

if ($.inArray(key, addKeyList) == -1) {
addKeyList.push(key);
addValueList.push(value);
sendList.push({ key: key, value: value});

$(
'#tagsToSend').addTag(value);
}

另外,我们对数组还需要了解,如何移除单个或者所有的集合,这个就用到了splice()函数,如下代码所示。

addKeyList.splice(i, 1);//移除某个对象
addValueList.splice(i, 1);//移除某个对象
sendList.splice(i, 1);//移除某个对象

如果需要清空数组,常用的做法如下所示,也是使用splice()函数。

//清空用户选择
functionClearChoice() {
$(
'#tagsToSend').importTags('');

sendList.splice(
0, sendList.length);//待发送的部门、标签、用户列表 addKeyList.splice(0, addKeyList.length);//键的集合 addValueList.splice(0, addValueList.length);//值的集合 }

以上就是我们操作数据的时候,经常使用的一些方法。

10)查询即时列表展示

有时候,我们需要根据查询的条件,对数据库的信息进行检索,然后即时的显示在列表中,供选择使用,如下界面所示。

上图是我项目中,根据拼音码或者名称对药品进行检索,返回的数据进行显示的处理。

为了使用搜索栏,我们使用了微信WeUI定义的搜索栏样式,以下是界面定义的搜索栏DIV层,如下代码。

    <!--搜索栏,类似于微信原生的搜索栏,应用于常见的搜索场景。-->
    <divclass="weui-search-bar"id="searchBar">
        <formclass="weui-search-bar__form"onkeydown="if(event.keyCode==13) return false;">
            <divclass="weui-search-bar__box">
                <iclass="weui-icon-search"></i>
                <inputtype="search"class="weui-search-bar__input"id="searchInput"placeholder="搜索"required="">
                <ahref="javascript:clear()"class="weui-icon-clear"id="searchClear"></a>
            </div>
            <labelclass="weui-search-bar__label"id="searchText"style="transform-origin: 0px 0px 0px; opacity: 1; transform: scale(1, 1);">
                <iclass="weui-icon-search"></i>
                <span>请输入你想要查看的关键字</span>
            </label>
        </form>
        <ahref="javascript:clear()"class="weui-search-bar__cancel-btn"id="searchCancel">取消</a>
    </div>

我们在JS代码上执行对应事件的处理,如下所示。

主要就是监控Input控件的内容变化,进行及时的查询和内容展示。

//查询框输入内容触发事件
$('#searchInput').bind('input propertychange', function() {var name = $("#searchInput").val();//获取Json对象集合,并生成数据显示内容
    var url = "/h5/FindDrugWithPager?page=1&rows=5&Name=" +encodeURI(name);
$.getJSON(url,
function(data) {
$(
"#searchContent").html("");var html = "";
$.each(data.rows,
function(i, item) {var background = (i % 2 == 0) ? "background-color:Highlight" : "";
html
+= `<li> <div class='pic'><img src='@ViewBag.WebsiteDomain/Content/images/drug.png' /></div> <div class='text'> <h2><i></i><span>` + item.CommonName + `</span><em>`+ item.Specification +`</em></h2> <p>商品名:` + item.ProductName + `</p> <p>厂 家:`+item.Manufacturer +`</p> </div> <a class='ture' href=\"javascript:;\" onclick=\"AddDrug('` + item.ID + `','`+ item.ProductName +`')\" ><img src='@ViewBag.WebsiteDomain/Content/images/add1-24.png'></a>
</li>`;
});
$(
"#searchContent").html(html);
});
});

function cancel() {
$(
"#searchContent").empty();
}

function clear() {

$('#searchInput').val('')
$(
"#searchContent").empty();
}

这里的HTML模板,我们有时候也使用代码块的方式,在模板代码里面插入变量,如下所示。

$.getJSON("/Qyh5Flow/GetImageList2?attachGuid=" + info.AttachGUID, function(data) {
$.each(data,
function(i, item) {
$(
"#previewImage").append(`<img class="weui-preview-box" src='${item.Text}' id='img_${item.Value}'/>`); });
});

这里的代码块,开始和结束符使用了
` `
符号块,其中插入了变量${item.Text} 、${item.Value}等变量字符串,并没有打断模板的内容,如果多行,我们一样的处理,非常方便。

$.getJSON(url, function(data) {var html = "";
$.each(data.rows,
function(i, item) {
html
+= `<div class="weui-panel weui-panel_access">
<div class="weui-panel__hd">
<span>${item.FormName}</span>:<span>${item.EditorName}</span>,<span>${item.Edittime}</span>
<span class="ord-status-txt-ts fr"><b>${GetStatus(item.Status)}</b></span>
</div>
<div class="weui-media-box__bd pd-10">
<div class="weui-media-box_appmsg ord-pro-list">
<div class="weui-media-box__bd">
<p class="weui-media-box__desc">标题:<span>${item.Title}</span></p>
<div class="clear mg-t-10">
<div class="pro-amount fl"><span class="font-13">当前处理类型:<em class="num font-15">${item.Proc_TypeName}</em></span></div>
<div class="pro-amount fr"><span class="font-13">当前处理人:<em class="name">${item.Proc_UserName}</em></span></div>
</div>
</div>
</div>
</div>
<div class="weui-panel__ft">
<div class="weui-cell weui-cell_access weui-cell_link oder-opt-btnbox">
<a href="${item.ViewUrl}?id=${item.ID}" class="ords-btn-dele">查看详情</a>
</div>
</div>
</div>`;
});

$(
"#" +divname).html(html);
});

这是我们推荐使用的JS代码块,整块代码都不影响阅读,而且可以换行排版,非常直观。

11)界面弹窗处理

在微信Weui里面定义了几种常规的弹窗处理。

对话框只能通过 JavaScript 进行调用,微信WeUI提供了三种常用的对话框 Alert, Confirm, Prompt 和 Login。我们也可以通过
$.modal
来自定义对话框

实际上, Alert, Confirm, Prompt 和 Login 都是 Modal 的一种定制而已。

一般的JS调用代码如下所示。

//常规提示窗口
$.alert("自定义的消息内容", "自定义的标题");//确认提示窗口
$.confirm("自定义的消息内容", "自定义的标题", function() {//点击确认后的回调函数
  }, function() {//点击取消后的回调函数
  });

例如我们在流程表单处理里面,会在操作前进行提示用户是否确认处理。

//退回拟稿人重新处理
functionApplyCancel(opinion) {var tipMessage2 = "数据检查完毕,如确认无误将【退回拟稿人重新处理】,您确实要提交吗?";
$.confirm(tipMessage2,
"退回拟稿人重新处理?", function() {var userid = "@ViewBag.userid";var applyid = "@Request["id"]";var url = "/QyH5Flow/SkipFirstApply";//构造参数发送给后台 var postData ={
userid: userid,
applyId: applyid,
opinion: opinion
};
$.post(url, postData,
function(json) {var data =$.parseJSON(json);if(data.Success) {//console.log(data); //提示处理结果 $.toast("您已经退回经办人进行修改。");

$.closePopup();
initControls();
//重新加载 }else{
$.toast(
"操作失败:" +data.ErrorMessage);
}
});

},
function() {//取消操作 });
}

登陆窗口界面效果如下所示。

$.login("自定义的消息内容", function(username, password) {//这里进行登录操作
}, function() {
});
//如果参数过多,建议通过 object 方式传入 $.login({
title:
'标题',
text:
'内容文案',
username:
'tom', //默认用户名 password: 'tom', //默认密码 onOK: function(username, password) {//点击确认 },
onCancel:
function() {//点击取消 }
});

我们可以利用登录框进行一个系统账号的绑定,如下是实际项目的处理代码。

$(document).on('click', '#btnBind', function() {
$.login({
title:
'登录',
text:
'请输入用户名和密码',
onOK:
function(username, password) {var url = "/H5/VerifyUser?openid=@ViewBag.openid";var postData ={
username: username,
password: password
};

$.post(url, postData,
function(json) {//转义JSON为对象 var data =$.parseJSON(json);if(data.Success) {
$.toptip(
'登录成功!');
location.reload();
//刷新页面 //WeixinJSBridge.call('closeWindow');//关闭窗口 }else{
$.toptip(
"登录失败:" + data.ErrorMessage, "forbidden");
}
});
},
onCancel:
function() {
$.toptip(
'取消登录!', 'cancel');
}
});
});

上述的几种对话框都是
$.modal
的一种特殊形式。你可以通过
$.modal
来定制对话框

$.modal({
title:
"Hello",
text:
"我是自定义的modal",
buttons: [
{ text:
"支付宝", onClick: function(){ console.log(1)} },
{ text:
"微信支付", onClick: function(){ console.log(2)} },
{ text:
"取消", className: "default", onClick: function(){ console.log(3)} },
]
});

默认情况下,点击对话框的按钮都会先关闭对话框再触发回调函数。可以通过JS来关闭任何正在显示的对话框:

$.closeModal();

除了上面的几种情况的模态对话框,还有一种是Popup弹出遮盖下面页面的场景,如常规的购物车的情况。

Popup 是一个覆盖式的弹出层,可以完全盖住当前页面或者遮盖一半。

例如我在流程处理页面中,往往需要进行审核、会签处理的时候,弹出对应的处理内容,这个就是Popup的使用场景。

我们通过JS进行调用显示窗口。

$("#SignAction").popup();//主动弹出窗口

其中SignAction层就是一个popup的窗口层,视图HTML代码如下所示。

我们只需要注意到 class='weui-popup__container popup-bottom' 的说明,就知道它的样式了,从底部弹出的Popup窗口。

如果没有加上Popup-bottom的样式,那么就是全屏覆盖的模式。

关闭 Popup

给任何链接加上
class='close-popup'
则点击之后可以关闭当前打开的 popup. 你也可以通过
$.closePopup()
来关闭。

12)微信JSSDK的整合等处理

微信JSSDK提供了很多丰富的功能,我们可以利用JSSDK实现图片的上传、预览、扫码等操作,图片上传由于我使用了附件上传的方式,因此不在介绍较为繁琐的JSSDK图片上传模式,有兴趣可以参考我之前随笔《
微信开发中使用微信JSSDK和使用URL.createObjectURL上传预览图片的不同处理对比
》了解下不同。

a) 图片预览

一般我们在一些查看内容的视图界面里面,往往对图片进行预览操作,这时候可以利用JSSDK的图片预览接口,在使用JSSDK接口前,需要进行接口的初始化,然后才能调用,下面是初始化和调用的案例代码。

    <!--微信API初始化-->
    <scriptlanguage="javascript">
        varappid= '@ViewBag.appid';varnoncestr= '@ViewBag.noncestr';varsignature= '@ViewBag.signature';vartimestamp= '@ViewBag.timestamp';

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp,//必填,生成签名的时间戳 nonceStr: noncestr,//必填,生成签名的随机串 signature: signature,//必填,签名,见附录1 jsApiList: ['checkJsApi','chooseImage','previewImage','uploadImage','downloadImage']
});
//所有准备好后,通过样式查找的方式,加入对应集合,然后绑定click事件 wx.ready(function() {var$images=$(".preview").find("img");varimgPaths=[];
$images.each(
function(){
imgPaths.push(
this.src);
});

$images.on(
"click",function(){
wx.previewImage({
current:
this.src,
urls: imgPaths
});
});
});
</script>

而图片的初始化,我们可以在HTML代码进行处理即可。

<divid="previewImage"class="preview">@if (ViewBag.PresImages != null)
{
for (var i = 0; i
<ViewBag.PresImages.Count; i++)
{
<img class
="weui-preview-box"src='@ViewBag.PresImages[i]'alt="处方图片">}
}
</div>

b) 调用扫码处理

我们可以利用JSSDK调用手机的扫码操作,让其进行相应的扫码处理,如下是使用JSSDK进行扫码的JS代码。

    <scriptlanguage="javascript">
        varappid= '@ViewBag.appid';varnoncestr= '@ViewBag.noncestr';varsignature= '@ViewBag.signature';vartimestamp= '@ViewBag.timestamp';

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp,//必填,生成签名的时间戳 nonceStr: noncestr,//必填,生成签名的随机串 signature: signature,//必填,签名,见附录1 jsApiList: ['checkJsApi','chooseImage','previewImage','uploadImage','downloadImage','scanQRCode']
});
//所有准备好后 wx.ready(function() {
document.querySelector(
'#scancode').onclick= function() {//调用扫码事件返回扫码值 wx.scanQRCode({
needResult:
1,
desc:
'scanQRCode desc',
success:
function(res) {//console.log(res.resultStr); varisUrl=CheckUrl(res.resultStr);if(isUrl) {
location.href
=res.resultStr;//跳转页面 }
}
});
};
});
//验证URL字符串 functionCheckUrl(str) {varRegUrl= newRegExp();
RegUrl.compile(
"^[A-Za-z]+://[A-Za-z0-9-_]+\\.[A-Za-z0-9-_%&\?\/.=]+$");//jihua.cnblogs.com if(!RegUrl.test(str)) {return false;
}
return true;
}
</script>

上面扫码后,识别判断URL,如果是URL,那么页面地址自动调整到相应的界面上去。

以上就是一些实际项目中运用到的各种界面处理和JS处理代码,抛砖引玉,希望大家多多支持和鼓励。

因业务需要了解Modbus协议的使用,因此对Modbus的协议,以及相应的C#处理应用进行了解,针对协议的几种方式(RTU、ASCII、TCPIP)进行了封装,以及对Modbus的各种功能码的特点进行了详细的了解,本篇随笔基于这些知识进行了一定的梳理和介绍,主要内容包括Modbus协议简要介绍、Modbus模拟工具使用和Modbus应用开发几个部分。

1)Modbus协议简要介绍

Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。它已经成为一通用工业标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。

此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如果回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。

当在一Modbus网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成反馈信息并用Modbus协议发出。在其它网络上,包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。

Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的modbus协议:modbusTCP。

Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。

标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

对于串行连接,存在两个变种,它们在数值数据表示不同和协议细节上略有不同。Modbus RTU是一种紧凑的,采用二进制表示数据的方式,Modbus ASCII是一种人类可读的,冗长的表示方式。这两个变种都使用串行通信(serial communication)方式。RTU格式后续的命令/数据带有循环冗余校验的校验和,而ASCII格式采用纵向冗余校验的校验和。被配置为RTU变种的节点不会和设置为ASCII变种的节点通信,反之亦然。
对于通过TCP/IP(例如以太网)的连接,存在多个Modbus/TCP变种,这种方式不需要校验和计算。
对于所有的这三种通信协议在数据模型和功能调用上都是相同的,只有封装方式是不同的。
Modbus有一个扩展版本Modbus Plus(Modbus+或者MB+),不过此协议是Modicon专有的,和Modbus不同。它需要一个专门的协处理器来处理类似HDLC的高速令牌旋转。它使用1Mbit/s的双绞线,并且每个节点都有转换隔离装置,是一种采用转换/边缘触发而不是电压/水平触发的装置。连接Modbus Plus到计算机需要特别的接口,通常是支持ISA(SA85),PCI或者PMCIA总线的板卡。

MODBUS 协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。

Modbus串行连路上的的PDU如下所示。

错误检验域是对报文内容执行"冗余校验" 的计算结果。根据不同的传输模式(RTU or ASCII) 使用两种不同的计算方法。

RTU的报文格式如下所示。

ASCII码的报文格式如下所示。

在 ASCII 模式, 报文用特殊的字符区分帧起始和帧结束。一个报文必须以一个‘冒号’ ( : ) (ASCII 十六进制3A )起始,以‘回车-换行’ (CR LF) 对(ASCII 十六进制0D 和0A) 结束。

而Modbus TCP数据帧包含报文头、功能代码和数据3部分。

MBAP Header长度共7个字节,分别为Transaction identifier(事务标识符),Protocol identifier(协议标识符),Length(长度),

Unitidentifier(单元标识符)组成,具体如下表所示:

请求和响应带有六个字节的前缀,如下:
byte 0:     事务处理标识符 –由服务器复制 –通常为 0
byte 1:     事务处理标识符 –由服务器复制 –通常为 0
byte 2:     协议标识符= 0
byte 3:     协议标识符= 0
byte 4:     长度字段 (上半部分字节) = 0 (所有的消息长度小于256)
byte 5:     长度字段  (下半部分字节)   = 后面字节的数量
byte 6:     单元标识符 (原“从站地址”)
byte 7:     MODBUS 功能代码
byte 8 on:   所需的数据

数据区:数据区是根据不同的功能码而不同。数据区可以是实际数值、设置点、主机发送给从机或从机发送给主机的地址。

标准的Modicon控制器使用RS232C实现串行的Modbus。Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用Maser/Slave方式。
Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验.
ModbusTCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。

对于常规的Modbus串口协议,我们来看看03功能码的读取寄存器的操作请求和响应代码了解下。

请求PDU格式如下所示。

响应的PDU格式如下所示。

一个请求读寄存器108-110 的实例:

可以注意到,很多数据的处理,需要拆分高位低位,高位在前,低位在后的模式。

根据这些RTU、ASCII、TCPIP的Modbus协议的不同,我们可以构建一个通用的处理程序来处理这些操作,在后面的应用开发部分继续介绍。

2)Modbus模拟工具使用

一般在做Mobus前期的开发的时候,一般不是针对具体的Modbus设备进行寄存器的处理,而是使用Modbus模拟工具来进行调试,一般我们需要配合Modbus Slave、Modbus Poll、Virtual Serial Port Driver这几个模拟软件来进行开发的。

Modbus Poll :Modbus主机仿真器,用于测试和调试Modbus从设备。该软件支持Modbus的RTU、ASCII、TCP/IP。用来帮助开发人员测试Modbus从设备,或者其它Modbus协议的测试和仿真。

Modbus Slave: Modbus从设备仿真器,可以仿真32个从设备/地址域。每个接口都提供了对EXCEL报表的OLE自动化支持。主要用来模拟Modbus从站设备,接收主站的命令包,回送数据包。帮助Modbus通讯设备开发人员进行Modbus通讯协议的模拟和测试,用于模拟、测试、调试Modbus通讯设备。

Virtual Serial Port Driver:虚拟串口工具,不需要串口接线,提供虚拟的串口,适合学习和调试使用。

配合这几款软件,我们就可以实现串口Modbus协议的模拟测试了,如果我们使用Modbus的TCPIP协议,那么我们不需要VSPD也可以。

如果我们使用Modbus协议的串口通讯方式,那么我们先要使用VSPD进行串口的配对模拟,模拟出两个通讯的串口端口,端口配对模拟成功后,我们可以看到设备管理器中增加了两个端口了。

接着使用从机模拟器,模拟一个Modbus从机供测试,通过菜单【Connection】【Connect】启动,我们选择连接方式为串口,端口则选择我们配对的其中一个端口即可,如下图所示。

其中模式选择RTU或者ASCII都可以,这两个模式协议有所不同,一旦从机选择RTU模式,那么Modbus主机也需要选择对应的RTU模式,反之亦然。

其他串口设置,如波特率、数据位、奇偶位、停止位等默认配置即可。

如果我们选择TCPIP模式,那么对应Modbus主机也需要选择TCPIP方式。

一旦Modbus从机启动,就会处理来自Modbus主机的指令请求(如果有的话),并做相应处理,我们可以通过【Display】【Communication】菜单弹出的对话框,了解到对应请求和应答的协议详细信息。

Modbus主机的启动和ModBus从机类似,我们根据ModBus从机的配置,选择对应的主机配置,Modbus模拟主机启动和查看通讯记录界面如下所示。

另外我们可以通过【Display】里面选择内容显示的进制格式。

在从机的设置里面,我们可以修改从机的定义信息,以便设置对应的从机ID,功能码,其实地址,长度或者数量的信息,如下界面所示。

我们可以根据实际的寄存器地址和数量,设置对应的数值,如下是显示4个数据的内容设置和显示内容。

设置后正常的内容显示如下。

同时我们也需要设置对应Modbus主机模拟器的地址和数量,正确设置后可以正常显示。

通过更深一步的设置或者调整,我们可以极大程度的进行模拟Modbus实际设备的处理方式,从而在没有实际Modbus硬件设备的情况下尽可能通过前期的模拟完成常规功能的测试和准备。

在我们开发Modbus应用的时候,我们对照相应的主从机Modbus协议请求和应答,能够检查我们程序的输出是否正常,从而可以快速的开发Modbus的应用处理功能。

3)Modbus应用开发

为了模拟对接Modbus的RTU、ASCII、TCP/IP协议处理,我根据不同协议的处理方式定义了一个辅助函数,然后统一进行处理,以便达到统一调用的处理便利。

首先我们来看看使用串口模式下(RTU、ASCII)的处理界面效果,这个直接获取模拟器Modbus Slave从机的数值进行显示的。

TCPIP网络方式对接Modbus界面处理效果如下所示。

两者数据均来源于Modbus Slave从机的数值,只是它们对接的方式不同。

串口的处理,我通过SerialPortUtil类来使用Windows的串口类,处理对应的串口操作,通过定义事件的方式,使得串口收到数据的时候,及时通知调用者进行界面更新处理即可。

//使用字符串参数构造
serial = new SerialPortUtil(portname, this.txtBaudRate.Text, this.txtParity.Text, this.txtDataBits.Text, this.txtStopBit.Text);//收到数据处理的事件
serial.DataReceived +=Serial_DataReceived;
serial.RTUMode
= this.radRTU.Checked;//默认RTU模式为True,否则使用ASCII模式

收到数据后,及时通过委托方式,通知UI进行界面的更新显示。

/// <summary>
///收到串口响应事件后,及时进行处理(更新在界面上)/// </summary>
/// <param name="e"></param>
private voidSerial_DataReceived(DataReceivedEventArgs e)
{
//记录在日志,方便复制 LogTextHelper.Info(e.DataReceived);//使用委托进行处理界面控件的数据更新 this.txtResponse.Invoke(new MethodInvoker(()=>{//显示在界面上 this.txtResponse.AppendText(e.DataReceived);this.txtResponse.AppendText(Environment.NewLine);var dataBytes =e.BytesReceived;if(dataBytes != null && dataBytes.Length > 2)
{
var function = dataBytes[1];if(function > 0x80)//128 {//Modbus的异常代码大于128,如果是异常,则可以解析错误 var newFunction = function - 0x80;
lblTips.Text
= "响应有异常,功能代码:" + newFunction.ToString("D2");
lblTips.Text
+= ",错误描述:" +((ModBusExceptionCode)newFunction).ToString();
}
else{
lblTips.Text
= "响应正常";//小于128的为正常响应 }
}
}));
}

而对于网络方式,我们先要定义一个Socket通讯的基类,封装相关的通讯处理操作。

然后简单构建一个子类进行使用,如下所示。

    /// <summary>
    ///通信类子类/// </summary>
    public classModbusClient : BaseSocketClient
{
publicModbusClient()
{
this.Name = "ModbusClient";
}
}

界面处理的时候,我们只需要初始化一个ModbusClient类来使用即可,如下代码所示。

client = newModbusClient();//收到数据处理的事件
client.DataReceived += Client_DataReceived;

收到数据通知界面进行更新的操作如下所示。

private voidClient_DataReceived(DataReceivedEventArgs e)
{
//记录在日志,方便复制 LogTextHelper.Info(e.DataReceived);//使用委托进行处理界面控件的数据更新 this.txtResponse.Invoke(new MethodInvoker(() =>{this.txtResponse.AppendText(e.DataReceived);this.txtResponse.AppendText(Environment.NewLine);var dataBytes =e.BytesReceived;if (dataBytes != null && dataBytes.Length > 2)
{
//串口功能码为第二个字节,TCP/IP功能码为第8个 var function = dataBytes[7];if (function > 0x80)//128 {//Modbus的异常代码大于128,如果是异常,则可以解析错误 var newFunction = function - 0x80;
lblTips.Text
= "响应有异常,功能代码:" + newFunction.ToString("D2");
lblTips.Text
+= ",错误描述:" +((ModBusExceptionCode)newFunction).ToString();
}
else{
lblTips.Text
= "响应正常";//小于128的为正常响应 }
}
}));
}

不管是串口的RTU或者ASCII,又或者是TCPIP的协议,我们可以通过定义一个协议封装的辅助类ModbusQueryHelper来处理协议的具体细节。

    /// <summary>
    ///Modbus查询消息生成辅助类,可以用于串口RTU/ASCII协议,也可以用于TCPIP协议。///用于生成各种功能代码的消息内容。/// </summary>
    public classModbusQueryHelper
{
/// <summary> ///是否为RTU模式,默认为True,否则为ASCII方式/// </summary> public ModbusProtocol Protocol { get; set; } =ModbusProtocol.RTU;/// <summary> ///默认函数/// </summary> publicModbusQueryHelper()
{
}
/// <summary> ///参数化构造,指定RTU模式/// </summary> /// <param name="protocal">Modbus协议:ASCII,RTU, TCP,默认为RTU</param> publicModbusQueryHelper(ModbusProtocol protocal)
{
this.Protocol =protocal;
}

而其中ModbusProtocol是一个枚举,定义如下所示。

    /// <summary>
    ///几种常用的Modbus协议/// </summary>
    public enumModbusProtocol
{
/// <summary> ///串口的ASCII模式/// </summary> ASCII,/// <summary> ///串口的RTU模式/// </summary> RTU,/// <summary> ///网络TCPIP模式/// </summary> TCP
}

我们通过ModbusQueryHelper 类,可以处理不同协议之间的封装细节,并可以对各种功能码的协议进行封装处理。

以上就是 相关Modbus的应用处理和封装,对于常规的Modbus协议可以极大简化对接处理,在实际对接Modbus设备的时候,我们只需要根据对应的说明书,获取对应的内容,就可以把例如温度、湿度、转速等一些设备或者机器人的参数获得,并记录在数据库里面,然后在应用模块中整合一些图表展示就可以很好的实现看板功能了。

在较早随笔《
微信小程序结合后台数据管理实现商品数据的动态展示、维护
》中介绍过使用小程序实现商品的展示,其实基于对应的接口,我们使用H5页面来开发基于公众号的商品展示和支付,也是产不多的原理,不过H5页面和小程序的界面处理代码有一些差异吧了,整体的实现思路倒是差不多的。本篇随笔介绍基于H5页面的开发,参照一些微店的展示页面,实现商品的展示和支付等操作。

1、商品的列表展示

和小程序一样的处理,我们首先需要利用微信开发框架的后台,对商品数据进行统一的录入和管理。并提供对应的接口让我们获取数据,就可以在前端获取进行展示了。我们可以都是基于一个Web API进行数据的处理操作,不管是小程序,还是公众号的H5页面,或者其他Web展示平台,我们都可以以一个统一的Web API接口来处理。

我们先来回顾了解下整体性的功能展示(小程序版本)。

以上界面效果是之前使用小程序开发的,我们这次利用公众号H5页面做,也希望做成类似的布局和界面,可以稍作调整。

我们后台对商品数据进行维护,以便能够在实际应用中通过API来获取对应的商品数据。

商品编辑界面包括对基础信息的修改、封面和Banner图片的维护、以及商品多个展示图片、商品详细介绍的内容维护,如下界面所示。

以上包括商品基础信息,轮播的展示图片,以及明细内容的维护,这些数据我们最终会通过JSON方式返回前端使用。

下图是小程序的商品展示首图,其中包括了顶部Banner栏目、中间的商品分类、底部的商品信息展示几部分。

我们使用公众号H5的开发场景的时候,也希望大概做成类似的页面展示效果。

我们公众号处理一般使用一个对应的控制器来处理对应的页面视图或者数据接口,例如我这里使用H5Controller页面控制器。

我们先来定义一些常用到的视图入口页面。

    /// <summary>
    ///H5页面的处理操作控制(产品信息部分)/// </summary>
    public partial classH5Controller
{
/// <summary> ///产品列表页面/// </summary> /// <returns></returns> publicActionResult ProductList()
{
return View("ProductList");
}
/// <summary> ///产品详细页面/// </summary> /// <returns></returns> publicActionResult ProductDetail()
{
//JSSDK参数 RefreshTicket();return View("ProductDetail");
}
/// <summary> ///购物车页面/// </summary> /// <returns></returns> publicActionResult ShopCart()
{
//JSSDK参数 RefreshTicket();return View("Shopcart");
}

从上面的视图控制器代码,我们定义了ProductList.cshtml视图用来展示商品列表大类和商品列表的。

商品列表最终的界面效果如下所示。

顶部的图片轮播,我们也是根据产品的属性来获取推荐产品的列表,然后展示对应的图片。

顶部轮播部分的页面代码如下所示

@{
Layout = null;
}
<!DOCTYPE html> <html> <head> <metacharset="utf-8"> <metahttp-equiv="Content-Type"content="text/html; charset=utf-8" /> <metahttp-equiv="X-UA-Compatible"content="IE=edge"> <metaname="viewport"content="width=device-width, initial-scale=1, user-scalable=no"> <title>产品列表</title> <linkrel="stylesheet"href="~/Content/wechat/web/lib/weui.min.css"> <linkrel="stylesheet"href="~/Content/wechat/web/css/jquery-weui.css"> <linkrel="stylesheet"href="~/Content/wechat/web/css/style.css" /> </head> <bodyontouchstart> <divclass='weui-content'> <!--顶部轮播--> <divclass="swiper-container swiper-banner"> <divclass="swiper-wrapper"id="slide">@*<divclass="swiper-slide"><ahref="pro_info.html"><imgsrc="~/Content/wechat/web/upload/ban1.jpg" /></a></div>
<divclass="swiper-slide"><ahref="pro_list.html"><imgsrc="~/Content/wechat/web/upload/ban2.jpg" /></a></div>
*@</div> <divclass="swiper-pagination"></div> </div>

其中高亮部分,就是我们需要动态构建的轮播图片代码,这里使用了注释,方便参考生成而已。

我们来看看轮播图片的异步加载代码。

//绑定Banner图片轮换
functionBindBanners() {var url = "/H5/GetProductList";var data ={
status:
1, //推荐 pageindex: 1,
pagesize:
10}//获取数据并绑定界面 $.getJSON(url, data, function(result) {
$(
"#slide").html("");var html = "";
$.each(result.list,
function(i, item) {
html
+= `<div class="swiper-slide"><a href="/h5/ProductDetail?id=${item.ID}"><img src="${item.Banner}" style="width:100%;height:auto" /></a></div>`; });
$(
"#slide").html(html);//设置循环时间 $(".swiper-banner").swiper({
loop:
true,
autoplay:
3000});
});
}

可以看到,主要就是构建一个getJSON的请求处理,然后返回产品的JSON数据,对数据遍历进行生成HTML,然后给界面的元素赋值即可,后面大多数处理都是如此的做法。

由于是轮播处理,我们需要对对应的控件进行初始化,如下就是轮播的JS初始化设置。

//设置循环时间
$(".swiper-banner").swiper({
loop:
true,
autoplay:
3000});

对于产品列表,我们的页面视图代码如下所示。

<divclass="weui-tab">
    <divclass="weui-navbar"style="left:0; right:0; height:44px; background:#fff;">
        <aclass="weui-navbar__item proinfo-tab-tit font-14 weui-bar__item--on"href="#tab1">全部</a>
        <aclass="weui-navbar__item proinfo-tab-tit font-14"href="#tab2">框架产品</a>
        <aclass="weui-navbar__item proinfo-tab-tit font-14"href="#tab3">软件产品</a>
        <aclass="weui-navbar__item proinfo-tab-tit font-14"href="#tab4">开发组件</a>
    </div>
    <divclass="weui-tab__bd proinfo-tab-con">
        <divid="tab1"class="weui-tab__bd-item weui-tab__bd-item--active">
            <divclass="wy-Module">
                <divclass="wy-Module-tit-line"></div>
                <divclass="wy-Module-con">
                    <ulclass="wy-pro-list clear">@*<li>
<ahref="pro_info.html">
<divclass="proimg"><imgsrc="~/Content/wechat/web/upload/pro1.jpg"></div>
<divclass="protxt">
<divclass="name">洋河蓝色瓶装经典Q7浓香型白酒500ml52度高端纯粮食白酒2瓶装包邮</div>
<divclass="wy-pro-pri">¥<span>296.00</span></div>
</div>
</a>
</li>
*@</ul> </div> </div> </div>......................</div> </div>

上面我们主要就是根据产品的类别构建几个Tab选项卡,然后在其中的内容区域,动态获取数据,组装层类似加亮代码部分的产品信息列表即可。

由于几个不同的Tab处理都需要进行分页获取 ,我们初始化一个DIV来做下拉页面进行分页数据展示区域,如下所示。

<divclass="weui-loadmore">
    <iclass="weui-loading"></i>
    <spanclass="weui-loadmore__tips">正在加载</span>
</div>

我们来看看获取产品列表的JS加载处理操作代码,可以看到我们依旧利用异步获取数据并绑定视图控件的方式。

        //绑定产品列表
        functionBindProducts(pageindex) {if (categoryId == 0) {
categoryId
= "";
}
currentPage
= pageindex;//设置当前的页吗 //构建分页获取数据的请求信息 var url = "/H5/GetProductList";var data ={
type: categoryId,
status:
'', //所有状态 pageindex: pageindex,
pagesize: rows
};
//首页加载时候,清空信息处理 if (pageindex == 1) {
$(`${tabid} .wy
-pro-list`).html("");
$(
".weui-loadmore").show();
}
//获取数据并绑定界面 $.getJSON(url, data, function(result) {//$(`${tabid} .wy-pro-list`).html(""); var html = "";
$.each(result.list,
function(i, item) {
html
+=`<li> <a href="/h5/ProductDetail?id=${item.ID}"> <div class="proimg"><img src="${item.Picture}"></div> <div class="protxt"> <div class="name">${item.ProductName}</div> <div class="wy-pro-pri">¥<span>${item.Price}</span></div> </div> </a> </li>`; });
$(`${tabid} .wy
-pro-list`).append(html);//如果没有多余的数据,则隐藏“正在加载..”的提示 if (result.list.length == 0) {
$(
".weui-loadmore").hide();
}
else{
currentPage
+= 1;
}
});
}
//实现下拉分页刷新数据 var loading = false;
$(document.body).infinite().on(
"infinite", function() {if (loading) return;
loading
= true;
setTimeout(
function() {
BindProducts(currentPage);
loading
= false;
},
2000);
});

由于我使用了不同的Tab页面来承载不同的商品数据,那么我们单击不同的Tab的时候,需要处理不同的分类,如下代码所示。

$(function() {
FastClick.attach(document.body);

BindBanners();
BindProducts(currentPage,
"#tab1");

$(
'.weui-navbar__item').on('click', function() {
tabid
= jQuery(this).attr("href");
LoadTabData();
});
});
//触发加载选项页面数据 functionLoadTabData() {if (tabid == "#tab1") {
categoryId
= "0";
BindProducts(
1);
}
else if (tabid == "#tab2") {
categoryId
= "1";
BindProducts(
1);
}
else if (tabid == "#tab3") {
categoryId
= "2";
BindProducts(
1);
}
else if (tabid == "#tab4") {
categoryId
= "3";
BindProducts(
1);
}
}

商品列表最终的界面效果如下所示。

2、商品的明细处理

前面介绍了商品的分类列表展示处理操作,商品的明细处理就是我们在单击某个商品的时候,进一步展示其图片和介绍内容,方便客户进行了解或者购买操作。

商品详细页面会带入一个id参数,然后页面根据对应的ID,加载详细的商品信息。

页面包括基础的介绍,和商品的详细HTML介绍内容,这些一样是可以在后台页面进行动态维护的。

页面内容的处理,主要就是获取JSON加载产品明细的数据进行展示。

<script>$(function() {
FastClick.attach(document.body);
//头部返回按钮,返回主页 $(".wy-header-icon-back").click(function() {
location.href
= "/h5/ProductList";
});

GetProductDetail();
refreshShopCartNumber();
});
//绑定产品明细 functionGetProductDetail() {varurl= "/H5/GetProductDetail";vardata={
id:
"@Request["ID"]"}//获取数据并绑定界面 $.getJSON(url, data,function(result) {

$(
"#slide").html("");varhtml= "";if(result.pics&&result.pics.length> 0) {
$.each(result.pics,
function(i, item) {
html
+=`<div class="swiper-slide"><a href="javascript:;"><img src="${item.pic}"style="width:100%;height:auto" /></a></div>`; });
$(
"#slide").html(html);
}
//设置循环时间 $(".swiper-zhutu").swiper({
loop:
true,
paginationType:
'fraction',
autoplay:
5000});

$(
".wy-media-box__title").text(result.ProductName);
$(
"#description").text(result.Description);
$(
"#price").text(result.Price);
$(
"#picture").val(result.Picture);//产品详情 $(".pro-detail").html(result.Note);
});
}
</script>

对于页面里面,还有购物车的处理和展示,以及购买支付的处理,我们留到下一个随笔进行细讲。

购物车列表

购买选择和确认