2023年2月

基于Metronic的Bootstrap开发框架是我们稍早一点的框架产品,界面部分采用较新的Bootstrap技术,框架后台数据库支持Oracle、SqlServer、MySql、PostgreSQL、DB2、Sqlite、Access等常规数据库,可通过配置进行自由切换,使用Enterprise Library模块进行数据访问的控制的。Bootstrap开发框架的页面以及后台代码,通过代码生成工具Database2Sharp进行快速开发,实现整体性开发的最大效率提高。

Bootstrap开发框架已经应用在很多客户的BS架构的软件管理系统中,基于底层代码的重用,应客户的需要,我们在这个基础上扩展增加了Vue&Element的前端,由于Vue&Element的前端需要采用Web API的接口,我们在这个基础上借鉴了ABP框架的Web API接口处理及前端管理界面的内容,在Bootstrap开发框架基础上增加WebApi+Vue&Element的前端。

1、Bootstrap框架的架构及增加Web API + Vue&Element前端的处理

Bootstrap框架架构:

在之前Bootstrap开发框架的架构如下所示,其UI层是各种应用功能模块,而底层是和Winform框架、混合框架公用的BLL业务逻辑层等分层,并通过实体层实现数据的传递处理。

数据访问层通过微软企业库,较好统一了数据处理的模型,很容易实现了 Oracle、SqlServer、MySql、PostgreSQL、DB2、Sqlite、达梦、Access等常规数据库的支持操作。

控制器设计:

Bootstrap开发框架沿用了我的《
Winform开发框架
》和《
基于EasyUI的Web框架》
的很多架构设计思路和特点,对Controller进行了封装。使得控制器能够获得很好的继承关系,并能以更少的代码,更高效的开发效率,实现Web项目的开发工作,整个控制器的设计思路如下所示。

增加WebApi+Vue&Element前端后的架构设计:

Web API后端的架构设计:

这个是属于前端、后端完全分离的架构设计,后端采用基于Asp.net的Web API技术,并提供按域来管理API的分类,Web API如下架构所示。

通过上面的架构设计,可以看出,底层BLL、DAL、Entity、IDAL、公用类库等分层都是完全通用的,不同的是,我们后端只是提供Web API的接口服务给前端,这个和我们的《
ABP框架使用
》的理念一致,前后端分离。

为了更好的进行后端Web API控制器的相关方法的封装处理,我们把一些常规的接口处理放在BaseApiController里面,而把基于业务表的操作接口放在BusinessController里面定义,如下所示。

在BaseApiController里面,我们使用了结果封装和异常处理的过滤器统一处理。

    /// <summary>
    ///所有接口基类/// </summary>
[ExceptionHandling]
[WrapResult]
public class BaseApiController : ApiController

其中ExceptionHandling 和WrapResult的过滤器处理,可以参考我的随笔《
利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
》进行详细了解。

而业务类的接口通用封装,则放在了BusinessController控制器里面,其中使用了泛型定义,包括实体类,业务操作类,分页条件类等内容作为约束参数,如下所示。

    /// <summary>
    ///本控制器基类专门为访问数据业务对象而设的基类/// </summary>
    /// <typeparam name="B">业务对象类型</typeparam>
    /// <typeparam name="T">实体类类型</typeparam>
[ApiAuthorize]public class BusinessController<B, T, TGetAllInput>: BaseApiControllerwhere B : class
        whereTGetAllInput : IPagedAndSortedResultRequestwhere T : BaseEntity, new()

通过Web API接口返回结果的统一封装处理,我们定义了相关的格式如下所示。

其中result是返回对应的对象信息,如果没有则返回null.

如果是分页查询返回结果集合,其结果如下所示。

展开单条记录明细如下所示。

Vue&Element的前端的架构设计:

而Vue&Element的前端的架构设计,也借鉴了我们ABP框架的前端管理部分,详细也可以了解下我的相关随笔《
循序渐进VUE+Element
》。

