ASP.NET MVC搭建項目后台UI框架—7、統計報表


  1. ASP.NET MVC搭建項目后台UI框架—1、后台主框架
  2. ASP.NET MVC搭建項目后台UI框架—2、菜單特效
  3. ASP.NET MVC搭建項目后台UI框架—3、面板折疊和展開
  4. ASP.NET MVC搭建項目后台UI框架—4、tab多頁簽支持
  5. ASP.NET MVC搭建項目后台UI框架—5、Demo演示Controller和View的交互
  6. ASP.NET MVC搭建項目后台UI框架—6、客戶管理(添加、修改、查詢、分頁)
  7. ASP.NET MVC搭建項目后台UI框架—7、統計報表
  8. ASP.NET MVC搭建項目后台UI框架—8、將View中選擇的數據行中的部分數據傳入到Controller中
  9. ASP.NET MVC搭建項目后台UI框架—9、服務器端排序

本節,我將通過一個Demo,演示Datatables 和ASP.NET MVC的完美結合,可以這么說,如果這樣的界面都能做出來,后台系統90%的界面功能都可以開發出來了。

用jquery Datatables 來開發確實是件比較蛋疼的事情(和Jquery EasyUI、MiniUI、ExtJs相比),用其它的第三方UI框架來實現相同的功能真是非常非常的簡單,可是使用Datatables卻是那么的吃力,至少我這么覺得,可能是因為我對這個控件使用得還不夠純熟。在官網,datatables默認使用的是bootstraps的樣式,這里我已經把樣式重寫了一部分。

看見公司原有的系統,同樣是使用ASP.NET MVC做的,在頁面隨便點擊個東東,整個界面就刷新了,刷得我自己都受不了,更別指望固定表頭啊什么什么的了,完全不存在用戶體驗啊!於是我就自己寫了UI框架(也可以說是組裝,但是我重寫了許多東西)。

技術點:1、服務器端分頁。2、查詢(模糊查詢)3、界面操作刷新后依舊保留當前分頁 4、固定表頭、表尾 5、動態控制列的隱藏和顯示 6、全選、反選(數據行中復選框全部選中時,全選按鈕自動選中,我發現很多程序員這個功能一直沒做,可是說是bug么?)  7、服務器排序(功能我已經開發出來了,但是這里我沒有寫上去,這個網上我還沒有看到實現的Demo) 8、特殊字段標紅顯示 9、滑動變色 10、單擊行選中變色 ....

先看下效果:

點擊圖片,折疊或展開列

新建Reconciliation控制器

using Core.CostFlow;
using Core.Filters;
using Core.Reconciliation;
using Data.Reconciliation;
using ProjectBase.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ProjectBase.Utils.Entities;

namespace Site.Controllers
{
    public class ReconciliationController : Controller
    {
        //運單對賬
        public ActionResult WayBill()
        {
            return View();
        }

