using _29WxOrderWpf.Ctrls.Statistics.Models; using AllBase; using AllBase.Helper; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Work; using WxOrder.Framework.Commons; namespace _29WxOrderWpf.Ctrls.Statistics.Services { /// /// 店铺营收统计 - 数据服务调用接口。 /// 页面只依赖此接口,接入其他数据源时新建实现类, /// 并通过 ViewModel 构造函数注入即可,无需改动页面。 /// public interface IShopRevenueStatisticsService { /// /// 综合统计查询:返回汇总卡片、销售趋势(按天)、店铺销售占比、店铺统计表格。 /// 用 GroupType=Day 一次请求取回按店铺按天的记录,再由客户端汇总,无缓存。 /// Task QueryAsync(ShopRevenueStatisticsQuery query); /// /// 天/周/月周期统计查询:返回汇总卡片 + 按周期(天/周/月)聚合的表格数据。 /// 接口不返回整段汇总,Summary 由返回的周期记录在客户端合计。 /// Task QueryPeriodStatisticsAsync(ShopRevenueStatisticsQuery query, PeriodStatisticType periodType); /// 获取店铺下拉列表(用于查询条件) Task> GetShopOptionsAsync(); /// /// 订单明细列表(有效订单 / 退款订单 / 买手未退回金额的订单),供订单列表弹窗使用。 /// Task> QueryOrderRowsAsync(ShopRevenueStatisticsQuery query, ShopOrderDetailType detailType); /// /// 把某笔买手订单标记为退款成功(订单列表弹窗“退款成功”按钮,仅买手未退回金额的订单使用)。 /// /// 买手退款表ID(,即表格当前行的 id) Task UpdateBuyerRefundStatusAsync(long id); Task UpdateBuyerAfterOrderRemark(long id, string remark); } /// /// 统计聚合器:把接口返回的 聚合成页面所需的汇总/趋势/占比/表格数据。 /// 数据来源:shop_revenue_statistics/GetShopStats(GroupType = Day/Week/Month), /// 接口只返回“按周期聚合的记录”,页面上所有汇总口径都在这里由客户端合计。 /// 口径说明(依据接口字段含义): /// - OrderCount 为“有效订单数”,已不含退款订单;因此退款率分母 = OrderCount + RefundOrderCount。 /// - 店铺端退款:RefundDone(已成功)+ RefundPending(处理中)→ 退款金额。 /// - 买手端(采购)退款:TotalRefund = DoneRefund + PendingRefund + RejectedRefund → 售后采购金额; /// DoneRefund → 已退回采购金额;剩余(待退回)= TotalRefund - DoneRefund。 /// - 接口无“达人佣金”字段,Commission 固定为 0(保留属性与页面展示,待后端补字段)。 /// - 金额字段单位均为“分”,除以 100 转为元。 /// public static class ShopStatsAggregator { /// 金额换算:分 → 元 private const decimal FenPerYuan = 100m; /// 分转元 public static decimal ToYuan(long fen) { return fen / FenPerYuan; } private static List ToList(IEnumerable stats) { return (stats ?? Enumerable.Empty()) .Where(a => a != null) .ToList(); } private static IEnumerable> GroupByShop(IEnumerable stats) { return ToList(stats).GroupBy(a => a.ShopId ?? string.Empty); } /// 店铺显示名:优先按 ERP 店铺ID匹配,其次按平台店铺ID匹配,都匹配不到时退回店铺ID public static string ResolveShopName(string shopId) { if (string.IsNullOrEmpty(shopId)) return "未知店铺"; var shop = GlobalConfig.ShopLoginList.FirstOrDefault(a => a.ErpShopId == shopId) ?? GlobalConfig.ShopLoginList.FirstOrDefault(a => a.ShopId == shopId); if (shop == null || string.IsNullOrEmpty(shop.ShopAccount)) return shopId; return shop.ShopAccount; } /// 汇总卡片(整个查询时间段,所有选中店铺合计) public static ShopRevenueSummary GetSummary(IEnumerable stats) { var list = ToList(stats); decimal salesAmount = ToYuan(list.Sum(a => a.SalesAmount)); decimal profit = ToYuan(list.Sum(a => a.ProfitAmount)); decimal platformFeeTotal = ToYuan(list.Sum(a => a.PlatformFee)); decimal refundAmount = ToYuan(list.Sum(a => a.RefundDone) + list.Sum(a => a.RefundPending)); decimal buyerPaymentAmount = ToYuan(list.Sum(a => a.TotalPurchase)); decimal buyerRefundAmount = ToYuan(list.Sum(a => a.TotalRefund)); decimal buyerRefundSuccessedAmount = ToYuan(list.Sum(a => a.DoneRefund)); int validOrderCount = list.Sum(a => a.OrderCount); // 新接口 OrderCount 不含退款订单 int refundOrderCount = list.Sum(a => a.RefundOrderCount); int totalOrderCount = validOrderCount + refundOrderCount; return new ShopRevenueSummary { SalesAmount = salesAmount, ValidOrderCount = validOrderCount, Profit = profit, RefundAmount = refundAmount, RefundOrderCount = refundOrderCount, PlatformFeeTotal = platformFeeTotal, Commission = 0m, // 新接口无“达人佣金”字段,保留占位 AvgOrderAmount = validOrderCount == 0 ? 0m : salesAmount / validOrderCount, RefundRate = totalOrderCount == 0 ? 0m : (decimal)refundOrderCount * 100m / totalOrderCount, AverageProfitRate = buyerPaymentAmount == 0m ? 0m : profit / buyerPaymentAmount * 100m, BuyerPaymentAmount = buyerPaymentAmount, BuyerRefundAmount = buyerRefundAmount, BuyerRefundSuccessedAmount = buyerRefundSuccessedAmount, }; } /// 店铺统计表格(按店铺汇总,销售额倒序) public static List GetShopStatistics(IEnumerable stats) { return GroupByShop(stats) .Select(g => BuildShopStatisticsItem(g.Key, g)) .OrderByDescending(i => i.SalesAmount) .ToList(); } /// 店铺销售占比(饼图,按店铺汇总,销售额倒序) public static List GetShopSalesProportion(IEnumerable stats) { var items = GroupByShop(stats) .Select(g => new ShopSalesProportionItem { ShopName = ResolveShopName(g.Key), SalesAmount = ToYuan(g.Sum(a => a.SalesAmount)) }) .OrderByDescending(i => i.SalesAmount) .ToList(); decimal total = items.Sum(i => i.SalesAmount); foreach (var item in items) { item.Percentage = total == 0m ? 0d : (double)(item.SalesAmount / total * 100m); } return items; } /// /// 销售趋势(按天,缺失日期补 0)。 /// 数据来自 GetShopStatsTrend:服务端已经跨店铺合计,这里只做日期规整与补零。 /// public static List GetSalesTrend(IEnumerable trend, DateTime startDate, DateTime endDate) { var list = (trend ?? Enumerable.Empty()) .Where(a => a != null && a.StatDate != default(DateTime)) .ToList(); var map = list .Where(a => a.StatDate.Date >= startDate.Date && a.StatDate.Date <= endDate.Date) .GroupBy(a => a.StatDate.Date) .ToDictionary( g => g.Key, g => new RevenueTrendPoint { Date = g.Key, SalesAmount = ToYuan(g.Sum(a => a.SalesAmount)), OrderCount = g.Sum(a => a.OrderCount) }); var result = new List(); for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1)) { if (map.TryGetValue(d, out var point)) result.Add(point); else result.Add(new RevenueTrendPoint { Date = d, SalesAmount = 0m, OrderCount = 0 }); } return result; } /// /// 天/周/月周期统计表格(每天/每周/每月各一行,无数据的周期也补一行,金额单位:元)。 /// 结果按周期倒序(最新的周期在最上面)。 /// 说明:接口按“自然周期”聚合(周从周一起、月从 1 号起),所以筛选也按周期起点比较, /// 否则区间从周三开始时,那一周(周一起)的数据会被误判为越界而丢掉。 /// public static List GetPeriodStatistics(IEnumerable stats, DateTime startDate, DateTime endDate, PeriodStatisticType periodType) { var startPeriod = GetPeriodStart(startDate, periodType); var endPeriod = GetPeriodStart(endDate, periodType); var map = ToList(stats) .Where(a => a.StatDate != default(DateTime)) .Select(a => new { Stat = a, PeriodStart = GetPeriodStart(a.StatDate, periodType) }) .Where(a => a.PeriodStart >= startPeriod && a.PeriodStart <= endPeriod) .GroupBy(a => a.PeriodStart) .ToDictionary(g => g.Key, g => BuildPeriodStatisticsItem(g.Key, g.Select(a => a.Stat), periodType)); // 按周期补齐整段时间:无数据的周期也生成一行(全 0) var result = new List(); foreach (var periodStart in EnumeratePeriods(startDate, endDate, periodType)) { if (map.TryGetValue(periodStart, out var item)) result.Add(item); else result.Add(new ShopPeriodStatisticsItem { PeriodStart = periodStart, Period = GetPeriodText(periodStart, periodType), OrderCount = 0, SalesAmount = 0m, BuyerPaymentAmount = 0m, Profit = 0m, RefundAmount = 0m, PlatformFeeTotal = 0m, Commission = 0m, AverageProfitRate = 0m, CustomerCount = 0, AvgOrderAmount = 0m }); } // 倒序排列:最新的周期在最上面 result.Reverse(); return result; } /// 店铺统计行(金额单位:元) private static ShopStatisticsItem BuildShopStatisticsItem(string shopId, IEnumerable shopStats) { var list = ToList(shopStats); decimal salesAmount = ToYuan(list.Sum(a => a.SalesAmount)); decimal profit = ToYuan(list.Sum(a => a.ProfitAmount)); decimal buyerPaymentAmount = ToYuan(list.Sum(a => a.TotalPurchase)); int orderCount = list.Sum(a => a.OrderCount); return new ShopStatisticsItem { ShopName = ResolveShopName(shopId), OrderCount = orderCount, SalesAmount = salesAmount, BuyerPaymentAmount = buyerPaymentAmount, Profit = profit, RefundAmount = ToYuan(list.Sum(a => a.RefundDone) + list.Sum(a => a.RefundPending)), PlatformFeeTotal = ToYuan(list.Sum(a => a.PlatformFee)), Commission = 0m, // 新接口无“达人佣金”字段,保留占位 AvgOrderAmount = orderCount == 0 ? 0m : salesAmount / orderCount, AverageProfitRate = buyerPaymentAmount == 0m ? 0m : profit / buyerPaymentAmount * 100m }; } /// 周期统计行(金额单位:元) private static ShopPeriodStatisticsItem BuildPeriodStatisticsItem(DateTime periodStart, IEnumerable periodStats, PeriodStatisticType periodType) { var list = ToList(periodStats); decimal salesAmount = ToYuan(list.Sum(a => a.SalesAmount)); decimal profit = ToYuan(list.Sum(a => a.ProfitAmount)); decimal buyerPaymentAmount = ToYuan(list.Sum(a => a.TotalPurchase)); int orderCount = list.Sum(a => a.OrderCount); return new ShopPeriodStatisticsItem { PeriodStart = periodStart, Period = GetPeriodText(periodStart, periodType), OrderCount = orderCount, SalesAmount = salesAmount, BuyerPaymentAmount = buyerPaymentAmount, Profit = profit, RefundAmount = ToYuan(list.Sum(a => a.RefundDone) + list.Sum(a => a.RefundPending)), PlatformFeeTotal = ToYuan(list.Sum(a => a.PlatformFee)), Commission = 0m, // 新接口无“达人佣金”字段,保留占位 AverageProfitRate = buyerPaymentAmount == 0m ? 0m : profit / buyerPaymentAmount * 100m, CustomerCount = orderCount, AvgOrderAmount = orderCount == 0 ? 0m : salesAmount / orderCount }; } /// 枚举 startDate~endDate 覆盖的所有周期开始日期(天/周/月) private static IEnumerable EnumeratePeriods(DateTime startDate, DateTime endDate, PeriodStatisticType periodType) { var start = GetPeriodStart(startDate, periodType); var end = GetPeriodStart(endDate, periodType); switch (periodType) { case PeriodStatisticType.Week: for (var d = start; d <= end; d = d.AddDays(7)) yield return d; break; case PeriodStatisticType.Month: for (var d = start; d <= end; d = d.AddMonths(1)) yield return d; break; default: for (var d = start; d <= end; d = d.AddDays(1)) yield return d; break; } } /// 周期开始日期:天=当天;周=周一起始;月=当月1号 private static DateTime GetPeriodStart(DateTime time, PeriodStatisticType periodType) { switch (periodType) { case PeriodStatisticType.Week: var d = time.Date; int diff = d.DayOfWeek == DayOfWeek.Sunday ? 6 : (int)d.DayOfWeek - 1; return d.AddDays(-diff); case PeriodStatisticType.Month: return new DateTime(time.Year, time.Month, 1); default: return time.Date; } } /// 周期显示文本:天=yyyy-MM-dd;周=yyyy-MM-dd ~ yyyy-MM-dd;月=yyyy-MM private static string GetPeriodText(DateTime periodStart, PeriodStatisticType periodType) { switch (periodType) { case PeriodStatisticType.Week: return periodStart.ToString("yyyy-MM-dd") + " ~ " + periodStart.AddDays(6).ToString("yyyy-MM-dd"); case PeriodStatisticType.Month: return periodStart.ToString("yyyy-MM"); default: return periodStart.ToString("yyyy-MM-dd"); } } } /// /// 默认实现:请求新接口(无缓存,每次查询实时获取)。 /// shop_revenue_statistics/GetShopStats /// -> Result<Dictionary<店铺ID, List<ShopStatsResponse>>> /// 请求体:UserLogin.GetSignPostData(new { StartDate, EndDate, ShopIds, GroupType }); /// GroupType 决定聚合粒度(0=汇总 1=天 2=周 3=月),汇总时每个店铺只有一条。 /// shop_revenue_statistics/GetShopStatsTrend /// -> Result<List<ShopStatsTrendResponse>> /// 请求体:UserLogin.GetSignPostData(new { StartDate, EndDate, ShopIds }); /// 服务端跨店铺合计,固定按天返回,用于销售趋势折线图。 /// shop_revenue_statistics/GetShopOrderDetails /// -> Result<List<ShopOrderDetailResponse>> /// 请求体:UserLogin.GetSignPostData(new { StartDate, EndDate, ShopIds, OrderType }); /// 订单列表弹窗的明细数据(OrderType 见 ShopOrderDetailType)。 /// 接口不返回“整段汇总”,页面上的汇总卡片一律由客户端对返回记录合计。 /// 接入其他数据源时,新建实现类并注入即可(见 IShopRevenueStatisticsService 注释)。 /// public class ShopRevenueStatisticsService : IShopRevenueStatisticsService { /// 统计接口地址(聚合粒度由请求参数 GroupType 决定) private const string GetShopStatsApi = "shop_revenue_statistics/GetShopStats"; /// 销售趋势接口地址(服务端跨店铺合计,固定按天) private const string GetShopStatsTrendApi = "shop_revenue_statistics/GetShopStatsTrend"; /// 订单明细接口地址(订单列表弹窗用) private const string GetShopOrderDetailsApi = "shop_revenue_statistics/GetShopOrderDetails"; /// 更新买手订单退款状态接口地址(id 通过 URL 参数传递) private const string UpdateBuyerRefundStatusApi = "buyer/UpdateBuyerRefundStatsu"; /// Unix 纪元(本地时区),用于解析以时间戳作为 Key 的返回数据 private static readonly DateTime UnixEpoch = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); public async Task QueryAsync(ShopRevenueStatisticsQuery query) { // 综合统计页分两次取数,各取所需: // 1) Summary:每店铺一条(与区间长度无关),作为卡片/店铺统计表/销售占比的权威合计 // 2) Trend :跨店铺合计的按天序列,只给趋势折线图用(数据量只与天数有关,不随店铺数膨胀) // 顺序请求:签名中的 time/code 由当前时间生成,并发请求有被判重复签名的风险。 var summaryStats = await RequestStatsAsync(query, ShopStatsGroupType.Summary); var trend = await RequestTrendAsync(query); return new ShopRevenueStatisticsResult { Summary = ShopStatsAggregator.GetSummary(summaryStats), Trend = ShopStatsAggregator.GetSalesTrend(trend, query.StartDate, query.EndDate), ShopProportions = ShopStatsAggregator.GetShopSalesProportion(summaryStats), ShopStatistics = ShopStatsAggregator.GetShopStatistics(summaryStats), }; } public async Task QueryPeriodStatisticsAsync(ShopRevenueStatisticsQuery query, PeriodStatisticType periodType) { // 按天/周/月粒度取数;接口不返回整段汇总,卡片由返回的周期记录在客户端合计 var stats = await RequestStatsAsync(query, ToGroupType(periodType)); return new ShopPeriodStatisticsResult { Summary = ShopStatsAggregator.GetSummary(stats), Items = ShopStatsAggregator.GetPeriodStatistics(stats, query.StartDate, query.EndDate, periodType), }; } /// 页面统计周期 → 接口聚合方式 private static ShopStatsGroupType ToGroupType(PeriodStatisticType periodType) { switch (periodType) { case PeriodStatisticType.Week: return ShopStatsGroupType.Week; case PeriodStatisticType.Month: return ShopStatsGroupType.Month; default: return ShopStatsGroupType.Day; } } /// /// 请求 GetShopStats:返回按 GroupType 聚合的记录 /// (汇总时每店铺一条;天/周/月时每店铺每个自然日/自然周/自然月一条)。 /// private static async Task> RequestStatsAsync(ShopRevenueStatisticsQuery query, ShopStatsGroupType groupType) { var shopIds = BuildShopIds(query); if (shopIds.Count == 0) return new List(); var url = BaseClass.Build29WxObjString(GetShopStatsApi); var html = await GetHtml.PostJsonAsync(url, BuildPostData(query, shopIds, groupType)); var ret = Deserialize>>>(html); EnsureSuccess(ret); var result = new List(); if (ret.Data == null) return result; foreach (var kv in ret.Data) result.AddRange(Normalize(kv.Key, kv.Value)); return result; } /// /// 请求 GetShopStatsTrend:跨店铺合计的按天销售趋势(服务端已合计,不需要 GroupType)。 /// private static async Task> RequestTrendAsync(ShopRevenueStatisticsQuery query) { var shopIds = BuildShopIds(query); if (shopIds.Count == 0) return new List(); var url = BaseClass.Build29WxObjString(GetShopStatsTrendApi); var html = await GetHtml.PostJsonAsync(url, BuildTrendPostData(query, shopIds)); var ret = Deserialize>>(html); EnsureSuccess(ret); return ret.Data ?? new List(); } /// /// 组装请求体:StartDate / EndDate 传日期,ShopIds 传 ERP 店铺ID 列表, /// GroupType 传枚举数字(0=汇总 1=天 2=周 3=月)。 /// private static object BuildPostData(ShopRevenueStatisticsQuery query, List shopIds, ShopStatsGroupType groupType) { return UserLogin.GetSignPostData(new { StartDate = query.StartDate.Date, EndDate = query.EndDate.Date, ShopIds = shopIds, GroupType = (int)groupType, }); } /// 组装趋势接口请求体:StartDate / EndDate + ShopIds(无 GroupType) private static object BuildTrendPostData(ShopRevenueStatisticsQuery query, List shopIds) { return UserLogin.GetSignPostData(new { StartDate = query.StartDate.Date, EndDate = query.EndDate.Date, ShopIds = shopIds, }); } /// /// 组装订单明细请求体:StartDate / EndDate + ShopIds + OrderType /// (OrderType 传枚举数字:1=有效订单 2=退款订单 3=买手未退回金额的订单)。 /// private static object BuildOrderDetailPostData(ShopRevenueStatisticsQuery query, List shopIds, ShopOrderDetailType detailType) { return UserLogin.GetSignPostData(new { StartDate = query.StartDate, EndDate = query.EndDate, ShopIds = shopIds, OrderType = (int)detailType, }); } /// /// 查询店铺范围:query.ShopIds 为空表示全部店铺(取全部店铺的 ERP 店铺ID)。 /// 接口的 ShopIds 使用 ShopLoginInfo.ErpShopId。 /// private static List BuildShopIds(ShopRevenueStatisticsQuery query) { var selected = (query == null || query.ShopIds == null) ? new List() : query.ShopIds.Where(a => !string.IsNullOrEmpty(a)).Distinct().ToList(); if (selected.Count > 0) return selected; return GetShopOptions() .Select(a => a.ErpShopId) .Where(a => !string.IsNullOrEmpty(a)) .Distinct() .ToList(); } /// /// 反序列化接口返回;失败时统一抛出可读异常。 /// 说明:Newtonsoft 默认按属性名匹配(忽略大小写),因此 PascalCase / camelCase 均可解析。 /// private static T Deserialize(string html) { try { return JsonConvert.DeserializeObject(html); } catch (Exception ex) { throw new Exception("查询失败:接口返回数据解析异常", ex); } } /// 校验接口返回码 private static void EnsureSuccess(Result result) { if (result == null) throw new Exception("查询失败:接口无返回数据"); if (result.Code != 200) throw new Exception(string.IsNullOrEmpty(result.Msg) ? "查询失败" : result.Msg); } /// /// 摊平某个 Key(店铺ID)下的记录,并用 Key 兜底补齐缺失的 ShopId / StatDate。 /// private static List Normalize(string key, List items) { var result = new List(); if (items == null) return result; foreach (var item in items) { if (item == null) continue; if (string.IsNullOrEmpty(item.ShopId)) item.ShopId = key; if (item.StatDate == default(DateTime)) { var date = ParseDateFromKey(key); if (date.HasValue) item.StatDate = date.Value; } result.Add(item); } return result; } /// 尝试把返回数据的 Key 解析为日期(支持 yyyy-MM-dd / yyyyMMdd / 秒或毫秒时间戳) private static DateTime? ParseDateFromKey(string key) { if (string.IsNullOrEmpty(key)) return null; if (DateTime.TryParse(key, out var date)) return date; if (DateTime.TryParseExact(key, "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out date)) return date; if (long.TryParse(key, out var timestamp)) { return timestamp.ToString().Length == 13 ? UnixEpoch.AddMilliseconds(timestamp) : UnixEpoch.AddSeconds(timestamp); } return null; } /// 店铺下拉列表(同步读取,供查询与过滤使用) private static List GetShopOptions() { return GlobalConfig.ShopLoginList.Select(a => new ShopOption() { ShopId = a.ShopId, ErpShopId = a.ErpShopId, ShopName = a.ShopAccount }).ToList(); } public Task> GetShopOptionsAsync() { return Task.FromResult(GetShopOptions()); } /// /// 订单明细列表(有效订单 / 退款订单 / 买手未退回金额的订单),供订单列表弹窗使用。 /// 请求 shop_revenue_statistics/GetShopOrderDetails: /// StartDate / EndDate / ShopIds / OrderType(OrderType 由 detailType 决定,按数字传值)。 /// public async Task> QueryOrderRowsAsync(ShopRevenueStatisticsQuery query, ShopOrderDetailType detailType) { var shopIds = BuildShopIds(query); if (shopIds.Count == 0) return new List(); var url = BaseClass.Build29WxObjString(GetShopOrderDetailsApi); var html = await GetHtml.PostJsonAsync(url, BuildOrderDetailPostData(query, shopIds, detailType)); var ret = Deserialize>>(html); EnsureSuccess(ret); return ret.Data ?? new List(); } /// /// 请求 buyer/UpdateBuyerRefundStatsu 更新买手订单退款状态: /// id 拼在 URL 查询参数上(Build29WxObjString 生成的地址已带 ?obj=…,这里用 &id= 追加); /// 请求体走 UserLogin.GetSignPostData 常规签名(time/code 等)。 /// 返回码非 200 时抛出异常,由调用方提示。 /// public async Task UpdateBuyerRefundStatusAsync(long id) { if (id == 0) throw new Exception("更新失败:缺少买手订单ID"); var url = BaseClass.Build29WxObjString(UpdateBuyerRefundStatusApi) + "&id=" + id; var html = await GetHtml.PostJsonAsync(url, UserLogin.GetSignPostData(new { })); var ret = Deserialize>(html); EnsureSuccess(ret); return ret.Data; } public async Task UpdateBuyerAfterOrderRemark(long id, string remark) { if (id == 0) throw new Exception("更新失败:缺少买手订单ID"); var url = BaseClass.Build29WxObjString("buyer/UpdateBuyerAfterRemark"); var html = await GetHtml.PostJsonAsync(url, UserLogin.GetSignPostData(new { id = id, remark })); var ret = Deserialize>(html); EnsureSuccess(ret); return ret.Data; } } }