Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
# 🏗️ BackOffice — مرجع معماری و الگوها
|
||||
|
||||
> **تاریخ:** ۱۴۰۴/۱۱/۲۴ (February 13, 2026)
|
||||
> **پروژه:** BackOffice Admin Panel (Blazor WebAssembly)
|
||||
|
||||
---
|
||||
|
||||
## ۱. معماری کلی
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ BackOffice │
|
||||
│ (Blazor WebAssembly) │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ MudBlazor│ │ Mapster │ │ DateTimeCvt │ │
|
||||
│ │ v8 │ │ (mapping)│ │ (تاریخ شمسی) │ │
|
||||
│ └──────────┘ └──────────┘ └──────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ Pages / Components / Shared │ │
|
||||
│ │ BasePageComponent, Hub Pages, Dialogs │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ gRPC Clients │ │ HTTP REST Services│ │
|
||||
│ │ (Protobuf) │ │ (DiscountShop) │ │
|
||||
│ └──────┬───────┘ └────────┬─────────┘ │
|
||||
└─────────┼──────────────────────┼─────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ CMS Microservice │
|
||||
│ (ASP.NET Core + gRPC) │
|
||||
│ Domain → Application (CQRS) → Infra │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۲. Technology Stack
|
||||
|
||||
| لایه | تکنولوژی | نسخه |
|
||||
|------|----------|------|
|
||||
| Frontend Framework | Blazor WebAssembly | .NET 9 |
|
||||
| UI Library | MudBlazor | v8 |
|
||||
| Backend Communication (عادی) | gRPC / Protobuf | — |
|
||||
| Backend Communication (تخفیفی) | HTTP REST | — |
|
||||
| Object Mapping | Mapster | — |
|
||||
| تاریخ شمسی | DateTimeConverterCL | — |
|
||||
| Client State | Blazored.LocalStorage | — |
|
||||
| Auth | JWT Role-based | Administrator, Admin, Author |
|
||||
| Permission | IAuthorizationService.HasPermissionAsync | 18 permission |
|
||||
|
||||
---
|
||||
|
||||
## ۳. ساختار پوشهها
|
||||
|
||||
```
|
||||
BackOffice/src/BackOffice/
|
||||
├── Common/
|
||||
│ ├── BaseComponents/ ← کامپوننتهای پایه (BasePageComponent, DateRangePicker, Image)
|
||||
│ ├── Utilities/ ← RouteConstance, Extensions, Helpers
|
||||
│ └── ...
|
||||
├── Pages/
|
||||
│ ├── Category/ ← دستهبندی فروشگاه عادی
|
||||
│ ├── Products/ ← محصولات فروشگاه عادی
|
||||
│ ├── UserOrder/ ← سفارشات + گزارش فروش (Hub)
|
||||
│ ├── DiscountShop/ ← فروشگاه تخفیفی (محصولات + دستهبندی + سفارشات)
|
||||
│ │ └── Components/ ← دیالوگها و کامپوننتهای اختصاصی
|
||||
│ ├── Inventory/ ← انبارداری (4 صفحه)
|
||||
│ ├── Package/ ← پکیجها
|
||||
│ ├── Commission/ ← کمیسیون (5 صفحه)
|
||||
│ ├── Network/ ← شبکه (4 صفحه)
|
||||
│ ├── Club/ ← باشگاه مشتریان (Hub: اعضا + آمار + فیچرها)
|
||||
│ ├── Blog/ ← بلاگ (Hub: پست + دستهبندی + تگ)
|
||||
│ ├── Content/ ← صفحات سایت
|
||||
│ ├── Wallet/ ← کیفپول (تبها: لیست + تاریخچه)
|
||||
│ ├── Contract/ ← قراردادها
|
||||
│ ├── SystemManagement/ ← سیستم (Hub: تنظیمات + Worker + Health)
|
||||
│ └── ...
|
||||
├── Services/
|
||||
│ ├── DiscountProduct/ ← IDiscountProductService + implementation
|
||||
│ ├── DiscountCategory/ ← IDiscountCategoryService + implementation
|
||||
│ ├── DiscountOrder/ ← IDiscountOrderService + implementation
|
||||
│ └── Authorization/ ← IAuthorizationService
|
||||
├── Shared/
|
||||
│ ├── MainLayout.razor ← لایوت اصلی (AppBar + NavMenu + MudContainer)
|
||||
│ ├── NavMenu.razor ← منوی ناوبری
|
||||
│ ├── GlobalSearch.razor ← جستجوی سراسری
|
||||
│ └── AppBreadcrumb.razor ← Breadcrumb فارسی
|
||||
└── wwwroot/
|
||||
├── js/main.js ← jsSaveAsFile (Excel export)
|
||||
└── appsettings.json ← تنظیمات endpoints
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۴. الگوهای اصلی
|
||||
|
||||
### ۴.۱ BasePageComponent — پترن صفحات لیست
|
||||
|
||||
**هر صفحه لیست** از `BasePageComponent` استفاده میکند:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ BasePageComponent │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ 📋 Filter Panel (collapsible) │ │
|
||||
│ │ [فیلد ۱] [فیلد ۲] [فیلد ۳] │ │
|
||||
│ │ [پاک کردن فیلتر] [جستجو] │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ 📊 Content (DataGrid) │ │
|
||||
│ │ ToolBar: [عنوان] [Excel] [+] │ │
|
||||
│ │ Columns: ... │ │
|
||||
│ │ Pager: 20/50/100 │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**فایل:** `Common/BaseComponents/BasePageComponent.razor`
|
||||
|
||||
**پراپرتیها:**
|
||||
- `RenderFragment Filters` — محتوای فیلتر
|
||||
- `RenderFragment Content` — محتوای اصلی
|
||||
- `EventCallback OnSubmitClick` — کلیک جستجو
|
||||
- `EventCallback OnClearFilterClick` — کلیک پاک کردن
|
||||
- `bool IsFiltered` — آیا فیلتر فعال است (نشاندهنده badge «فعال»)
|
||||
|
||||
---
|
||||
|
||||
### ۴.۲ Hub Pages — پترن ادغام صفحات
|
||||
|
||||
صفحات مرتبط در یک Hub با `MudTabs` ادغام میشوند:
|
||||
|
||||
| Hub | Routeها | تبها |
|
||||
|-----|---------|-------|
|
||||
| `OrdersHub` | `/OrdersPage/`, `/OrdersSalesReportsPage/` | سفارشات + گزارش فروش |
|
||||
| `DiscountShopHub` | `/discount-shop`, `/discount-orders`, `/discount-sales-reports` | سفارشات + گزارش فروش |
|
||||
| `ClubHub` | `/club`, `/club/members`, `/club/statistics` | اعضا + آمار |
|
||||
| `BlogHub` | `/blog`, `/blog/posts`, `/blog/categories`, `/tags` | پست + دستهبندی + تگ |
|
||||
| `SystemHub` | `/system`, `/system/configuration`, `/system/worker-control`, `/system/health` | تنظیمات + Worker + Health |
|
||||
|
||||
---
|
||||
|
||||
### ۴.۳ Code-Behind — پترن جداسازی markup/logic
|
||||
|
||||
```
|
||||
MyPage.razor → فقط HTML/Razor markup
|
||||
MyPage.razor.cs → partial class + [Inject] + methods
|
||||
```
|
||||
|
||||
**قوانین:**
|
||||
1. فایلهایی که سرویس inject دارند **باید** code-behind داشته باشند (محدودیت Razor source generator)
|
||||
2. سرویسهای global (`_Imports.razor`) **نباید** دوباره `[Inject]` شوند
|
||||
3. `namespace` باید با مسیر فایل match کند
|
||||
|
||||
**سرویسهای Global (از `_Imports.razor`):**
|
||||
|
||||
| سرویس | نام متغیر | توضیح |
|
||||
|--------|-----------|-------|
|
||||
| `IDialogService` | `DialogService` | دیالوگ MudBlazor |
|
||||
| `ISnackbar` | `Snackbar` | نوتیفیکیشن MudBlazor |
|
||||
| `IJSRuntime` | `jsRuntime` | ⚠️ حرف کوچک `j` |
|
||||
| `NavigationManager` | `Navigation` | ناوبری |
|
||||
| `ILocalStorageService` | `LocalStorageService` | ذخیره محلی |
|
||||
| `AuthenticationStateProvider` | `AuthenticationStateProvider` | احراز هویت |
|
||||
|
||||
---
|
||||
|
||||
### ۴.۴ Excel Export — پترن خروجی CSV
|
||||
|
||||
```csharp
|
||||
private async Task ExportToExcel()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("ستون ۱,ستون ۲,ستون ۳"); // هدر فارسی
|
||||
foreach (var item in items)
|
||||
{
|
||||
sb.AppendLine($"{EscapeCsv(item.Col1)},{item.Col2},{item.Col3}");
|
||||
}
|
||||
var bytes = Encoding.UTF8.GetPreamble() // UTF-8 BOM
|
||||
.Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
||||
var base64 = Convert.ToBase64String(bytes);
|
||||
await jsRuntime.InvokeVoidAsync("jsSaveAsFile", "filename.csv", base64);
|
||||
}
|
||||
|
||||
private string EscapeCsv(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return "";
|
||||
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
||||
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||
return value;
|
||||
}
|
||||
```
|
||||
|
||||
**صفحات دارای Excel:** Products, UserOrders, ClubMembers, WithdrawalRequests, WeeklyReports, StockMovements, Users, DiscountOrders, ManualPayments, Inventory, DiscountProducts
|
||||
|
||||
---
|
||||
|
||||
### ۴.۵ Server-Side DataGrid — پترن بارگذاری صفحهای
|
||||
|
||||
```razor
|
||||
<MudDataGrid T="MyDto"
|
||||
ServerData="LoadServerData"
|
||||
Height="calc(100vh - 240px)"
|
||||
FixedHeader="true"
|
||||
Hover="true" Dense="true">
|
||||
```
|
||||
|
||||
```csharp
|
||||
private async Task<GridData<MyDto>> LoadServerData(GridState<MyDto> state)
|
||||
{
|
||||
var filter = new MyFilter
|
||||
{
|
||||
PageNumber = state.Page + 1, // MudDataGrid is 0-based
|
||||
PageSize = state.PageSize
|
||||
};
|
||||
var (items, totalCount, _) = await MyService.GetAsync(filter);
|
||||
return new GridData<MyDto> { Items = items, TotalItems = totalCount };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۴.۶ Permission System
|
||||
|
||||
NavMenu از `IAuthorizationService.HasPermissionAsync()` برای نمایش/مخفی کردن آیتمها استفاده میکند:
|
||||
|
||||
| Permission | صفحه(ها) |
|
||||
|-----------|----------|
|
||||
| `dashboard.view` | داشبورد |
|
||||
| `packages.manage` | پکیجها |
|
||||
| `products.manage` | محصولات + دستهبندی + ویرایش دستهجمعی |
|
||||
| `orders.view` | سفارشات |
|
||||
| `inventory.manage` | انبارداری (4 صفحه) |
|
||||
| `discountshop.manage` | فروشگاه تخفیفی |
|
||||
| `users.view` | کاربران |
|
||||
| `roles.manage` | نقشها |
|
||||
| `manualpayments.create` | پرداخت دستی |
|
||||
| `blog.manage` | بلاگ |
|
||||
| `sitepages.manage` | صفحات سایت |
|
||||
| `publicmessages.view` | پیامهای عمومی |
|
||||
| `settings.manage_configuration` | تنظیمات سیستم |
|
||||
|
||||
---
|
||||
|
||||
## ۵. مسیرهای (Routing)
|
||||
|
||||
### مسیرهای ثابت (`RouteConstance.cs`)
|
||||
```
|
||||
/ → Dashboard
|
||||
/PackagePage/ → Packages
|
||||
/ProductsPage/ → Products
|
||||
/CategoryPage/ → Categories
|
||||
/OrdersPage/ → Orders Hub
|
||||
/OrdersSalesReportsPage/ → Orders Sales Reports
|
||||
/InventoryPage/ → Inventory
|
||||
/InventoryLowStockPage/ → Low Stock
|
||||
/InventoryWarehousesPage/ → Warehouses
|
||||
/InventoryMovementsPage/ → Stock Movements
|
||||
/UserPage/ → Users
|
||||
/RolePage/ → Roles
|
||||
/ProductsBulkEditPage/ → Bulk Edit
|
||||
/ProductCategoriesPage/ → Product-Category DragDrop
|
||||
/CategoryProductsPage/ → Category-Product DragDrop
|
||||
```
|
||||
|
||||
### مسیرهای hardcode (فروشگاه تخفیفی + سایر)
|
||||
```
|
||||
/discount-products → Discount Products
|
||||
/discount-categories → Discount Categories
|
||||
/discount-shop → Discount Orders Hub
|
||||
/discount-orders → Discount Orders
|
||||
/discount-sales-reports → Discount Sales Reports
|
||||
/commission/* → Commission pages
|
||||
/network/* → Network pages
|
||||
/club/* → Club pages
|
||||
/blog/* → Blog pages
|
||||
/wallets → Wallets
|
||||
/contracts → Contracts
|
||||
/payment/manual-payments → Manual Payments
|
||||
/system/* → System pages
|
||||
/settings → Settings
|
||||
/content/pages → Content Pages
|
||||
/public-messages → Public Messages
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۶. ارتباط فروشگاه عادی vs تخفیفی
|
||||
|
||||
| جنبه | فروشگاه عادی | فروشگاه تخفیفی |
|
||||
|------|-------------|---------------|
|
||||
| **سرویس محصولات** | gRPC `ProductsContractClient` | HTTP `IDiscountProductService` |
|
||||
| **سرویس دستهبندی** | gRPC `CategoryContractClient` | HTTP `IDiscountCategoryService` |
|
||||
| **سرویس سفارشات** | gRPC `UserOrderContractClient` | HTTP `IDiscountOrderService` |
|
||||
| **Entity بکند** | `Product` | `DiscountProduct` |
|
||||
| **پرداخت** | فقط درگاه | ترکیبی (کیف تخفیفی + درگاه) |
|
||||
| **فیلد اختصاصی** | — | `MaxDiscountPercent` |
|
||||
| **UI Pattern** | BasePageComponent | BasePageComponent (یکسان) |
|
||||
| **ستونها** | یکسان | یکسان + ستون تخفیف |
|
||||
|
||||
---
|
||||
|
||||
## ۷. نقشه NavMenu
|
||||
|
||||
```
|
||||
داشبورد
|
||||
─────────────────────
|
||||
کمیسیون و شبکه
|
||||
├── کمیسیون (NavGroup)
|
||||
│ ├── داشبورد کمیسیون
|
||||
│ ├── گزارشهای هفتگی
|
||||
│ ├── پرداخت کاربران
|
||||
│ ├── درخواستهای برداشت [Badge]
|
||||
│ └── گزارش برداشتها
|
||||
├── شبکه (NavGroup)
|
||||
│ ├── درخت شبکه
|
||||
│ ├── گزارش موجودیها
|
||||
│ └── آمار شبکه
|
||||
└── باشگاه مشتریان (NavGroup)
|
||||
├── اعضا و آمار
|
||||
└── فیچرهای باشگاه
|
||||
─────────────────────
|
||||
فروشگاه [AuthorizeView: Administrator]
|
||||
├── پکیجها
|
||||
├── فروشگاه عادی (NavGroup)
|
||||
│ ├── محصولات
|
||||
│ ├── دستهبندیها
|
||||
│ └── سفارشات و گزارش
|
||||
├── انبارداری (NavGroup)
|
||||
│ ├── موجودی انبار
|
||||
│ ├── محصولات کمموجود
|
||||
│ ├── مدیریت انبارها
|
||||
│ └── تاریخچه تغییرات
|
||||
└── فروشگاه تخفیفی (NavGroup)
|
||||
├── محصولات
|
||||
├── دستهبندیها
|
||||
└── سفارشات و گزارش
|
||||
─────────────────────
|
||||
مدیریت [AuthorizeView: Administrator]
|
||||
├── کاربران
|
||||
├── نقشها
|
||||
├── پرداخت دستی
|
||||
├── کیفپول
|
||||
└── قراردادها
|
||||
─────────────────────
|
||||
مدیریت محتوا
|
||||
├── بلاگ
|
||||
├── صفحات سایت
|
||||
└── پیامهای عمومی
|
||||
─────────────────────
|
||||
سیستم [AuthorizeView: Administrator]
|
||||
├── مدیریت سیستم
|
||||
└── نسخه اپلیکیشنها
|
||||
─────────────────────
|
||||
تنظیمات
|
||||
```
|
||||
@@ -0,0 +1,195 @@
|
||||
# 🏪 یکسانسازی فروشگاه عادی و تخفیفی — BackOffice
|
||||
|
||||
> **تاریخ:** ۱۴۰۴/۱۱/۲۴ (February 13, 2026)
|
||||
> **وضعیت:** ✅ کامل
|
||||
> **Build:** 0 Error ✅
|
||||
|
||||
---
|
||||
|
||||
## ۱. هدف
|
||||
|
||||
فروشگاه عادی و فروشگاه تخفیفی در پنل مدیریت باید از نظر **ظاهری و UX** کاملاً یکسان باشند.
|
||||
قبل از این تغییرات، صفحات فروشگاه تخفیفی ظاهر و ساختار متفاوتی داشتند. هدف این فاز:
|
||||
|
||||
1. **NavMenu** — جداسازی دو فروشگاه در گروهبندیهای مجزا
|
||||
2. **دستهبندیها** — ظاهر یکسان با فروشگاه عادی (ستونها، درخت، اکشنها)
|
||||
3. **محصولات** — ظاهر یکسان (گالری، فیلترها، ستونهای گرید، اکسپورت)
|
||||
4. **سفارشات** — حذف گزارشهای کوچک اضافی، فقط لیست خالص + رفع باگ لیست خالی
|
||||
|
||||
---
|
||||
|
||||
## ۲. خلاصه تغییرات
|
||||
|
||||
### ۲.۱ بازسازی NavMenu
|
||||
|
||||
| قبل | بعد |
|
||||
|-----|-----|
|
||||
| یک بخش «فروشگاه» با زیرگروههای محصولات + دستهبندی + سفارش + ویرایش دستهجمعی | دو گروه مجزا: «فروشگاه عادی» و «فروشگاه تخفیفی» |
|
||||
| ویرایش دستهجمعی در منو | حذف شد از منو |
|
||||
| انبارداری داخل فروشگاه | انبارداری گروه مجزا |
|
||||
| پکیجها داخل فروشگاه | پکیجها آیتم مستقل |
|
||||
|
||||
**ساختار جدید:**
|
||||
```
|
||||
فروشگاه (بخش)
|
||||
├── پکیجها (مستقل)
|
||||
├── فروشگاه عادی (NavGroup)
|
||||
│ ├── محصولات → /ProductsPage/
|
||||
│ ├── دستهبندیها → /CategoryPage/
|
||||
│ └── سفارشات و گزارش → /OrdersPage/
|
||||
├── انبارداری (NavGroup مستقل)
|
||||
│ ├── موجودی انبار
|
||||
│ ├── محصولات کمموجود
|
||||
│ ├── مدیریت انبارها
|
||||
│ └── تاریخچه تغییرات
|
||||
└── فروشگاه تخفیفی (NavGroup)
|
||||
├── محصولات → /discount-products
|
||||
├── دستهبندیها → /discount-categories
|
||||
└── سفارشات و گزارش → /discount-orders
|
||||
```
|
||||
|
||||
**فایل:** `Shared/NavMenu.razor`
|
||||
|
||||
---
|
||||
|
||||
### ۲.۲ رفع لیست خالی سفارشات + حذف گزارشهای کوچک
|
||||
|
||||
**مشکل ۱ — لیست خالی:**
|
||||
- `PaymentDate.ToDateTime()` بدون null check باعث exception در WASM میشد
|
||||
- Exception در Blazor WASM silent است و grid خالی نشان میدهد
|
||||
- **رفع:** اضافه کردن `@if (context.Item.PaymentDate != null)` با fallback `"-"`
|
||||
|
||||
**مشکل ۲ — گزارشهای اضافی:**
|
||||
- کارتهای آماری (تعداد سفارشات + مجموع مبلغ) و نمودار Bar وضعیت ارسال بالای گرید بودند
|
||||
- این آمار اضافی بود چون تب جداگانه «گزارش فروش» وجود دارد
|
||||
- **رفع:** حذف کامل `MudGrid` (کارتها)، `MudChart` (نمودار)، فیلدهای `_stats`/`_statusChartLabels`/`_statusChartSeries`، متد `UpdateStats()`، کلاس `OrderStatsViewModel`
|
||||
- عنوان تولبار از «سفارشهای کاربر» به «لیست سفارشات» تغییر کرد
|
||||
|
||||
**فایلها:**
|
||||
- `Pages/UserOrder/UserOrderMainPage.razor`
|
||||
- `Pages/UserOrder/UserOrderMainPage.razor.cs`
|
||||
|
||||
---
|
||||
|
||||
### ۲.۳ بازنویسی صفحه محصولات تخفیفی
|
||||
|
||||
**قبل:** markup سفارشی بدون `BasePageComponent`، ستونهای ساده، بدون image preview
|
||||
**بعد:** کاملاً مطابق با `ProductsMainPage` فروشگاه عادی
|
||||
|
||||
| ویژگی | قبل | بعد |
|
||||
|-------|-----|-----|
|
||||
| Wrapper | markup دستی | `BasePageComponent` |
|
||||
| فیلترها | جستجو + دستهبندی | جستجو + دستهبندی + وضعیت + موجودی |
|
||||
| ستون عنوان | متن ساده | تصویر inline (MudAvatar) + متن truncate + tooltip |
|
||||
| ستون موجودی | عدد ساده | چیپ رنگی (قرمز/نارنجی/سبز) |
|
||||
| ستون وضعیت | متن | چیپ Error/Success |
|
||||
| خروجی Excel | ✅ (داشت) | ✅ (حفظ شد) |
|
||||
| گالری تصاویر | ✅ (داشت) | ✅ (حفظ شد) |
|
||||
| Server-side paging | ✅ | ✅ |
|
||||
|
||||
**فایلها:**
|
||||
- `Pages/DiscountShop/DiscountProductsMainPage.razor` — بازنویسی کامل
|
||||
- `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` — بازنویسی کامل (code-behind)
|
||||
|
||||
---
|
||||
|
||||
### ۲.۴ بازنویسی صفحه دستهبندیهای تخفیفی
|
||||
|
||||
**قبل:** markup دستی بدون `BasePageComponent`، ستونهای متفاوت
|
||||
**بعد:** کاملاً مطابق با `CategoryMainPage` فروشگاه عادی
|
||||
|
||||
| ویژگی | قبل | بعد |
|
||||
|-------|-----|-----|
|
||||
| Wrapper | markup دستی | `BasePageComponent` |
|
||||
| لایوت | درخت + گرید | درخت (3 col) + گرید (9 col) — بدون تغییر |
|
||||
| ستونها | شناسه، عنوان، توضیحات، وضعیت | شناسه، نام لاتین، عنوان، دستهبندی والد، تعداد محصولات، ترتیب، فعال؟ |
|
||||
| ستون والد | نداشت | resolve نام والد از لیست |
|
||||
| ستون محصولات | نداشت | چیپ Info |
|
||||
| ستون ترتیب | نداشت | PropertyColumn |
|
||||
| فیلتر | داخل page | داخل `BasePageComponent` |
|
||||
| حذف با فرزند | disabled | disabled (حفظ شد) |
|
||||
|
||||
**فایلها:**
|
||||
- `Pages/DiscountShop/DiscountCategoriesMainPage.razor` — بازنویسی کامل
|
||||
- `Pages/DiscountShop/DiscountCategoriesMainPage.razor.cs` — ایجاد (code-behind جدید)
|
||||
|
||||
---
|
||||
|
||||
## ۳. فایلهای تغییر یافته
|
||||
|
||||
| فایل | نوع تغییر | توضیح |
|
||||
|------|----------|-------|
|
||||
| `Shared/NavMenu.razor` | ✏️ ویرایش | بازسازی ساختار فروشگاه |
|
||||
| `Pages/UserOrder/UserOrderMainPage.razor` | ✏️ ویرایش | حذف آمار، رفع PaymentDate |
|
||||
| `Pages/UserOrder/UserOrderMainPage.razor.cs` | ✏️ ویرایش | حذف فیلدها/متدهای آمار |
|
||||
| `Pages/DiscountShop/DiscountProductsMainPage.razor` | 🔄 بازنویسی | BasePageComponent + ستونهای جدید |
|
||||
| `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` | 🔄 بازنویسی | code-behind کامل |
|
||||
| `Pages/DiscountShop/DiscountCategoriesMainPage.razor` | 🔄 بازنویسی | BasePageComponent + ستونهای جدید |
|
||||
| `Pages/DiscountShop/DiscountCategoriesMainPage.razor.cs` | 🆕 ایجاد | code-behind جدید (از @code درونخطی) |
|
||||
|
||||
---
|
||||
|
||||
## ۴. الگوی پیادهسازی — BasePageComponent
|
||||
|
||||
تمام صفحات لیست در BackOffice از `BasePageComponent` استفاده میکنند:
|
||||
|
||||
```razor
|
||||
<BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
|
||||
<Filters>
|
||||
<!-- فیلدهای فیلتر در MudItem -->
|
||||
</Filters>
|
||||
<Content>
|
||||
<!-- MudDataGrid اصلی -->
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
```
|
||||
|
||||
**در code-behind:**
|
||||
```csharp
|
||||
private BasePageComponent _basePage = default!;
|
||||
|
||||
private async Task OnFilterSubmit()
|
||||
{
|
||||
_basePage.IsFiltered = true;
|
||||
// اعمال فیلتر
|
||||
}
|
||||
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_basePage.IsFiltered = false;
|
||||
// ریست فیلترها
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۵. الگوی Code-Behind
|
||||
|
||||
به دلیل محدودیت Razor source generator در پروژه، **همه فایلهایی که سرویس inject دارند باید code-behind داشته باشند**:
|
||||
|
||||
```
|
||||
Page.razor → فقط markup (بدون @code)
|
||||
Page.razor.cs → partial class با [Inject] و منطق
|
||||
```
|
||||
|
||||
**نکته مهم:** سرویسهای global از `_Imports.razor` نباید دوباره با `[Inject]` تعریف شوند:
|
||||
- ❌ `[Inject] public IDialogService DialogService { get; set; }` — از قبل global
|
||||
- ❌ `[Inject] public ISnackbar Snackbar { get; set; }` — از قبل global
|
||||
- ❌ `[Inject] public IJSRuntime jsRuntime { get; set; }` — از قبل global (حرف کوچک!)
|
||||
- ✅ `[Inject] public IDiscountProductService DiscountProductService { get; set; }` — باید inject شود
|
||||
|
||||
---
|
||||
|
||||
## ۶. مقایسه نهایی فروشگاه عادی و تخفیفی
|
||||
|
||||
| جنبه | فروشگاه عادی | فروشگاه تخفیفی | وضعیت |
|
||||
|------|-------------|---------------|-------|
|
||||
| ارتباط با بکند | gRPC/Protobuf | HTTP REST (IDiscountXxxService) | تفاوت ذاتی |
|
||||
| BasePageComponent | ✅ | ✅ | 🟢 یکسان |
|
||||
| فیلترهای محصول | جستجو+دستهبندی+وضعیت | جستجو+دستهبندی+وضعیت+موجودی | 🟢 یکسان+ |
|
||||
| ستونهای محصول | تصویر+عنوان، قیمت، موجودی (چیپ)، وضعیت (چیپ) | تصویر+عنوان، قیمت، تخفیف، موجودی (چیپ)، وضعیت (چیپ) | 🟢 یکسان+ |
|
||||
| گالری تصاویر | ✅ GalleryDialog | ✅ ProductImageGallery | 🟢 هر دو دارند |
|
||||
| خروجی Excel | ✅ | ✅ | 🟢 یکسان |
|
||||
| درخت دستهبندی | ✅ | ✅ | 🟢 یکسان |
|
||||
| ستونهای دستهبندی | شناسه+نام+عنوان+والد+محصولات+ترتیب+فعال | شناسه+نام+عنوان+والد+محصولات+ترتیب+فعال | 🟢 یکسان |
|
||||
| سفارشات Hub | MudTabs (سفارشات + گزارش فروش) | MudTabs (سفارشات + گزارش فروش) | 🟢 یکسان |
|
||||
@@ -0,0 +1,118 @@
|
||||
# فاز ۱ — موجودیتهای بکند CMS ✅ تکمیل شد
|
||||
|
||||
> **تاریخ تکمیل:** ۱۴۰۴/۰۴/۲۱ (2026-02-11)
|
||||
> **وضعیت:** ✅ تکمیل — بیلد موفق + Migration ساخته شد
|
||||
|
||||
---
|
||||
|
||||
## خلاصه کارهای انجام شده
|
||||
|
||||
### 1.1 موجودیتهای دامین (8 فایل)
|
||||
|
||||
| فایل | مسیر | توضیح |
|
||||
|------|------|-------|
|
||||
| `BlogPostStatus.cs` | `Domain/Enums/` | enum: Draft=0, Published=1, Scheduled=2, Archived=3 |
|
||||
| `BlogPost.cs` | `Domain/Entities/Blog/` | پست بلاگ — عنوان، اسلاگ، خلاصه، محتوای HTML، تصویر، وضعیت، شمارنده بازدید |
|
||||
| `BlogCategory.cs` | `Domain/Entities/Blog/` | دستهبندی بلاگ — عنوان، اسلاگ، آیکون، ترتیب |
|
||||
| `BlogPostCategory.cs` | `Domain/Entities/Blog/` | جدول واسط پست-دستهبندی (Many-to-Many) |
|
||||
| `BlogPostTag.cs` | `Domain/Entities/Blog/` | جدول واسط پست-تگ (از Tag موجود استفاده شد) |
|
||||
| `BlogPostImage.cs` | `Domain/Entities/Blog/` | گالری تصاویر پست — مسیر، عنوان جایگزین، ترتیب |
|
||||
| `SitePage.cs` | `Domain/Entities/Content/` | صفحات سایت (درباره ما، تماس با ما) — با کلید یکتا |
|
||||
| `SitePageSection.cs` | `Domain/Entities/Content/` | بخشهای هر صفحه — محتوای HTML، آیکون، تصویر، داده اضافی JSON |
|
||||
|
||||
### 1.2 تنظیمات Entity Framework (7 فایل)
|
||||
|
||||
| فایل | مسیر | ایندکسها |
|
||||
|------|------|----------|
|
||||
| `BlogPostConfiguration.cs` | `Configurations/Blog/` | Slug (unique), Status, PublishedAt, IsFeatured, AuthorUserId, Status+PublishedAt |
|
||||
| `BlogCategoryConfiguration.cs` | `Configurations/Blog/` | Slug (unique), IsActive |
|
||||
| `BlogPostCategoryConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId + BlogCategoryId |
|
||||
| `BlogPostTagConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId + TagId |
|
||||
| `BlogPostImageConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId |
|
||||
| `SitePageConfiguration.cs` | `Configurations/Content/` | PageKey (unique) |
|
||||
| `SitePageSectionConfiguration.cs` | `Configurations/Content/` | SitePageId + SectionKey (compound) |
|
||||
|
||||
### 1.3 DbContext (2 فایل ویرایش شده)
|
||||
|
||||
- `IApplicationDbContext.cs` — افزودن 7 DbSet
|
||||
- `ApplicationDbContext.cs` — افزودن 7 DbSet property
|
||||
|
||||
### 1.4 تعاریف Proto (4 فایل + csproj)
|
||||
|
||||
| فایل | RPCها | csharp_namespace |
|
||||
|------|-------|-----------------|
|
||||
| `blogpost.proto` | 11 RPC (CRUD + Publish/Archive/ViewCount + BySlug + Published/Featured) | `BlogPost` |
|
||||
| `blogcategory.proto` | 6 RPC (CRUD + GetAll + GetActive) | `BlogCategory` |
|
||||
| `blogpostimage.proto` | 4 RPC (Add/Delete/Get/Reorder) | `BlogPostImage` |
|
||||
| `sitepage.proto` | 8 RPC (Get/GetByKey/Update/GetAll + Section CRUD + Reorder) | `SitePage` |
|
||||
|
||||
### 1.5 لایه CQRS Application (≈50 فایل)
|
||||
|
||||
#### BlogPost Commands (6 گروه، 14 فایل)
|
||||
- `CreateBlogPost` — Command + Handler + Validator (با اعتبارسنجی اسلاگ regex)
|
||||
- `UpdateBlogPost` — Command + Handler + Validator (الگوی delete-recreate برای دستهبندی/تگ)
|
||||
- `DeleteBlogPost` — Command + Handler (soft-delete)
|
||||
- `PublishBlogPost` — Command + Result + Handler (تنظیم Status و PublishedAt)
|
||||
- `ArchiveBlogPost` — Command + Result + Handler
|
||||
- `IncrementViewCount` — Command + Handler
|
||||
|
||||
#### BlogPost Queries (5 گروه، 10 فایل)
|
||||
- `GetBlogPost` — Query + DTO + Handler (با Include chain)
|
||||
- `GetBlogPostBySlug` — Query + Handler (بازاستفاده از BlogPostDto)
|
||||
- `GetAllBlogPosts` — Query + ResponseDto + Handler (فیلتر + مرتبسازی + صفحهبندی)
|
||||
- `GetPublishedBlogPosts` — Query + Handler (مشتریمحور، فقط Published)
|
||||
- `GetFeaturedBlogPosts` — Query + Handler (برای لندینگ پیج)
|
||||
|
||||
#### BlogCategory CQRS (11 فایل)
|
||||
- Commands: Create + Update + Delete (با Validator)
|
||||
- Queries: GetBlogCategory + GetAllBlogCategories + GetActiveBlogCategories
|
||||
|
||||
#### BlogPostImage CQRS (8 فایل)
|
||||
- Commands: Add + Delete + Reorder (با ImageSortItem)
|
||||
- Queries: GetBlogPostImages
|
||||
|
||||
#### SitePage CQRS (14 فایل)
|
||||
- Commands: UpdateSitePage + CreateSection + UpdateSection + DeleteSection + ReorderSections
|
||||
- Queries: GetSitePage + GetSitePageByKey + GetAllSitePages
|
||||
|
||||
### 1.6 سرویسهای gRPC WebApi (4 فایل)
|
||||
|
||||
| سرویس | الگو | توضیح |
|
||||
|-------|------|-------|
|
||||
| `BlogPostService.cs` | ترکیبی (دستی + dispatcher) | مپینگ دستی برای لیستها و RepeatedField |
|
||||
| `BlogCategoryService.cs` | ترکیبی | dispatcher برای CRUD ساده، دستی برای لیستها |
|
||||
| `BlogPostImageService.cs` | ترکیبی | dispatcher + مپینگ دستی Reorder |
|
||||
| `SitePageService.cs` | ترکیبی | dispatcher + مپینگ دستی Sections |
|
||||
|
||||
### 1.7 Mapping Profiles (2 فایل)
|
||||
|
||||
- `BlogPostProfile.cs` — مپینگ PublishBlogPostResult و ArchiveBlogPostResult
|
||||
- `BlogCategoryProfile.cs` — مپینگ long → CreateBlogCategoryResponse
|
||||
|
||||
### 1.8 EF Migration
|
||||
|
||||
- `20260210232742_AddBlogAndContentEntities.cs` — ایجاد 7 جدول جدید
|
||||
- **Build:** ✅ موفق (0 Error, warnings مربوط به کد قدیمی)
|
||||
|
||||
---
|
||||
|
||||
## آمار فاز ۱
|
||||
|
||||
| متریک | تعداد |
|
||||
|-------|-------|
|
||||
| فایلهای جدید | ~65 |
|
||||
| فایلهای ویرایش شده | ~4 |
|
||||
| موجودیتهای دامین | 7 (+1 enum) |
|
||||
| تنظیمات EF | 7 |
|
||||
| تعاریف Proto | 4 |
|
||||
| RPCهای gRPC | 29 |
|
||||
| Commands CQRS | 16 |
|
||||
| Queries CQRS | 12 |
|
||||
| سرویسهای WebApi | 4 |
|
||||
| جداول دیتابیس جدید | 7 |
|
||||
|
||||
---
|
||||
|
||||
## فاز بعدی
|
||||
|
||||
**فاز ۲ — پنل مدیریت بلاگ (BackOffice)** — صفحات Blazor WASM برای مدیریت پستها، دستهبندیها، تصاویر و صفحات سایت.
|
||||
@@ -0,0 +1,104 @@
|
||||
# فاز ۳: صفحات محتوای پویا (Dynamic Content Pages) ✅
|
||||
|
||||
## 📋 خلاصه
|
||||
تبدیل صفحات **درباره ما** و **تماس با ما** از محتوای هاردکد (hardcoded) به محتوای پویا که از CMS (سرویس SitePage) بارگذاری میشود، با پشتیبانی fallback به محتوای پیشفرض.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ معماری
|
||||
|
||||
```
|
||||
FrontOffice (Blazor Server)
|
||||
├── About.razor/cs ─── SitePageService ──► gRPC ──► CMS SitePageContract
|
||||
└── Contact.razor/cs ─── SitePageService ──► gRPC ──► CMS SitePageContract
|
||||
```
|
||||
|
||||
### الگوی Fallback:
|
||||
```
|
||||
OnInitializedAsync() → SitePageService.GetByKeyAsync("about")
|
||||
├── ✅ Data received → Render dynamic content
|
||||
└── ❌ Error/null → Render hardcoded fallback content
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 فایلهای ایجاد/تغییر یافته
|
||||
|
||||
### فایلهای جدید:
|
||||
| فایل | توضیحات |
|
||||
|------|---------|
|
||||
| `FrontOffice/src/FrontOffice.Main/Utilities/SitePageService.cs` | سرویس SitePage + DTOs (SitePageDto, SitePageSectionDto) |
|
||||
| `dbbkup/SeedSitePages.sql` | اسکریپت Seed Data برای درج محتوای اولیه صفحات |
|
||||
|
||||
### فایلهای تغییر یافته:
|
||||
| فایل | تغییرات |
|
||||
|------|---------|
|
||||
| `FrontOffice/src/FrontOffice.Main/ConfigureServices.cs` | اضافه شدن SitePageService + SitePageContractClient به DI |
|
||||
| `FrontOffice/src/FrontOffice.Main/Pages/About.razor` | تبدیل به محتوای پویا با fallback |
|
||||
| `FrontOffice/src/FrontOffice.Main/Pages/About.razor.cs` | اضافه شدن OnInitializedAsync + بارگذاری sections |
|
||||
| `FrontOffice/src/FrontOffice.Main/Pages/Contact.razor` | تبدیل hero/info/social به پویا، فرم بدون تغییر |
|
||||
| `FrontOffice/src/FrontOffice.Main/Pages/Contact.razor.cs` | اضافه شدن OnInitializedAsync + ExtraData DTOs |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 جزئیات فنی
|
||||
|
||||
### SitePageService
|
||||
```csharp
|
||||
public class SitePageService
|
||||
{
|
||||
Task<SitePageDto?> GetByKeyAsync(string pageKey) // "about" | "contact"
|
||||
}
|
||||
```
|
||||
|
||||
### SitePageDto Helpers
|
||||
```csharp
|
||||
GetSection(string sectionKey) // e.g. "vision", "mission", "contact-info"
|
||||
GetSections(string prefix) // e.g. "value-" → value-1, value-2, ...
|
||||
```
|
||||
|
||||
### SitePageSectionDto.GetExtraData<T>()
|
||||
JSON deserializer برای فیلد ExtraData — استفاده شده در Contact:
|
||||
- `ContactInfoData`: address, phone, email, hours
|
||||
- `SocialMediaData`: telegram, instagram, linkedin, whatsapp
|
||||
|
||||
---
|
||||
|
||||
## 📄 SectionKey Mapping
|
||||
|
||||
### صفحه درباره ما (PageKey: `about`)
|
||||
| SectionKey | کاربرد | فیلدهای اصلی |
|
||||
|------------|--------|--------------|
|
||||
| `vision` | کارت چشمانداز | Title, HtmlContent, IconName |
|
||||
| `mission` | کارت مأموریت | Title, HtmlContent, IconName |
|
||||
| `value-1` ... `value-6` | کارتهای ارزشها | Title, HtmlContent, IconName |
|
||||
| `team-1` ... `team-3` | کارتهای اعضای تیم | Title(نام), Subtitle(سمت), HtmlContent(توضیحات), ImagePath(آواتار) |
|
||||
|
||||
### صفحه تماس با ما (PageKey: `contact`)
|
||||
| SectionKey | کاربرد | فیلدهای اصلی |
|
||||
|------------|--------|--------------|
|
||||
| `contact-info` | اطلاعات تماس | ExtraData → `{address, phone, email, hours}` |
|
||||
| `social-media` | شبکههای اجتماعی | ExtraData → `{telegram, instagram, linkedin, whatsapp}` |
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Seed Data
|
||||
فایل `dbbkup/SeedSitePages.sql` شامل:
|
||||
- **2 صفحه**: about, contact
|
||||
- **13 سکشن**: 2 (vision/mission) + 6 (values) + 3 (team) + 2 (contact-info/social-media)
|
||||
- تمام محتوای فعلی hardcoded به عنوان داده اولیه درج شده
|
||||
|
||||
---
|
||||
|
||||
## ✅ بیلد
|
||||
```
|
||||
FrontOffice.Main: 0 Error(s), Build succeeded
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📌 نکات مهم
|
||||
1. **فرم تماس** (Contact Form) بدون تغییر باقی ماند — منطق سمت کلاینت است نه محتوای CMS
|
||||
2. **Fallback**: اگر CMS در دسترس نباشد، محتوای hardcoded نمایش داده میشود
|
||||
3. **Loading State**: صفحه About دارای حالت loading با spinner
|
||||
4. آیکونها در CMS به صورت string ذخیره میشوند (مثل `@Icons.Material.Filled.Security`)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user