        [HttpPost]
        public JsonResult WayBillList(WayBillReconciliationFilter filter)
        {
            DataTablesRequest parm = new DataTablesRequest(this.Request);    //處理對象
            int pageIndex = parm.iDisplayStart / parm.iDisplayLength;
            filter.PageIndex = pageIndex;    //頁索引
            filter.PageSize = parm.iDisplayLength;    //頁行數
            var DataSource = WayBillReconciliation.GetByFilter(filter) as WRPageOfList<WayBillReconciliation>;

            int i = parm.iDisplayLength * pageIndex;

            List<WayBillReconciliation> queryData = DataSource.ToList();
            var data = queryData.Select(u => new
           {
               Index = ++i, //行號
               ID = u.ID,
               CusName = u.CusName, //客戶簡稱
               PostingTime =u.PostingTime==null?string.Empty: u.PostingTime.Value.ToStringDate(),//收寄日期
               ExpressNo = u.ExpressNo, //郵件號
               BatchNO = u.LoadBillNum, //提單號
               Weight = u.Weight==null ? 0m : u.Weight / 100, //重量
               WayBillFee = u.WayBillFee, //郵資
               ProcessingFee = u.ProcessingFee, //郵政郵件處理費
               InComeWayBillFee = u.ExpressFee, //客戶運費
               InComeOprateFee = u.OperateFee, //客戶操作費
               WayBillMargins = u.WayBillProfit, //運費毛利
               TotalMargins = u.ExpressFee + u.OperateFee + u.InComeOtherFee-(u.WayBillFee + u.ProcessingFee + u.CostOtherFee), //總毛利
               Margin = (u.ExpressFee + u.OperateFee + u.InComeOtherFee == 0 ? 0m : (u.ExpressFee + u.OperateFee + u.InComeOtherFee - (u.WayBillFee + u.ProcessingFee + u.CostOtherFee)) / (u.ExpressFee + u.OperateFee + u.InComeOtherFee) * 100) + "%", //毛利率 毛利率=(總收入-總的支出的成本)/總收入*100% 
                           
               ReconcileDate=DateTime.Now.ToString("yyyy-MM"), //對賬日期
               CostOtherFee = u.CostOtherFee, //成本 其他費用
               CostTotalFee = u.WayBillFee + u.ProcessingFee+u.CostOtherFee, //成本 總費用
               CostStatus = u.CostStatus.ToChinese(),  //成本 狀態
               InComeOtherFee = u.InComeOtherFee, //收入 其他費用
               InComeTotalFee = u.ExpressFee + u.OperateFee+u.InComeOtherFee, //收入 總費用
               InComeStatus = u.InComeStatus.ToChinese(),  //收入 狀態
               TotalStatus=""
           });
            decimal totalProfit = 0m;      //總毛利求和
            //構造成Json的格式傳遞
            var result = new
            {
                iTotalRecords = DataSource.Count,
                iTotalDisplayRecords = DataSource.RecordTotal,
                data = data,
                TotalWeight = DataSource.StatModelBy.TotalWeight/100,
                TotalWayBillFee = DataSource.StatModelBy.TotalWayBillFee,
                               TotalProcessingFee = DataSource.StatModelBy.TotalProcessingFee,
                               TotalExpressFee = DataSource.StatModelBy.TotalExpressFee,
                               TotalOperateFee = DataSource.StatModelBy.TotalOperateFee,
                               SumWayBillProfit = DataSource.StatModelBy.TotalWayBillProfit,
                               SumTotalProfit = totalProfit
            };
            return Json(result, JsonRequestBehavior.AllowGet);
        }

        /// <summary>
        /// 提單對賬
        /// </summary>
        /// <returns></returns>
        public ActionResult LoadBill()
        {
            return View();
        }

        public JsonResult LoadBillList()
        {
            return Json(null, JsonRequestBehavior.AllowGet);
        }
    }
}
View Code

新建WayBill視圖

@{
    ViewBag.Title = "運費對賬";
}
<style type="text/css">
    .numberColor {
        color:red;
    }
