2023年2月

在上篇随笔《
基于微信小程序的系统开发准备工作
》介绍了开发微信小程序的一些前期的架构设计、技术路线 、工具准备等方面内容,本篇随笔继续这个步骤,逐步介绍我们实际开发过程中对SSL协议的申请及后期处理过程,包括证书的IIS端口绑定,以及解决“”小程序要求的 TLS 版本必须大于等于 1” 的问题。

1、证书申请及成功的后续处理

小程序的配置要求我们必须在网站提供443端口服务,需要加入HTTPS证书,这种证书可以从云服务商上购买,如腾讯云、阿里云上的云服务器后台都提供了购买证书服务的通道,以阿里云为例,使用阿里云账号登陆后,在【控制台】【安全云盾】【证书服务】里面进行申请。

购买证书,我们在没有太多资金支持的情况下,可以考虑先使用免费SSL证书,阿里云上提供 免费型DV SSL的申请,购买后,会在订单列表里面有一个待审核的订单,如下所示,等待审核通过即可使用。

一般情况下,如果我们填写的资料正确,会较快通过审核的,如果我们的DNS不在万网上,那么我们还需要到服务商的网站进行添加阿里云的DNS配置。通过我们在提交信息的时候,如果是Windows服务器,因此会勾选DNS方式验证,如下所示。

这样提交成功后,会同时在服务器的云解析上面自动增加一条记录,如下所示

如果我们的申请的免费SSL证书获得通过,那么状态会变为【已签发】,如下所示,这个时候就可以用了。

下载的证书包括几个文件,如下所示。

我们在IIS服务器上双击pfx文件,默认一步步操作即可把证书增加加到对应的目录里面了。

接着我们可以在控制台中查看到对应的证书位置。

然后在IIS里面绑定443端口,选择对应的SSL证书即可完成对SSL证书的绑定了,如下图所示。

这个时候,如果我们访问网站(我们官网是https://www.iqidi.com),那么 就可以在浏览器的左侧看到证书的信息了。

2、微信小程序整合处理

为了整合远程HTTPS连接获取数据,我们需要进行部署一个Web API的接口,那么我们可以建立一个进行MVC控制器进行测试,如下我们在控制器里面添加一个方法来获取第三方接口的数据,然后返回来给我们的小程序使用。

例如,我们以连接地址:http://m.maoyan.com/movie/list.json返回的数据为例,这个接口用来获取电影的数据,获得的结果如下所示。

由于小程序对域名的限制,我们不能使用第三方的API接口,因此需要在自己域名内部的API进行封装处理,然后再提供给自己的小程序使用,我们建立一个MVC的控制器方法,如下代码所示。

        /// <summary>
        ///增加一个域名内的接口,供小程序测试/// </summary>
        /// <returns></returns>
[HttpPost]public ActionResult Movies(int offset = 0, string type = "hot", int limit=6)
{
var url = "http://m.maoyan.com/movie/list.json";var postData = string.Format("?offset={0}&type={1}&limit={2}", offset,type,limit);

HttpHelper helper
= newHttpHelper();string result = helper.GetHtml(url+ postData, "", false);returnContent(result);
}

这样我们使用Fiddler测试的时候,确信能够获得返回的JSON数据,在进行小程序的测试即可。

执行POST数据的处理,可以获得对应的JSON数据,如下所示。

不过如果我们这个时候整合小程序进行测试,如下代码所示。

onShow: function() {var that = thiswx.request({
url:
'https://www.iqidi.com/h5/movies',
data: {
offset:
0,
type:
'hot',
limit: that.data.limit
},
method :
'POST',
header: {
'Content-Type': 'application/json'},
success:
function(res) {
console.log(res.data)
that.setData({
films: res.data.data.movies,
loading:
true})
}
})

那么上述的处理操作,还是没有能够获取正确的结果的,调试小程序发现,它提示”
小程序要求的 TLS 版本必须大于等于 1.2
“”。

在网站上找到对应的解决方案,测试后正确解决问题:
在 PowerShell中运行以下内容, 然后重启服务器

# Enables TLS 1.2 on windows Server 2008 R2 and Windows 7

# These keys do not exist so they need to be created prior to setting values.
md "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2"
md "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"
md "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client"

# Enable TLS 1.2 forclient and server SCHANNEL communications
new-itemproperty -
path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server" -name "Enabled" -value 1 -PropertyType "DWord"new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server" -name "DisabledByDefault" -value 0 -PropertyType "DWord"new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" -name "Enabled" -value 1 -PropertyType "DWord"new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" -name "DisabledByDefault" -value 0 -PropertyType "DWord" # Disable SSL 2.0 (PCI Compliance) md "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server"new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server" -name Enabled -value 0 -PropertyType "DWord" # Enables TLS 1.2 on Windows Server 2008 R2 and Windows 7 # These keys do not exist so they need to be created prior to setting values. md "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2" md "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server" md "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" # Enable TLS 1.2 for client and server SCHANNEL communications new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server" -name "Enabled" -value 1 -PropertyType "DWord" new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server" -name "DisabledByDefault" -value 0 -PropertyType "DWord" new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" -name "Enabled" -value 1 -PropertyType "DWord" new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" -name "DisabledByDefault" -value 0 -PropertyType "DWord" # Disable SSL 2.0 (PCI Compliance) md "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server" new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server" -name Enabled -value 0 -PropertyType "DWord"

这样启动接口,获得数据并在小程序中进行正确展示了。

微信小程序在无论在功能、文档及相关支持方面,都是优于前面几种微信账号类型,它提供了很多原生程序才有的接口,使得我们的小程序在很多方面突破H5页面应用的限制,更加接近原生程序的功能,因此微信小程序具有很大的前景想象力。它提供了自己的视图层描述语言 WXML 和 WXSS,以及基于 JavaScript 的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,可以让开发者可以方便的聚焦于数据与逻辑上。

1、小程序的目录结构

小程序的目录结构如下所示。

其中项目级别的的文件包括 app.js、app.json、app.wxss 这三个。其中
.js
后缀的是脚本文件,
.json
后缀的文件是配置文件,
.wxss
后缀的是样式表文件。

页面级别可以包含很多个页面,如index,detail等页面,每个页面都包含.js,.json,.wxml,.wxss这些文件,其中.wxml为页面视图文件,是自定义标签的HTML页面。

app.js是小程序的脚本代码。我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量,可以调用框架提供的丰富的 API。

app.json 是对整个小程序的全局配置。我们可以在这个文件中配置小程序是由哪些页面组成,配置小程序的窗口背景色,配置导航条样式,配置默认标题。

app.wxss 是整个小程序的公共样式表。我们可以在页面组件的 class 属性上直接使用 app.wxss 中声明的样式规则。

一个小程序主体部分由三个文件组成,必须放在项目的根目录。

微信小程序中的每一个页面的【路径+页面名】都需要写在 app.json 的 pages 中,且 pages 中的第一个页面是小程序的首页。

每一个
小程序页面
是由同路径下同名的四个不同后缀文件的组成,如:index.js、index.wxml、index.wxss、index.json。
.js
后缀的文件是脚本文件,
.json
后缀的文件是配置文件,
.wxss
后缀的是样式表文件,
.wxml
后缀的文件是页面结构文件。

index.json 是页面的配置文件:页面的配置文件是非必要的。当有页面的配置文件时,配置项在该页面会覆盖 app.json 的 window 中相同的配置项。如果没有指定的页面配置文件,则在该页面直接使用 app.json 中的默认配置。

逻辑层(App Service)

小程序开发框架的逻辑层是由JavaScript编写。

逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。 在 JavaScript 的基础上,我们做了一些修改,以方便地开发小程序。

  • 增加
    App

    Page
    方法,进行程序和页面的注册。
  • 增加 getApp 和 getCurrentPages 方法,分别用来获取 App 实例和当前页面栈。
  • 提供丰富的
    API
    ,如微信用户数据,扫一扫,支付等微信特有能力。
  • 每个页面有独立的
    作用域
    ,并提供
    模块化
    能力。
  • 由于框架并非运行在浏览器中,所以 JavaScript 在 web 中一些能力都无法使用,如 document,window 等。
  • 开发者写的所有代码最终将会打包成一份 JavaScript,并在小程序启动的时候运行,直到小程序销毁。类似 ServiceWorker,所以逻辑层也称之为 App Service。

视图层

框架的视图层由 WXML 与 WXSS 编写,由组件来进行展示。

将逻辑层的数据反应成视图,同时将视图层的事件发送给逻辑层。

WXML(WeiXin Markup language)用于描述页面的结构。

WXSS(WeiXin Style Sheet)用于描述页面的样式。

组件(Component)是视图的基本组成单元。

基础组件

框架为开发者提供了一系列基础组件,开发者可以通过组合这些基础组件进行快速开发。

什么是组件:

  • 组件是视图层的基本组成单元。
  • 组件自带一些功能与微信风格的样式。
  • 一个组件通常包括
    开始标签

    结束标签

    属性
    用来修饰这个组件,
    内容
    在两个标签之内。

    <tagname property="value">
    Content goes here ...
    </tagename>

    注意:所有组件与属性都是小写,以连字符
    -
    连接

API

框架提供丰富的微信原生API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。

说明:

  • wx.on 开头的 API 是监听某个事件发生的API接口,接受一个 CALLBACK 函数作为参数。当该事件触发时,会调用 CALLBACK 函数。
  • 如未特殊约定,其他 API 接口都接受一个OBJECT作为参数。
  • OBJECT中可以指定
    success
    ,
    fail
    ,
    complete
    来接收接口调用结果。
参数名 类型 必填 说明
success Function 接口调用成功的回调函数
fail Function 接口调用失败的回调函数
complete Function 接口调用结束的回调函数(调用成功、失败都会执行)

2、小程序接口功能列表

框架提供丰富的微信原生API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。

1)网络 API 列表:

wx.request 发起网络请求
wx.uploadFile 上传文件
wx.downloadFile 下载文件
wx.connectSocket 创建 WebSocket 连接
wx.onSocketOpen 监听 WebSocket 打开
wx.onSocketError 监听 WebSocket 错误
wx.sendSocketMessage 发送 WebSocket 消息
wx.onSocketMessage 接受 WebSocket 消息
wx.closeSocket 关闭 WebSocket 连接
wx.onSocketClose 监听 WebSocket 关闭

2)媒体 API 列表:

