[] 인트로 아카데미 리스트 로직 추가 및 API 로직 변경

This commit is contained in:
SEAN-59 2025-06-19 18:00:00 +09:00
parent e8580650cc
commit d89db8c890
19 changed files with 229 additions and 98 deletions

View File

@ -2,10 +2,8 @@
@implements IDisposable
<div class="min-h-screen flex flex-col bg-gray-50 text-gray-900">
<!-- Top 영역 -->
@* <TopBanner /> *@
@if(isAcademy)
{
<div class="flex flex-1 flex-col md:flex-row">
@ -52,7 +50,7 @@
@if (LoadingService.IsLoading)
{
<div class="fixed inset-0 bg-black/80 flex items-center justify-center z-50">
<div class="fixed inset-0 bg-black/30 flex items-center justify-center z-50">
<div class="bg-gray-200/60 px-6 py-4 rounded-lg">
<div class="animate-spin h-8 w-8 border-4 border-gray-600 border-t-transparent rounded-full"></div>
</div>

View File

@ -16,12 +16,14 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
// 경로의 시작 부분
// protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("auth", StringComparison.OrdinalIgnoreCase);
protected bool isIntro => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("am/intro", StringComparison.OrdinalIgnoreCase);
// 경로의 끝 부분
protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).EndsWith("auth", StringComparison.OrdinalIgnoreCase);
protected bool isAcademy => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("am", StringComparison.OrdinalIgnoreCase);
// 경로 일치
protected bool isIntro => Navigation.ToBaseRelativePath(Navigation.Uri).Equals("am/intro", StringComparison.OrdinalIgnoreCase);
// protected bool isIntro => Navigation.ToBaseRelativePath(Navigation.Uri).Equals("am/intro", StringComparison.OrdinalIgnoreCase);
protected override void OnInitialized()
{

View File

@ -18,5 +18,5 @@ public class Academy
public class SimpleAcademy
{
public required string bid { get; set; }
public string business_name { get; set; } = string.Empty;
public string name { get; set; }
}

View File

@ -1,19 +1,18 @@
using System.Net.Http.Json;
using System.Text.Encodings.Web;
using System.Text.Json;
using Front.Program.Models;
using Microsoft.JSInterop;
namespace Front.Program.Services;
public class APIService
public class APIService(HttpClient http,
StorageService storageService,
SecureService secureService,
IJSRuntime js)
{
private readonly HttpClient _http;
public APIService(HttpClient http)
{
_http = http;
}
private string ChangeToString<T>(T data)
{
if (data == null) return string.Empty;
@ -25,9 +24,60 @@ public class APIService
public async Task<APIResponseStatus<TResponse>?> GetJsonAsync<TResponse,TRequest>(string url, TRequest value)
{
string parameter = ChangeToString(value);
var response = await _http.GetFromJsonAsync<APIResponseStatus<TResponse>>($"{url}?{parameter}");
var response = await http.GetFromJsonAsync<APIResponseStatus<TResponse>>($"{url}?{parameter}");
return response;
}
public async Task<(bool success,T? data)> GetConnectServerAsnyc<T>(string url) {
var headerValue = await storageService.GetItemAsync("Web-AM-Connect-Key");
if (string.IsNullOrEmpty(headerValue)) return (false, default);
var args = new {
url = $"{url}",
method = "GET",
headerKey = "Web-AM-Connect-Key",
headerValue = headerValue,
token = "VO00"
};
var response = await js.InvokeAsync<JsonElement>(
"fetchWithHeaderAndReturnUrl",
args
);
Console.WriteLine($"JSON 응답: {response.ToString()}");
if (response.TryGetProperty("data", out var dataElement))
{
try
{
// 전체 데이터 암호화 저장
var dataJson = dataElement.ToString();
var serialData = JsonSerializer.Deserialize<T>(dataJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
Console.WriteLine("[GetConnectServerAsnyc] 사용자 데이터 Json: " + dataJson);
Console.WriteLine("[GetConnectServerAsnyc] 사용자 데이터 변환: " + serialData.ToString());;
if (serialData != null)
{
var encryptedData = await secureService.EncryptAsync(dataJson);
await storageService.SetItemAsync("USER_DATA", encryptedData);
return (true, serialData);
}
else
{
Console.WriteLine("사용자 데이터에 필수 정보가 없습니다");
}
}
catch (Exception ex)
{
Console.WriteLine($"사용자 데이터 처리 중 오류: {ex.Message}");
}
}
Console.WriteLine("데이터를 찾을 수 없습니다");
return (false, default);
}
}

View File

@ -2,12 +2,14 @@
using Front.Program.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace Front.Program.Services;
// 뷰를 참조 안하고도 로딩 상태를 알 수 있게 바꾸기
public class LoadingService
{
private readonly IJSRuntime _jsRuntime;
public bool IsLoading { get; private set; }
private bool isNavigationLoading { get; set; }
@ -40,4 +42,29 @@ public class LoadingService
}
private void NotifyStateChanged() => OnChange?.Invoke();
public LoadingService(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task ShowLoadingAsync(bool isNavigation = false)
{
IsLoading = true;
isNavigationLoading = isNavigation;
NotifyStateChanged();
await _jsRuntime.InvokeVoidAsync("setBodyOverflowHidden", true);
}
public async Task HideLoadingAsync()
{
if (!isNavigationLoading)
{
IsLoading = false;
NotifyStateChanged();
await _jsRuntime.InvokeVoidAsync("setBodyOverflowHidden", false);
}
}
}

View File

@ -5,12 +5,14 @@ using Microsoft.JSInterop;
namespace Front.Program.ViewModels;
public class UserStateService(StorageService _storageService,SecureService _secureService, IJSRuntime _js)
public class UserStateService(StorageService _storageService,SecureService _secureService, APIService _APIService,
IJSRuntime _js)
{
public UserData UserData { get; set; } = new UserData();
public bool isFirstCheck { get; set; } = false;
public bool isLogin { get; set; } = false;
public Models.SimpleAcademy[] academyItems = Array.Empty<Models.SimpleAcademy>();
public async Task<(bool success, UserData? userData)> GetUserDataFromStorageAsync()
@ -46,59 +48,7 @@ public class UserStateService(StorageService _storageService,SecureService _secu
public async Task<(bool success, UserData? userData)> GetUserDataFromServerAsync()
{
var headerValue = await _storageService.GetItemAsync("Web-AM-Connect-Key");
if (string.IsNullOrEmpty(headerValue)) return (false, null);
var args = new {
url = "/api/v1/in/user",
method = "GET",
headerKey = "Web-AM-Connect-Key",
headerValue = headerValue,
token = "VO00"
};
var response = await _js.InvokeAsync<JsonElement>(
"fetchWithHeaderAndReturnUrl",
args
);
// var response = await _js.InvokeAsync<JsonElement>("fetchWithHeaderAndReturnUrl",
// "/api/v1/in/user/",
// "GET",
// "Web_AM_Connect_Key",
// headerValue);
Console.WriteLine($"JSON 응답: {response.ToString()}");
if (response.TryGetProperty("data", out var dataElement))
{
try
{
// 전체 데이터 암호화 저장
var userDataJson = dataElement.ToString();
var userData = JsonSerializer.Deserialize<UserData>(userDataJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (userData != null && !string.IsNullOrEmpty(userData.Name))
{
var encryptedData = await _secureService.EncryptAsync(userDataJson);
await _storageService.SetItemAsync("USER_DATA", encryptedData);
return (true, userData);
}
else
{
Console.WriteLine("사용자 데이터에 필수 정보가 없습니다");
}
}
catch (Exception ex)
{
Console.WriteLine($"사용자 데이터 처리 중 오류: {ex.Message}");
}
}
Console.WriteLine("사용자 데이터를 찾을 수 없습니다");
return (false, null);
return await _APIService.GetConnectServerAsnyc<UserData>("/api/v1/in/user");
}
public async Task<bool> GetUserDataAsync()
@ -163,5 +113,38 @@ public class UserStateService(StorageService _storageService,SecureService _secu
// return false;
}
}
}
public async Task<(bool success, List<SimpleAcademy>? simpleAcademy)> GetAcademy()
{
return await _APIService.GetConnectServerAsnyc<List<SimpleAcademy>>("/api/v1/in/user/academy");
}
//
// var headerValue = await _storageService.GetItemAsync("Web-AM-Connect-Key");
// if (string.IsNullOrEmpty(headerValue)) return (false, null);
//
// var args = new {
// url = "/api/v1/in/user/academy",
// method = "GET",
// headerKey = "Web-AM-Connect-Key",
// headerValue = headerValue,
// token = "VO00"
// };
//
// var response = await _js.InvokeAsync<JsonElement>(
// "fetchWithHeaderAndReturnUrl",
// args
// );
//
// Console.WriteLine($"JSON 응답: {response.ToString()}");
//
// if (response.TryGetProperty("data", out var dataElement))
// {
// try
// {
// // 전체 데이터 암호화 저장
// var userDataJson = dataElement.ToString();
// var userData = JsonSerializer.Deserialize<UserData>(userDataJson,
// new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
// }
// }
}

View File

@ -7,7 +7,7 @@
<div class="container pt-24">
<div class="px-4 md:px-8 py-4">
<div class="w-full bg-center bg-no-repeat bg-contain md:bg-contain flex flex-col justify-center items-center overflow-hidden bg-white/35 rounded-xl min-h-[218px] md:h-[300px]"
style="background-image: url('/Resources/Images/Logo/Crystal_Icon.png');">
style="background-image: url('Resources/Images/Logo/Crystal_Icon.png');">
</div>
</div>
</div>
@ -33,12 +33,12 @@
학원을 선택해주세요.<br />
</h3>
<div class="max-h-[180px] overflow-y-auto rounded-xl bg-second-normal/10 border-2 border-text-detail">
@foreach (var academy in academyItems)
@foreach (var academy in UserStateService.academyItems)
{
<a href="/am/@academy.bid"
<a href="/am/main?@academy.bid"
class="block w-full px-4 py-3 hover:bg-second-dark border-b border-gray-200 last:border-b-0 group">
<div class="flex items-center justify-between">
<span class="text-text-black group-hover:text-text-white font-medium">@academy.business_name</span>
<span class="text-text-black group-hover:text-text-white font-medium">@academy.name</span>
<svg class="w-5 h-5 text-text-detail group-hover:text-text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>

View File

@ -12,31 +12,61 @@ public partial class AcademyIntro : ComponentBase, IDisposable
[Inject] StorageService StorageService { get; set; } = default!;
[Inject] QueryParamService QueryParamService { get; set; } = default!;
[Inject] UserStateService UserStateService { get; set; } = default!;
[Inject] LoadingService LoadingService { get; set; } = default!;
private bool _isProcessing = false;
protected Models.SimpleAcademy[] academyItems = Array.Empty<Models.SimpleAcademy>();
protected override async void OnInitialized()
{
Navigation.LocationChanged += HandleLocationChanged;
HandleLocationChanged(this, new LocationChangedEventArgs(Navigation.Uri, false));
if (!UserStateService.isFirstCheck) await UserStateService.GetUserDataAsync();
academyItems = new[]
try
{
new SimpleAcademy{ bid = "AA0000", business_name = "테스트 학원1"},
new SimpleAcademy{ bid = "AA0001", business_name = "테스트 학원2"},
new SimpleAcademy{ bid = "AA0002", business_name = "테스트 학원3"},
new SimpleAcademy{ bid = "AA0003", business_name = "테스트 학원4"},
new SimpleAcademy{ bid = "AA0004", business_name = "테스트 학원5"},
new SimpleAcademy{ bid = "AA0005", business_name = "테스트 학원6"},
new SimpleAcademy{ bid = "AA0006", business_name = "테스트 학원7"},
};
Navigation.LocationChanged += HandleLocationChanged;
HandleLocationChanged(this, new LocationChangedEventArgs(Navigation.Uri, false));
if (!UserStateService.isFirstCheck)
{
LoadingService.ShowLoading();
await UserStateService.GetUserDataAsync();
var aca = await UserStateService.GetAcademy();
if (aca.success)
{
if (aca.simpleAcademy.Count > 0)
{
UserStateService.academyItems = aca.simpleAcademy.ToArray();
Console.WriteLine($"academyItems: {UserStateService.academyItems.Length}개");
}
Console.WriteLine("아카데미 정보가 없습니다. 로그인 상태를 확인해주세요.");
}
Console.WriteLine($"academy: {string.Join(", ", UserStateService.academyItems.Select(a => a.name))}");
}
}
finally
{
LoadingService.HideLoading();
await InvokeAsync(StateHasChanged);
}
// 유저 값 가져오면서 같이 academy 정보도 가져와야지
// academyItems = new[]
// {
// new SimpleAcademy{ bid = "AA0000", business_name = "테스트 학원1"},
// new SimpleAcademy{ bid = "AA0001", business_name = "테스트 학원2"},
// new SimpleAcademy{ bid = "AA0002", business_name = "테스트 학원3"},
// new SimpleAcademy{ bid = "AA0003", business_name = "테스트 학원4"},
// new SimpleAcademy{ bid = "AA0004", business_name = "테스트 학원5"},
// new SimpleAcademy{ bid = "AA0005", business_name = "테스트 학원6"},
// new SimpleAcademy{ bid = "AA0006", business_name = "테스트 학원7"},
// };
await InvokeAsync(StateHasChanged);
}
public void Dispose()
@ -58,16 +88,30 @@ public partial class AcademyIntro : ComponentBase, IDisposable
// 쿼리 파라미터가 있는 경우에만 처리
if (!string.IsNullOrEmpty(uri.Query))
{
LoadingService.ShowLoading();
var queryParam = QueryParamService.ParseQueryParam(uri);
await QueryParamService.AuthCheck(queryParam, StorageService);
// 유저 정보 확인하는거 (로그인 했으니 값 가져와야지)
await UserStateService.GetUserDataAsync();
var aca = await UserStateService.GetAcademy();
// 쿼리 파라미터를 제거한 기본 URI로 리다이렉트
if (aca.success)
{
if (aca.simpleAcademy.Count > 0)
{
UserStateService.academyItems = aca.simpleAcademy.ToArray();
Console.WriteLine($"academyItems: {UserStateService.academyItems.Length}개");
}
Console.WriteLine("아카데미 정보가 없습니다. 로그인 상태를 확인해주세요.");
}
Console.WriteLine($"academy: {string.Join(", ", UserStateService.academyItems.Select(a => a.name))}");
// // 쿼리 파라미터를 제거한 기본 URI로 리다이렉트
var baseUri = uri.GetLeftPart(UriPartial.Path);
Console.WriteLine($"리다이렉트할 URI: {baseUri}");
await InvokeAsync(StateHasChanged); // StateHasChanged를 호출하여 UI 업데이트
// await InvokeAsync(StateHasChanged); // StateHasChanged를 호출하여 UI 업데이트
Navigation.NavigateTo(baseUri, forceLoad: false);
}
}
@ -77,7 +121,13 @@ public partial class AcademyIntro : ComponentBase, IDisposable
}
finally
{
LoadingService.HideLoading();
_isProcessing = false;
// var baseUri = uri.GetLeftPart(UriPartial.Path);
// Console.WriteLine($"리다이렉트할 URI: {baseUri}");
// await InvokeAsync(StateHasChanged); // StateHasChanged를 호출하여 UI 업데이트
// Navigation.NavigateTo(baseUri, forceLoad: false);
}
}

View File

@ -0,0 +1,6 @@
@page "/am/main"
<h3>AcademyMain</h3>
@code {
}

View File

@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Academy;
public partial class AcademyMain : ComponentBase
{
}

View File

@ -18,7 +18,7 @@
@foreach (var academy in academyItems)
{
<a href="/am/@academy.bid" class="block px-4 py-2 text-text-title hover:bg-gray-100">
@academy.business_name
@academy.name
</a>
}
</div>
@ -26,10 +26,18 @@
}
</div>
<div class="hidden md:flex flex-1 justify-end gap-8">
<div class="hidden md:flex flex-1 justify-end gap-8 mr-4">
<div class="flex flex-1 justify-end gap-8">
<div class="flex items-center gap-9">
<a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/about">About</a>
<div class="flex items-center gap-6">
<img src="Resources/Images/Icon/Notification_SET.png" alt="알림" class="w-6 h-6 object-cover" />
<img src="Resources/Images/Icon/Setting.png" alt="설정" class="w-6 h-6 object-cover"/>
<img src="Resources/Images/Icon/Link.png" alt="바로가기" class="w-6 h-6 object-cover"/>
<img src="Resources/Images/Icon/Logout.png" alt="로그아웃" class="w-6 h-6 object-cover"/>
@* <div class="w-10 h-10 rounded-full bg-gray-200 overflow-hidden"> *@
@* <img src="Resources/Images/Icon/Logout.png" alt="로그아웃" class="w-full h-full object-cover"/> *@
@* <a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/about">About</a> *@
@* </div> *@
</div>
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 945 B

After

Width:  |  Height:  |  Size: 945 B

File diff suppressed because one or more lines are too long