2023年2月

在我们传统的Javascript开发函数编写中,我们习惯了回调函数的处理,不过随着回调函数的增多,以及异步处理的复杂性等原因,代码越来越难读,因此诞生了使用Promise来优化JS函数处理的需求,引入Promise确实能够很好的解决异步回调函数的可读性等问题,同时也使得我们调用的时候代码简洁一些,本文介绍如何在小程序的JS代码里面使用Promise来封装一些函数的做法。

1、小程序传统的回调处理

例如我们生成一个小程序,里面的app.js里面就自动带有一个getUserInfo的函数,这个是使用传统模式的回调函数。

//app.js
App({
onLaunch: function () {
//调用API从本地缓存中获取数据 var logs = wx.getStorageSync('logs') ||[]
logs.unshift(Date.now())
wx.setStorageSync(
'logs', logs)
},

getUserInfo:function(cb){
var that = this if(this.globalData.userInfo){typeof cb == "function" && cb(this.globalData.userInfo)
}
else{//调用登录接口 wx.login({
success: function () {
wx.getUserInfo({
success: function (res) {
that.globalData.userInfo
=res.userInfotypeof cb == "function" &&cb(that.globalData.userInfo)
}
})
}
})
}
},
globalData:{
userInfo:
null,
openid:
null}
})

这种是通过 传入一个cb的回调函数进行处理,使用的时候为了安全性,还需要进一步判断其类型是否为函数:typeof cb == "function",这种处理还是相对比较易懂。

但是,如果我们一段代码中,异步操作太多,又要保证这些异步操作是有顺序的执行,那我们的代码就看起来非常糟糕,就像这样的极端情况:

asyncFunc1(function(){//...
  asyncFunc2(function(){//...
    asyncFunc3(function(){//...
      asyncFunc4(function(){//...
        asyncFunc5(function(){//...
});
});
});
});
});

如果我们改用Promise来处理,那么进行一层简单的包装即可。

functionasyncFunc1(){return new Promise(function(resolve, reject) {//...
})
}

2、Promise的使用介绍

Promise的使用相对比较简单,我们入门可以参考下相关介绍:
阮一峰 promise入门
,如果我们在JS函数里面引入它的话,那么需要包含对应的javascript组件

我们可以在Github上下载对应的组件JS,引入小程序项目即可:
es6-promise

我们为了方便,在项目中创建一个辅助类utils.js,然后在其中引入Promise的脚本,如下所示。

const Promise = require('./Promise')

然后在APP.js里面,我们修改原来的getUserInfo函数如下

//app.js
const utils = require('./utils/util.js')

App({
onLaunch:
function() {//调用API从本地缓存中获取数据 var logs = wx.getStorageSync('logs') ||[]
logs.unshift(Date.now())
wx.setStorageSync(
'logs', logs)
},

getUserInfo() {
return new utils.Promise((resolve, reject) =>{if (this.globalData.userInfo) {
resolve(
this.globalData.userInfo)
}
return utils.getUserInfo().then(res =>{
resolve(
this.globalData.userInfo =res.userInfo)
})
})
},
//获取系统信息 getSystemInfo() {return new utils.Promise((resolve, reject) =>{var that = this if(that.globalData.systemInfo) {
resolve(that.globalData.systemInfo)
}
else{
wx.getSystemInfo({
success:
function(res) {
resolve(that.globalData.systemInfo
=res);
}
})
}
})
},
//全局对象 globalData: {
userInfo:
null,
systemInfo:
null},
utils
})

我们看到,所有原先的函数,我们如果需要引入Promise处理的话,增加一层的函数体即可。

return new utils.Promise((resolve, reject) =>{//原来的函数体代码
});

这样我们调用的时候,使用then函数进行处理即可,类似下面的代码。

getUserInfo().then(user => this.setData({userInfo:user})).catch(console.log);

引入这个Promise后,我们为了进一步实现代码的重用,可以把一些常见的函数放到utils.js来,这样可以实现代码的重用。

//用户登录
functionlogin(){return new Promise((resolve,reject) =>wx.login({
success:resolve,
fail:reject
}))
}
//获取用户信息 functiongetUserInfo(){return login().then(res => new Promise((resolve,reject) =>wx.getUserInfo({
success:resolve,
fail:reject
})
))
}
functionrequstGet(url,data){return requst(url,'GET',data)
}
functionrequstPost(url,data){return requst(url,'POST',data)
}
//封装Request请求方法 function requst(url,method,data ={}){
wx.showNavigationBarLoading()
data.method
=methodreturn new Promise((resove,reject) =>{
wx.request({
url: url,
data: data,
header: {},
method: method.toUpperCase(),
//OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT success: function(res){
wx.hideNavigationBarLoading()
resove(res.data)
},
fail:
function(msg) {
console.log(
'reqest error',msg)
wx.hideNavigationBarLoading()
reject(
'fail')
}
})
})
}