</style>
<link href="~/libs/DataTables-1.10.6/media/css/jquery.dataTablesNew.css" rel="stylesheet" />
<script src="~/libs/DataTables-1.10.6/media/js/jquery.dataTables.min.js"></script>
<script src="~/Scripts/DataTablesExt.js"></script>
<script src="~/libs/My97DatePicker/WdatePicker.js"></script>
<script type="text/javascript">
    $(function () {
        var h = $(document).height() - 312;
        var table = $("#table_local").dataTable({
            bProcessing: true,
            "scrollY": h,
            "scrollCollapse": "true",
            "dom": 'tr<"bottom"lip><"clear">',
            "bServerSide": true,                    //指定從服務器端獲取數據  
            sServerMethod: "POST",
            showRowNumber:true,
            sAjaxSource: "@Url.Action("WayBillList", "Reconciliation")",
            "initComplete": function (data, args) {
                //getTotal(args);
                var arr = new Array(7,8,9,12,13,14);
                controlColumnShow(table, arr,false);
            },
            "fnServerParams": function (aoData) {  //查詢條件
                aoData.push(
                    { "name": "CusShortName", "value": $("#CusShortName").val() },
                    { "name": "LoadBillNum", "value": $("#LoadBillNum").val() },
                    { "name": "ExpressNo", "value": $("#ExpressNo").val() },
                    { "name": "PostingTime", "value": $("#PostingTime").val() },
                    { "name": "PostingTimeTo", "value": $("#PostingTimeTo").val() }
                    // ,{ "name": "PostingTimeTo", "value": $("#sltMargin").val() }
                     );
            },
            //跟數組下標一樣,第一列從0開始,這里表格初始化時,第四列默認降序
            "order": [[ 2, "asc" ]],
            columns: [
               {
                   "data": "ID", orderable: false,
                   "render": function (data, type, row, meta) {
                       return " <input id='cbx" + data + "' type='checkbox' onclick='controlSelectAll(" + data + ")' class='cbx' value='" + data + "'/>  " + row.Index;
                   }
               },                                                      
                { "data": "ReconcileDate",visible:false},//對賬日期
                { "data": "CusName" }, //客戶名稱
                { "data": "PostingTime"},//收寄日期
                { "data": "ExpressNo", orderable: false }, //郵件號
                { "data": "BatchNO"},//提單號
                { "data": "Weight"},//重量
                { "data": "WayBillFee"},//郵政郵資
                { "data": "ProcessingFee" },//郵政郵件處理費
                { "data": "CostOtherFee"},//其它費用
                { "data": "CostTotalFee" },//總成本
                { "data": "CostStatus", orderable: false },//狀態
                { "data": "InComeWayBillFee" },//客戶運費
                { "data": "InComeOprateFee"},//客戶操作費
                { "data": "InComeOtherFee"},//其它費用
                { "data": "InComeTotalFee" },//總收入
                { "data": "InComeStatus", orderable: false },//狀態
                 {
                     "data": "WayBillMargins", orderable: false, "render": function (data, type, row, meta) { //運費毛利
                         var css = "";
                         if (data < 0) {
                             css=" class='numberColor'";
                         }
                         var re = "<div"+css+">"+data+"</div>";
                         return re;
                     }
                 },
                  {
                      "data": "TotalMargins", orderable: false, "render": function (data, type, row, meta) { //總毛利
                          var css = "";
                          if (data < 0) {
                              css = " class='numberColor'";
                          }
                          var re = "<div" + css + ">" + data + "</div>";
                          return re;
                      }
                  },
                { "data": "Margin", orderable: false },//毛利率
                { "data": "TotalStatus", orderable: false },
                 {
                     "data": "ID", orderable: false, width: "80", "render": function (data, type, row, meta) { //操作
                         var re = "<div style='text-align:center'><a style='visibility:visible' onclick='openDetail(" + data + ")'>詳情</a>&nbsp;&nbsp;";
                         return re;
                     }
                 }
            ],
            paging: true,//分頁
            ordering: true,//是否啟用排序
            searching: true,//搜索
            language: {
                "sProcessing": "處理中...",
                lengthMenu: '每頁顯示:<select class="form-control input-xsmall">' + '<option value="10">10</option>' + '<option value="20">20</option>' + '<option value="30">30</option>'
                    + '<option value="50">50</option>' + '<option value="100">100</option>' + '<option value="150">150</option>' + '<option value="200">200</option>' + '<option value="250">250</option>',//左上角的分頁大小顯示。
                search: '<span class="label label-success">搜索:</span>',//右上角的搜索文本,可以寫html標簽

                paginate: {//分頁的樣式內容。
                    previous: "上一頁",
                    next: "下一頁",
                    first: "",
                    last: ""
                },

                zeroRecords: "暫無記錄",//table tbody內容為空時,tbody的內容。
                //下面三者構成了總體的左下角的內容。
                info: "總共 <span class='pagesStyle'>(_PAGES_) </span>頁,顯示 _START_ -- _END_ ,共<span class='recordsStyle'> (_TOTAL_)</span> 條",//左下角的信息顯示,大寫的詞為關鍵字。初始_MAX_ 條 
                infoEmpty: "0條記錄",//篩選為空時左下角的顯示。
                infoFiltered: ""//篩選之后的左下角篩選提示,
            },
            pagingType: "full_numbers"//分頁樣式的類型
      });
        //設置選中行樣式
        $('#table_local tbody').on('click', 'tr', function () {
            if ($(this).hasClass('selected')) {
                $(this).removeClass('selected');
            }
            else {
                table.$('tr.selected').removeClass('selected');
                $(this).addClass('selected');
            }
        });
        //展開折疊列
        $("#imgIncome").click(function () {
            var url = $("#imgIncome").attr("src");
            var arr = new Array(7, 8, 9);
            if (url == "/images/icon_9.png") {
                controlColumnShow(table, arr, true);
                $("#imgIncome").attr("src", "/images/icon_10.png");
            }
            else {
                controlColumnShow(table, arr, false);
                $("#imgIncome").attr("src", "/images/icon_9.png");
            }
            
        });
        //收入展開折疊
        $("#imgCost").click(function () {
            var url = $("#imgCost").attr("src");
            var arr = new Array(12, 13, 14);
            if (url == "/images/icon_9.png") {
                controlColumnShow(table, arr, true);
                $("#imgCost").attr("src", "/images/icon_10.png");
            }
            else {
                controlColumnShow(table, arr, false);
                $("#imgCost").attr("src", "/images/icon_9.png");
            }
        });
    });
    function reloadList() {
        var tables = $('#table_local').dataTable().api();//獲取DataTables的Api,詳見 http://www.datatables.net/reference/api/
        tables.ajax.reload(function () {
            var json = tables.context[0].json;
            getTotal(json);
        }, false);
    }
    //統計
    function getTotal(json) {
        if (json) {
            if (json.TotalWeight) {
                $("#spnTotalWeight").html(json.TotalWeight);
                $("#spnTotalWayBillFee").html(json.TotalWayBillFee);
                $("#spnTotalProcessingFee").html(json.TotalProcessingFee);
                $("#spnTotalExpressFee").html(json.TotalExpressFee);
                $("#spnTotalOperateFee").html(json.TotalOperateFee);
                $("#spnSumWayBillProfit").html(json.SumWayBillProfit);
                $("#spnSumTotalProfit").html(json.SumTotalProfit);
            }
        }
    }
    //控制指定定列的隱藏和顯示(table,列索引數組,隱藏or顯示:true,false)
    function controlColumnShow(table, arr,tag) {
        for (var i = 0; i < arr.length; i++) {
            table.fnSetColumnVis(arr[i],tag);
        }
    }