wx.chooseImage 从相册选择图片,或者拍照
wx.previewImage 预览图片
wx.startRecord 开始录音
wx.stopRecord 结束录音
wx.playVoice 播放语音
wx.pauseVoice 暂停播放语音
wx.stopVoice 结束播放语音
wx.getBackgroundAudioPlayerState 获取音乐播放状态
wx.playBackgroundAudio 播放音乐
wx.pauseBackgroundAudio 暂停播放音乐
wx.seekBackgroundAudio 控制音乐播放进度
wx.stopBackgroundAudio 停止播放音乐
wx.onBackgroundAudioPlay 监听音乐开始播放
wx.onBackgroundAudioPause 监听音乐暂停
wx.onBackgroundAudioStop 监听音乐结束
wx.chooseVideo 从相册选择视频,或者拍摄

3)文件 API 列表:

wx.saveFile 保存文件
wx.getSavedFileList 获取已保存的文件列表
wx.getSavedFileInfo 获取已保存的文件信息
wx.removeSavedFile 删除已保存的文件信息
wx.openDocument 打开文件

4)数据 API 列表:

wx.getStorage 获取本地数据缓存
wx.getStorageSync 获取本地数据缓存
wx.setStorage 设置本地数据缓存
wx.setStorageSync 设置本地数据缓存
wx.getStorageInfo 获取本地缓存的相关信息
wx.getStorageInfoSync 获取本地缓存的相关信息
wx.removeStorage 删除本地缓存内容
wx.removeStorageSync 删除本地缓存内容
wx.clearStorage 清理本地数据缓存
wx.clearStorageSync 清理本地数据缓存

5)位置 API 列表:

wx.getLocation 获取当前位置
wx.chooseLocation 打开地图选择位置
wx.openLocation 打开内置地图
wx.createMapContext 地图组件控制

6)设备 API 列表:

wx.getNetworkType 获取网络类型
wx.onNetworkStatusChange 监听网络状态变化
wx.getSystemInfo 获取系统信息
wx.getSystemInfoSync 获取系统信息
wx.onAccelerometerChange 监听加速度数据
wx.startAccelerometer 开始监听加速度数据
wx.stopAccelerometer 停止监听加速度数据
wx.onCompassChange 监听罗盘数据
wx.startCompass 开始监听罗盘数据
wx.stopCompass 停止监听罗盘数据
wx.setClipboardData 设置剪贴板内容
wx.getClipboardData 获取剪贴板内容
wx.makePhoneCall 拨打电话
wx.scanCode 扫码

7)界面 API 列表:

wx.showToast 显示提示框
wx.showLoading 显示加载提示框
wx.hideToast 隐藏提示框
wx.hideLoading 隐藏提示框
wx.showModal 显示模态弹窗
wx.showActionSheet 显示菜单列表
wx.setNavigationBarTitle 设置当前页面标题
wx.showNavigationBarLoading 显示导航条加载动画
wx.hideNavigationBarLoading 隐藏导航条加载动画
wx.navigateTo 新窗口打开页面
wx.redirectTo 原窗口打开页面
wx.switchTab 切换到 tabbar 页面
wx.navigateBack 退回上一个页面
wx.createAnimation 动画
wx.createContext 创建绘图上下文
wx.drawCanvas 绘图
wx.stopPullDownRefresh 停止下拉刷新动画

8)WXML节点信息 API 列表:

wx.createSelectorQuery 创建查询请求
selectorQuery.select 根据选择器选择单个节点
selectorQuery.selectAll 根据选择器选择全部节点
selectorQuery.selectViewport 选择显示区域
nodesRef.boundingClientRect 获取布局位置和尺寸
nodesRef.scrollOffset 获取滚动位置
nodesRef.fields 获取任意字段
selectorQuery.exec 执行查询请求

9)开放接口:

wx.login 登录
wx.getUserInfo 获取用户信息
wx.chooseAddress 获取用户收货地址
wx.requestPayment 发起微信支付
wx.addCard 添加卡券
wx.openCard 打开卡券

3、相关处理代码

1)注册程序处理代码

App({
onLaunch:
function(options) {//Do something initial when launch. },
onShow:
function(options) {//Do something when show. },
onHide:
function() {//Do something when hide. },
onError:
function(msg) {
console.log(msg)
},
globalData:
'I am global data'})

2)注册页面处理代码

//index.js
Page({
data: {
text:
"This is page data."},
onLoad:
function(options) {//Do some initialize when page load. },
onReady:
function() {//Do something when page ready. },
onShow:
function() {//Do something when page show. },
onHide:
function() {//Do something when page hide. },
onUnload:
function() {//Do something when page close. },
onPullDownRefresh:
function() {//Do something when pull down. },
onReachBottom:
function() {//Do something when page reach bottom. },
onShareAppMessage:
function() {//return custom share data when user share. },
onPageScroll:
function() {//Do something when page scroll },//Event handler. viewTap: function() {this.setData({
text:
'Set some data for updating view.'})
},
customData: {
hi:
'MINA'}
})

3)JS函数模块化

//common.js
functionsayHello(name) {
console.log(`Hello ${name}
!`)
}
functionsayGoodbye(name) {
console.log(`Goodbye ${name}
!`)
}

module.exports.sayHello
=sayHello
exports.sayGoodbye
= sayGoodbye

在需要使用这些模块的文件中,使用
require(path)
将公共代码引入

var common = require('common.js')
Page({
helloMINA:
function() {
common.sayHello(
'MINA')
},
goodbyeMINA:
function() {
common.sayGoodbye(
'MINA')
}
})

4)网络数据请求处理代码

    //点击搜索按钮调用的函数
search:function(e){var that = this;//数据加载完成之前,显示加载中提示框
wx.showToast({
title:
'加载中。。。',
icon:
'loading',
duration:
10000});//发起请求,注意 wx.request发起的是 HTTPS 请求 wx.request({
url: url
+ "?city=" + that.data.inputValue + "&key=" +apiKey,
data: {},
header: {
'content-type': 'application/json'},
success: function(res) {
var data =res.data;//将数据从逻辑层发送到视图层,同时改变对应的 this.data 的值 that.setData({
restation: data.result,
condition:
false});//数据加载成功后隐藏加载中弹框 wx.hideToast();
}
})

}

在我前面有很多篇随笔介绍了Web API 接口层的架构设计,以及对微信公众号、企业号、小程序等模块的分类划分。例如在《
C#开发微信门户及应用(43)--微信各个项目模块的定义和相互关系
》介绍了相关模块的划分,在《
基于微信小程序的系统开发准备工作
》介绍了Web API的架构设计思路。本篇随笔对之前介绍的架构内容进行统一的调整更新,以便更加方便实际项目的应用开发,以期达到统一、重用、清晰的目的。

1、公众号、企业号、小程序模块的划分

我们知道,目前微信企业应用,分为公众号、企业号(企业微信)、小程序三种应用模式,对于常规的开发来说,我们对每个模式的应用都分为了两个不同的部分,一个是和业务数据相关的数据管理、一个是和API接口相关的API管理,两者整合为一个完整的应用。

公众号、企业号(企业微信)、小程序三种应用模式的模块划分如下图所示。

业务数据管理模块,一般还需要调用API接口进行相关的处理操作,因此他们之间的项目引用关系如下所示

另外,这三种类型的API接口也公用了一些业务对象和实体类,因此把它们抽取出来作为公共项目模块,如这三类接口项目统一使用了一个公共实体类项目。

除了这些之外,我们做项目,一般还涉及到一些基础功能模块,如公用类库,以及附件管理、通讯录管理、权限管理模块等内容,我们可以把后者几个模块放在一起,组成基础模块。

2、基于微信的Web API 架构设计

随着基于JSON格式的Web API的广泛应用,越来越多的企业采用Web API接口服务层,作为统一接口的核心所在,也成为Web API核心层。基于JSON格式的接口,可以广泛地、跨平台的应用于IOS、安卓等移动端,也可以应用在常规的Web业务系统,Winform业务系统、微信应用、微信小程序等方方面面,因此企业内部形成自己是的一套Web API标准和详细的文档非常重要,一旦完善了,就可以供各个业务场景使用,这些业务可以外包给其他软件公司或者团队,各自分离开发,而自己内部则只需要花费精力来统一维护Web API核心层和提高整个核心层的功能接口稳定、缓存处理等方面事情即可。其他业务团队开发的系统只需要遵循整个大接口平台的统一规划,完成各自的功能需求即可,不会造成数据库的不一致,更不会让某家公司掌握核心的技术资源,尾大不掉的尴尬情形。

基于上面的分析,我们企业最终围绕着Web API核心层做了不同的业务应用,如下图所示。

再进一步详细各个模块的分层,我们可以细化为下面的架构设计图,所有模块均围绕着Web API 接口层进行扩展,底层的数据存储对上层的应用是完全透明,我们可以根据需要拆分各种业务数据库,以及使用我们认为合适的数据库。

其中我们在Web API接口层上还看到一个微信消息交互的模块,这个模块我们为了方便域名端口的处理,和Web API 是统一放在一起的,它负责和腾讯微信服务器进行消息的交互处理,从而实现各种消息推送处理。

微信的服务器架起了客户手机和开发者服务器的一个桥梁,通过消息的传递和响应,实现了与用户的交互操作,下面是它的消息流程图。

通过对这几类业务应用的模块分析,我们就可以建立相关的项目了,来分别对这些数据和API进行管理,如我们根据这些分类,在Visual Studio的项目管理中看到的项目如下所示。

其中由于我们这里的Web API 是一个统一的出口,因此会整合很多Web API控制器,以提供所有业务的接口,因此对Web API 控制器的管理就显得很重要,这里建议引入Area区域进行管理控制器类,这种各个模块就能够很好分门别类的进行管理了。

如下图所示是我们的Web API项目的控制器Area区域分类,把微信公众号、企业号、小程序、基础框架、第三方接口、CRM等内容进行不同的划分。

在业务系统开发中,对表格记录的查询、分页、排序等处理是非常常见的,在Web开发中,可以采用很多功能强大的插件来满足要求,且能极大的提高开发效率,本随笔介绍这个bootstrap-table是一款非常有名的开源表格插件,在很多项目中广泛的应用。Bootstrap-table插件提供了非常丰富的属性设置,可以实现查询、分页、排序、复选框、设置显示列、Card view视图、主从表显示、合并列、国际化处理等处理功能,而且该插件同时也提供了一些不错的扩展功能,如移动行、移动列位置等一些特殊的功能,插件可以用基于HTML5的data-*属性标识设置,也可以使用Javascript方式进行设置,非常方便。本篇随笔介绍bootstrap-table插件在我实际项目中的应用情况,总结相关使用中碰到的问题处理经验。

1、Bootstrap-table资源及使用介绍

在GitHub上Bootstrap-table的源码地址是:
https://github.com/wenzhixin/bootstrap-table

Bootstrap-table的文档地址:
http://bootstrap-table.wenzhixin.net.cn/

Bootstrap-table的各种样例:
https://github.com/wenzhixin/bootstrap-table-examples

Bootstrap-Table显示数据到表格的方式有两种,一种是客户端(client)模式,一种是服务器(server)模式。

所谓客户端模式,指的是在服务器中把要显示到表格的数据一次性加载出来,然后转换成JSON格式传到要显示的界面中,客户端模式较为简单,它是把数据一次性加载出来放到界面上,然后根据你设置的每页记录数,自动生成分页。当点击第二页时,会自动加载出数据,不会再向服务器发送请求。同时用户可以使用其自带的搜索功能,可以实现全数据搜索。对于数据量较少的时候,可以使用这个方法。

所谓服务器模式,指的是根据设定的每页记录数和当前要显示的页码,发送数据到服务器进行查询,然后再显示到表格中。该方法可以根据用户的需要动态的加载数据,节省了服务器的资源,但是不能使用其自带的全数据搜索功能。

Bootstrap-table是基于Boostrap开发的插件,因此使用的时候,需要引入Bootstrap的脚本和样式。

如果我们项目中没有引入相关的文件,则需要引入这些样式和脚本文件,如下所示。

<linkrel="stylesheet"href="bootstrap.min.css">
<scriptsrc="jquery.min.js"></script>
<scriptsrc="bootstrap.min.js"></script>

不过以上内容,在我们开发项目的时候都肯定有的了,所以我们还是把中心放到使用这个插件所需要的引入文件上来。

CSS文件引入

<linkrel="stylesheet"href="bootstrap-table.css">

脚本文件引入

<scriptsrc="bootstrap-table.js"></script>
<--汉化文件,放在bootstrap-table.js 后面-->
<scriptsrc="bootstrap-table-zh-CN.js"></script>

一般来说我们如果是基于MVC开发的系统,CSS和JS的代码,都是放在BundleConfig.cs里面初始化的,如下所示

bootstrap-table在页面中的使用,可以分为两种,一种是纯粹用HTML5的写法,通过data-*的方式指定各种属性设置,一种是HTML+JS方式实现弹性设置。

如果我们采用HTML5标识的方式初始化HTML代码,则是下面的代码。

<tabledata-toggle="table"data-url="data1.json">
    <thead>
        <tr>
            <thdata-field="id">Item ID</th>
            <thdata-field="name">Item Name</th>
            <thdata-field="price">Item Price</th>
        </tr>
    </thead>
</table>

如果我们采用JS代码方式来初始化表格插件,那么只需要在HTML上声明一个表格对象即可,如下代码。

<tableid="table"></table>

然后简单的JS代码初始化如下所示

$('#table').bootstrapTable({
url:
'data1.json',
columns: [{
field:
'id',
title:
'Item ID'}, {
field:
'name',
title:
'Item Name'}, {
field:
'price',
title:
'Item Price'}, ]
});

不过实际上我们使用 bootstrap-table的JS配置功能肯定比这个复杂很多,下面界面效果是实际表的数据展示。

2、bootstrap-table的详细使用

1)整个JS属性配置

在上图中,我们是使用JS方式进行初始化表格内容的,JS代码如下所示

        var$table;//初始化bootstrap-table的内容
        functionInitMainTable () {//记录页面bootstrap-table全局变量$table,方便应用
            var queryUrl = '/TestUser/FindWithPager?rnd=' +Math.random()
$table
= $('#grid').bootstrapTable({
url: queryUrl,
//请求后台的URL(*) method: 'GET', //请求方式(*) //toolbar: '#toolbar', //工具按钮用哪个容器 striped: true, //是否显示行间隔色 cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*) pagination: true, //是否显示分页(*) sortable: true, //是否启用排序 sortOrder: "asc", //排序方式 sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*) pageNumber: 1, //初始化加载第一页,默认第一页,并记录 pageSize: rows, //每页的记录行数(*) pageList: [10, 25, 50, 100], //可供选择的每页的行数(*) search: false, //是否显示表格搜索 strictSearch: true,
showColumns:
true, //是否显示所有的列(选择显示的列) showRefresh: true, //是否显示刷新按钮 minimumCountColumns: 2, //最少允许的列数 clickToSelect: true, //是否启用点击选中行 //height: 500, //行高,如果没有设置height属性,表格自动根据记录条数觉得表格高度 uniqueId: "ID", //每一行的唯一标识,一般为主键列 showToggle: true, //是否显示详细视图和列表视图的切换按钮 cardView: false, //是否显示详细视图 detailView: false, //是否显示父子表 //得到查询的参数 queryParams : function(params) {//这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的 var temp ={
rows: params.limit,
//页面大小 page: (params.offset / params.limit) + 1, //页码 sort: params.sort, //排序列名 sortOrder: params.order //排位命令(desc,asc) };returntemp;
},
columns: [{
checkbox:
true,
visible:
true //是否显示复选框 }, {
field:
'Name',
title:
'姓名',
sortable:
true}, {
field:
'Mobile',
title:
'手机',
sortable:
true}, {
field:
'Email',
title:
'邮箱',
sortable:
true,
formatter: emailFormatter
}, {
field:
'Homepage',
title:
'主页',
formatter: linkFormatter
}, {
field:
'Hobby',
title:
'兴趣爱好'}, {
field:
'Gender',
title:
'性别',
sortable:
true}, {
field:
'Age',
title:
'年龄'}, {
field:
'BirthDate',
title:
'出生日期',
formatter: dateFormatter
}, {
field:
'Height',
title:
'身高'}, {
field:
'Note',
title:
'备注'}, {
field:
'ID',
title:
'操作',
width:
120,
align:
'center',
valign:
'middle',
formatter: actionFormatter
}, ],
onLoadSuccess:
function() {
},
onLoadError:
function() {
showTips(
"数据加载失败!");
},
onDblClickRow:
function(row, $element) {var id =row.ID;
EditViewById(id,
'view');
},
});
};

上面JS代码的配置属性,基本上都加了注释说明,是比较容易理解的了。

2)查询及分页

这里的表格数据分页是采用服务器分页的方式,根据搜索条件从服务器返回数据记录的,并使用了排序的处理方式,这里的queryParams参数就是提交到服务器端的参数了

                //得到查询的参数
                queryParams : function(params) {//这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的
                    var temp ={   
rows: params.limit,
//页面大小 page: (params.offset / params.limit) + 1, //页码 sort: params.sort, //排序列名 sortOrder: params.order //排位命令(desc,asc) };returntemp;
},

另外我们看到返回数据的URL地址接口是FindWithPager,我们来看看这个MVC控制器方法是如何处理数据返回的。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <returns>指定对象的集合</returns>
        public overrideActionResult FindWithPager()
{
//检查用户是否有权限,否则抛出MyDenyAccessException异常 base.CheckAuthorized(AuthorizeKey.ListKey);string where =GetPagerCondition();
PagerInfo pagerInfo
=GetPagerInfo();var sort =GetSortOrder();

List
<TestUserInfo> list = null;if (sort != null && !string.IsNullOrEmpty(sort.SortName))
{
list
= baseBLL.FindWithPager(where, pagerInfo, sort.SortName, sort.IsDesc);
}
else{
list
= baseBLL.FindWithPager(where, pagerInfo);
}
//Json格式的要求{total:22,rows:{}}//构造成Json的格式传递 var result = new { total = pagerInfo.RecordCount, rows =list };returnToJsonContent(result);
}

上面代码处理了两个部分的对象信息,一个是分页实体类信息,一个是排序信息,然后根据这些条件获取记录,返回类似

{total:22,rows:{}}

格式的JSON数据记录。

            var result = new { total = pagerInfo.RecordCount, rows =list };return ToJsonContent(result);

获取分页的参数信息如下所示

        /// <summary>
        ///根据Request参数获取分页对象数据/// </summary>
        /// <returns></returns>
        protected virtualPagerInfo GetPagerInfo()
{
int pageIndex = Request["page"] == null ? 1 : int.Parse(Request["page"]);int pageSize = Request["rows"] == null ? 10 : int.Parse(Request["rows"]);

PagerInfo pagerInfo
= newPagerInfo();
pagerInfo.CurrenetPageIndex
=pageIndex;
pagerInfo.PageSize
=pageSize;returnpagerInfo;
}

获取排序参数信息的代码如下所示

        /// <summary>
        ///获取排序的信息/// </summary>
        /// <returns></returns>
        protectedSortInfo GetSortOrder()
{
var name = Request["sort"];var order = Request["sortOrder"];return newSortInfo(name, order);
}

最后就是具体实现具体条件、具体页码、具体排序条件下的数据记录了,这部分可以根据自己的要求实现逻辑,这里只是给出一个封装好的处理调用即可。

baseBLL.FindWithPager(where, pagerInfo, sort.SortName, sort.IsDesc);

实际情况下,我们列表的展示,一般需要使用不同的条件进行数据的查询的,虽然这个Bootstrap-table控件提供了一个默认的查询按钮,不过一般是在客户端分页的情况下使用,而且略显简单,我们一般使用自己查询条件进行处理,如下界面所示。

或者如下的

那么这样对于上面的js属性就需要调整下接受查询条件参数queryParams 了

                //得到查询的参数
                queryParams : function(params) {//这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的
                    var temp ={   
rows: params.limit,
//页面大小 page: (params.offset / params.limit) + 1, //页码 sort: params.sort, //排序列名 sortOrder: params.order //排位命令(desc,asc) };returntemp;
},

对于自定义查询条件,我们可以用下面的常规方式增加参数,如下所示

但是查询条件的参数我们不方便一一设置,我们想通过一种较为快捷的方式来处理,那么就需要对这个处理方式进行一个特别的修改了,首先添加一个扩展函数来处理表单的条件(参考博客http://www.cnblogs.com/zcsj/p/6635677.html的介绍)

        //自定义函数处理queryParams的批量增加
        $.fn.serializeJsonObject = function() {var json ={};var form = this.serializeArray();
$.each(form,
function() {if (json[this.name]) {if (!json[this.name].push) {
json[
this.name] = [json[this.name]];
}
json[
this.name].push();
}
else{
json[
this.name] = this.value || '';
}
});
returnjson;
}

然后我们就可以批量处理表单的查询条件了

                queryParams : function(params) {//这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的
                    var temp = $("#ffSearch").serializeJsonObject();
temp[
"rows"] = params.limit; //页面大小 temp["page"] = (params.offset / params.limit) + 1; //页码 temp["sort"] = params.sort; //排序列名 temp["sortOrder"] = params.order; //排位命令(desc,asc) //特殊格式的条件处理 temp["WHC_Age"] = $("#WHC_Age").val() + "~" + $("#WHC_Age2").val();
temp[
"WHC_BirthDate"] = $("#WHC_BirthDate").val() + "~" + $("#WHC_BirthDate2").val();returntemp;
},

然后后端统一按照逻辑处理查询参数即可。

3)格式化输出函数及其他

对于上面JS的配置信息,我们再来回顾一下,例如对于数据转义函数,可以格式化输出的内容的,如下界面代码。

格式化的数据转义函数如下,主要就是根据内容进行格式化输出的JS函数,好像是需要放在一个文件内。

        //连接字段格式化
        functionlinkFormatter(value, row, index) {return "<a href='" + value + "' title='单击打开连接' target='_blank'>" + value + "</a>";
}
//Email字段格式化 functionemailFormatter(value, row, index) {return "<a href='mailto:" + value + "' title='单击打开连接'>" + value + "</a>";
}
//性别字段格式化 functionsexFormatter(value) {if (value == "女") { color = 'Red'; }else if (value == "男") { color = 'Green'; }else { color = 'Yellow'; }return '<div style="color: ' + color + '">' + value + '</div>';
}

另外,我们看到行记录的最后增加了几个操作按钮,方便对当前记录的查看、编辑和删除操作,如下效果图所示。

这部分我们也是通过格式化函数进行处理的

        //操作栏的格式化
        functionactionFormatter(value, row, index) {var id =value;var result = "";
result
+= "<a href='javascript:;' class='btn btn-xs green' onclick=\"EditViewById('" + id + "', view='view')\" title='查看'><span class='glyphicon glyphicon-search'></span></a>";
result
+= "<a href='javascript:;' class='btn btn-xs blue' onclick=\"EditViewById('" + id + "')\" title='编辑'><span class='glyphicon glyphicon-pencil'></span></a>";
result
+= "<a href='javascript:;' class='btn btn-xs red' onclick=\"DeleteByIds('" + id + "')\" title='删除'><span class='glyphicon glyphicon-remove'></span></a>";returnresult;
}

如果我们需要双击弹出编辑界面的层,我们可以处理表格的双击事件,如下代码所示。

                onDblClickRow: function(row, $element) {var id =row.ID;
EditViewById(id,
'view');
},

如果我们需要设置行的不同的样式展示,可以通过增加rowStyle的JS处理函数即可,如下代码所示

                rowStyle: function (row, index) { //设置行的特殊样式
                    //这里有5个取值颜色['active', 'success', 'info', 'warning', 'danger'];
                    var strclass = "";if (index == 0) {
strclass
= "warning";
}
return{ classes: strclass }
}

对于表格记录的获取,我们可以通过下面的代码进行获取:$table.bootstrapTable('getSelections')

            var rows = $table.bootstrapTable('getSelections');if (rows.length > 0) {
ID
= rows[0].ID;
}

如果是多条记录的处理,例如删除记录

        //实现删除数据的方法
        functionDelete() {var ids = "";//得到用户选择的数据的ID
            var rows = $table.bootstrapTable('getSelections');for (var i = 0; i < rows.length; i++) {
ids
+= rows[i].ID + ',';
}
ids
= ids.substring(0, ids.length - 1);

DeleteByIds(ids);
}

如果需要设置显示列显示,如下界面所示

以及排序处理

这些需要在JS代码开启相关的属性即可。

还有就是一种CardView的卡片视图格式,如下所示。

另外一种是父子表的展开明细的格式,如下所示

以上就是bootstrap-table插件在我实际项目中的应用情况,基本上对JS各个属性的使用进行了一些介绍了,具体的应用我们可以参考它的文档,获取对应属性、方法、事件的详细说明,这样我们就可以更加详细的应用这个插件的各种功能了。

http://bootstrap-table.wenzhixin.net.cn/documentation/

希望上面的总结介绍对你使用bootstrap-table插件有所助益,感谢耐心阅读。

相关主题文章如下所示:

基于Metronic的Bootstrap开发框架经验总结(15)-- 更新使用Metronic 4.75版本

从开发框架提高开发效率说起

基于Metronic的Bootstrap开发框架经验总结(14)--条码和二维码的生成及打印处理

基于Metronic的Bootstrap开发框架经验总结(13)--页面链接收藏夹功能的实现2

基于Metronic的Bootstrap开发框架经验总结(12)--页面链接收藏夹功能的实现

基于Metronic的Bootstrap开发框架经验总结(11)--页面菜单的几种呈现方式

基于Metronic的Bootstrap开发框架经验总结(10)--优化Bootstrap图标管理

在MVC控制器里面使用dynamic和ExpandoObject,实现数据转义的输出

基于Metronic的Bootstrap开发框架经验总结(9)--实现Web页面内容的打印预览和保存操作

基于Metronic的Bootstrap开发框架经验总结(8)--框架功能总体界面介绍

基于Metronic的Bootstrap开发框架经验总结(7)--数据的导入、导出及附件的查看处理

基于Metronic的Bootstrap开发框架经验总结(6)--对话框及提示框的处理和优化

基于Metronic的Bootstrap开发框架经验总结(5)--Bootstrap文件上传插件File Input的使用

基于Metronic的Bootstrap开发框架经验总结(4)--Bootstrap图标的提取和利用

基于Metronic的Bootstrap开发框架经验总结(3)--下拉列表Select2插件的使用

基于Metronic的Bootstrap开发框架经验总结(2)--列表分页处理和插件JSTree的使用

基于Metronic的Bootstrap开发框架经验总结(1)-框架总览及菜单模块的处理

1、bootstrap-fileinpu的简单介绍

在前面的随笔,我介绍了Bootstrap-table表格插件的具体项目应用过程,本篇随笔介绍另外一个Bootstrap FieInput插件的使用,整合两者可以实现我们常规的Web数据导入操作,导入数据操作过程包括有上传文件,预览数据,选择并提交记录等一系列操作。

关于这个插件,我在早期随笔《
Bootstrap文件上传插件File Input的使用
》也做了一次介绍,这是一个增强的 HTML5 文件输入控件,是一个 Bootstrap 3.x 的扩展,实现文件上传预览,多文件上传等功能。

bootstrap-fileinput源码:
https://github.com/kartik-v/bootstrap-fileinput

bootstrap-fileinput在线API:
http://plugins.krajee.com/file-input

bootstrap-fileinput Demo展示:
http://plugins.krajee.com/file-basic-usage-demo

这个插件主要是介绍如何处理图片上传的处理操作,原先我的Excel导入操作使用的是Uploadify插件,可以参考我随笔《
附件上传组件uploadify的使用
》,不过这个需要Flash控件支持,在某些浏览器(如Chrome)就比较麻烦了,因此决定使用一种较为通用的上传插件,这次首先对基于Bootstrap前端架构的框架系统进行升级,替代原来的Uploadify插件,这样页面上传功能,在各个浏览器就可以无差异的实现了。

一般情况下,我们需要引入下面两个文件,插件才能正常使用:

bootstrap-fileinput/css/fileinput.min.css
bootstrap
-fileinput/js/fileinput.min.js

在File input 插件使用的时候,如果是基于Asp.NET MVC的,那么我们可以使用BundleConfig.cs进行添加对应的引用,加入到Bundles集合引用即可。

    //添加对bootstrap-fileinput控件的支持
    css_metronic.Include("~/Content/MyPlugins/bootstrap-fileinput/css/fileinput.min.css");
js_metronic.Include(
"~/Content/MyPlugins/bootstrap-fileinput/js/fileinput.min.js");
js_metronic.Include(
"~/Content/MyPlugins/bootstrap-fileinput/js/locales/zh.js");

在页面中,我们使用以下HTML代码实现界面展示,主要的bootstrap fileinput插件声明,主要是基础的界面代码

<inputid="excelFile"type="file"> 

Excel导入的的界面展示如下所示。

选择指定文件后,我们可以看到Excel的文件列表,如下界面所示。

上传文件后,数据直接展示在弹出层的列表里面,这里直接使用了 Bootstrap-table表格插件进行展示。

这样我们就可以把Excel的记录展示出来,实现了预览的功能,勾选必要的记录,然后保存即可提交到服务器进行保存,实现了Excel数据的真正导入数据库处理。

2、Excel导出操作详细介绍

我们在实际导入Excel的界面中,HTML代码如下所示。

<!--导入数据操作层-->
<divid="import"class="modal fade bs-modal-lg"tabindex="-1"role="dialog"aria-labelledby="myModalLabel"aria-hidden="true">
    <divclass="modal-dialog modal-lg">
        <divclass="modal-content">
            <divclass="modal-header bg-primary">
                <buttontype="button"class="close"data-dismiss="modal"aria-hidden="true"></button>
                <h4class="modal-title">文件导入</h4>
            </div>
            <divclass="modal-body">
                <divstyle="text-align:right;padding:5px">
                    <ahref="~/Content/Template/TestUser-模板.xls"onclick="javascript:Preview();">
                        <imgalt="测试用户信息-模板"src="~/Content/images/ico_excel.png" />
                        <spanstyle="font-size:larger;font-weight:200;color:red">TestUser-模板.xls</span>
                    </a>
                </div>
                <hr/>
                <formid="ffImport"method="post">
                    <divtitle="Excel导入操作"style="padding: 5px">
                        <inputtype="hidden"id="AttachGUID"name="AttachGUID" /> 
                        <inputid="excelFile"type="file"> 
                    </div>
                </form>

                <!--数据显示表格-->
                <tableid="gridImport"class="table table-striped table-bordered table-hover"cellpadding="0"cellspacing="0"border="0">
                </table>
            </div>
            <divclass="modal-footer">
                <buttontype="button"class="btn btn-default"data-dismiss="modal">关闭</button>
                <buttontype="button"class="btn btn-primary"onclick="SaveImport()">保存</button>
            </div>
        </div>
    </div>
</div>

