/// <summary>
        /// 构建用户留存率表格
        /// </summary>
        /// <param name="period"></param>
        /// <param name="retainedUsers"></param>
        /// <param name="forDown"></param>
        /// <param name="tableName"></param>
        /// <param name="activeUsers"></param>
        /// <returns></returns>
        public static string BuildStatRetainedUsersTable(int period, List <net91com.Reports.Entities.DataBaseEntity.Sjqd_StatRetainedUsers> retainedUsers, bool forDown, string tableName, bool activeUsers = false)
        {
            #region 表头开始

            StringBuilder htmlBuilder = new StringBuilder();
            if (forDown)
            {
                htmlBuilder.Append("<table border=\"1\">");
            }
            else
            {
                htmlBuilder.AppendFormat("<table id=\"tab0\" class=\"tablesorter\" name=\"{0}\" cellspacing=\"1\">", tableName);
            }

            if (statRetainedUsersXmlDoc == null)
            {
                statRetainedUsersXmlDoc = new XmlDocument();
                statRetainedUsersXmlDoc.Load(HttpContext.Current.Server.MapPath("~/DataTables/StatRetainedUsers.xml"));
            }
            TableTemplate.IsTrueHandler     IsTrue    = new TableTemplate.IsTrueHandler(IsTrue_StatRetainedUsers);
            NodeCondition_StatRetainedUsers nCodition = new NodeCondition_StatRetainedUsers
            {
                Period = (net91com.Stat.Core.PeriodOptions)period
            };
            TableTemplate.ColumnNode rootNode = new TableTemplate.ColumnNode {
                CorrespondXmlNode = statRetainedUsersXmlDoc.DocumentElement
            };
            //生成表头,并返回数据绑定列
            List <TableTemplate.ColumnNode> dataColumns;
            string head = TableTemplate.BuildTableHead(rootNode, IsTrue, nCodition, out dataColumns);
            htmlBuilder.Append(activeUsers ? head.Replace("新增", "活跃") : head);

            #endregion

            #region 表体开始

            htmlBuilder.Append("<tbody>");
            var usersGrpByOrgDate = retainedUsers.GroupBy(a => a.OriginalDate).OrderByDescending(a => a.Key);
            var dataTable         = new double[usersGrpByOrgDate.Count(), dataColumns.Count];

            for (int i = 0; i < usersGrpByOrgDate.Count(); i++)
            {
                htmlBuilder.Append("<tr style=\"text-align:right;\">");
                var columnIndex = 0;
                foreach (TableTemplate.ColumnNode col in dataColumns)
                {
                    htmlBuilder.Append("<td>");
                    DateTime key  = usersGrpByOrgDate.ElementAt(i).Key;
                    var      temp = usersGrpByOrgDate.ElementAt(i).ToList();

                    switch (col.Name)
                    {
                    case "OrignalDate":
                        htmlBuilder.AppendFormat("{0:yyyy-MM-dd}", key);
                        break;

                    case "OrignalNewUserCount":
                        htmlBuilder.AppendFormat("{0:N0}", temp.ElementAt(0).OriginalNewUserCount);
                        dataTable[i, columnIndex] = temp.ElementAt(0).OriginalNewUserCount;
                        break;

                    default:
                        var str = GetRetainedUserCountString(temp, col, (net91com.Stat.Core.PeriodOptions)period);
                        htmlBuilder.Append(GetRetainedUserCountString(temp, col, (net91com.Stat.Core.PeriodOptions)period));
                        var val = 0.0;
                        double.TryParse(str.Replace("%", ""), out val);
                        if (str.Contains("%"))
                        {
                            val = val / 100;
                        }

                        dataTable[i, columnIndex] = val;
                        break;
                    }
                    htmlBuilder.Append("</td>");
                    columnIndex++;
                }
                htmlBuilder.Append("</tr>");
            }

            var sum = new double[dataColumns.Count];
            for (int col = 0; col < dataColumns.Count; col++)
            {
                int counter = 0;
                for (int row = 0; row < usersGrpByOrgDate.Count(); row++)
                {
                    sum[col] += dataTable[row, col];
                    if (!dataTable[row, col].Equals(0))
                    {
                        counter++;
                    }
                }
                sum[col] = sum[col] / counter;
            }
            htmlBuilder.Append("<tr style='text-align:right;'><td>均值</td>");
            for (int i = 1; i < sum.Length; i++)
            {
                if (double.IsNaN(sum[i]))
                {
                    htmlBuilder.Append("<td></td>");
                    continue;
                }
                htmlBuilder.Append("<td>" + (sum[i] > 1 ? sum[i].ToString("N0") : sum[i].ToString("0.00%")) + "</td>");
            }
            htmlBuilder.Append("</tr>");


            #endregion

            #region 表尾开始

            htmlBuilder.Append("</tbody></table>");

            #endregion

            return(htmlBuilder.ToString());
        }
        /// <summary>
        /// 获取留存量索引
        /// </summary>
        /// <param name="period"></param>
        /// <param name="orignalDate"></param>
        /// <param name="statDate"></param>
        private static string GetRetainedUserCountString(List <net91com.Reports.Entities.DataBaseEntity.Sjqd_StatRetainedUsers> retainedUsers, TableTemplate.ColumnNode col, net91com.Stat.Core.PeriodOptions period)
        {
            int index = int.Parse(col.Name.Replace("RetainedUserCount", string.Empty).Replace("RetainedRate", string.Empty));

            for (int t = 0; t < retainedUsers.Count; t++)
            {
                switch (period)
                {
                case net91com.Stat.Core.PeriodOptions.Daily:
                    if (retainedUsers[t].StatDate.Subtract(retainedUsers[t].OriginalDate).Days != index)
                    {
                        continue;
                    }
                    break;

                case net91com.Stat.Core.PeriodOptions.Weekly:
                    if (retainedUsers[t].StatDate.Subtract(retainedUsers[t].OriginalDate).Days / 7 != index)
                    {
                        continue;
                    }
                    break;

                default:     //月
                    if (retainedUsers[t].StatDate.Subtract(retainedUsers[t].OriginalDate).Days / 28 != index)
                    {
                        continue;
                    }
                    break;
                }
                if (col.Name.StartsWith("RetainedUserCount"))
                {
                    return(String.Format("{0:N0}", retainedUsers[t].RetainedUserCount));
                }
                //否则返回留存率
                return(retainedUsers[t].OriginalNewUserCount == 0 ? "100.00%" : String.Format("{0:0.00}%", retainedUsers[t].RetainedUserCount / (double)retainedUsers[t].OriginalNewUserCount * 100));
            }
            return(string.Empty);
        }
        /// <summary>
        /// 构建用户量表格
        /// </summary>
        /// <param name="softId"></param>
        /// <param name="platform"></param>
        /// <param name="isInternalSoft">是否是内部软件</param>
        /// <param name="byChannel">按渠道查询</param>
        /// <param name="period">统计周期</param>
        /// <param name="reportType"></param>
        /// <param name="onlyOldUser"></param>
        /// <param name="users"></param>
        /// <param name="forDown">是否用于下载(EXCEL)</param>
        /// <param name="tableIndex">多个表格时,用于指明第几张表格</param>
        /// <param name="tableName">表格名称</param>
        /// <returns></returns>
        public static string BuildStatUsersTable(int softId, MobileOption platform, bool isInternalSoft, bool byChannel, net91com.Stat.Core.PeriodOptions period, string reportType, bool onlyOldUser, List <SoftUser> users, bool forDown, int tableIndex, string tableName)
        {
            #region 表头开始

            StringBuilder htmlBuilder = new StringBuilder();
            if (forDown)
            {
                htmlBuilder.Append("<table border=\"1\">");
            }
            else
            {
                htmlBuilder.AppendFormat("<table id=\"tab{0}\" class=\"tablesorter\" name=\"{1}\" {2} cellspacing=\"1\">", tableIndex, tableName, tableIndex != 0 ? "style=\"display:none\"" : "");
            }

            if (statUsersXmlDoc == null)
            {
                statUsersXmlDoc = new XmlDocument();
                statUsersXmlDoc.Load(HttpContext.Current.Server.MapPath("~/DataTables/StatUsers.xml"));
            }
            TableTemplate.IsTrueHandler IsTrue    = new TableTemplate.IsTrueHandler(IsTrue_StatUsers);
            NodeCondition_StatUsers     nCodition = new NodeCondition_StatUsers
            {
                ByChannel      = byChannel,
                SoftID         = softId,
                Platform       = platform,
                IsInternalSoft = isInternalSoft,
                Period         = period,
                OnlyOldUser    = onlyOldUser,
                ReportType     = reportType
            };
            TableTemplate.ColumnNode rootNode = new TableTemplate.ColumnNode {
                CorrespondXmlNode = statUsersXmlDoc.DocumentElement
            };
            //生成表头,并返回数据绑定列
            List <TableTemplate.ColumnNode> dataColumns;
            htmlBuilder.Append(TableTemplate.BuildTableHead(rootNode, IsTrue, nCodition, out dataColumns));

            #endregion

            #region 表体开始

            htmlBuilder.Append("<tbody>");
            StringBuilder averageBuilder = new StringBuilder();
            StringBuilder sumBuilder     = new StringBuilder();
            for (int i = 0; i < users.Count; i++)
            {
                bool red = period == net91com.Stat.Core.PeriodOptions.Daily && (users[i].StatDate.DayOfWeek == DayOfWeek.Sunday || users[i].StatDate.DayOfWeek == DayOfWeek.Saturday);
                htmlBuilder.Append("<tr style=\"text-align:right;\">");
                foreach (TableTemplate.ColumnNode col in dataColumns)
                {
                    htmlBuilder.Append(red ? "<td style=\"color:red;\">" : "<td>");
                    switch (col.Name)
                    {
                    case "StatDate":
                        htmlBuilder.AppendFormat("{0:yyyy-MM-dd}", users[i].StatDate);
                        if (i == 0)
                        {
                            averageBuilder.Append("<td>平均</td>");
                            sumBuilder.Append("<td>总计</td>");
                        }
                        break;

                    case "Hour":
                        htmlBuilder.Append(users[i].Hour);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0}</td>", averageBuilder.Length == 0 ? "平均" : "--");
                            sumBuilder.AppendFormat("<td>{0}</td>", sumBuilder.Length == 0 ? "总计" : "--");
                        }
                        break;

                    case "NewUserCount":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].NewNum);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.NewNum));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => a.NewNum));
                        }
                        break;

                    case "NewUserCount_NotFromCache":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].FirstNewUserCount);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.FirstNewUserCount));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => a.FirstNewUserCount));
                        }
                        break;

                    case "NewUserCount_Shanzhai":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].NewUserCount_Shanzhai);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.NewUserCount_Shanzhai));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => a.NewUserCount_Shanzhai));
                        }
                        break;

                    case "NewUserCount_SecAct":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].NewUserCount_SecAct);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.NewUserCount_SecAct));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => a.NewUserCount_SecAct));
                        }
                        break;

                    case "NewUserCount_SecAct2":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].NewUserCount_SecAct2);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.NewUserCount_SecAct2));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => a.NewUserCount_SecAct2));
                        }
                        break;

                    case "NewUserCount_Broken":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].NewNum_Broken);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.NewNum_Broken));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => a.NewNum_Broken));
                        }
                        break;

                    case "NewUserCount_NotBroken":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].NewNum_NotBroken);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.NewNum_NotBroken));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => a.NewNum_NotBroken));
                        }
                        break;

                    case "NewUserCount_ZJS":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].NewNum_ZJS);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.NewNum_ZJS));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => a.NewNum_ZJS));
                        }
                        break;

                    case "NewUserCount_ValuedUsers":
                        htmlBuilder.AppendFormat("{0:N0}", Math.Max(users[i].FuncValueUsersForNew, users[i].DownValueUsersForNew));
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => Math.Max(a.FuncValueUsersForNew, a.DownValueUsersForNew)));
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Sum(a => Math.Max(a.FuncValueUsersForNew, a.DownValueUsersForNew)));
                        }
                        break;

                    case "ActiveUserCount":
                        htmlBuilder.AppendFormat("{0:N0}", onlyOldUser ? users[i].ActiveNum : users[i].ActiveNum + users[i].NewNum);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", onlyOldUser ? (int)users.Average(a => a.ActiveNum) : (int)users.Average(a => a.ActiveNum + a.NewNum));
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "OldUserCount":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].ActiveNum);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", (int)users.Average(a => a.ActiveNum));
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "OldPercent":
                        htmlBuilder.Append((((double)users[i].ActiveNum) * 100 / (users[i].ActiveNum + users[i].NewNum)).ToString("0.00") + "%");
                        if (i == 0)
                        {
                            averageBuilder.Append("<td>--</td>");
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "ActiveUserCount_NotFromCache":
                        htmlBuilder.AppendFormat("{0:N0}", onlyOldUser ? users[i].FirstActiveUserCount : users[i].FirstActiveUserCount + users[i].FirstNewUserCount);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", onlyOldUser ? (int)users.Average(a => a.FirstActiveUserCount) : (int)users.Average(a => a.FirstActiveUserCount + a.FirstNewUserCount));
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "ActiveUserCount_Shanzhai":
                        htmlBuilder.AppendFormat("{0:N0}", onlyOldUser ? users[i].ActiveUserCount_Shanzhai : users[i].ActiveUserCount_Shanzhai + users[i].NewUserCount_Shanzhai);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", onlyOldUser ? (int)users.Average(a => a.ActiveUserCount_Shanzhai) : (int)users.Average(a => a.ActiveUserCount_Shanzhai + a.NewUserCount_Shanzhai));
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "ActiveUserCount_Broken":
                        htmlBuilder.AppendFormat("{0:N0}", onlyOldUser ? users[i].ActiveNum_Broken : users[i].ActiveNum_Broken + users[i].NewNum_Broken);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", onlyOldUser ? (int)users.Average(a => a.ActiveNum_Broken) : (int)users.Average(a => a.ActiveNum_Broken + a.NewNum_Broken));
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "ActiveUserCount_NotBroken":
                        htmlBuilder.AppendFormat("{0:N0}", onlyOldUser ? users[i].ActiveNum_NotBroken : users[i].ActiveNum_NotBroken + users[i].NewNum_NotBroken);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", onlyOldUser ? (int)users.Average(a => a.ActiveNum_NotBroken) : (int)users.Average(a => a.ActiveNum_NotBroken + a.NewNum_NotBroken));
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "ActiveUserCount_ZJS":
                        htmlBuilder.AppendFormat("{0:N0}", onlyOldUser ? users[i].ActiveNum_ZJS : users[i].ActiveNum_ZJS + users[i].NewNum_ZJS);
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", onlyOldUser ? (int)users.Average(a => a.ActiveNum_ZJS) : (int)users.Average(a => a.ActiveNum_ZJS + a.NewNum_ZJS));
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "ActiveUserCount_ValuedUsers":
                        htmlBuilder.AppendFormat("{0:N0}", onlyOldUser ? Math.Max(users[i].FuncValueUsersForAct, users[i].DownValueUsersForAct) : Math.Max(users[i].FuncValueUsersForAct + users[i].FuncValueUsersForNew, users[i].DownValueUsersForAct + users[i].DownValueUsersForNew));
                        if (i == 0)
                        {
                            averageBuilder.AppendFormat("<td>{0:N0}</td>", onlyOldUser ? (int)users.Average(a => Math.Max(a.FuncValueUsersForAct, a.DownValueUsersForAct)) : (int)users.Average(a => Math.Max(a.FuncValueUsersForAct + a.FuncValueUsersForNew, a.DownValueUsersForAct + a.DownValueUsersForNew)));
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "TotalUserCount":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].TotalNum);
                        if (i == 0)
                        {
                            averageBuilder.Append("<td>--</td>");
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Max(a => a.TotalNum));
                        }
                        break;

                    case "TotalUserCount_Shanzhai":
                        htmlBuilder.AppendFormat("{0:N0}", users[i].TotalUserCount_Shanzhai);
                        if (i == 0)
                        {
                            averageBuilder.Append("<td>--</td>");
                            sumBuilder.AppendFormat("<td>{0:N0}</td>", users.Max(a => a.TotalUserCount_Shanzhai));
                        }
                        break;

                    case "Growth":
                        htmlBuilder.Append(users[i].Growth);
                        if (i == 0)
                        {
                            averageBuilder.Append("<td>--</td>");
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;

                    case "ActivePercent":
                        htmlBuilder.Append(onlyOldUser ? users[i].ActivityPercent : users[i].UsePercent);
                        if (i == 0)
                        {
                            averageBuilder.Append("<td>--</td>");
                            sumBuilder.Append("<td>--</td>");
                        }
                        break;
                    }
                    htmlBuilder.Append("</td>");
                }
                htmlBuilder.Append("</tr>");
            }

            #endregion

            #region 表尾开始

            htmlBuilder.Append("</tbody><tr style=\"text-align:right;\">");
            htmlBuilder.Append(averageBuilder.ToString());
            htmlBuilder.Append("</tr>");
            if (period != net91com.Stat.Core.PeriodOptions.LatestOneMonth)
            {
                htmlBuilder.Append("<tr style=\"text-align:right;\">");
                htmlBuilder.Append(sumBuilder.ToString());
                htmlBuilder.Append("</tr>");
            }
            htmlBuilder.Append("</tr></table>");

            #endregion

            return(htmlBuilder.ToString());
        }