</script>
<div class="areabx clear">
    @using (Html.BeginForm("List", null, FormMethod.Get, new { @clase = "form-inline", @role = "form" }))
    {
        <div class="areabx_header">@ViewBag.Title</div>
        <ul class="formod mgt10">
            <li><span>客戶簡稱:</span>@Html.TextBox("CusShortName","",new { @class = "trade-time wid153" })</li>
            <li><span>提單號:</span>@Html.TextBox("LoadBillNum","", new { @class = "trade-time" })</li>
        </ul>
        <ul class="formod mgt10">
            <li><span>運單號:</span>@Html.TextBox("ExpressNo","", new { @class = "trade-time wid153" })</li>
            <li><span>收寄日期:</span>@Html.TextBox("PostingTime", "", new { @class = "trade-time wid153", @onClick = "WdatePicker({maxDate:'#F{$dp.$D(\\'PostingTimeTo\\')}'})" })</li>
            <li><span style="text-align:left;width:25px;margin-left:-20px;"></span>  @Html.TextBox("PostingTimeTo", "", new { @class = "trade-time wid153", @onClick =  "WdatePicker({minDate:'#F{$dp.$D(\\'PostingTime\\')}'})"  })</li>
            <li><span>毛利:</span><select class="trade-time" id="sltMargin"><option value="" selected="selected">全部</option><option value="+">+</option><option value="-">-</option></select></li>
        </ul>
        <div class="botbtbx pdb0">
            <input type="button" value="查詢" id="btnSearch" onclick="reloadList();" class="btn btn-primary" />
        </div>
    }
    <div class="tob_box mgt15">
        <table id="table_local" class="display" cellspacing="0" cellpadding="0" border="0" style="width: 100%">
            <thead>
                <tr>
                    <th rowspan="2">
                     <input type='checkbox' id='chkAllColl' onclick='selectAll()' />序號</th>
                    <th rowspan="2">對賬日期</th>
                    <th rowspan="2">客戶簡稱</th>
                    <th rowspan="2">收寄日期</th>
                    <th rowspan="2">郵件號</th>
                    <th rowspan="2">提單號</th>
                    <th rowspan="2">重量(kg)</th>
                    <th colspan="5"><span>成本</span><span class="divIncome1"><img id="imgIncome" src="/images/icon_9.png"  alt="收起/展開"/></span></th>
                    <th colspan="5"><span>收入</span><span class="divIncome1"><img id="imgCost" src="/images/icon_9.png" alt="收起/展開"/></span></th>
                    <th colspan="3">毛利</th>
                    <th rowspan="2">狀態</th>
                    <th rowspan="2">操作</th>
                </tr>
                <tr>
                    <th>郵政郵資</th>
                    <th>郵政郵件處理費</th>
                    <th>其它費用</th>
                    <th>總成本</th>
                    <th>狀態</th>
                    <th>客戶運費</th>
                    <th>客戶操作費</th>
                    <th>其它費用</th>
                    <th>總收入</th>
                    <th>狀態</th>
                    <th>運費毛利</th>
                    <th>總毛利</th>
                    <th>毛利率</th>
                </tr>
            </thead>
            <tfoot>
                <tr>
                    <td>總計</td>
                    <td></td>
                    <td></td>
                    <td></td>
                    <td></td>
                    <td></td>
                    <td><span id="spnTotalWeight"></span></td>
                    <td><span id="spnTotalWayBillFee"></span></td>
                    <td><span id="spnTotalProcessingFee"></span></td>
                    <td></td>
                    <td></td>
                    <td></td>
                    <td><span id="spnTotalExpressFee"></span></td>
                    <td><span id="spnTotalOperateFee"></span></td>
                    <td></td>
                    <td></td>
                    <td></td>
                    <td><span id="spnSumWayBillProfit"></span></td>
                    <td><span id="spnSumTotalProfit"></span></td>
                    <td></td>
                    <td></td>
                    <td></td>
                </tr>
            </tfoot>
        </table>
    </div>