对于bootstrap fileinput的各种属性,我们这里使用JS进行初始化,这样方便统一管理和修改。

        //初始化Excel导入的文件
        functionInitExcelFile() {//记录GUID
            $("#AttachGUID").val(newGuid());

$(
"#excelFile").fileinput({
uploadUrl:
"/FileUpload/Upload",//上传的地址 uploadAsync: true, //异步上传 language: "zh", //设置语言 showCaption: true, //是否显示标题 showUpload: true, //是否显示上传按钮 showRemove: true, //是否显示移除按钮 showPreview : true, //是否显示预览按钮 browseClass: "btn btn-primary", //按钮样式 dropZoneEnabled: false, //是否显示拖拽区域 allowedFileExtensions: ["xls", "xlsx"], //接收的文件后缀 maxFileCount: 1, //最大上传文件数限制 previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
allowedPreviewTypes:
null,
previewFileIconSettings: {
'docx': '<i class="glyphicon glyphicon-file"></i>','xlsx': '<i class="glyphicon glyphicon-file"></i>','pptx': '<i class="glyphicon glyphicon-file"></i>','jpg': '<i class="glyphicon glyphicon-picture"></i>','pdf': '<i class="glyphicon glyphicon-file"></i>','zip': '<i class="glyphicon glyphicon-file"></i>',
},
uploadExtraData: {
//上传的时候,增加的附加参数 folder: '数据导入文件', guid: $("#AttachGUID").val()
}
})
//文件上传完成后的事件 .on('fileuploaded', function(event, data, previewId, index) {var form = data.form, files = data.files, extra =data.extra,
response
= data.response, reader =data.reader;var res = data.response; //返回结果 if(res.Success) {
showTips(
'上传成功');var guid = $("#AttachGUID").val();//提示用户Excel格式是否正常,如果正常加载数据 $.ajax({
url:
'/TestUser/CheckExcelColumns?guid=' +guid,
type:
'get',
dataType:
'json',
success:
function(data) {if(data.Success) {
InitImport(guid);
//重新刷新表格数据 showToast("文件已上传,数据加载完毕!");//重新刷新GUID,以及清空文件,方便下一次处理 RefreshExcel();
}
else{
showToast(
"上传的Excel文件检查不通过。请根据页面右上角的Excel模板格式进行数据录入。", "error");
}
}
});
}
else{
showTips(
'上传失败');
}
});
}

上面的逻辑具体就是,设置上传文件的后台页面为:/FileUpload/Upload,以及各种插件的配置参数,uploadExtraData里面设置的是提交的附加参数,也就是后台控制器接收的参数,其中

.on('fileuploaded', function (event, data, previewId, index) {

的函数处理文件上传后的处理函数,如果上传文件返回的结果是成功的,那么我们再次调用ajax来检查这个Excel的字段是否符合要求,如下地址:

url: '/TestUser/CheckExcelColumns?guid=' + guid,

如果这个检查的后台返回成功的记录,那么再次需要把Excel记录提取出来预览,并清空bootstrap fileinput文件上传插件,方便下次上传文件。如下代码所示。

    if(data.Success) {
InitImport(guid);
//重新刷新表格数据 showToast("文件已上传,数据加载完毕!");//重新刷新GUID,以及清空文件,方便下一次处理 RefreshExcel();
}
else{
showToast(
"上传的Excel文件检查不通过。请根据页面右上角的Excel模板格式进行数据录入。", "error");
}

其中RefreshExcel就是重新更新上传的附加参数值,方便下次上传,否则附加参数的值一直不变化,就会导致我们设置的GUID没有变化而出现问题。

        //重新更新GUID的值,并清空文件
        functionRefreshExcel() {
$(
"#AttachGUID").val(newGuid());
$(
'#excelFile').fileinput('clear');//清空所有文件 //附加参数初始化后一直不会变化,如果需要发生变化,则需要使用refresh进行更新 $('#excelFile').fileinput('refresh', {
uploadExtraData: { folder:
'数据导入文件', guid: $("#AttachGUID").val() },
});
}

而其中InitImport就是获取预览数据并展示在Bootstrap-table表格插件上的,关于这个插件的详细使用,可以回顾下随笔《
基于Metronic的Bootstrap开发框架经验总结(16)-- 使用插件bootstrap-table实现表格记录的查询、分页、排序等处理
》进行了解即可。

        //根据条件查询并绑定结果
        var$import;functionInitImport(guid) {var url = "/TestUser/GetExcelData?guid=" +guid;
$import
= $('#gridImport').bootstrapTable({
url: url,
//请求后台的URL(*) method: 'GET', //请求方式(*) striped: true, //是否显示行间隔色 cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*) pagination: false, //是否显示分页(*) sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*) pageNumber: 1, //初始化加载第一页,默认第一页,并记录 pageSize: 100, //每页的记录行数(*) pageList: [10, 25, 50, 100], //可供选择的每页的行数(*) search: false, //是否显示表格搜索 strictSearch: true,
showColumns:
true, //是否显示所有的列(选择显示的列) showRefresh: true, //是否显示刷新按钮 minimumCountColumns: 2, //最少允许的列数 clickToSelect: true, //是否启用点击选中行 uniqueId: "ID", //每一行的唯一标识,一般为主键列 queryParams: function(params) { },
columns: [{
checkbox:
true,
visible:
true //是否显示复选框 }, {
field:
'Name',
title:
'姓名'}, {
field:
'Mobile',
title:
'手机'}, {
field:
'Email',
title:
'邮箱',
formatter: emailFormatter
}, {
field:
'Homepage',
title:
'主页',
formatter: linkFormatter
}, {
field:
'Hobby',
title:
'兴趣爱好'}, {
field:
'Gender',
title:
'性别',
formatter: sexFormatter
}, {
field:
'Age',
title:
'年龄'}, {
field:
'BirthDate',
title:
'出生日期',
formatter: dateFormatter
}, {
field:
'Height',
title:
'身高'}, {
field:
'Note',
title:
'备注'}],
onLoadSuccess:
function() {
},
onLoadError:
function() {
showTips(
"数据加载失败!");
},
});
}

最后就是确认提交后,会通过JS提交数据到后台进行处理,如下代码所示。

        //保存导入的数据
function SaveImport() {var list = [];//构造集合对象
            var rows = $import.bootstrapTable('getSelections');for (var i = 0; i < rows.length; i++) {
list.push({
'Name': rows[i].Name, 'Mobile': rows[i].Mobile, 'Email': rows[i].Email, 'Homepage': rows[i].Homepage,'Hobby': rows[i].Hobby, 'Gender': rows[i].Gender, 'Age': rows[i].Age, 'BirthDate': rows[i].BirthDate,'Height': rows[i].Height, 'Note': rows[i].Note
});
}
if (list.length == 0) {
showToast(
"请选择一条记录", "warning");return;
}
var postData = { 'list': list };//可以增加其他参数,如{ 'list': list, 'Rucanghao': $("#Rucanghao").val() }; postData =JSON.stringify(postData);

$.ajax({
url:
'/TestUser/SaveExcelData',
type:
'post',
dataType:
'json',
contentType:
'application/json;charset=utf-8',
traditional:
true,
success: function (data) {
if(data.Success) {//保存成功 1.关闭弹出层,2.清空记录显示 3.刷新主列表 showToast("保存成功");

$(
"#import").modal("hide");
$(bodyTag).html(
"");
Refresh();
}
else{
showToast(
"保存失败:" + data.ErrorMessage, "error");
}
},
data: postData
});
}

