在傳統桌面項目中,進度條隨處可見,但做爲一個很好的用戶體驗,卻沒有在現在主流的B/S程序中獲得傳承,不能不說是個遺憾。這個遺憾並不是WEB程序不支持進度條,很大的緣由應該是咱們因爲種種麻煩懶的去實現。前段時間因爲新項目等待客戶驗收,有點閒暇時間,因而突發奇想決定給新系統的全部導出功能添加進度提示,替換現正使用的只簡單顯示一個Loading圖片做爲提示的方法,因而有了本文。javascript
實現的思路很簡單:服務器端收到客戶端一個須要較長時間執行的任務 -> 服務器端開始執行這個任務並建立一個 Progress 狀態,保存在 Cache 中,在任務執行過程當中,不斷更新進度 -> 同時,客戶端新取一個線程(異步),每隔0.5秒(通過測試,0.5秒是一個比較好的時間,既不容易形成客戶端網絡擁堵,也能帶來至關好的用戶體驗)訪問一次服務器以得到該任務的進度並呈現給終端用戶。java
下面是本人的實現方法,它能支持全部須要精確計算進度的任務,以爲有必定的參考性,先後臺代碼分別採用 Javascript 和 C# 實現,分享給你們。json
服務器端咱們須要作 2 件事情:進度管理類和一個供客戶端查詢狀態的頁面(採用 Handler實現)。api
首先是進度管理類,主要用於記錄任務總數和當前已經完成的數目,同時自行管理緩存狀態,以方便客戶端隨時訪問。代碼以下:緩存
using Cache; using System; namespace RapidWebTemplate.WebForm { /// <summary> /// 服務器事件進度服務 /// </summary> public sealed class ProgressService { // 緩存保存的時間,可根據本身的項目設置 private const int CACHE_SECONDS = 600; // 任務惟一ID private string _key = null; private ProgressService() { } /// <summary> /// 獲取或設置總進度 /// </summary> public int Total { get; set; } /// <summary> /// 獲取或設置已經完成的進度 /// </summary> public int Elapsed { get; set; } /// <summary> /// 獲取已經完成的進度百分比 /// </summary> public byte ElapsedPercent { get { if (Finished) { return 100; } double d = (double)Elapsed / (double)Total * 100d; var tmp = Convert.ToInt32(Math.Ceiling(d)); if (tmp > 100) { tmp = 100; } if (tmp < 0) { tmp = 0; } return Convert.ToByte(tmp); } } /// <summary> /// 獲取一個值,該值指示當前進度是否已經完成 /// </summary> public bool Finished { get { return Elapsed >= Total; } } public void Remove() { try { CacheFactory.Remove(_key); } catch { } } /// <summary> /// 獲取一個緩存中的進度對象或建立一個全新的進度對象並添加到緩存中 /// </summary> /// <param name="key"></param> /// <returns></returns> public static ProgressService GetInstance(string key) { var obj = CacheFactory.GetCache(key) as ProgressService; if (obj == null) { obj = new ProgressService(); obj._key = key; CacheFactory.Add(key, obj, DateTime.Now.AddSeconds(CACHE_SECONDS)); } return obj; } } }
接下來是查詢頁面,命名爲 Progress.ashx,後臺代碼以下:服務器
using RapidWebTemplate.WebForm; using System; using System.Collections.Generic; using System.Linq; using System.Web; using Utility; using Utility.Http; namespace Web.Handlers { /// <summary> /// 獲取服務端指定任務執行的進度 /// </summary> public class Progress : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/json"; var key = FormHelper.GetString(context.Request.QueryString["progress_key"]); var obj = ProgressService.GetInstance(key); context.Response.Write(obj.ToJSON()); if (obj.Finished) { obj.Remove(); } } public bool IsReusable { get { return false; } } } }
到此,咱們已經完成了後臺代碼的編寫,下面將是前臺代碼的編寫,這裏以導出 Excel 爲例,代碼以下:網絡
var js={ doExport:function(icon){ var key=''+(new Date().getTime())+(Math.floor(Math.random()*10)); var btns=$('#btnExport'); var showProgress=false; if(btns.size()>0){ //var form=btns.first().parent('form'); //form.attr('target','_blank'); var input=$('#download_key'); if(input.size()>0){ input.val(key); }else{ btns.first().parents('form').append('<input type="hidden" id="download_key" name="download_key" value="'+key+'"/>'); } btns.first().trigger('click'); showProgress=true; }else{ js.info('Not supported.'); } var me=this; setTimeout(function(){ $(document.body).hideLoading(); if(showProgress){ me._showProgress(key); } },500); }, _showProgress:function(key){ var id='progress_bar'; var me=this; if($('#'+id).size()>0){ }else{ $(document.body).append('<div id="'+id+'_dialog"><div id="'+id+'"></div></div>'); } $('#'+id+'_dialog').dialog({ //title:'\u8bf7\u7a0d\u540e...', // please wait width:400, //height:60, modal:true, closable:false, border:false, noheader:true, onOpen:function(){ $(this).children().first().progressbar({value:0}); setTimeout(function(){ me._updateProgessState(key,id,me); },1000); } }); }, _progressStateTimer:null, _updateProgessState:function(key,id,ns){ var url='/Handlers/Progress.ashx?progress_key='+key; url+='&ran='+(new Date().getTime()); $.get(url,function(res){ //res={"Total":0,"Elapsed":0,"ElapsedPercent":100,"Finished":true} if(res.Finished){ $('#'+id).progressbar('setValue',100); setTimeout(function(){ $('#'+id+'_dialog').dialog('destroy');},500); // Wait for 0.5 seconds to close the progress dialog clearTimeout(ns._progressStateTimer); }else{ //alert(res.Elapsed); $('#'+id).progressbar('setValue',res.ElapsedPercent); ns._progressStateTimer=setTimeout(function(){ns._updateProgessState(key,id,ns);},500); } }); }, };
全部必要的代碼已經編寫完成,下面是服務器端對進度服務的使用,仍是以導出 Excel 爲例,須要在原有的代碼基礎上,加入進度的管理(註釋部分的A、B、C 3 段),代碼以下:app
/// <summary> /// 導出當前列表數據到Excel /// </summary> protected void ExportToExcel() { using (var outputStm = new MemoryStream()) { using (var document = new SLDocument()) { var fields = this.ExportedFields; //先輸出表頭 for (var i = 0; i < fields.Count; i++) { document.SetCellValue(1, i + 1, fields[i].Label); } //輸出內容 if (GridControl != null) { var ps = ProgressService.GetInstance(FormHelper.GetString(Request.Form["download_key"])); // A:建立進度 var f = GetFilter(); var itemCount = 0; var source = GetGridSource(f, GridControl.GetSortExpression().ToOrder(CurrentDAL), int.MaxValue, 1, out itemCount); ps.Total = itemCount; // B: 設置總進度 var row = 2; object value = null; foreach (var item in source) { for (var col = 0; col < fields.Count; col++) { #if DEBUG System.Threading.Thread.Sleep(50); #endif value = item.GetType().GetProperty(fields[col].Field).GetValue(item, null); document.SetCellValue(row, col + 1, value); } ps.Elapsed += 1; // C: 更新已經完成的進度 row++; } } document.SaveAs(outputStm); } outputStm.Position = 0; WebUtility.WriteFile(outputStm, outputStm.Length, this.Response, ExportedFileName + ".xlsx"); } }
最後附上效果圖dom