Vue&Element的前端的架构设计如下所示。

引入了前后端分离的Vue + Element 作为前端技术路线,那么前后端的边界则非常清晰,前端可以在通过网络获取对应的JSON就可以构建前端的应用了。

一般来说,我们页面模块可能会涉及到Store模块,用来存储对应的状态信息,也可能是直接访问API模块,实现数据的调用并展示。在页面开发过程中,多数情况下,不需要Store模块进行交互,一般只需要存储对应页面数据为全局数据状态的情况下,才可能启用Store模块的处理。

通过WebProxy代理的处理,我们可以很容易在前端中实现跨域的处理,不同的路径调用不同的域名地址API都可以,最终转换为本地的API调用,这就是跨域的处理操作。

前端根据ABP后端的接口进行前端JS端的类的封装处理,引入了ES6类的概念实现业务基类接口的统一封装,简化代码。

权限模块我们涉及到的用户管理、机构管理、角色管理、菜单管理、功能管理、操作日志、登录日志等业务类,那么这些类继承BaseApi,就会具有相关的接口了,如下所示继承关系。

2、WebApi+Vue&Element的前端界面展示

主体框架界面采用的是基于后台配置的菜单动态生成,左侧是菜单,右边顶部是特定导航条和内容区,这个和我们ABP框架的前端界面是一致的。

系统主界面的开发,基本上都是标准的界面,在系统左侧放置系统菜单,右侧中间区域则放置列表展示内容,但是在系统菜单比较多的时候,就需要把菜单分为几级处理。系统菜单在左侧放置一个自定义菜单组件列表,这样通过树形列表的收缩折叠,就可以放置非常多的菜单功能了。

用户管理界面,沿袭Bootstrap框架的布局进行管理,通过用户机构方式,快速展示用户分页列表,如下界面所示。

机构管理界面如下所示。

角色管理界面如下所示。

其角色的编辑界面如下所示,包括了基础信息、用户、菜单、权限等项目。

整个系统的菜单,既可以通过默认Mock的初始菜单,也可以通过后端API获得的菜单资源,动态在界面上进行展示,系统界面左侧的菜单是动态获取并展示出来的,结合路由的判断可以限制用户访问的菜单权限。

菜单管理界面如下所示。

菜单资源在角色管理中分配给具体角色,即可实现对菜单的动态控制管理了。

前端的界面管理,依旧可以通过分拆模块的方式,完成更加方便的组合处理

利用模块化的处理方式,我们可以把界面部分内容作为一个组件进行封装处理,如在权限管理中,我们定义了一部分重用的组件界面,如下所示。

定义好各种界面的自定义组件后,在主界面中进行Import导入使用即可,非常方便重用。

最近把微信框架的前端改造一下,在原来基于Bootstrap框架基础上的微信后台管理,增加一套Vue&Element的前端,毕竟Vue的双向绑定开发起来也还是很方便的,而且Element本身也提供了很强大的界面组件。因此在原来数据库的基础上,增加Web API+Vue&Element的前端,相当于双前端的后台管理,可以根据自己的喜欢选择不同的开发方式和管理界面。

1、基于WebAPI+Element的方式改造微信框架

在原先的微信框架功能基础上,我们在他们的基础上增加了一个新的前端,完全的前后端分离,后端是基于C#的Web API,通过Area的域方式构建多个模块的应用Web API服务。

前端、后端完全分离的架构设计,后端采用基于Asp.net的Web API技术,并提供按域来管理API的分类,Web API如下架构所示。

原先的解决方案项目继续可以重用。解决方案的工程如下所示,分为对公众号、企业微信、小程序的所有接口封装,以及构建的Bootstrap框架基础的后台管理界面。

在重用这些模块的基础上,我们增加了一个基于Area分区的Web API服务项目。

基础改造过程可以参考《
在Bootstrap开发框架基础上增加WebApi+Vue&Element的前端
》 、《
利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
》、《
利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理
》等随笔。

2、基于Vue&Element前端,通过动态构建投票选项,实现单选、复选的投票操作

基于Vue&Element前端,可以根据方便把界面拆分作为组件,同时也可以利用双向绑定的方式,减少JS脚本的赋值处理等过程。

首先我们先来了解一下微信投票的设计和后台处理过程,以及后面应用于公众号投票、企业微信投票的处理。

投票相关信息的表设计如下所示。

以上表的意思,就是一个投票发起记录,有多个投票问题,每个投票问题有多个选项,而投票结果存储则比较简单,把投票项目整合即可,相关信息用外键获取处理。

我们来看看改造后的微信投票管理界面如下所示,列表展示可维护的投票记录信息。

新建投票界面如下所示

问题列表可以通过添加多项的方式动态创建,这里面使用了

vue-dynamic-form-component
组件,具体可以参考使用。

查看界面如下所示

测试桌面端的投票界面如下所示

上图中红色方框里面的就是利用Vue&Element动态生成选项的处理方式。

这里拿来详细介绍一下,主要是动态构建属性,往往需要通过Vue.Set的方式来强行关联对象的双向绑定的响应关系。

如上面的动态构建的界面代码如下所示。

  <templatev-for="(item, index) in voteForm.voteItems">
    <el-form-item:label="(index+1) + '、' + item.itemTitle":key="index">
      <el-checkbox-groupv-model="item.choice"v-if="item.choiceType === 1">
        <el-checkboxv-for="(opt, idx) in item.options":label="opt.id":key="idx">{{opt.optionTitle}}</el-checkbox>
      </el-checkbox-group>
      <el-radio-groupv-model="item.choice"v-if="item.choiceType === 0">
        <el-radiov-for="(opt, idx) in item.options":label="opt.id":key="idx">{{opt.optionTitle}}</el-radio>
      </el-radio-group>
    </el-form-item>
  </template>

这里不论是单选框或者多选项,这里面绑定的属性item.choice 不是响应式的,我们需要通过JS代码的方式来关联他们和视图的响应关系,才能记录用户的选择,否则用户不能选择复选框或者单选框的。

我们窗体视图绑定的表单模型如下代码所示。

但是voteitems里面则没有固定的选项,因为这个是动态构建的,另外它的选择项目也是需要设置响应关系才可以绑定视图上。

如下代码就是通过Vue.set的方式来建立他们的关系的。

  vote.GetVoteResult(param).then(data =>{
Object.assign(
this.voteForm, data.result)//给复选框增加选择的集合为空 if (this.voteForm.voteItems && this.voteForm.voteItems.length > 0) {this.voteForm.voteItems.filter(item => item.choiceType === 1).map(function(item, index) {
Vue.set(item,
'choice', [])//设置视图和模型的响应关系 })this.voteForm.voteItems.filter(item => item.choiceType === 0).map(function(item, index) {
Vue.set(item,
'choice', null) //设置视图和模型的响应关系 })
}
})

注意使用Vue.set之前,我们需要导入Vue 对象再使用。

import Vue from 'vue'

这样相当于每条投票项目里面,都这就了一个choice的属性,并且和界面建立了双向绑定的关系,可以响应界面的选择处理。

我们通过工具跟踪到表单的变量就可以很清晰看到它们的数据结构了,如下界面所示。

以上就是在微信框架模块中,基于Vue&Element前端,通过动态构建投票选项,实现单选、复选的投票操作的一些经验,希望对大家有所帮助和增益。

在我早期的随笔《
在GridControl表格控件中实现多层级主从表数据的展示
》中介绍过GridControl实现二级、三级的层级列表展示,主要的逻辑就是构建GridLevelNode并添加到LevelTree集合里面,手动创建多个承载显示的gridview,然后添加到视图集合里面去吧,不过这个是已知层级的情况下,如果是无穷级,这种手工创建视图的方式,肯定不太适合,而且很繁琐,我们本篇随笔来改进一下,介绍如何实现多层级或无穷级的嵌套列表展示。

1、回顾二级、三级层级的处理

根据数据展示的定义,我们构建一些测试的数据,如下所示。

    //创建测试数据
    var result = newDetail2Result()
{
Name
= "测试",
Description
= "描述内容",
Detail2List
= new List<DetailInfo>()
{
newDetailInfo()
{
Name
= "666666测试",
Description
= "666666描述内容"},newDetailInfo()
{
Name
= "222测试",
Description
= "222描述内容"},newDetailInfo()
{
Name
= "333测试",
Description
= "333描述内容"}
}
};
//构造一个记录的集合 var list = new List<Detail2Result>() { result };

接着通过代码创建我们需要的视图信息,如创建主表的GridView显示如下所示。

GridView grv2 = null;/// <summary>
///创建第二个视图/// </summary>
private voidCreateLevelView()
{
var grv = this.gridView1;var gridControl = this.gridControl1;//创建一个从表的GridView对象 grv2 = newGridView();
grv2.ViewCaption
= "记录明细";
grv2.Name
= "grv2";
grv2.GridControl
=gridControl;//构建GridLevelNode并添加到LevelTree集合里面 var node = newGridLevelNode();
node.LevelTemplate
=grv2;
node.RelationName
= "Detail2List";//这里对应集合的属性名称 gridControl.LevelTree.Nodes.AddRange(newGridLevelNode[]
{
node
});
//添加对应的视图集合显示 gridControl.ViewCollection.Clear();
gridControl.ViewCollection.AddRange(
newBaseView[] { grv, grv2 });//创建从表显示的列 grv2.Columns.Clear();
grv2.CreateColumn(
"ID", "ID");
grv2.CreateColumn(
"Name", "名称");
grv2.CreateColumn(
"Description", "描述内容");//设置非只读、可编辑 grv2.OptionsBehavior.ReadOnly = false;
grv2.OptionsBehavior.Editable
= true;
}

剩下的就是数据的绑定处理了,这里构建列表,绑定数据源展示即可

    //构造一个记录的集合
    var list = new List<Detail2Result>() { result };//绑定数据源
    this.gridControl1.DataSource = list;

可以看到例子的效果界面如下所示。

二级和三级处理的方式类似,就不再赘述了。

2、多层级或无穷级的嵌套列表展示