3、后台控制器代码分析

这里我们的JS代码里面,涉及了几个MVC后台的方法处理:Upload、CheckExcelColumns、GetExcelData、SaveExcelData。这里分别进行介绍。

文件上传的后台控制器方法如下所示。

        /// <summary>
        ///上传附件到服务器上/// </summary>
        /// <param name="fileData">附件信息</param>
        /// <param name="guid">附件组GUID</param>
        /// <param name="folder">指定的上传目录</param>
        /// <returns></returns>
[AcceptVerbs(HttpVerbs.Post)]public ActionResult Upload(string guid, stringfolder)
{
CommonResult result
= newCommonResult();

HttpFileCollectionBase files
=HttpContext.Request.Files;if (files != null)
{
foreach (string key infiles.Keys)
{
try{#region MyRegionHttpPostedFileBase fileData=files[key];if (fileData != null)
{
HttpContext.Request.ContentEncoding
= Encoding.GetEncoding("UTF-8");
HttpContext.Response.ContentEncoding
= Encoding.GetEncoding("UTF-8");
HttpContext.Response.Charset
= "UTF-8";//文件上传后的保存路径 string filePath = Server.MapPath("~/UploadFiles/");
DirectoryUtil.AssertDirExist(filePath);
string fileName = Path.GetFileName(fileData.FileName); //原始文件名称 string fileExtension = Path.GetExtension(fileName); //文件扩展名//string saveName = Guid.NewGuid().ToString() + fileExtension;//保存文件名称 FileUploadInfo info= newFileUploadInfo();
info.FileData
=ReadFileBytes(fileData);if (info.FileData != null)
{
info.FileSize
=info.FileData.Length;
}
info.Category
=folder;
info.FileName
=fileName;
info.FileExtend
=fileExtension;
info.AttachmentGUID
=guid;

info.AddTime
=DateTime.Now;
info.Editor
= CurrentUser.Name;//登录人 result= BLLFactory<FileUpload>.Instance.Upload(info);if (!result.Success)
{
LogTextHelper.Error(
"上传文件失败:" +result.ErrorMessage);
}
}
#endregion}catch(Exception ex)
{
result.ErrorMessage
=ex.Message;
LogTextHelper.Error(ex);
}
}
}
else{
result.ErrorMessage
= "fileData对象为空";
}
returnToJsonContent(result);
}

文件上传处理后,返回一个通用的CommonResult 的结果对象,也方便我们在JS客户端进行判断处理。

而其中检查我们导入Excel的数据是否满足列要求的处理,就是判断它的数据列和我们预先设置好的列名是否一致即可。

        //导入或导出的字段列表
        string columnString = "姓名,手机,邮箱,主页,兴趣爱好,性别,年龄,出生日期,身高,备注";/// <summary>
        ///检查Excel文件的字段是否包含了必须的字段/// </summary>
        /// <param name="guid">附件的GUID</param>
        /// <returns></returns>
        public ActionResult CheckExcelColumns(stringguid)
{
CommonResult result
= newCommonResult();try{
DataTable dt
=ConvertExcelFileToTable(guid);if (dt != null)
{
//检查列表是否包含必须的字段 result.Success =DataTableHelper.ContainAllColumns(dt, columnString);
}
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
result.ErrorMessage
=ex.Message;
}
returnToJsonContent(result);
}

而GetExcelData则是格式化Excel数据到具体的List<TestUserInfo>集合里面,这样我们方便在客户端进行各种属性的操作,它的代码如下所示。

        /// <summary>
        ///获取服务器上的Excel文件,并把它转换为实体列表返回给客户端/// </summary>
        /// <param name="guid">附件的GUID</param>
        /// <returns></returns>
        public ActionResult GetExcelData(stringguid)
{
if (string.IsNullOrEmpty(guid))
{
return null;
}

List
<TestUserInfo> list = new List<TestUserInfo>();

DataTable table
=ConvertExcelFileToTable(guid);if (table != null)
{
#region 数据转换 int i = 1;foreach (DataRow dr intable.Rows)
{
bool converted = false;
DateTime dtDefault
= Convert.ToDateTime("1900-01-01");
DateTime dt;
TestUserInfo info
= newTestUserInfo();

info.Name
= dr["姓名"].ToString();
info.Mobile
= dr["手机"].ToString();
info.Email
= dr["邮箱"].ToString();
info.Homepage
= dr["主页"].ToString();
info.Hobby
= dr["兴趣爱好"].ToString();
info.Gender
= dr["性别"].ToString();
info.Age
= dr["年龄"].ToString().ToInt32();
converted
= DateTime.TryParse(dr["出生日期"].ToString(), outdt);if (converted && dt >dtDefault)
{
info.BirthDate
=dt;
}
info.Height
= dr["身高"].ToString().ToDecimal();
info.Note
= dr["备注"].ToString();

info.Creator
=CurrentUser.ID.ToString();
info.CreateTime
=DateTime.Now;
info.Editor
=CurrentUser.ID.ToString();
info.EditTime
=DateTime.Now;

list.Add(info);
}
#endregion}var result = new { total = list.Count, rows =list };returnToJsonContent(result);
}

另一个SaveExcelData的函数就是处理数据导入的最终处理函数,主要就是把集合写入到具体的数据库里面即可,具体代码如下所示。

        /// <summary>
        ///保存客户端上传的相关数据列表/// </summary>
        /// <param name="list">数据列表</param>
        /// <returns></returns>
        public ActionResult SaveExcelData(List<TestUserInfo>list)
{
CommonResult result
= newCommonResult();if (list != null && list.Count > 0)
{
#region 采用事务进行数据提交DbTransaction trans= BLLFactory<TestUser>.Instance.CreateTransaction();if (trans != null)
{
try{//int seq = 1; foreach (TestUserInfo detail inlist)
{
//detail.Seq = seq++;//增加1 detail.CreateTime =DateTime.Now;
detail.Creator
=CurrentUser.ID.ToString();
detail.Editor
=CurrentUser.ID.ToString();
detail.EditTime
=DateTime.Now;

BLLFactory
<TestUser>.Instance.Insert(detail, trans);
}
trans.Commit();
result.Success
= true;
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
result.ErrorMessage
=ex.Message;
trans.Rollback();
}
}
#endregion}else{
result.ErrorMessage
= "导入信息不能为空";
}
returnToJsonContent(result);
}

上面这几个函数的代码一般是比较有规律的,不需要一个个去编写,一般通过代码生成工具Database2Sharp批量生成即可。这样可以有效提高Web的界面代码和后台代码的开发效率,减少出错的机会。

整个导入Excel数据的处理过程,所有代码都贴出来了,基本上整个逻辑了解了就可以很好的了解这个过程的代码了。