整理了好久才完善了这个功能,仅供大家参考。这种功能主要用于一些比较耗时的事务处理。比如大量数据导入导出操作,需要耗费一定时间的复杂计算操作等。为了增加前台体验感受,所以需要实时反馈后台处理进度。
有一种做法是,事务处理期间前台展示一个等待遮罩,但缺点是无法看到事务进度。
本程序缺点是没有展示进度百分比(不过你可以自己添加这个功能),优点是:1、有处理总时间,甚至还可以展示每一个步骤的用时;2、可以实时获取事务进度,进度个数和名称随你定义;3、封装完善,前台页面不需要其他代码,除JS功能代码外。后台代码也进行了一定的封装,方便移植。
先说下原理:
1、开始执行或者提交文件后,利用JQuery的$.post或者$.get或者$.ajax方法不断请求事务进度。
2、如果是第一次发起请求,后台新开线程进行事务计算,如果不是,则只输出进度信息。
以下详细讲解:
共4个文件,.js文件封装功能函数,.css文件修饰遮罩效果,,.aspx页面前台展示,.cs文件后台事务处理。
以下是.js文件,封装了一些功能函数,写法用了JQuery框架(这个框架想必大家都熟悉了)
/*
遮罩层,一般用于文件上传后显示处理进度
注意:html布局结构不要随意修改,否则样式也需要修改
*/
//显示遮罩层
function ShadeLoad() {
//遮罩层里的内容
var t = "";
t += "<div style=\"float:right;padding:5px 5px 0 0;\"><input type=\"button\" onclick=\"shadeClose()\" value='关闭' /></div>";
t += "<div class='shadeShowProgress'>";
t += "<h1>总共用时:<span>-1</span>秒</h1>";
t += "<h3><div>执行进度提示</div><ol></ol></h3>";
t += "</div>";
//创建遮罩
var sDiv = $("<div class='shadeDiv'></div>").html(t);
$("body").append(sDiv);
//遮罩显示(我的页面是运行在iframe框架里的,所以高度需要这样获取,如果不是,请修改获取页面高度方式)
$(".shadeDiv").css({ "display": "block", "height": "" + $(window.top.document).find("#ifmContent").css("height") + "" });
//启动计时器
shadeTimerStart();
}
//关闭遮罩层
function shadeClose() {
$(".shadeDiv").css({ "display": "none" }); //隐藏遮罩
shadeTimerEnd(); //停止计时
$(".shadeShowProgress h1").children("span").html("-1"); //重置计时器(如果不重置,再次提交事务的时候会累积计时)
$(".shadeShowProgress h3 ol").empty(); //清空进度内容
}
//展示进度内容
function shadePushMsg(msg) {
var textArr = $(".shadeShowProgress h3 ol").html();
if (!msg) { return; }
if (textArr.indexOf(msg) > -1) { return; } //如果获取到的进度是同一个状态,则忽略
$(".shadeShowProgress h3 ol").append("<li>" + msg + "</li>");
}
var shadeTimerHand;
//启动计时
function shadeTimerStart() {
$(".shadeShowProgress h1 span").html(function (index, oldValue) {
return parseInt(oldValue) + 1;
})
shadeTimerHand = setTimeout("shadeTimerStart()", 1000);
}
//停止计时
function shadeTimerEnd() {
clearTimeout(shadeTimerHand);
}
以上是一个简单的遮罩层,带计时功能,用有序项目方式列出每一次的请求状态。
以下.CSS文件时修饰遮罩层用的,大家可以修改成适合自己的方式:
/* 遮罩层 */
.shadeDiv {
position: absolute;
background-color: black;
line-height:180%;
left: 0;
top: 0;
width: 100%;
opacity: 1; /*不透明度*/
z-index: 999;
display: none;
}
.shadeDiv h1, h2, h3, h4 {
font-weight: normal;
font-size: 14px;
}
/* 时间和进度显示区 */
.shadeDiv .shadeShowProgress {
opacity:1; /*这里有个疑问待解决,无论怎么修改此值,不透明度不起作用*/
width: 400px;
height: 250px;
overflow-y: scroll;
margin: 50px auto;
padding: 20px;
background-color: white;
z-index: 9999;
}
/* 计秒部分 */
.shadeDiv h1 span{ color:red; }
.shadeDiv h3 div{font-weight:bolder;}
.shadeDiv h3 ol {padding-left:20px; } /*显示进度文字*/
关于遮罩的事情以上两步就基本解决掉了。
下面是.aspx的前台展示,主要理解如何多次请求事务进度就简单了:(我用的是easyui框架)
<a class="easyui-linkbutton" id="btn" onclick="kaishi('')">点击开始</a>
<script>
var hand2;
function kaishi(zt) {
var url = "456.ashx"; //我用的是一般处理程序来处理后台代码
//zt参数主要为了区分是第一次请求还是重复请求
if (zt == "") {
//显示遮罩
ShadeLoad();
}
else {
url += "?action=" + zt;
}
//用post方法请求进度
$.post(url, function (reData) {
//返回值用json格式,方便处理
var json = $.parseJSON(reData);
shadePushMsg(json.状态); //列出进度
if (json.错误) {
shadePushMsg("<strong style='color:red'>" + json.错误 + "</strong>");
clearTimeout(hand2); //停止状态请求
shadeTimerEnd(); //停止计时器
return;
}
if (json.状态 != "完成") {
//继续请求状态,开始函数的参数是什么无所谓,只要有就可以,就可以区分是否第一次提交。如果不需要这么频繁得请求,修改毫秒数即可。
hand2 = setTimeout("kaishi('1')", 1000);
}
else {
clearTimeout(hand2); //停止状态请求
shadeTimerEnd(); //停止计时器
//easyui函数,如果不清楚的话可以直接用js的alert()
$.messager.alert("结果", "操作完成", "info", function () {
shadeClose();
});
return;
}
});
}
</script>
以下是.cs功能代码,我用的是.ashx一般处理程序,如果不是的话大家稍微改下就可以。最重要的是程序中的多线程和委托的异步调用写法。这方面的知识请参考ASP.NET程序。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;
public class ajax_456 : IHttpHandler
{
public static string zhuangtai; //重要:静态全局变量记录进度信息
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string action = context.Request.QueryString["action"];
//如果是第一次提交请求
if (action == null)
{
//新开线程进行事务处理
Thread t = new Thread(new ThreadStart(workThread));
t.Start();
//重要:由于是静态变量,第一次使用的时候一定要赋初始值,否则它的值还是上次执行完毕的值(可以参考静态变量的生命周期)。
//至于多少个步骤由你程序写好后再确定
zhuangtai = "{\"状态\":\"任务开始(共9个步骤)\"}";
context.Response.Write(zhuangtai);
}
else
{
//如果是重复请求,只反馈回进度状态即可
context.Response.Write(zhuangtai);
context.Response.Flush();
context.Response.End();
}
}
//假设业务复杂,则用类进行封装最好了
private void workThread()
{
操作类 cls = new 操作类();
cls.主函数();
}
public class 操作类
{
string wrong = ""; //记录是否有错误信息
delegate string mydele(); //委托申明。关于委托请参考其他手册。本例最大化提高事务处理性能,所以使用委托和异步调用的方式
public void 主函数()
{
mydele dele1 = new mydele(获取订单表);
mydele dele2 = new mydele(获取宝贝表);
IAsyncResult r1 = dele1.BeginInvoke(null, null);
IAsyncResult r2 = dele2.BeginInvoke(null, null);
//关于BeginInvoke和EndInvoke请参考ASP.NET手册
//简单的可以这么理解:BeginInvoke开启异步调用,程序执行到这一句并不会等它执行完,而是继续往下
//当遇到EndInvoke时,才会返回异步调用的处理结果。
//这一知识点请参考:https://blog.csdn.net/wangzhen199009/article/details/40593603
//或者:https://blog.csdn.net/xxy0403/article/details/51469853
wrong += dele1.EndInvoke(r1);
if (!string.IsNullOrEmpty(wrong)) { zhuangtai = "{\"错误\":\"" + wrong + "\"}"; return; }
wrong += dele2.EndInvoke(r2);
if (!string.IsNullOrEmpty(wrong)) { zhuangtai = "{\"错误\":\"" + wrong + "\"}"; return; }
//线程休眠用于演示事务处理所需要的时间
//至于实际应用中此任务到底用了多少时间,可以用
//DateTime startDateTime = DateTime.Now;
//int minSecond=(int)DateTime.Now.Subtract(startDateTime).TotalMilliseconds
//这两句获得
Thread.Sleep(3000);
zhuangtai = "{\"状态\":\"检查宝贝表是否全部在订单表中用时3秒\"}";
Thread t1 = new Thread(new ThreadStart(更新订单表));
Thread t2 = new Thread(new ThreadStart(更新宝贝表));
t1.Start();
t2.Start();
t1.Join();
t2.Join();
zhuangtai = "{\"状态\":\"完成\"}";
}
public string 获取订单表()
{
Thread.Sleep(5000);
zhuangtai = "{\"状态\":\"获取订单表用时5秒\"}";
Thread.Sleep(2000);
zhuangtai = "{\"状态\":\"检查订单表用时2秒\"}";
return "";
}
public string 获取宝贝表()
{
Thread.Sleep(2000);
zhuangtai = "{\"状态\":\"获取宝贝表用时2秒\"}";
Thread.Sleep(2000);
//如果程序中有需求检测到错误信息则退出线程,可以直接返回错误信息即可。
//亲们测试的时候可以试试不注解下面这句错误信息。(等下讲解中也会截图这样的效果)
//return "检查宝贝表发生错误";
zhuangtai = "{\"状态\":\"检查宝贝表用时2秒\"}";
return "";
}
public void 更新订单表()
{
Thread.Sleep(5000);
zhuangtai = "{\"状态\":\"更新订单表用时5秒\"}";
}
public void 更新宝贝表()
{
Thread.Sleep(3000);
zhuangtai = "{\"状态\":\"更新宝贝表用时3秒\"}";
}
}
//.ashx自动生成的方法
public bool IsReusable
{
get
{
return false;
}
}
}
四个主要文件和步骤就是这样,以下附上测试截图:
以下是程序执行中,每过一秒返回一次状态:
以下是程序顺利执行完成,共返回了8个进度提示。最后的确定框大家可以不需要。笔者这里是再提示状态完成,点击确定后遮罩关闭,如果点击的是提示框的关闭按钮(右上角),则保留遮罩页面。
只是关闭了结果提示框后的界面,再点击遮罩层右上角的关闭按钮,界面回复到初始状态。
以下演示如果程序运行时有错误提示(不是指程序错误,而是事务处理遇到错误信息):
获取到错误信息后程序停止,不再进行下一步。
以上就是全部的讲解。刨去示例中的委托和异步调用,这样的代码还算简单的。有不足之处欢迎指正!