然后发布对应的接口,以供其他模块使用即可。

//发布的接口
module.exports ={
Promise,makeArray,getUserInfo,
get:requstGet,post:requstPost,requst,decodeHtml,

formatTime,getDateDiff
}

封装好这些公用方法后,我们在页面里面进行调用即可,调用的代码如下所示(演示代码从地址里面获取数据,并绑定到界面上)

      //使用Comprise的封装函数展现
      var url ='http://localhost:27206/api/Framework/Information/FindByCode';var companyurl = "http://www.iqidi.com";var json = {code: 'company'};
app.utils.get(url, json).then(
function(res) {var data ={ url: companyurl, image: res.Picture, content: res.Content };
that.setData({
item : data
});
WxParse.wxParse(
'content', 'html', data.content, that, 25);
});

而如果我们使用原来的函数,那么实现代码如下所示。

      //使用标准的wx.request函数实现展现
      var url ='http://localhost:27206/api/Framework/Information/FindByCode';var companyurl = "http://www.iqidi.com";var json = {code: 'company'};
wx.request({
url: url,
data: json,
success:
function(res) {
console.log(res);
var data ={ url: companyurl, image: res.data.Picture, content: res.data.Content };
that.setData({
item : data
});
WxParse.wxParse(
'content', 'html', data.content, that, 25);
}
})

如果对于复杂流程的函数处理,使用Promise来处理,会显得更加简洁易读。

小程序其他文章:


在微信小程序中使用富文本转化插件wxParse

在微信小程序中我们往往需要展示一些丰富的页面内容,包括图片、文本等,基本上要求能够解析常规的HTML最好,由于微信的视图标签和HTML标签不一样,但是也有相对应的关系,因此有人把HTML转换做成了一个富文本转化插件wxParse,方便我们使用,前人种树后人乘凉,我们使用它来解析HTML就很方便了,这对于我们在后端已经完成的一些HTML内容,展示在小程序里就非常不错。

这个插件的Github地址如下:
wxParse-微信小程序富文本解析组件
,官方称它支持支持HTML及markdown解析,我这里主要用在小程序端解析HTML内容。

1、硬编码HTML测试

首先我们根据官方网站说明,下载内容后,复制文件夹wxParse到项目目录中(这里放在utils目录中),如下所示。

- wxParse/
-wxParse.js(必须存在)
-html2json.js(必须存在)
-htmlparser.js(必须存在)
-showdown.js(必须存在)
-wxDiscode.js(必须存在)
-wxParse.wxml(必须存在)
-wxParse.wxss(必须存在)
-emojis(可选)

然后在使用的JS文件中引入文件。

//引入对HTML内容解析的处理类
var WxParse = require('../../utils/wxParse/wxParse.js');

接着在视图中引入样式文件,如下所示

//在使用的Wxss中引入WxParse.css,可以在app.wxss/**app.wxss**/@import"./utils/wxParse/wxParse.wxss";

接着在视图文件中需要引入对应的模板文件,如下所示

//引入模板
<import src="你的路径/wxParse/wxParse.wxml"/>
//这里data中article为bindName
<template is="wxParse" data="{{wxParseData:article.nodes}}"/>

一般我们会对模板的展示进行一定的调整,如下是我实际项目的界面代码

<!--pages/company/index.wxml-->
<importsrc="../../utils/wxParse/wxParse.wxml"/> 
<viewclass="index">
    <viewclass="news">
        <viewclass="news-item line">
            <viewclass="news-details-content wxParse">
                <templateis="wxParse"data="{{wxParseData:content.nodes}}"/>
            </view>
        </view>
    </view>
</view>

前面我们在JS文件里面只是做了文件的引用,为了实现数据的展示,我们需要插件wxParse对它进行数据绑定处理,我们为了方便,先定义一个HTML文档内容在本地,方便展示处理。

在逻辑JS文件里面,我们先包含这个文件进来,如下代码所示。

//引入内置HTML对象文件
const company = require("./company.js");

然后我们先来测试内置HTML的转换处理。

  /**
* 生命周期函数--监听页面加载
*/onLoad:function(options) {var that = this;//使用内置HTML展现 var data ={ url: company.url, image: company.image, content: company.content };this.setData({
item: data
});
WxParse.wxParse(
'content', 'html', data.content, that, 25);

解析得到界面效果如下所示。

2、动态获取HTML并展示