</div>
View Code

這里面  table.fnSetColumnVis(arr[i], tag);這行代碼控制列動態隱藏和展示的時候,會重新加載數據,可以在后面加一個false參數,取消刷新。 如: table.fnSetColumnVis(arr[i], tag,false);

請求參數封裝類DataTablesRequest,這個類是從冠軍的博客下載的,它主要用於解析datatables的請求參數,由於datatables支持多列排序,所以比較復雜。下載的這個類有點問題,那就是獲取的排序方式一直是asc,於是我進行了修改,修改后的代碼如下:

/* ==============================================================================
   * 功能描述:DataTablesRequest  
   * 創 建 者:Zouqj
   * 創建日期:2015/4/21 17:47:35
   ==============================================================================*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace ProjectBase.Utils
{
    // 排序的方向
    public enum SortDirection
    {
        Asc,    // 升序
        Desc    // 降序
    }

    // 排序列的定義
    public class SortColumn
    {
        public int Index { get; set; }                  // 列序號
        public SortDirection Direction { get; set; }    // 列的排序方向
    }

    // 列定義
    public class Column
    {
        public string Name { get; set; }        // 列名
        public bool Sortable { get; set; }      // 是否可排序
        public bool Searchable { get; set; }    // 是否可搜索
        public string Search { get; set; }      // 搜索串
        public bool EscapeRegex { get; set; }   // 是否正則
    }

    public class DataTablesRequest
    {
        private HttpRequestBase request;        // 內部使用的 Request 對象

        public DataTablesRequest(System.Web.HttpRequestBase request)    // 用於 MVC 模式下的構造函數
        {
            this.request = request;

            this.echo = this.ParseStringParameter(sEchoParameter);
            this.displayStart = this.ParseIntParameter(iDisplayStartParameter);
            this.displayLength = this.ParseIntParameter(iDisplayLengthParameter);
            this.sortingCols = this.ParseIntParameter(iSortingColsParameter);

            this.search = this.ParseStringParameter(sSearchParameter);
            this.regex = this.ParseStringParameter(bRegexParameter) == "true";

            // 排序的列
            int count = this.iSortingCols;
            this.sortColumns = new SortColumn[count];
            for (int i = 0; i < count; i++)
            {
                SortColumn col = new SortColumn();
                col.Index = this.ParseIntParameter(string.Format("iSortCol_{0}", i));

                if (this.ParseStringParameter(string.Format("sSortDir_{0}", i)) == "desc")
                {
                    col.Direction = SortDirection.Desc;
                }
                else
                {
                    col.Direction = SortDirection.Asc;
                }
                this.sortColumns[i] = col;
            }

            this.ColumnCount = this.ParseIntParameter(iColumnsParameter);

            count = this.ColumnCount;
            this.columns = new Column[count];

            if(this.ParseStringParameter(sColumnsParameter)==null||!this.ParseStringParameter(sColumnsParameter).Contains(','))
            {
                   return;
            }
            string[] names = this.ParseStringParameter(sColumnsParameter).Split(',');

            for (int i = 0; i < count; i++)
            {
                Column col = new Column();
                col.Name = names[i];
                col.Sortable = this.ParseStringParameter(string.Format("bSortable_{0}", i)) == "true";
                col.Searchable = this.ParseStringParameter(string.Format("bSearchable_{0}", i)) == "true";
                col.Search = this.ParseStringParameter(string.Format("sSearch_{0}", i));
                col.EscapeRegex = this.ParseStringParameter(string.Format("bRegex_{0}", i)) == "true";
                columns[i] = col;
            }
        }
        public DataTablesRequest(HttpRequest httpRequest)       // 標准的 WinForm 方式下的構造函數
            : this(new HttpRequestWrapper(httpRequest))
        { }

        #region
        private const string sEchoParameter = "sEcho";

        // 起始索引和長度
        private const string iDisplayStartParameter = "iDisplayStart";
        private const string iDisplayLengthParameter = "iDisplayLength";

        // 列數
        private const string iColumnsParameter = "iColumns";
        private const string sColumnsParameter = "sColumns";

        // 參與排序列數
        private const string iSortingColsParameter = "iSortingCols";
        private const string iSortColPrefixParameter = "iSortCol_";         // 排序列的索引
        private const string sSortDirPrefixParameter = "sSortDir_";         // 排序的方向 asc, desc

        // 每一列的可排序性
        private const string bSortablePrefixParameter = "bSortable_";

        // 全局搜索
        private const string sSearchParameter = "sSearch";
        private const string bRegexParameter = "bRegex";

        // 每一列的搜索
        private const string bSearchablePrefixParameter = "bSearchable_";
        private const string sSearchPrefixParameter = "sSearch_";
        private const string bEscapeRegexPrefixParameter = "bRegex_";
        #endregion

        private readonly string echo;
        public string sEcho
        {
            get { return echo; }
        }

        private readonly int displayStart;
        public int iDisplayStart
        {
            get { return this.displayStart; }
        }

        private readonly int displayLength;
        public int iDisplayLength
        {
            get { return this.displayLength; }
        }

        // 參與排序的列
        private readonly int sortingCols;
        public int iSortingCols
        {
            get { return this.sortingCols; }
        }

        // 排序列
        private readonly SortColumn[] sortColumns;
        public SortColumn[] SortColumns
        {
            get { return sortColumns; }
        }

        private readonly int ColumnCount;
        public int iColumns
        {
            get { return this.ColumnCount; }
        }

        private readonly Column[] columns;
        public Column[] Columns
        {
            get { return this.columns; }
        }

        private readonly string search;
        public string Search
        {
            get { return this.search; }
        }

        private readonly bool regex;
        public bool Regex
        {
            get { return this.regex; }
        }

        #region 常用的幾個解析方法
        private int ParseIntParameter(string name)          // 解析為整數
        {
            int result = 0;
            string parameter = this.request[name];
            if (!string.IsNullOrEmpty(parameter))
            {
                int.TryParse(parameter, out result);
            }
            return result;
        }

        private string ParseStringParameter(string name)    // 解析為字符串
        {
            return this.request[name];
        }

        private bool ParseBooleanParameter(string name)     // 解析為布爾類型
        {
            bool result = false;
            string parameter = this.request[name];
            if (!string.IsNullOrEmpty(parameter))
            {
                bool.TryParse(parameter, out result);
            }
            return result;
        }
        #endregion
    }
}
View Code

本篇我不想做過多的說明,我寫了非常詳實的注釋,而且代碼非常通俗易懂,界面的功能還是非常強大的,我相信,從這些犀利的代碼中,你一定會獲益良多。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM