diff --git a/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysis.razor b/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysis.razor index fd7295a9..f0273dd3 100644 --- a/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysis.razor +++ b/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysis.razor @@ -87,26 +87,42 @@ -@if (_allData.Count > 0) -{ - + + @if (_platforms.Count > 0) + { + } + + @if (_brands.Count > 0) + { + } + + @if (_appVersions.Count > 0) + { + } + + @if (_devices.Count > 0) + { + } + + @if (_models.Count > 0) + { - -} + } + @code { @@ -124,11 +140,11 @@ private void OnFilter() { - RefreshPlatformECharts(); - RefreshBrandECharts(); - RefreshAppVersionECharts(); - RefreshDeviceECharts(); - RefreshModelECharts(); + Task.Run(RefreshPlatformECharts); + Task.Run(RefreshBrandECharts); + Task.Run(RefreshAppVersionECharts); + Task.Run(RefreshDeviceECharts); + Task.Run(RefreshModelECharts); } } \ No newline at end of file diff --git a/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysis.razor.cs b/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysis.razor.cs index 7f81c40f..ce32f660 100644 --- a/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysis.razor.cs +++ b/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysis.razor.cs @@ -1,17 +1,18 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the Apache License. See LICENSE.txt in the project root for license information. +using GraphQL; using GraphQL.Client.Http; using GraphQL.Client.Serializer.SystemTextJson; namespace Masa.Tsc.Web.Admin.Rcl.Pages.Analysis { - public partial class TerminalAnalysis + public partial class TerminalAnalysis : IDisposable { - [Inject] private IConfiguration Configuration { get; set; } = null!; - [Inject] private IPopupService PopupService { get; set; } = null!; + [Inject] private IHttpClientFactory HttpClientFactory { get; set; } = null!; + private object _brandOption = new { }; private object _platformOption = new { }; private object _modelOption = new { }; @@ -20,151 +21,135 @@ public partial class TerminalAnalysis private const int Take = 50; - private List _allData = []; + private GraphQLHttpClient _graphClient = null!; - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override void OnInitialized() { - if (firstRender) - { - - var token = Configuration.GetValue("CUBE_JWT_TOKEN"); - if (string.IsNullOrWhiteSpace(token)) - { - await PopupService.EnqueueSnackbarAsync("token未找到, 请检查配置", AlertTypes.Error); - return; - } - - var httpClient = new HttpClient(); - httpClient.DefaultRequestHeaders.Add("Authorization", $"bearer {token}"); - httpClient.DefaultRequestHeaders.Add("X-Request-Type", "GraphQL"); - - try - { - PopupService.ShowProgressLinear(); - var graphClient = new GraphQLHttpClient("http://10.130.0.33:4000/cubejs-api/graphql", - new SystemTextJsonSerializer(), - httpClient: httpClient); + base.OnInitialized(); - var response = await graphClient.SendQueryAsync(GetQuery()); - _allData = response.Data.Items; - - OnFilter(); - StateHasChanged(); - } - catch (Exception e) - { - await PopupService.EnqueueSnackbarAsync(title: "获取数据失败", content: e.Message, AlertTypes.Error); - } - finally - { - PopupService.HideProgressLinear(); - } - } + var httpClient = HttpClientFactory.CreateClient("analysis"); + _graphClient = new GraphQLHttpClient("http://10.130.0.33:4000/cubejs-api/graphql", + new SystemTextJsonSerializer(), + httpClient: httpClient); } - private IEnumerable ComputedData + protected override async Task OnAfterRenderAsync(bool firstRender) { - get + if (firstRender) { - return _allData - .Where(u => _selectedPlatforms.Count == 0 || _selectedPlatforms.Contains(u.DeviceVisit.Platform)) - .Where(u => _selectedBrands.Count == 0 || _selectedBrands.Contains(u.DeviceVisit.Brand)) - .Where(u => _selectedModels.Count == 0 || _selectedModels.Contains(u.DeviceVisit.Model)) - .Where(u => _selectedDevices.Count == 0 || _selectedDevices.Contains(u.DeviceVisit.Device)) - .Where(u => _selectedAppVersions.Count == 0 || - _selectedAppVersions.Contains(u.DeviceVisit.AppVersion)); + OnFilter(); } } - private void RefreshBrandECharts() + private async Task RefreshBrandECharts() { - var brandData = ComputedData - .GroupBy(u => u.DeviceVisit.Brand) - .ToDictionary(g => g.Key, v => v.Sum(u => u.DeviceVisit.Qty)); + var dataItems = await QueryAsync("brand"); + + var data = dataItems.OrderByDescending(u => u.DeviceVisit.Qty).ToList(); if (_brands.Count == 0) { - _brands = brandData.Keys.Order().ToList(); + _brands = data.Select(u => u.DeviceVisit.Brand).ToList(); } - var data = brandData - .OrderByDescending(u => u.Value) - .Select(item => new + var items = data + .Select(u => new { - name = item.Key, - value = item.Value, + name = u.DeviceVisit.Brand, + value = u.DeviceVisit.Qty, itemStyle = new { - color = TerminalAnalysisData.KnowBrandColor.GetValueOrDefault(item.Key) + color = TerminalAnalysisData.KnowBrandColor.GetValueOrDefault(u.DeviceVisit.Brand) } - }); + }) + .ToList(); + + _brandOption = GetPieOption("品牌", items); + + await InvokeAsync(StateHasChanged); + } - _brandOption = GetPieOption("品牌", data); + private async Task> QueryAsync(string type) + { + try + { + var query = GetQuery(type); + var result = await _graphClient.SendQueryAsync(query); + return result.Data.Items; + } + catch (Exception e) + { + await PopupService.EnqueueSnackbarAsync("查询失败", e.Message, AlertTypes.Error); + return []; + } } - private void RefreshPlatformECharts() + private async Task RefreshPlatformECharts() { - var platformData = ComputedData - .GroupBy(u => u.DeviceVisit.Platform) - .ToDictionary(g => g.Key, v => v.Sum(u => u.DeviceVisit.Qty)); + var dataItems = await QueryAsync("platform"); + + var data = dataItems.OrderByDescending(u => u.DeviceVisit.Qty).ToList(); if (_platforms.Count == 0) { - _platforms = platformData.Keys.Order().ToList(); + _platforms = data.Select(u => u.DeviceVisit.Platform).ToList(); } - var data = platformData - .OrderByDescending(u => u.Value) - .Select(item => new + var items = data + .Select(u => new { - name = item.Key, - value = item.Value, + name = u.DeviceVisit.Platform, + value = u.DeviceVisit.Qty, itemStyle = new { - color = TerminalAnalysisData.KnowPlatformColor.GetValueOrDefault(item.Key) + color = TerminalAnalysisData.KnowPlatformColor.GetValueOrDefault(u.DeviceVisit.Platform) } - }); - _platformOption = GetPieOption("平台", data); + }) + .ToList(); + + _platformOption = GetPieOption("平台", items); + + await InvokeAsync(StateHasChanged); } - private void RefreshModelECharts() + private async Task RefreshModelECharts() { - var modelData = ComputedData - .GroupBy(u => u.DeviceVisit.Model) - .ToDictionary(g => g.Key, v => v.Sum(u => u.DeviceVisit.Qty)); - - var data = modelData - .OrderByDescending(u => u.Value) - .ToList(); + var dataItems = await QueryAsync("modle"); if (_models.Count == 0) { - _models = data.Select(u => u.Key).ToList(); + _models = dataItems.OrderByDescending(u => u.DeviceVisit.Qty) + .Select(u => u.DeviceVisit.Model).ToList(); } + var dict = dataItems.ToDictionary(u => u.DeviceVisit.Model, u => u.DeviceVisit.Qty); + + var data = dict + .OrderByDescending(u => u.Value) + .ToList(); + var newData = data.Take(Take).OrderBy(u => u.Value).ToList(); var sumOfOther = data.Skip(Take).Sum(u => u.Value); if (sumOfOther > 0) { - newData.Insert(0, new KeyValuePair($"剩余({data.Count - Take})", sumOfOther)); + newData.Insert(0, + new KeyValuePair($"剩余({data.Count - Take})", sumOfOther)); } var max = Math.Max(TryGetMaxQty(data), sumOfOther / 3); - _modelOption = GetBarOption( - "机型", - newData.Select(u => u.Key).ToArray(), - newData.Select(u => u.Value).ToArray(), - max); + _modelOption = GetBarOption("机型", newData.Select(u => u.Key), newData.Select(u => u.Value), max); + + await InvokeAsync(StateHasChanged); } - private void RefreshDeviceECharts() + private async Task RefreshDeviceECharts() { - var deviceData = ComputedData - .GroupBy(u => u.DeviceVisit.Device) - .ToDictionary(g => g.Key, v => v.Sum(u => u.DeviceVisit.Qty)); + var dataItems = await QueryAsync("devicever"); + + var dict = dataItems.ToDictionary(u => u.DeviceVisit.Device, u => u.DeviceVisit.Qty); - var data = deviceData + var data = dict .OrderByDescending(u => u.Value) .ToList(); @@ -182,30 +167,24 @@ private void RefreshDeviceECharts() var max = TryGetMaxQty(data); - _deviceOption = GetBarOption( - "系统版本", - newData.Select(u => u.Key).ToArray(), - newData.Select(u => u.Value).ToArray(), - max); - } + _deviceOption = GetBarOption("系统版本", newData.Select(u => u.Key).ToArray(), + newData.Select(u => u.Value).ToArray(), max); - private static int TryGetMaxQty(List> data) - { - return data.Count != 0 ? (int)(data.Max(u => u.Value) * 1.1) : 0; + await InvokeAsync(StateHasChanged); } - private void RefreshAppVersionECharts() + private async Task RefreshAppVersionECharts() { - var appVersionData = ComputedData - .GroupBy(u => u.DeviceVisit.AppVersion) - .ToDictionary(g => g.Key, v => v.Sum(u => u.DeviceVisit.Qty)); + var dataItems = await QueryAsync("appversion"); + + var dict = dataItems.ToDictionary(u => u.DeviceVisit.AppVersion, u => u.DeviceVisit.Qty); if (_appVersions.Count == 0) { - _appVersions = appVersionData.OrderByDescending(u => u.Value).Select(u => u.Key).ToList(); + _appVersions = dict.OrderByDescending(u => u.Value).Select(u => u.Key).ToList(); } - var data = appVersionData + var data = dict .OrderBy(u => u.Value) .ToList(); @@ -216,6 +195,13 @@ private void RefreshAppVersionECharts() data.Select(u => u.Key).ToArray(), data.Select(u => u.Value).ToArray(), max); + + await InvokeAsync(StateHasChanged); + } + + private static int TryGetMaxQty(List> data) + { + return data.Count != 0 ? (int)(data.Max(u => u.Value) * 1.1) : 0; } private static object GetPieOption(string name, IEnumerable data) @@ -320,19 +306,46 @@ private static object GetBarOption(string name, IEnumerable keys, IEnume }; } - private static GraphQLHttpRequest GetQuery() + private GraphQLHttpRequest GetQuery(string type) { + List filters = []; + + if (_selectedPlatforms.Count > 0) + { + filters.Add("{platform: {in: [" + string.Join(", ", _selectedPlatforms.Select(u => $"\"{u}\"")) + + "]}}"); + } + + if (_selectedBrands.Count > 0) + { + filters.Add("{brand: {in: [" + string.Join(", ", _selectedBrands.Select(u => $"\"{u}\"")) + "]}}"); + } + + if (_selectedModels.Count > 0) + { + filters.Add("{modle: {in: [" + string.Join(", ", _selectedModels.Select(u => $"\"{u}\"")) + "]}}"); + } + + if (_selectedDevices.Count > 0) + { + filters.Add("{devicever: {in: [" + string.Join(", ", _selectedDevices.Select(u => $"\"{u}\"")) + "]}}"); + } + + if (_selectedAppVersions.Count > 0) + { + filters.Add("{appversion: {in: [" + string.Join(", ", _selectedAppVersions.Select(u => $"\"{u}\"")) + + "]}}"); + } + + var filter = string.Join(", ", filters); + return new GraphQLHttpRequest( $$""" query { cube { - devicevisit { + devicevisit(where: {AND: [{{filter}}]}) { qty - brand - platform - modle - devicever - appversion + {{type}} } } } @@ -354,5 +367,10 @@ private record DeviceVisit( string Device, string AppVersion, int Qty); + + public void Dispose() + { + _graphClient.Dispose(); + } } } \ No newline at end of file diff --git a/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysisData.cs b/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysisData.cs index f171cf54..4d758518 100644 --- a/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysisData.cs +++ b/src/Web/Masa.Tsc.Web.Admin.Rcl/Pages/Analysis/TerminalAnalysisData.cs @@ -3,45 +3,38 @@ namespace Masa.Tsc.Web.Admin.Rcl.Pages.Analysis { - public static class TerminalAnalysisData + internal static class TerminalAnalysisData { - private const string KnowBrandColorJson - = """ - { - "华为": "#ff4d4f", - "vivo": "#66b3ff", - "OPPO": "#66ff66", - "荣耀": "#99ccff", - "Apple": "#333333", - "小米": "#ff9f00", - "realme": "#ffe74c", - "一加": "#ff6b6b", - "三星": "#6699ff", - "Motorola": "#cd5c5c", - "中兴": "#6699ff", - "努比亚": "#ff6666", - "黑鲨": "#4d4d4d", - "酷派": "#6699ff", - "联想": "#4d4d4d", - "索尼": "#4d4d4d", - "坚果": "#ff6666", - "华硕": "#ff6666" - } - """; + private static readonly string[] EchartsBuiltInColor = + ["#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de", "#3ba272", "#fc8452", "#9a60b4", "#ea7ccc"]; - private const string KnowPlatformColorJson - = """ - { - "Android": "#c9d89a", - "iOS": "#333333", - "HarmonyOS": "#66a3ff" - } - """; + private static readonly string[] KnowBrands = + [ + "华为", "vivo", "OPPO", "荣耀", "Apple", "小米", "realme", "一加", "三星", "Motorola", "中兴", "努比亚", "黑鲨", "酷派", "联想", + "索尼", "坚果", "华硕" + ]; - public static IReadOnlyDictionary KnowBrandColor { get; } = - JsonSerializer.Deserialize>(KnowBrandColorJson)!; + private static readonly string[] KnowPlatforms = ["Android", "iOS", "HarmonyOS"]; - public static IReadOnlyDictionary KnowPlatformColor { get; } = - JsonSerializer.Deserialize>(KnowPlatformColorJson)!; + static TerminalAnalysisData() + { + for (int i = 0; i < KnowBrands.Length; i++) + { + var brand = KnowBrands[i]; + var colorIndex = i % EchartsBuiltInColor.Length; + KnowBrandColor[brand] = EchartsBuiltInColor[colorIndex]; + } + + for (int i = 0; i < KnowPlatforms.Length; i++) + { + var platform = KnowPlatforms[i]; + var colorIndex = i % EchartsBuiltInColor.Length; + KnowPlatformColor[platform] = EchartsBuiltInColor[colorIndex]; + } + } + + internal static Dictionary KnowBrandColor { get; } = []; + + internal static Dictionary KnowPlatformColor { get; } = []; } } \ No newline at end of file diff --git a/src/Web/Masa.Tsc.Web.Admin.Server/Program.cs b/src/Web/Masa.Tsc.Web.Admin.Server/Program.cs index 5b6c42c1..190a2d43 100644 --- a/src/Web/Masa.Tsc.Web.Admin.Server/Program.cs +++ b/src/Web/Masa.Tsc.Web.Admin.Server/Program.cs @@ -10,6 +10,13 @@ builder.Services.AddServerSideBlazor(); builder.Services.AddRcl().AddScoped(); +builder.Services.AddHttpClient("analysis", client => +{ + var token = builder.Configuration.GetValue("CUBE_JWT_TOKEN"); + client.DefaultRequestHeaders.Add("Authorization", $"bearer {token}"); + client.DefaultRequestHeaders.Add("X-Request-Type", "GraphQL"); +}); + builder.Services.Configure(option => { option.JsonSerializerOptions.Converters.Add(new QueryResultDataResponseConverter());