上面我们是通过内置HTML文件的方式,然后把它包含进来,对内容进行转换显示,实际情况下,我们一般需要对内容进行动态化处理,通过请求服务器端的URL获取内容,并进行展示,如下代码所示。

      //使用Comprise的封装函数展现
      var url ='http://localhost:27206/api/Framework/Information/FindByCode';var companyurl = "http://www.iqidi.com";var json = {code: 'company'};
app.utils.get(url, json).then(res
=>{var data ={ url: companyurl, image: res.Picture, content: res.Content };
that.setData({
item : data
});
WxParse.wxParse(
'content', 'html', data.content, that, 25);
});

其中的app.utils.get 是对wxRequest的封装处理(使用了Promise插件),如下所示。

//封装Request请求方法
function requst(url,method,data ={}){
wx.showNavigationBarLoading()
data.method
=methodreturn new Promise((resove,reject) =>{
wx.request({
url: url,
data: data,
header: {},
method: method.toUpperCase(),
//OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT success: function(res){
wx.hideNavigationBarLoading()
resove(res.data)
},
fail:
function(msg) {
console.log(
'reqest error',msg)
wx.hideNavigationBarLoading()
reject(
'fail')
}
})
})
}

另外我们服务器返回的JSON如下所示

这个后台是采用C#的MVC后台生成的JSON,具体后台代码如下所示。

        /// <summary>
        ///通过代码获取对应的文章内容/// </summary>
        /// <param name="id">产品ID</param>
        /// <returns></returns>
[HttpGet]public ExpandoObject FindByCode(stringcode)
{
//如果需要修改字段显示,则参考下面代码处理 dynamic obj = newExpandoObject();var info = baseBLL.FindSingle(string.Format("Code='{0}' AND IsDoc=0", code));if (info != null)
{
obj.ID
=info.ID;
obj.Title
=info.Title;
obj.Content
=info.Content;
obj.Picture
=info.Picture;
obj.Category
=info.Category;
}
returnobj;
}

解析得到界面效果和上面的一致。


以上就是使用富文本转化插件wxParse对HTML内容进行的转换实现,这些动态内容,我们可以利用网站后台进行动态的维护即可。

文章列表管理界面如下所示:

文章内容编辑界面如下所示

微信小程序给我们提供了一个很好的开发平台,可以用于展现各种数据和实现丰富的功能,本篇随笔介绍微信小程序结合后台数据管理实现商品数据的动态展示、维护,介绍如何实现商品数据在后台管理系统中的维护管理,并通过小程序的请求Web API 平台获取JSON数据在小程序界面上进行动态展示。

1、整体性的架构设计

我们整体性的架构设计,包含一个Web管理后台、一个Web API统一接口层、当然还有数据库什么,另外还有一个小程序客户端。整个架构体系还是以我之前随笔介绍的《
整合微信小程序的Web API接口层的架构设计
》内容为蓝本

整个体系以Web API为主提供服务,同时后台管理系统通过各种界面维护着数据的增删改等基础管理工作。

Web API的分层,我们可以通过下图来了解具体的分层结构。

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

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

而后台管理系统,我们通过下面的来了解整体功能,整个后台管理系统使用了Bootstrap的框架进行前端处理。

各种账号的维护如下所示。

2、后台管理系统的数据维护

前面介绍了,后台管理和Web API层是分开的,它们的数据最终都是集中在一个数据库上,实现我们所要的数据集中化管理。

我们言归正题,介绍如何实现商品数据的后台管理,数据数据我们分为几种类型,方便在前端界面展示。

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

除了商品的封面图片以及Banne图片外,我们在小程序的商品详细界面里面,需要在顶端展示多个可以滚动的图片效果,那么我们需要维护商品的图片,如下界面所示。

当然商品的详细信息需要一个富文本的编辑器来进行图片文字的编辑处理,如下界面所示。

HTML图文的编辑,我们这里是用SummerNote插件来进行处理,这个控件的使用非常方便,另外通过修改onImageUpload回调函数,可以实现图片的随时上传处理。

        functioninitEditor() {
$(
'#Note').summernote({
lang:
'zh-CN', //default: 'en-US' height: 600, //set editor height minHeight: null, //set minimum height of editor maxHeight: null, //set maximum height of editor focus: true, //set focus to editable area after initializing summe callbacks: {
onImageUpload:
function (files) { //the onImageUpload API img = sendFile(files[0]);
}
}
});
}
//提交文件到服务器处理 functionsendFile(file) {
data
= newFormData();
data.append(
"file", file);//增加额外的参数 data.append("folder", '商品信息');
data.append(
"guid", $("#ID").val());

