feat: Add SitePageSettings management UI
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m21s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m21s
- ISitePageSettingsService + SitePageSettingsService (gRPC client) - PageSettingsManagementPage: 4 cards for fixed pages - PageSettingsEditDialog: edit title, hero, meta, settingsJson - PageImagesDialog: CRUD for page images (licenses, team, values) - PageImageEditDialog: add/edit single image with upload - Proto NuGet updated to 0.0.180 - NavMenu: added link to page settings
This commit is contained in:
@@ -138,7 +138,7 @@
|
|||||||
|
|
||||||
<!-- CMS Protobuf NuGet package (used for both local dev and Docker builds) -->
|
<!-- CMS Protobuf NuGet package (used for both local dev and Docker builds) -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.178" />
|
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.180" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ using CMSMicroservice.Protobuf.Protos.BlogPost;
|
|||||||
using CMSMicroservice.Protobuf.Protos.BlogCategory;
|
using CMSMicroservice.Protobuf.Protos.BlogCategory;
|
||||||
using CMSMicroservice.Protobuf.Protos.BlogPostImage;
|
using CMSMicroservice.Protobuf.Protos.BlogPostImage;
|
||||||
using CMSMicroservice.Protobuf.Protos.SitePage;
|
using CMSMicroservice.Protobuf.Protos.SitePage;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.SitePageSettings;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserContract;
|
using CMSMicroservice.Protobuf.Protos.UserContract;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserWallet;
|
using CMSMicroservice.Protobuf.Protos.UserWallet;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog;
|
using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog;
|
||||||
@@ -91,6 +92,7 @@ public static class ConfigureServices
|
|||||||
services.AddScoped<IBlogCategoryService, BlogCategoryService>();
|
services.AddScoped<IBlogCategoryService, BlogCategoryService>();
|
||||||
services.AddScoped<IBlogPostImageService, BlogPostImageService>();
|
services.AddScoped<IBlogPostImageService, BlogPostImageService>();
|
||||||
services.AddScoped<ISitePageService, SitePageService>();
|
services.AddScoped<ISitePageService, SitePageService>();
|
||||||
|
services.AddScoped<ISitePageSettingsService, SitePageSettingsService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@@ -158,6 +160,7 @@ public static class ConfigureServices
|
|||||||
services.AddTransient(sp => new BlogCategoryContract.BlogCategoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new BlogCategoryContract.BlogCategoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
services.AddTransient(sp => new BlogPostImageContract.BlogPostImageContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new BlogPostImageContract.BlogPostImageContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
services.AddTransient(sp => new SitePageContract.SitePageContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new SitePageContract.SitePageContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
services.AddTransient(sp => new SitePageSettingsContract.SitePageSettingsContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
|
||||||
// User Contracts Service
|
// User Contracts Service
|
||||||
services.AddTransient(sp => new UserContractContract.UserContractContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new UserContractContract.UserContractContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
@using BackOffice.Services.Content
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudForm @ref="_form" @bind-IsValid="@_isValid">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
|
||||||
|
<MudSelect T="string" @bind-Value="Model.ImageGroup"
|
||||||
|
Label="گروه تصویر"
|
||||||
|
Required="true"
|
||||||
|
Variant="Variant.Outlined">
|
||||||
|
@foreach (var group in GetAvailableGroups())
|
||||||
|
{
|
||||||
|
<MudSelectItem T="string" Value="@group.Key">@group.Value</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.Title"
|
||||||
|
Label="عنوان"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.Subtitle"
|
||||||
|
Label="زیرعنوان"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.Description"
|
||||||
|
Label="توضیحات"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="3" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.LinkUrl"
|
||||||
|
Label="لینک"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Placeholder="https://..." />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.IconName"
|
||||||
|
Label="نام آیکون"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
HelperText="نام آیکون Material Design — مثلاً: Verified, Star, Group" />
|
||||||
|
|
||||||
|
<MudNumericField @bind-Value="Model.SortOrder"
|
||||||
|
Label="ترتیب نمایش"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Min="0" />
|
||||||
|
|
||||||
|
<MudText Typo="Typo.subtitle2">تصویر</MudText>
|
||||||
|
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_imagePreview))
|
||||||
|
{
|
||||||
|
<Image Src="@_imagePreview" Width="200" Height="150" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(ExistingImagePath))
|
||||||
|
{
|
||||||
|
<Image Src="@ExistingImagePath" Width="200" Height="150" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudPaper Class="d-flex align-center justify-center" Style="width:200px;height:150px;">
|
||||||
|
<MudText Typo="Typo.caption">تصویری انتخاب نشده است</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnImageSelected">
|
||||||
|
<ActivatorContent>
|
||||||
|
<MudButton HtmlTag="label"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
ButtonType="ButtonType.Button"
|
||||||
|
StartIcon="@Icons.Material.Filled.Image">
|
||||||
|
انتخاب تصویر
|
||||||
|
</MudButton>
|
||||||
|
</ActivatorContent>
|
||||||
|
<SelectedTemplate>
|
||||||
|
@if (context != null)
|
||||||
|
{
|
||||||
|
<MudText Class="mt-1" Typo="Typo.caption">@context.Name</MudText>
|
||||||
|
}
|
||||||
|
</SelectedTemplate>
|
||||||
|
</MudFileUpload>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudSwitch T="bool" @bind-Value="Model.IsActive" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.body2">فعال</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="SubmitAsync"
|
||||||
|
Disabled="@(!_isValid || _loading)">
|
||||||
|
@(IsNew ? "افزودن" : "ذخیره تغییرات")
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Color="Color.Default" Variant="Variant.Text" OnClick="Cancel">
|
||||||
|
انصراف
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using BackOffice.Services.Content;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Content.Components;
|
||||||
|
|
||||||
|
public partial class PageImageEditDialog
|
||||||
|
{
|
||||||
|
[CascadingParameter] IMudDialogInstance DialogInstance { get; set; } = default!;
|
||||||
|
[Inject] public ISitePageSettingsService PageSettingsService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public SavePageImageDto Model { get; set; } = new();
|
||||||
|
[Parameter] public string PageKey { get; set; } = string.Empty;
|
||||||
|
[Parameter] public bool IsNew { get; set; }
|
||||||
|
[Parameter] public string? ExistingImagePath { get; set; }
|
||||||
|
|
||||||
|
private MudForm? _form;
|
||||||
|
private bool _isValid = true;
|
||||||
|
private bool _loading;
|
||||||
|
|
||||||
|
private IBrowserFile? _imageFile;
|
||||||
|
private byte[]? _imageBuffer;
|
||||||
|
private string? _imagePreview;
|
||||||
|
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
|
||||||
|
|
||||||
|
private async Task OnImageSelected(IBrowserFile? file)
|
||||||
|
{
|
||||||
|
_imageFile = file;
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
await file.OpenReadStream(_maxAllowedSize).CopyToAsync(ms);
|
||||||
|
_imageBuffer = ms.ToArray();
|
||||||
|
_imagePreview = $"data:{file.ContentType};base64," + Convert.ToBase64String(_imageBuffer);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SubmitAsync()
|
||||||
|
{
|
||||||
|
if (_form != null)
|
||||||
|
{
|
||||||
|
await _form.Validate();
|
||||||
|
if (!_isValid) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loading = true;
|
||||||
|
|
||||||
|
if (_imageFile != null && _imageBuffer != null)
|
||||||
|
{
|
||||||
|
Model.ImageFile = _imageBuffer;
|
||||||
|
Model.ImageMime = _imageFile.ContentType;
|
||||||
|
Model.ImageFileName = Path.GetFileNameWithoutExtension(_imageFile.Name);
|
||||||
|
}
|
||||||
|
else if (IsNew && _imageBuffer == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً یک تصویر انتخاب کنید", Severity.Warning);
|
||||||
|
_loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PageSettingsService.SaveImageAsync(Model);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
DialogInstance.Close(DialogResult.Ok(true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در ذخیره تصویر", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, string> GetAvailableGroups() => PageKey switch
|
||||||
|
{
|
||||||
|
"about" => new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "team", "اعضای تیم" },
|
||||||
|
{ "values", "ارزشها" }
|
||||||
|
},
|
||||||
|
"licenses" => new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "licenses", "مجوزها" }
|
||||||
|
},
|
||||||
|
"landing" => new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "values", "ارزشها" }
|
||||||
|
},
|
||||||
|
_ => new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "values", "ارزشها" },
|
||||||
|
{ "team", "اعضای تیم" },
|
||||||
|
{ "licenses", "مجوزها" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void Cancel()
|
||||||
|
{
|
||||||
|
DialogInstance.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
@using BackOffice.Services.Content
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.subtitle1">تصاویر صفحه «@PageTitle»</MudText>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
OnClick="AddImage">
|
||||||
|
افزودن تصویر
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@if (!Images.Any())
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">
|
||||||
|
تصویری ثبت نشده است.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudDataGrid T="PageImageItemDto"
|
||||||
|
Items="Images"
|
||||||
|
Hover="true"
|
||||||
|
Dense="true"
|
||||||
|
Elevation="0">
|
||||||
|
<Columns>
|
||||||
|
<TemplateColumn Title="تصویر" Sortable="false">
|
||||||
|
<CellTemplate>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(context.Item.ThumbnailPath))
|
||||||
|
{
|
||||||
|
<Image Src="@context.Item.ThumbnailPath" Width="48" Height="48" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(context.Item.ImagePath))
|
||||||
|
{
|
||||||
|
<Image Src="@context.Item.ImagePath" Width="48" Height="48" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.ImageNotSupported" Size="Size.Medium" />
|
||||||
|
}
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.ImageGroup" Title="گروه" />
|
||||||
|
<PropertyColumn Property="x => x.Title" Title="عنوان" />
|
||||||
|
<PropertyColumn Property="x => x.SortOrder" Title="ترتیب" />
|
||||||
|
<PropertyColumn Property="x => x.IsActive" Title="وضعیت">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string"
|
||||||
|
Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
|
||||||
|
Size="Size.Small">
|
||||||
|
@(context.Item.IsActive ? "فعال" : "غیرفعال")
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
<TemplateColumn Title="عملیات" Sortable="false">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Row="true" Spacing="1">
|
||||||
|
<MudTooltip Text="ویرایش">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Primary"
|
||||||
|
OnClick="@(() => EditImage(context.Item))" />
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="حذف">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Error"
|
||||||
|
OnClick="@(() => DeleteImage(context.Item))" />
|
||||||
|
</MudTooltip>
|
||||||
|
</MudStack>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
</MudDataGrid>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton Color="Color.Default" Variant="Variant.Text" OnClick="Close">
|
||||||
|
بستن
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using BackOffice.Services.Content;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Content.Components;
|
||||||
|
|
||||||
|
public partial class PageImagesDialog
|
||||||
|
{
|
||||||
|
[CascadingParameter] IMudDialogInstance DialogInstance { get; set; } = default!;
|
||||||
|
[Inject] public ISitePageSettingsService PageSettingsService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public long PageId { get; set; }
|
||||||
|
[Parameter] public string PageKey { get; set; } = string.Empty;
|
||||||
|
[Parameter] public string PageTitle { get; set; } = string.Empty;
|
||||||
|
[Parameter] public List<PageImageItemDto> Images { get; set; } = new();
|
||||||
|
|
||||||
|
private bool _hasChanges;
|
||||||
|
|
||||||
|
private async Task AddImage()
|
||||||
|
{
|
||||||
|
var dto = new SavePageImageDto
|
||||||
|
{
|
||||||
|
SitePageSettingsId = PageId,
|
||||||
|
ImageGroup = GetDefaultImageGroup(),
|
||||||
|
IsActive = true,
|
||||||
|
SortOrder = Images.Count + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<PageImageEditDialog>
|
||||||
|
{
|
||||||
|
{ nameof(PageImageEditDialog.Model), dto },
|
||||||
|
{ nameof(PageImageEditDialog.PageKey), PageKey },
|
||||||
|
{ nameof(PageImageEditDialog.IsNew), true }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PageImageEditDialog>(
|
||||||
|
"افزودن تصویر", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
Snackbar.Add("تصویر با موفقیت اضافه شد", Severity.Success);
|
||||||
|
_hasChanges = true;
|
||||||
|
await ReloadImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EditImage(PageImageItemDto item)
|
||||||
|
{
|
||||||
|
var dto = new SavePageImageDto
|
||||||
|
{
|
||||||
|
Id = item.Id,
|
||||||
|
SitePageSettingsId = PageId,
|
||||||
|
ImageGroup = item.ImageGroup,
|
||||||
|
Title = item.Title,
|
||||||
|
Subtitle = item.Subtitle,
|
||||||
|
Description = item.Description,
|
||||||
|
LinkUrl = item.LinkUrl,
|
||||||
|
IconName = item.IconName,
|
||||||
|
SortOrder = item.SortOrder,
|
||||||
|
IsActive = item.IsActive
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<PageImageEditDialog>
|
||||||
|
{
|
||||||
|
{ nameof(PageImageEditDialog.Model), dto },
|
||||||
|
{ nameof(PageImageEditDialog.PageKey), PageKey },
|
||||||
|
{ nameof(PageImageEditDialog.IsNew), false },
|
||||||
|
{ nameof(PageImageEditDialog.ExistingImagePath), item.ImagePath }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PageImageEditDialog>(
|
||||||
|
"ویرایش تصویر", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
Snackbar.Add("تصویر با موفقیت ویرایش شد", Severity.Success);
|
||||||
|
_hasChanges = true;
|
||||||
|
await ReloadImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteImage(PageImageItemDto item)
|
||||||
|
{
|
||||||
|
var confirm = await DialogService.ShowMessageBox(
|
||||||
|
"تایید حذف",
|
||||||
|
$"آیا از حذف تصویر «{item.Title ?? item.ImageGroup}» اطمینان دارید؟",
|
||||||
|
yesText: "بله، حذف شود",
|
||||||
|
cancelText: "انصراف");
|
||||||
|
|
||||||
|
if (confirm != true) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await PageSettingsService.DeleteImageAsync(item.Id);
|
||||||
|
Snackbar.Add("تصویر با موفقیت حذف شد", Severity.Success);
|
||||||
|
_hasChanges = true;
|
||||||
|
await ReloadImages();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در حذف تصویر: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadImages()
|
||||||
|
{
|
||||||
|
var details = await PageSettingsService.GetByKeyAsync(PageKey);
|
||||||
|
if (details != null)
|
||||||
|
{
|
||||||
|
Images = details.Images;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDefaultImageGroup() => PageKey switch
|
||||||
|
{
|
||||||
|
"about" => "team",
|
||||||
|
"licenses" => "licenses",
|
||||||
|
_ => "values"
|
||||||
|
};
|
||||||
|
|
||||||
|
private void Close()
|
||||||
|
{
|
||||||
|
if (_hasChanges)
|
||||||
|
DialogInstance.Close(DialogResult.Ok(true));
|
||||||
|
else
|
||||||
|
DialogInstance.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
@using BackOffice.Services.Content
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudForm @ref="_form" @bind-IsValid="@_isValid">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudTextField @bind-Value="Model.Title"
|
||||||
|
Label="عنوان صفحه"
|
||||||
|
Required="true"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.MetaDescription"
|
||||||
|
Label="توضیحات متا (SEO)"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="2" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.HeroTitle"
|
||||||
|
Label="عنوان هیرو"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.HeroSubtitle"
|
||||||
|
Label="زیرعنوان هیرو"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="2" />
|
||||||
|
|
||||||
|
<MudText Typo="Typo.subtitle2">تصویر هیرو</MudText>
|
||||||
|
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_imagePreview))
|
||||||
|
{
|
||||||
|
<Image Src="@_imagePreview" Width="280" Height="160" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(ExistingHeroImagePath))
|
||||||
|
{
|
||||||
|
<Image Src="@ExistingHeroImagePath" Width="280" Height="160" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudPaper Class="d-flex align-center justify-center" Style="width:280px;height:160px;">
|
||||||
|
<MudText Typo="Typo.caption">تصویری انتخاب نشده است</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnImageSelected">
|
||||||
|
<ActivatorContent>
|
||||||
|
<MudButton HtmlTag="label"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
ButtonType="ButtonType.Button"
|
||||||
|
StartIcon="@Icons.Material.Filled.Image">
|
||||||
|
انتخاب تصویر هیرو
|
||||||
|
</MudButton>
|
||||||
|
</ActivatorContent>
|
||||||
|
<SelectedTemplate>
|
||||||
|
@if (context != null)
|
||||||
|
{
|
||||||
|
<MudText Class="mt-1" Typo="Typo.caption">@context.Name</MudText>
|
||||||
|
}
|
||||||
|
</SelectedTemplate>
|
||||||
|
</MudFileUpload>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudDivider Class="my-2" />
|
||||||
|
|
||||||
|
<MudText Typo="Typo.subtitle2">تنظیمات اختصاصی (JSON)</MudText>
|
||||||
|
<MudTextField @bind-Value="Model.SettingsJson"
|
||||||
|
Label="تنظیمات JSON"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="8"
|
||||||
|
HelperText="تنظیمات اختصاصی هر صفحه — مثلاً خدمات، مراحل، FAQ و..." />
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudSwitch T="bool" @bind-Value="Model.IsActive" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.body2">فعال</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="SubmitAsync" Disabled="@(!_isValid || _loading)">
|
||||||
|
ذخیره تغییرات
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Color="Color.Default" Variant="Variant.Text" OnClick="Cancel">
|
||||||
|
انصراف
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using BackOffice.Services.Content;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Content.Components;
|
||||||
|
|
||||||
|
public partial class PageSettingsEditDialog
|
||||||
|
{
|
||||||
|
[CascadingParameter] IMudDialogInstance DialogInstance { get; set; } = default!;
|
||||||
|
[Inject] public ISitePageSettingsService PageSettingsService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public SavePageSettingsDto Model { get; set; } = new();
|
||||||
|
[Parameter] public string? ExistingHeroImagePath { get; set; }
|
||||||
|
[Parameter] public string PageTitle { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private MudForm? _form;
|
||||||
|
private bool _isValid = true;
|
||||||
|
private bool _loading;
|
||||||
|
|
||||||
|
private IBrowserFile? _imageFile;
|
||||||
|
private byte[]? _imageBuffer;
|
||||||
|
private string? _imagePreview;
|
||||||
|
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
|
||||||
|
|
||||||
|
private async Task OnImageSelected(IBrowserFile? file)
|
||||||
|
{
|
||||||
|
_imageFile = file;
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
await file.OpenReadStream(_maxAllowedSize).CopyToAsync(ms);
|
||||||
|
_imageBuffer = ms.ToArray();
|
||||||
|
_imagePreview = $"data:{file.ContentType};base64," + Convert.ToBase64String(_imageBuffer);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SubmitAsync()
|
||||||
|
{
|
||||||
|
if (_form != null)
|
||||||
|
{
|
||||||
|
await _form.Validate();
|
||||||
|
if (!_isValid) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loading = true;
|
||||||
|
|
||||||
|
if (_imageFile != null && _imageBuffer != null)
|
||||||
|
{
|
||||||
|
Model.ImageFile = _imageBuffer;
|
||||||
|
Model.ImageMime = _imageFile.ContentType;
|
||||||
|
Model.ImageFileName = Path.GetFileNameWithoutExtension(_imageFile.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PageSettingsService.SaveSettingsAsync(Model);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
DialogInstance.Close(DialogResult.Ok(true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در ذخیره تنظیمات", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel()
|
||||||
|
{
|
||||||
|
DialogInstance.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
@page "/content/page-settings"
|
||||||
|
|
||||||
|
@using BackOffice.Services.Content
|
||||||
|
|
||||||
|
@inject ISitePageSettingsService PageSettingsService
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudText Typo="Typo.h5">مدیریت تنظیمات صفحات</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">تنظیمات ۴ صفحه ثابت سایت: صفحه اصلی، درباره ما، تماس با ما و مجوزها</MudText>
|
||||||
|
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudGrid>
|
||||||
|
@foreach (var item in _pages)
|
||||||
|
{
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudCard Elevation="2" Class="pa-2">
|
||||||
|
<MudCardHeader>
|
||||||
|
<CardHeaderContent>
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@GetPageIcon(item.PageKey)" Size="Size.Large" Color="Color.Primary" />
|
||||||
|
<MudStack Spacing="0">
|
||||||
|
<MudText Typo="Typo.h6">@item.Title</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">@item.PageKey</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</CardHeaderContent>
|
||||||
|
<CardHeaderActions>
|
||||||
|
<MudChip T="string"
|
||||||
|
Color="@(item.IsActive ? Color.Success : Color.Error)"
|
||||||
|
Size="Size.Small">
|
||||||
|
@(item.IsActive ? "فعال" : "غیرفعال")
|
||||||
|
</MudChip>
|
||||||
|
</CardHeaderActions>
|
||||||
|
</MudCardHeader>
|
||||||
|
<MudCardContent>
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Image" Size="Size.Small" Class="ml-1" />
|
||||||
|
@item.ImageCount تصویر
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
آخرین ویرایش: @item.LastModified.MiladiToJalaliWithTime()
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudCardContent>
|
||||||
|
<MudCardActions>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.Edit"
|
||||||
|
OnClick="@(() => EditPageSettings(item))">
|
||||||
|
ویرایش
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Info"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.Collections"
|
||||||
|
OnClick="@(() => ManageImages(item))">
|
||||||
|
تصاویر
|
||||||
|
</MudButton>
|
||||||
|
</MudCardActions>
|
||||||
|
</MudCard>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using BackOffice.Services.Content;
|
||||||
|
using BackOffice.Pages.Content.Components;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Content;
|
||||||
|
|
||||||
|
public partial class PageSettingsManagementPage
|
||||||
|
{
|
||||||
|
private List<PageSettingsSummaryDto> _pages = new();
|
||||||
|
private bool _loading = true;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadPagesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadPagesAsync()
|
||||||
|
{
|
||||||
|
_loading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pages = await PageSettingsService.GetAllAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در دریافت صفحات: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EditPageSettings(PageSettingsSummaryDto summary)
|
||||||
|
{
|
||||||
|
var details = await PageSettingsService.GetByKeyAsync(summary.PageKey);
|
||||||
|
if (details == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در دریافت تنظیمات صفحه", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dto = new SavePageSettingsDto
|
||||||
|
{
|
||||||
|
PageKey = details.PageKey,
|
||||||
|
Title = details.Title,
|
||||||
|
MetaDescription = details.MetaDescription,
|
||||||
|
HeroTitle = details.HeroTitle,
|
||||||
|
HeroSubtitle = details.HeroSubtitle,
|
||||||
|
IsActive = details.IsActive,
|
||||||
|
SettingsJson = details.SettingsJson
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<PageSettingsEditDialog>
|
||||||
|
{
|
||||||
|
{ nameof(PageSettingsEditDialog.Model), dto },
|
||||||
|
{ nameof(PageSettingsEditDialog.ExistingHeroImagePath), details.HeroImagePath },
|
||||||
|
{ nameof(PageSettingsEditDialog.PageTitle), GetPageDisplayName(summary.PageKey) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PageSettingsEditDialog>(
|
||||||
|
$"ویرایش تنظیمات «{GetPageDisplayName(summary.PageKey)}»", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
Snackbar.Add("تنظیمات صفحه با موفقیت ذخیره شد", Severity.Success);
|
||||||
|
await LoadPagesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ManageImages(PageSettingsSummaryDto summary)
|
||||||
|
{
|
||||||
|
var details = await PageSettingsService.GetByKeyAsync(summary.PageKey);
|
||||||
|
if (details == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در دریافت اطلاعات صفحه", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<PageImagesDialog>
|
||||||
|
{
|
||||||
|
{ nameof(PageImagesDialog.PageId), details.Id },
|
||||||
|
{ nameof(PageImagesDialog.PageKey), details.PageKey },
|
||||||
|
{ nameof(PageImagesDialog.PageTitle), GetPageDisplayName(summary.PageKey) },
|
||||||
|
{ nameof(PageImagesDialog.Images), details.Images }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PageImagesDialog>(
|
||||||
|
$"مدیریت تصاویر «{GetPageDisplayName(summary.PageKey)}»", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Large, FullWidth = true });
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
await LoadPagesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPageIcon(string pageKey) => pageKey switch
|
||||||
|
{
|
||||||
|
"landing" => Icons.Material.Filled.Home,
|
||||||
|
"about" => Icons.Material.Filled.Info,
|
||||||
|
"contact" => Icons.Material.Filled.ContactMail,
|
||||||
|
"licenses" => Icons.Material.Filled.VerifiedUser,
|
||||||
|
_ => Icons.Material.Filled.Article
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetPageDisplayName(string pageKey) => pageKey switch
|
||||||
|
{
|
||||||
|
"landing" => "صفحه اصلی",
|
||||||
|
"about" => "درباره ما",
|
||||||
|
"contact" => "تماس با ما",
|
||||||
|
"licenses" => "مجوزها",
|
||||||
|
_ => pageKey
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
namespace BackOffice.Services.Content;
|
||||||
|
|
||||||
|
public interface ISitePageSettingsService
|
||||||
|
{
|
||||||
|
Task<List<PageSettingsSummaryDto>> GetAllAsync();
|
||||||
|
Task<PageSettingsDetailsDto?> GetByKeyAsync(string pageKey);
|
||||||
|
Task<SaveResultDto> SaveSettingsAsync(SavePageSettingsDto dto);
|
||||||
|
Task<SaveResultDto> SaveImageAsync(SavePageImageDto dto);
|
||||||
|
Task DeleteImageAsync(long id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Summary DTO (لیست صفحات) ──
|
||||||
|
public class PageSettingsSummaryDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string PageKey { get; set; } = string.Empty;
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public int ImageCount { get; set; }
|
||||||
|
public DateTime LastModified { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Details DTO (تنظیمات کامل + تصاویر) ──
|
||||||
|
public class PageSettingsDetailsDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string PageKey { get; set; } = string.Empty;
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string? MetaDescription { get; set; }
|
||||||
|
public string? HeroTitle { get; set; }
|
||||||
|
public string? HeroSubtitle { get; set; }
|
||||||
|
public string? HeroImagePath { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public string? SettingsJson { get; set; }
|
||||||
|
public List<PageImageItemDto> Images { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Image Item DTO ──
|
||||||
|
public class PageImageItemDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string ImageGroup { get; set; } = string.Empty;
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Subtitle { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string ImagePath { get; set; } = string.Empty;
|
||||||
|
public string? ThumbnailPath { get; set; }
|
||||||
|
public string? LinkUrl { get; set; }
|
||||||
|
public string? IconName { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Save Settings DTO ──
|
||||||
|
public class SavePageSettingsDto
|
||||||
|
{
|
||||||
|
public string PageKey { get; set; } = string.Empty;
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string? MetaDescription { get; set; }
|
||||||
|
public string? HeroTitle { get; set; }
|
||||||
|
public string? HeroSubtitle { get; set; }
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
public string? SettingsJson { get; set; }
|
||||||
|
|
||||||
|
// Hero image upload
|
||||||
|
public byte[]? ImageFile { get; set; }
|
||||||
|
public string? ImageMime { get; set; }
|
||||||
|
public string? ImageFileName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Save Image DTO ──
|
||||||
|
public class SavePageImageDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; } // 0 = جدید
|
||||||
|
public long SitePageSettingsId { get; set; }
|
||||||
|
public string ImageGroup { get; set; } = string.Empty;
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Subtitle { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? LinkUrl { get; set; }
|
||||||
|
public string? IconName { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
|
// Image upload
|
||||||
|
public byte[]? ImageFile { get; set; }
|
||||||
|
public string? ImageMime { get; set; }
|
||||||
|
public string? ImageFileName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Save Result ──
|
||||||
|
public class SaveResultDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
using CMSMicroservice.Protobuf.Protos.SitePageSettings;
|
||||||
|
|
||||||
|
namespace BackOffice.Services.Content;
|
||||||
|
|
||||||
|
public class SitePageSettingsService : ISitePageSettingsService
|
||||||
|
{
|
||||||
|
private readonly SitePageSettingsContract.SitePageSettingsContractClient _client;
|
||||||
|
|
||||||
|
public SitePageSettingsService(SitePageSettingsContract.SitePageSettingsContractClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<PageSettingsSummaryDto>> GetAllAsync()
|
||||||
|
{
|
||||||
|
var response = await _client.GetAllPageSettingsAsync(new GetAllPageSettingsRequest());
|
||||||
|
|
||||||
|
return response.Pages.Select(m => new PageSettingsSummaryDto
|
||||||
|
{
|
||||||
|
Id = m.Id,
|
||||||
|
PageKey = m.PageKey,
|
||||||
|
Title = m.Title,
|
||||||
|
IsActive = m.IsActive,
|
||||||
|
ImageCount = m.ImageCount,
|
||||||
|
LastModified = m.LastModified?.ToDateTime() ?? DateTime.MinValue
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PageSettingsDetailsDto?> GetByKeyAsync(string pageKey)
|
||||||
|
{
|
||||||
|
var response = await _client.GetPageSettingsAsync(
|
||||||
|
new GetPageSettingsRequest { PageKey = pageKey });
|
||||||
|
|
||||||
|
if (response == null || response.Id <= 0) return null;
|
||||||
|
|
||||||
|
var dto = new PageSettingsDetailsDto
|
||||||
|
{
|
||||||
|
Id = response.Id,
|
||||||
|
PageKey = response.PageKey,
|
||||||
|
Title = response.Title,
|
||||||
|
MetaDescription = response.MetaDescription,
|
||||||
|
HeroTitle = response.HeroTitle,
|
||||||
|
HeroSubtitle = response.HeroSubtitle,
|
||||||
|
HeroImagePath = response.HeroImagePath,
|
||||||
|
IsActive = response.IsActive,
|
||||||
|
SettingsJson = response.SettingsJson
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var img in response.Images)
|
||||||
|
{
|
||||||
|
dto.Images.Add(new PageImageItemDto
|
||||||
|
{
|
||||||
|
Id = img.Id,
|
||||||
|
ImageGroup = img.ImageGroup,
|
||||||
|
Title = img.Title,
|
||||||
|
Subtitle = img.Subtitle,
|
||||||
|
Description = img.Description,
|
||||||
|
ImagePath = img.ImagePath,
|
||||||
|
ThumbnailPath = img.ThumbnailPath,
|
||||||
|
LinkUrl = img.LinkUrl,
|
||||||
|
IconName = img.IconName,
|
||||||
|
SortOrder = img.SortOrder,
|
||||||
|
IsActive = img.IsActive
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SaveResultDto> SaveSettingsAsync(SavePageSettingsDto dto)
|
||||||
|
{
|
||||||
|
var request = new SavePageSettingsRequest
|
||||||
|
{
|
||||||
|
PageKey = dto.PageKey,
|
||||||
|
Title = dto.Title,
|
||||||
|
MetaDescription = dto.MetaDescription ?? string.Empty,
|
||||||
|
HeroTitle = dto.HeroTitle ?? string.Empty,
|
||||||
|
HeroSubtitle = dto.HeroSubtitle ?? string.Empty,
|
||||||
|
IsActive = dto.IsActive,
|
||||||
|
SettingsJson = dto.SettingsJson ?? string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dto.ImageFile != null && !string.IsNullOrEmpty(dto.ImageMime) && !string.IsNullOrEmpty(dto.ImageFileName))
|
||||||
|
{
|
||||||
|
request.HeroImageFile = new PageSettingsImageFile
|
||||||
|
{
|
||||||
|
File = Google.Protobuf.ByteString.CopyFrom(dto.ImageFile),
|
||||||
|
Mime = dto.ImageMime,
|
||||||
|
FileName = dto.ImageFileName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await _client.SavePageSettingsAsync(request);
|
||||||
|
return new SaveResultDto { Id = response.Id, Success = response.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SaveResultDto> SaveImageAsync(SavePageImageDto dto)
|
||||||
|
{
|
||||||
|
var request = new SavePageImageRequest
|
||||||
|
{
|
||||||
|
Id = dto.Id,
|
||||||
|
SitePageSettingsId = dto.SitePageSettingsId,
|
||||||
|
ImageGroup = dto.ImageGroup,
|
||||||
|
Title = dto.Title ?? string.Empty,
|
||||||
|
Subtitle = dto.Subtitle ?? string.Empty,
|
||||||
|
Description = dto.Description ?? string.Empty,
|
||||||
|
LinkUrl = dto.LinkUrl ?? string.Empty,
|
||||||
|
IconName = dto.IconName ?? string.Empty,
|
||||||
|
SortOrder = dto.SortOrder,
|
||||||
|
IsActive = dto.IsActive
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dto.ImageFile != null && !string.IsNullOrEmpty(dto.ImageMime) && !string.IsNullOrEmpty(dto.ImageFileName))
|
||||||
|
{
|
||||||
|
request.ImageFile = new PageSettingsImageFile
|
||||||
|
{
|
||||||
|
File = Google.Protobuf.ByteString.CopyFrom(dto.ImageFile),
|
||||||
|
Mime = dto.ImageMime,
|
||||||
|
FileName = dto.ImageFileName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await _client.SavePageImageAsync(request);
|
||||||
|
return new SaveResultDto { Id = response.Id, Success = response.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteImageAsync(long id)
|
||||||
|
{
|
||||||
|
await _client.DeletePageImageAsync(new DeletePageImageRequest { Id = id });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -231,9 +231,9 @@
|
|||||||
@if (CanManageSitePages)
|
@if (CanManageSitePages)
|
||||||
{
|
{
|
||||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
Href="/content/pages"
|
Href="/content/page-settings"
|
||||||
Icon="@Icons.Material.Filled.WebAsset">
|
Icon="@Icons.Material.Filled.WebAsset">
|
||||||
صفحات سایت
|
تنظیمات صفحات
|
||||||
</MudNavLink>
|
</MudNavLink>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user