feat(admin): stabilize blog and admin patterns
TaxBaik CI/CD / build-and-deploy (push) Has been cancelled

This commit is contained in:
2026-07-02 10:46:27 +09:00
parent b3cab87539
commit cb47349a25
67 changed files with 1354 additions and 486 deletions
@@ -1,11 +1,9 @@
@page "/admin/blog/create"
@attribute [Authorize]
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@using TaxBaik.Application.DTOs
@using TaxBaik.Application.Services
@using TaxBaik.Domain.Interfaces
@inject BlogService BlogService
@inject ICategoryRepository CategoryRepository
@using TaxBaik.Web.Components.Admin.Pages.Blog
@inject IBlogBrowserClient BlogClient
@inject ICategoryBrowserClient CategoryClient
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@@ -21,62 +19,16 @@
</section>
<MudPaper Class="pa-4 mt-4" Elevation="1">
<MudForm @ref="form">
<MudTextField @bind-Value="model.Title" Label="제목 *"
Variant="Variant.Outlined" Class="mb-4" Required="true" RequiredError="제목을 입력하세요." Counter="100" MaxLength="100" />
<MudSelect T="int?" @bind-Value="model.CategoryId" Label="카테고리"
Variant="Variant.Outlined" Class="mb-4">
@foreach (var category in categories)
{
<MudSelectItem T="int?" Value="@((int?)category.Id)">@category.Name</MudSelectItem>
}
</MudSelect>
<div class="mb-4">
<label class="d-block mb-2" style="font-weight: 500;">본문 내용 (마크다운) *</label>
<textarea id="markdown-editor" @bind="model.Content" style="display: none;"></textarea>
<div id="editor-container" style="border: 1px solid #d0d0d0; border-radius: 4px; min-height: 400px;"></div>
</div>
<MudTextField @bind-Value="model.Tags" Label="태그 (쉼표로 구분)"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="model.SeoTitle" Label="SEO 제목"
Variant="Variant.Outlined" Class="mb-4" />
<MudTextField @bind-Value="model.SeoDescription" Label="SEO 설명"
Variant="Variant.Outlined" Lines="3" Class="mb-4" />
<MudCheckBox @bind-Checked="model.IsPublished" Label="즉시 발행" Class="mb-4" />
<div class="d-flex gap-2">
<MudButton Variant="Variant.Filled" Color="Color.Primary"
@onclick="SavePost">저장</MudButton>
</div>
</MudForm>
<BlogForm Model="model" Categories="categories" SubmitText="저장" OnSubmit="SavePost" OnCancel="GoBack" />
</MudPaper>
@code {
private MudForm? form;
private List<Domain.Entities.Category> categories = [];
private CreatePostModel model = new();
private EasyMDE.Editor? editor;
[Inject]
private IJSRuntime JS { get; set; } = null!;
private IReadOnlyList<Domain.Entities.Category> categories = [];
private BlogForm.BlogFormModel model = new();
protected override async Task OnInitializedAsync()
{
categories = (await CategoryRepository.GetAllAsync()).ToList();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("window.initMarkdownEditor", "markdown-editor", model.Content ?? "");
}
categories = await CategoryClient.GetAllAsync();
}
private void GoBack()
@@ -86,25 +38,9 @@
private async Task SavePost()
{
if (form == null)
return;
// 에디터에서 최신 내용 가져오기
model.Content = await JS.InvokeAsync<string>("window.getMarkdownContent");
if (string.IsNullOrWhiteSpace(model.Content))
{
Snackbar.Add("본문 내용을 입력하세요.", Severity.Error);
return;
}
await form.Validate();
if (!form.IsValid)
return;
try
{
await BlogService.CreateAsync(new CreateBlogPostDto
var result = await BlogClient.CreateAsync(new CreateBlogPostDto
{
Title = model.Title,
Content = model.Content,
@@ -115,6 +51,12 @@
IsPublished = model.IsPublished
});
if (result == null)
{
Snackbar.Add("포스트 저장에 실패했습니다.", Severity.Error);
return;
}
Snackbar.Add("포스트가 저장되었습니다.", Severity.Success);
Navigation.NavigateTo("/taxbaik/admin/blog");
}
@@ -123,45 +65,4 @@
Snackbar.Add(ex.Message, Severity.Error);
}
}
private class CreatePostModel
{
public string Title { get; set; } = "";
public string Content { get; set; } = "";
public int? CategoryId { get; set; }
public string? Tags { get; set; }
public string? SeoTitle { get; set; }
public string? SeoDescription { get; set; }
public bool IsPublished { get; set; }
}
}
<!-- EasyMDE 초기화 스크립트 -->
<script>
window.initMarkdownEditor = function(editorId, initialContent) {
if (!window.easyMDEInstance) {
window.easyMDEInstance = new EasyMDE({
element: document.getElementById(editorId),
spellChecker: false,
autoDownloadFontAwesome: false,
initialValue: initialContent || "",
toolbar: [
"bold", "italic", "strikethrough", "|",
"heading", "code", "|",
"unordered-list", "ordered-list", "|",
"link", "image", "table", "|",
"quote", "horizontal-rule", "|",
"preview", "side-by-side", "fullscreen", "|",
"guide"
],
previewRender: function(plainText) {
return marked.parse(plainText);
}
});
}
};
window.getMarkdownContent = function() {
return window.easyMDEInstance ? window.easyMDEInstance.value() : "";
};
</script>