$.ajax({
data: data,
type:
"POST",
url:
"/FileUpload/Upload",
cache:
false,
contentType:
false,
processData:
false,
success:
function(json) {var data =$.parseJSON(json);var url = data.urls[0];
$(
"#Note").summernote('insertImage', url, 'image name'); //the insertImage API }
});
}

3、小程序整合Web API接口实现数据展示

上面介绍了管理后台的数据维护,我们就是基于上面的数据模型,在小程序上实现商品数据的展示的。

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

其中Banner栏目的是一个swiper界面组件,商品类型使用了scroll-view来展示,而商品信息则是使用普通的View处理即可。

整个界面的视图部分代码如下所示。

<!--pages/product/index.wxml-->
<!--1px = 750/320 = 2.34rpx;-->
<viewclass="container">
   <viewclass="swiper-container">
        <swiperclass="swiper_box"autoplay="{{autoplay}}"interval="{{interval}}"duration="{{duration}}"bindchange="swiperchange">
            <blockwx:for="{{banners}}">
                <swiper-item>
                    <imagebindtap="tapBanner"data-id="{{item.ID}}"src="{{item.Banner}}"class="slide-image"width="750rpx"height="562.5rpx"/>
                </swiper-item>
            </block>
        </swiper>
        <viewclass="dots">  
            <blockwx:for="{{banners}}"wx:key="unique">  
                <viewclass="dot{{index == swiperCurrent ? ' active' : ''}}"></view>  
            </block>  
        </view>  
   </view>
   
    <viewclass="type-container">
        <scroll-viewclass="type-navbar"scroll-x="true"style="width: 100%">
            <viewclass="type-box"wx:for-items="{{categories}}">
                <viewid="{{item.id}}"class="type-navbar-item {{activeCategoryId == item.id ? 'type-item-on' : ''}}"bindtap="tabClick">{{item.name}}</view>
            </view>
        </scroll-view>
    </view>
    <viewclass="goods-container">
        <viewclass="goods-box"wx:for-items="{{goods}}"wx:key="{{index}}"bindtap="toDetailsTap"data-id="{{item.ID}}">
           <viewclass="img-box">
              <imagesrc="{{item.Picture}}"class="image"/>
           </view>
           <viewclass="goods-title">{{item.ProductName}}</view>
        </view>
    </view>
    <viewhidden="{{loadingMoreHidden ? true : false}}"class="no-more-goods">没有更多啦</view>
</view>

其中小程序的数据是通过后台的JS文件实现数据的加载绑定的。

  /**
* 生命周期函数--监听页面加载
*/onLoad:function(options) {var that = this;this.getCategorys();this.getTopBanners();this.getGoodsList(0);
},

其中上面的几个函数就是分别通过Web API来获取对应的JSON数据的,函数代码如下所示。

 //获取顶部Banner的数据
  getTopBanners : function() {//获取产品列表
    var url = config.product_api;//'http://localhost:27206/api/Framework/Product/list'
    var data ={
status :
1, //推荐 pageindex : 1,
pagesize:
10}
app.utils.get(url, data).then(res
=>{this.setData({
banners : res.list
})
});
},
//获取商品类型 getCategorys : function() {var url = config.category_api;//'http://localhost:27206/api/Framework/Product/GetProductType' app.utils.get(url, {}).then(res=>{var that = this;var categories = [{id:0, name:"全部"}];for(var i=0;i<res.length;i++){
categories.push(res[i]);
}
that.setData({
categories:categories,
activeCategoryId:
0});
});
},
//获取商品列表 getGoodsList: function(categoryId) {if (categoryId == 0) {
categoryId
= "";
}
this.setData({
goods: [],
loadingMoreHidden:
true});//获取产品列表 var url = config.product_api;//'http://localhost:27206/api/Framework/Product/list' var data ={
type: categoryId,
status:
'', //所有状态 pageindex: 1,
pagesize:
50}
app.utils.get(url, data).then(res
=>{this.setData({
goods: res.list,
loadingMoreHidden:
false,
})
});
},

如果你对上面请求数据的代码

    app.utils.get(url, data).then(res=>{this.setData({
banners : res.list
})
});

有疑问,你可以参考我的随笔《
在微信小程序的JS脚本中使用Promise来优化函数处理
》了解Promise插件的使用过程,这里通过引入Promise以及JS的模块化方式,可以直接重用这些通用的JS函数,

而详细部分内容,则是需要滚动展示商品的多个图片,另外还需要展示详细的HTML内容,HTML内容的展示使用富文本转化插件wxParse即可实现,这部分在随笔《
在微信小程序中使用富文本转化插件wxParse
》有详细的使用介绍。

商品详细内容的视图代码如下所示。