如果是不确定的多层级列表展示,那么我们应该如何处理呢,手工逐一创建视图的方式,肯定不合适,毕竟有时候数据层次是不确定的,如下数据结构所示,它是一个嵌套的数据结构。

    /// <summary>
    ///本类用于演示多层级的数据列表的展示/// </summary>
    public classDetailNodeInfo
{
publicDetailNodeInfo()
{
this.ID =Guid.NewGuid().ToString();
}
/// <summary> ///ID标识/// </summary> public string ID { get; set; }/// <summary> ///名称/// </summary> public string Name { get; set; }/// <summary> ///描述信息/// </summary> public string Description { get; set; }public List<DetailNodeInfo> Children { get; set; } = new List<DetailNodeInfo>();//初始化 }

这里面我们的嵌套列表使用了Children属性。

这样我们创建视图的时候,只需要创建主视图gridview即可,如下所示

        /// <summary>
        ///创建第一个视图/// </summary>
        private voidCreateGridView()
{
var grv = this.gridView1;var gridControl = this.gridControl1;//创建从表显示的列 grv.Columns.Clear();
grv.CreateColumn(
"ID", "ID");//.Visible = false; grv.CreateColumn("Name", "名称");
grv.CreateColumn(
"Description", "描述内容");//列表是否只读 grv.OptionsBehavior.ReadOnly = false;//列表是否可以编辑 grv.OptionsBehavior.Editable = true;//gridview列表注册的时候,触发处理 gridControl.ViewRegistered +=GridControl_ViewRegistered;
}

其中红色的ViewRegistered是用来触发从表视图的时候,我们处理列头的中文信息的

        private void GridControl_ViewRegistered(objectsender, ViewOperationEventArgs e)
{
var detailView = e.View as GridView; if (detailView != null)
{
SetGridViewCaption(detailView);
}
}
private voidSetGridViewCaption(GridView view)
{
foreach (GridColumn column inview.Columns)
{
SetColumnCaption(column,
"id", "编号");
SetColumnCaption(column,
"name", "名称");
SetColumnCaption(column,
"name", "描述内容");
}
}
private void SetColumnCaption(GridColumn column, string fieldName, stringcaption)
{
if(column != null &&column.FieldName.Equals(fieldName, StringComparison.OrdinalIgnoreCase))
{
column.Caption
=caption;
}
}

为了测试无穷级嵌套列表的展示,我们创建了一个多级的嵌套列表数据,如下所示。

        /// <summary>
        ///绑定数据源/// </summary>
        private voidBindData()
{
//创建测试数据 var result = newDetailNodeInfo()
{
Name
= "测试1",
Description
= "描述内容",//二级列表 Children = new List<DetailNodeInfo>()
{
newDetailNodeInfo()
{
Name
= "测试2",
Description
= "描述内容",//三级列表 Children = new List<DetailNodeInfo>()
{
newDetailNodeInfo()
{
Name
= "3测试",
Description
= "描述内容",//四级列表 Children = new List<DetailNodeInfo>()
{
newDetailNodeInfo()
{
Name
= "4测试",
Description
= "描述内容",
Children
= new List<DetailNodeInfo>()
{
newDetailNodeInfo()
{
Name
= "5测试",
Description
= "描述内容",
Children
= null}
}
}
}
}
}
}
}
};

绑定操作代码和之前处理没有什么异样。

  //构造一个记录的集合
  var list = new List<DetailNodeInfo>() { result };//绑定数据源
    this.gridControl1.DataSource = list;

注意,以上列表只是展示,并没有保存处理。

如果需要绑定可以直接录入并保存的操作,列表必须为BindingList<T>类型,这个才能记录状态的,如下数据结构定义所示。

详细可以参考我随笔《
基于主从表数据录入的处理
》 中的介绍。

微信开发包括公众号、企业微信、微信小程序等方面的开发内容,需要对腾信的微信API接口进行封装:包括事件、菜单、订阅用户、多媒体文件、图文消息、消息群发、微信支付和企业红包、摇一摇设备、语义理解、微信小店、微信卡劵等相关接口处理,另外还包括扫一扫、图库和照片管理、地理位置、H5页面开发等内容,以及企业微信通讯录接口、成员消息相互发送等方面接口。

本篇介绍微信后台管理功能,在原先基于Asp.net的Bootstrap前端基础上,增加的Vue&Element前端的后台管理功能介绍。

我们在原先基础上增加的Vue&Element的前端管理界面,用于微信的后台管理,界面通过左侧菜单的综合导航进行管理,如下界面所示。

1、微信多账号管理

在微信后台管理系统中,需要对系统设计到的一些微信账号信息进行配置,包括订阅号、公众号、企业微信、小程序等账号列表的管理。

账号列表界面如下所示,便于综合的管理配置处理。

在账号列表中,可以管理我们业务系统所涉及到的各种微信账号信息,如公众号、企业微信、小程序等,可以在其中维护相关的配置信息。

微信账号的编辑界面如下所示,在其中管理各种用到的微信账号信息,便于后台API的接口访问。

微信账号管理,主要就是配置好和微信公众号、企业微信的相关参数,方便调用微信API接口的随时,根据这些参数获得访问接口的Token,以便实现对菜单、事件、通讯录、发送数据等等的相关处理 ,另外也可以接收腾讯微信回调信息,如事件响应、订单支付等操作的事件。

2、微信菜单管理

我们知道,公众号或者企业微信的手机端中底部都有相关的功能菜单,这些我们可以通过后台系统的API接口提交,那么我们后台管理也需要对微信菜单数据进行管理。

我们在系统中管理菜单,并通过把菜单提交到服务器上,实现菜单的动态配置和生成,能够为我们系统适应各种的需要,实现灵活的处理。

首先我们在管理的入口先维护菜单的分组,菜单分组其实也是一个菜单记录,只是作为一个顶端的菜单记录存在,方便维护而已。

菜单分组可以禁用、启用,新增、编辑,以及展示该分组下的树形列表和提交到微信服务器上的功能,如下所示是我们对菜单分组的界面设计。

以上是一个全览的菜单管理,包括菜单分组以及分组下的菜单列表状态,我们有时候需要进入菜单列表明细的方式查看相关菜单数据,如下界面所示。

这样我们就有一个对菜单群进行统一维护的菜单分组,也方便我们随时启用某个菜单组群,以便对某个应用进行简单的测试或者体验,特别在我们微信框架整合了多个项目,以便客户切换不同项目进行使用,就显得更加方便。某个时候,随时提交一下就可以及时进行测试,不需要的时候,再更换一个不同的应用菜单群组。

多个项目之间随时提交切换,非常方便。

在微信后台管理中,我们需要定义好菜单对应的事件管理,因为微信通过菜单触发相关的事件,因此菜单事件的响应关系,我们如果处理好,就能构建出我们的微信应用入口了。通过入口,我们可以响应用户菜单的事件,如响应文本内容,图片内容,以及扫码处理,或者定位到自己的页面中去进行业务数据的录入和展示等等。

1、微信公众号的事件处理

如以下是我们公众号的关注成功页面,弹出一个标准介绍的图文消息,可以查看具体的公司介绍内容,也可以在菜单里面定位到自己所需要的功能中去。

微信的消息和事件的分类众多,我们大概了解一下

一般定义好入口菜单事件,我们除了可以响应文本内容、图片、图文消息外,也可以定位到自定义页面里面去,这样可以较好的处理我们自己的业务数据。

如以下是我们在前端定义的业务界面效果。

购物车结算界面如下所示。

2、微信账号和对接管理

在前面随笔《》中介绍过了,基于Vue&Element的微信账号管理,如下图所示。

在微信后台管理系统中,需要对系统设计到的一些微信账号信息进行配置,包括订阅号、公众号、企业微信、小程序等账号列表的管理。

我们微信开发框架,提供了标准的微信账号信息填写界面,录入对应的信息,完成微信后台的对接,即可开启开发微信应用之旅了。

微信账号的编辑界面如下所示,在其中管理各种用到的微信账号信息,便于后台API的接口访问。

账号登录公众号后台或者企业微信后台,我们根据和账号配置一致对应的信息,完成系统对接即可,以下是微信公众号的配置对接界面。

后台系统配置和官方微信后台的配置对接成功后,就需要设置好对应的公众号或者企业微信等的菜单了。

其中每项菜单,都包含一个触发的事件,要么是普通的单击事件,要么是视图事件,要么是扫码事件等等,如下菜单类似所示。

3、菜单的事件和内容管理

除了菜单的网页链接事件外,其他菜单的事件,如click类型事件等,都需要后台管理系统在接收到腾讯微信事件回调的时候,进行一一响应的,我们可以自定义我们的响应内容,因此引入了一个后台管理中的事件和内容管理,用于一一响应对应的事件代码。

如下是内容管理列表和详细内容界面。

有了这些内容定义,我们就可以在事件定义中,选取一个或者多个内容定义作为事件的主要内容承载了。

事件管理列表界面如下所示。

在新建或者编辑事件界面中,我们增加一个选择内容的按钮事件入口,如下所示

在弹出界面中,我们选择对应的内容进行处理即可。

选中的内容,以标签的形式展示出来。

以上就是在微信框架模块中,基于Vue&Element前端的事件和内容管理