<importsrc="../../utils/wxParse/wxParse.wxml" />
<viewclass="container"> 
   <viewclass="swiper-container">
        <swiperclass="swiper_box"autoplay="{{autoplay}}"interval="{{interval}}"duration="{{duration}}"bindchange="swiperchange">
            <blockwx:for="{{goodsDetail.pics}}">
                <swiper-item>
                    <imagesrc="{{item.pic}}"class="slide-image"width="355"height="150"/>
                </swiper-item>
            </block>
        </swiper>
        <viewclass="dots">  
            <blockwx:for="{{goodsDetail.pics}}"wx:key="unique">  
                <viewclass="dot{{index == swiperCurrent ? ' active' : ''}}"></view>  
            </block>  
        </view>  
   </view>
   <viewclass="goods-info">
        <viewclass="goods-title">{{goodsDetail.ProductName}}</view>
        <viewclass="goods-price"hidden="true">¥ {{goodsDetail.Price}}</view>
        <viewclass="goods-text">{{goodsDetail.Description}}</view>
   </view>
    <viewclass="goods-des-info">
        <viewclass="label-title">商品介绍</view>
        <viewclass="goods-text">
            <templateis="wxParse"data="{{wxParseData:article.nodes}}"/>
         </view>
    </view>

</view>

其中后台的JS主要负责详细信息的获取和绑定工作。

  onLoad: function(e) {var that = this;//获取商品详细信息
    var url = config.product_detail_api;//'http://localhost:27206/api/Framework/Product/getdetail';
    var data ={id: e.id};
app.utils.get(url, data).then(res
=>{
console.log(res);
that.data.goodsDetail
=res;
that.setData({
goodsDetail:res
});
WxParse.wxParse(
'article', 'html', res.Note, that, 5);
});
},

最后来段视频了解下整体性的功能展示。

在很多场合,我们需要在线编辑HTML内容,然后在页面上或者其他终端上(如小程序、APP应用等)显示,编辑HTML内容的插件有很多,本篇介绍基于Bootstrap的 summernote插件实现HTML文档的编辑和图片插入操作,这个控件的使用非常方便,并且用户群也很大。

Summernote 是一个简单灵活,所见即所得(WYSIWYG)的编辑器,Summernote是一个轻量级、灵活基于Bootstrap和jQuery的HTML文本编辑器,拥有强大的API配置功能,多国语言支持支持Bootstrap2.x和3.0,支持视频和图片上传以及代码的高亮显示,多种
后台语言版本示例以及多主体支持,在浏览器兼容方面,支持IE9+以及现代的浏览器Chrome,Firefox,Safari等,在移动端应该有不错的表现,还是很不错的,值得使用。

该插件是开源的,官网地址:
http://summernote.org/
,GitHub地址:
https://github.com/summernote/summernote/
、安装调整都很方便。

1、Summernote的简单使用

使用方法:
1)、加载JS和CSS
<!--include libraries(jQuery, bootstrap)-->
<linkhref="http://netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css"rel="stylesheet">
<scriptsrc="http://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script> 
<scriptsrc="http://netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.js"></script> 

<!--include summernote css/js-->
<linkhref="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.7/summernote.css"rel="stylesheet">
<scriptsrc="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.7/summernote.js"></script>

基于MVC的Asp.net应用程序中,我们一般在BundleConfig.cs里面初始化我们的引入JS文件,如下所示。

            //添加对bootstrap-summernote的支持
            css_metronic.Include("~/Content/metronic/assets/global/plugins/bootstrap-summernote/summernote.css");
js_metronic.Include(
"~/Content/metronic/assets/global/plugins/bootstrap-summernote/summernote.min.js");
js_metronic.Include(
"~/Content/metronic/assets/global/plugins/bootstrap-summernote/lang/summernote-zh-CN.min.js");
bundles.Add(css_metronic);
bundles.Add(js_metronic);

2)、编写HTML内容

在HTML页面代码增加下面的标记。

<divid="summernote">Hello Summernote</div>

3)、初始化调用

简单的初始化代码如下所示。

$(document).ready(function() {
$(
'#summernote').summernote();
});

如果增加控件高度的定义,则如下所示。

$('#summernote').summernote({
height:
300,
focus:
true});

而详细的初始化界面,一般还包括语言国际化、图片上传等处理代码,如下所示

functioninitEditor() {
$(
'#Note').summernote({
lang:
'zh-CN', //default: 'en-US' height: 600, //set editor height minHeight: null, //set minimum height of editor maxHeight: null, //set maximum height of editor focus: true, //set focus to editable area after initializing summe callbacks: {
onImageUpload:
function (files) { //the onImageUpload API img = sendFile(files[0]);
}
}
});
}

4)、获取内容

var markupStr = $('#summernote').summernote('code');

5)、设置内容

设置内容和获取内容类似,在code后面增加需要写入的HTML内容即可。

var markupStr = 'hello world';
$(
'#summernote').summernote('code', markupStr);

2、 summernote插件在实际项目中的使用

上面介绍了常规 summernote插件的使用过程,一般情况下,我们使用它除了编辑HTML内容外,还需要对图片上传进行处理,以确保我们可以直接把文件上传到后台文件系统里面去,而不是把它们转换为Base64的编码放在页面内。

一般的初始化代码如下所示。

        functioninitEditor() {
$(
'#Note').summernote({
lang:
'zh-CN', //default: 'en-US' height: 600, //set editor height minHeight: null, //set minimum height of editor maxHeight: null, //set maximum height of editor focus: true, //set focus to editable area after initializing summe callbacks: {
onImageUpload:
function (files) { //the onImageUpload API img = sendFile(files[0]);
}
}
});
}

其中的sendFile的函数如下所示,主要是调用附件管理模块进行文件的存储。

        //提交文件到服务器处理
        functionsendFile(file) {
data
= newFormData();
data.append(
"file", file);//增加额外的参数 data.append("folder", '商品信息');
data.append(
"guid", $("#ID").val());

$.ajax({
data: data,
type:
"POST",
url:
"/FileUpload/Upload",
cache:
false,
contentType:
false,
processData:
false,
success:
function(json) {var data =$.parseJSON(json);var url = data.urls[0];
$(
"#Note").summernote('insertImage', url, 'image name'); //the insertImage API }
});
}

一般来说,我们的文件为了在多个应用之间共享处理,一般建议以FTP方式进行处理,这方面可以参考随笔《
在附件管理模块中增加对FTP 上传和预览的支持
》,FTP上传文件可以使用FluentFTP这个组件(GitHub地址:
https://github.com/hgupta9/FluentFTP
),这个是一个应用很广,功能很强大的FTP组件。

上传附件到服务器上的后台控制器代码如下所示,主要是构建文件信息,上传后返回对应的URL,然后就可以在 summernote插件上显示图片了。

        /// <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)
{
//如果需要修改字段显示,则参考下面代码处理 dynamic obj = newExpandoObject();
List
<string> urls = new List<string>();var 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 fileName = Path.GetFileName(fileData.FileName); //原始文件名称 string fileExtension = Path.GetExtension(fileName); //文件扩展名 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;//登录人//info.Owner_ID = OwerId;//所属主表记录ID result= BLLFactory<FileUpload>.Instance.Upload(info);if (!result.Success)
{
LogTextHelper.Error(
"上传文件失败:" +result.ErrorMessage);
}
else{//返回具体路径地址 string serverRealPath =info.BasePath.UriCombine(info.SavePath);if (!Path.IsPathRooted(info.BasePath) && !info.BasePath.StartsWith("http://") && !info.BasePath.StartsWith("https://"))
{
//如果是相对目录,加上当前程序的目录才能定位文件地址 var url =HttpContext.Request.Url;var baseurl = url.AbsoluteUri.Replace(url.PathAndQuery, "");
serverRealPath
= baseurl.UriCombine(serverRealPath).Replace('\\', '/');
}
urls.Add(serverRealPath);
}
}
#endregion}catch(Exception ex)
{
result.ErrorMessage
=ex.Message;
LogTextHelper.Error(ex);
}
}
obj.urls
=urls;
}
else{
result.ErrorMessage
= "fileData对象为空";
}
var newResult = new { Success = result.Success, ErrorMessage = result.ErrorMessage, urls =urls };returnToJsonContent(newResult);
}

界面效果如我在随笔介绍的一样《
微信小程序结合后台数据管理实现商品数据的动态展示、维护
》。

案例对商品的详细信息的富文本进行编辑,来进行图片文字的编辑处理,如下界面所示。

当我们插入图片的时候,弹出一个对话框界面,选择文件上传后返回URL显示在SummerNote插件上。

上传文件成功后,通过下面的代码插入一个图片,如下代码。

$.ajax({
data: data,
type:
"POST",
url:
"/FileUpload/Upload",
cache:
false,
contentType:
false,
processData:
false,
success: function (json) {
var data =$.parseJSON(json);var url = data.urls[0];
$(
"#Note").summernote('insertImage', url, 'image name'); //the insertImage API }
});

以上就是我在实际的Bootstrap框架项目中使用 summernote插件实现HTML文档的编辑和图片插入操作,整体性还是比较容易上手的,经验供大家借鉴。

其他相关随笔可以参考阅读下:


结合bootstrap fileinput插件和Bootstrap-table表格插件,实现文件上传、预览、提交的导入Excel数据操作流程


基于Metronic的Bootstrap开发框架经验总结(16)-- 使用插件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)-框架总览及菜单模块的处理

微信小程序给我们提供了一个很好的开发平台,可以用于展现各种数据和实现丰富的功能,通过小程序的请求Web API 平台获取JSON数据后,可以在小程序界面上进行数据的动态展示。在数据的关键 一环中,我们设计和编写Web API平台是非常重要的,通过这个我们可以实现数据的集中控制和管理,本篇随笔介绍基于Asp.NET MVC的Web API接口层的设计和常见接口代码的展示,以便展示我们常规Web API接口层的接口代码设计、参数的处理等内容。

1、Web API整体性的架构设计

我们整体性的架构设计,包含一个Web管理后台、一个Web API统一接口层、当然还有数据库什么,另外还有一个小程序客户端。整个架构体系还是以我之前随笔介绍的《
整合微信小程序的Web API接口层的架构设计
》内容为蓝本

整个体系以Web API为主提供服务,同时后台管理系统通过各种界面维护着数据的增删改等基础管理工作。

Web API的分层,我们可以通过下图来了解具体的分层结构。

随着基于JSON格式的Web API的广泛应用,越来越多的企业采用Web API接口服务层,作为统一接口的核心所在,也成为Web API核心层。基于JSON格式的接口,可以广泛地、跨平台的应用于IOS、安卓等移动端,也可以应用在常规的Web业务系统,Winform业务系统、微信应用、微信小程序等方方面面,因此企业内部形成自己是的一套Web API标准和详细的文档非常重要。

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

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

2、基于Asp.NET MVC的Web API接口的实现

1)GET方式

GET方式,接口参数包括有零或一个参数,以及多个参数的方式,返回的值可以是简单的字符串等基础类型,也可以是复杂的自定义对象类型等,如下面几种接口代码所示。

        /// <summary>
        ///简单的GET方式获取数据/// </summary>
        /// <param name="id">字符串ID</param>
        /// <param name="token">接口访问令牌</param>
        /// <returns>返回字符串值</returns>
[HttpGet]public string Test(string id, stringtoken)
{
return string.Format("返回结果, id:{0}", id);
}
/// <summary> ///多个参数的GET方式获取数据/// </summary> /// <param name="id">字符串ID</param> /// <param name="name">名称</param> /// <param name="token">接口访问令牌</param> /// <returns>返回字符串值</returns> [HttpGet]public string TestMulti(string id, string name, stringtoken)
{
return string.Format("返回结果, id:{0} name:{1}", id, name);
}
/// <summary> ///参数测试GET返回自定义实体类对象/// </summary> /// <param name="id">字符串ID</param> /// <param name="token">接口访问令牌</param> /// <returns>返回自定义实体类对象</returns> [HttpGet]public virtual CommonResult TestObject(string id, stringtoken)
{
return new CommonResult() { Data1 = id, Success = true};
}
/// <summary> ///测试GET返回列表对象/// </summary> /// <param name="token">接口访问令牌</param> /// <returns>返回列表对象</returns> [HttpGet]public List<string> TestAction(stringtoken)
{
List
<string> list = new List<string>() { "123", "234", "345"};returnlist;
}

2)POST方式

POST方式,同样也和GET方式的一样,接口参数包括有零或一个参数,以及多个参数的方式,返回的值可以是简单的字符串等基础类型,也可以是复杂的自定义对象类型等,这就是几种常规的接口处理。但是,对于多个参数的接口定义,我们需要对它们进行转换处理,需要使用JObject param的方式进行定义,这样可以很好对多个参数或者自定义的实体类参数进行解析。

下面是几种常规的POST接口定义方式。

        /// <summary>
        ///测试使用POST方式提交数据,参数输入为多个,使用JObject处理/// </summary>
        /// <returns>返回字符串</returns>
[HttpPost]public string TestPost(JObject param, stringtoken)
{
dynamic obj =param;string id =obj.id;if (obj != null)
{
return string.Format("返回结果, id:{0}", id);
}
else{throw new MyApiException("传递参数出现错误");
}
}
/// <summary> ///测试使用POST方式提交数据,参数输入为多个,使用JObject处理/// </summary> /// <returns>返回参数计算数值</returns> [HttpPost]public intTestPostSimple(JObject param)
{
dynamic obj =param;if (obj != null)
{
return obj.x * obj.y * 10;
}
else{throw new MyApiException("传递参数出现错误");
}
}
/// <summary> ///测试POST的方法,方法统一采用JObject param 方式定义,包含一个msg字符串对象,以及一个CListItem对象/// </summary> /// <returns>返回一个通用的CommonResult对象,包括Data1,Data2,Data3的信息</returns> [HttpPost]publicCommonResult TestPostObject(JObject param)
{
dynamic obj =param;if (obj != null)
{
string msg = obj.msg; //消息对象//如果obj.item为类对象,那么需要转换为JObject然后使用ToObject转换为对应类型 CListItem item = ((JObject)obj.item).ToObject<CListItem>();var result = new CommonResult(true, msg);
result.Data1
=msg;
result.Data2
=item.Text;
result.Data3
=item.Value;returnresult;
}
else{throw new MyApiException("传递参数出现错误");
}
}
/// <summary> ///修改分组,方法统一采用JObject param 方式定义,包括一个字符串对象contactId,一个字符串列表对象groupIdList/// </summary> /// <returns>返回一个通用的对象</returns> [HttpPost]publicCommonResult TestPostList(JObject param)
{
dynamic obj =param;if (obj != null)
{
string contactId = obj.contactId; //联系人ID//如果是List<string>的类似列表,不能直接转换,先转换为JArray后使用ToObject转换为对应列表 List<string> groupIdList = ((JArray)obj.groupIdList).ToObject<List<string>>();var result = true; //BLLFactory<Address>.Instance.ModifyAddressGroup(contactId, groupIdList); return newCommonResult(result);
}
else{throw new MyApiException("传递参数出现错误,请检查是否包含了contactId和groupIdList");
}
}

接口类,我们一般把类继承自自己的API接口基类,并对它的异常处理进行处理,以便对错误统一格式回应,如下接口类的代码定义所示。

    /// <summary>
    ///此控制器用来详细介绍各种GET/POST的接口设计///对于GET方式,方法可以接受多个参数///对于POST方式,方法如果有参数使用POST方式,统一采用JObject param对象参数。///如果POST方式有多个参数,如Web API接口加token,则需要客户端把该参数追加在URL上,如url?token=123,然后在使用POST操作/// </summary>
[ExceptionHandling]public class TestController : BaseApiController

其中ExceptionHandling是我们的统一异常过滤处理定义,代码如下所示。

    /// <summary>
    ///API自定义错误过滤器属性/// </summary>
    public classExceptionHandlingAttribute : ExceptionFilterAttribute
{
/// <summary> ///统一对调用异常信息进行处理,返回自定义的异常信息/// </summary> /// <param name="context">HTTP上下文对象</param> public override voidOnException(HttpActionExecutedContext context)
{
//自定义异常的处理 MyApiException ex = context.Exception asMyApiException;if (ex != null)
{
//记录关键的异常信息 LogHelper.Error(context.Exception);throw new HttpResponseException(newHttpResponseMessage(HttpStatusCode.InternalServerError)
{
//封装处理异常信息,返回指定JSON对象 Content = new StringContent(new BaseResultJson(ex.Message, false, ex.errcode).ToJson()),
ReasonPhrase
= "Exception"});
}
//常规异常的处理 string msg = string.IsNullOrEmpty(context.Exception.Message) ? "接口出现了错误,请重试或者联系管理员": context.Exception.Message;throw new HttpResponseException(newHttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content
= newStringContent(msg),
ReasonPhrase
= "Critical Exception"});
}
}

3)小程序端代码处理

小程序端主要是通过JS代码进行处理,实现数据的获取及提交处理等。

如我们列举一个代表性的POST处理代码,如下所示。

  //测试POst方法
wx.request({
url:
'http://localhost:27206/api/SmallApp/Test/TestPostObject',
data: {
msg :
'测试内容',
item: {Text :
'Text', Value:'testValue'}
},
header: {
'Content-Type': 'application/json'},
method:
'POST',
success:
function(res) {
console.log(res.data);
}
});

而对于GET方式,我们的小程序调用方式如下所示。

  getFilms: function(start) {
console.log(
'start:' +start);var that = thiswx.request({
url:
'http://www.iqidi.com/api/h5/test/movies',
data: {
offset: start,
type:
'hot',
limit: that.data.limit
},
header: {
'Content-Type': 'application/json'},
success:
function(res) {
console.log(res.data)
var data =res.data.data;
console.log(data);
if (data.movies.length === 0) {
that.setData({
hasMore:
false,
hideLoading :
true,
})
}
else{
that.setData({
films: that.data.films.concat(data.movies),
start: that.data.start
+data.movies.length,
hasMore:
true,
hideLoading :
true,
});
}
}
})

以上就是我们常规接口(单个参数或者多个参数,简单对象和复杂对象的处理)的定义代码,希望读者在开发Web API接口的时候,可以有所帮助。