From 3dbf03a57a56e8d2305b5e16409f54fb27e08fdd Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Mon, 13 Jun 2022 19:43:51 +0800 Subject: [PATCH] Introduce a quick-start home page in the sample app --- .../Localization/EShopSample/en.json | 31 +- .../Localization/EShopSample/zh-Hans.json | 43 ++- .../Localization/EShopSample/zh-Hant.json | 43 ++- .../Data/SampleDataConsts.cs | 7 + .../Data/SampleDataSeedContributor.cs | 181 +++++++++++ .../EShopSample.Web/EShopSample.Web.csproj | 1 + .../src/EShopSample.Web/Pages/Index.cshtml | 115 ++++++- .../src/EShopSample.Web/Pages/Index.cshtml.cs | 137 +++++++- .../src/EShopSample.Web/Pages/Index.css | 53 +++- .../src/EShopSample.Web/Pages/Index.js | 128 +++++++- .../src/EShopSample.Web/Pages/SkuSelector.js | 299 ++++++++++++++++++ 11 files changed, 1013 insertions(+), 25 deletions(-) create mode 100644 samples/EShopSample/aspnet-core/src/EShopSample.Domain/Data/SampleDataConsts.cs create mode 100644 samples/EShopSample/aspnet-core/src/EShopSample.Domain/Data/SampleDataSeedContributor.cs create mode 100644 samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/SkuSelector.js diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/en.json b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/en.json index 38faa8df..8a479bc4 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/en.json +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/en.json @@ -4,6 +4,35 @@ "Menu:EasyAbpEShop": "EShop", "Menu:Home": "Home", "Welcome": "Welcome", - "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io." + "LongWelcomeMessage": "Welcome to the application. This is a demo project of the EShop module. For more information, visit easyabp.io.", + "Product": "Product", + "Cake": "Cake", + "OrderCake": "Order a cake", + "CreateOrder": "Create order", + "PayForOrder": "Pay for order", + "TopUp-1-CNY": "Top-up 1.00 CNY", + "OrderHistory": "Order history", + "Flavor": "Flavor", + "Size": "Size", + "Chocolate": "Chocolate", + "Vanilla": "Vanilla", + "YourChoice": "Your choice", + "SkuId": "SKU ID", + "OrderCreated": "Order created", + "OrderCanceled": "Order canceled", + "TopUpSucceeded": "Top-up succeeded", + "PaymentSucceeded": "Payment succeeded", + "InsufficientBalance": "Insufficient account balance", + "OrderNumber": "Order number", + "Created": "Created", + "OrderStatus": "Status", + "OrderStatusPending": "Pending", + "OrderStatusProcessing": "Processing", + "OrderStatusCompleted": "Completed", + "OrderStatusCanceled": "Canceled", + "TotalPrice": "Price", + "AccountBalance": "Account balance", + "TimeToAutoCancel": "Order auto-cancel in", + "CancelOrder": "Cancel order" } } \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hans.json b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hans.json index 038ece66..da46a8de 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hans.json +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hans.json @@ -1,9 +1,38 @@ { - "culture": "zh-Hans", - "texts": { + "culture": "zh-Hans", + "texts": { "Menu:EasyAbpEShop": "EShop 商城", - "Menu:Home": "首页", - "Welcome": "欢迎", - "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io." - } - } \ No newline at end of file + "Menu:Home": "首页", + "Welcome": "欢迎", + "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个 EShop 模块的示例项目. 有关更多信息, 请访问 easyabp.io.", + "Product": "商品", + "Cake": "蛋糕", + "OrderCake": "下单买蛋糕", + "CreateOrder": "创建订单", + "PayForOrder": "支付订单", + "TopUp-1-CNY": "充值 1.00 元", + "OrderHistory": "历史订单", + "Flavor": "口味", + "Size": "分量", + "Chocolate": "巧克力味", + "Vanilla": "香草味", + "YourChoice": "你的选择", + "SkuId": "SKU ID", + "OrderCreated": "订单创建成功", + "OrderCanceled": "订单已取消", + "TopUpSucceeded": "充值成功", + "PaymentSucceeded": "支付成功", + "InsufficientBalance": "账户余额不足", + "OrderNumber": "订单编号", + "Created": "创建时间", + "OrderStatus": "状态", + "OrderStatusPending": "待支付", + "OrderStatusProcessing": "进行中", + "OrderStatusCompleted": "已完成", + "OrderStatusCanceled": "已取消", + "TotalPrice": "价格", + "AccountBalance": "账户余额", + "TimeToAutoCancel": "剩余可支付时间", + "CancelOrder": "取消订单" + } +} \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hant.json b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hant.json index 75e9122e..9b79a497 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hant.json +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Domain.Shared/Localization/EShopSample/zh-Hant.json @@ -1,9 +1,38 @@ { - "culture": "zh-Hant", - "texts": { + "culture": "zh-Hant", + "texts": { "Menu:EasyAbpEShop": "EShop 商城", - "Menu:Home": "首頁", - "Welcome": "歡迎", - "LongWelcomeMessage": "歡迎來到此應用程式. 這是一個基於ABP框架的起始專案. 有關更多訊息, 請瀏覽 abp.io." - } - } \ No newline at end of file + "Menu:Home": "首頁", + "Welcome": "歡迎", + "LongWelcomeMessage": "歡迎來到此應用程式. 這是一個 EShop 模組的示例專案. 有關更多訊息, 請瀏覽 easyabp.io.", + "Product": "商品", + "Cake": "蛋糕", + "OrderCake": "下單買蛋糕", + "CreateOrder": "創建訂單", + "PayForOrder": "支付訂單", + "TopUp-1-CNY": "給賬戶充值", + "OrderHistory": "歷史訂單", + "Flavor": "口味", + "Size": "分量", + "Chocolate": "巧克力味", + "Vanilla": "香草味", + "YourChoice": "你的選擇", + "SkuId": "SKU ID", + "OrderCreated": "訂單創建成功", + "OrderCanceled": "訂單已取消", + "TopUpSucceeded": "充值成功", + "PaymentSucceeded": "支付成功", + "InsufficientBalance": "賬戶餘額不足", + "OrderNumber": "訂單編號", + "Created": "創建時間", + "OrderStatus": "狀態", + "OrderStatusPending": "待支付", + "OrderStatusProcessing": "進行中", + "OrderStatusCompleted": "已完成", + "OrderStatusCanceled": "已取消", + "TotalPrice": "價格", + "AccountBalance": "賬戶餘額", + "TimeToAutoCancel": "剩餘可支付時間", + "CancelOrder": "取消訂單" + } +} \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Domain/Data/SampleDataConsts.cs b/samples/EShopSample/aspnet-core/src/EShopSample.Domain/Data/SampleDataConsts.cs new file mode 100644 index 00000000..1bb1bb65 --- /dev/null +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Domain/Data/SampleDataConsts.cs @@ -0,0 +1,7 @@ +namespace EShopSample.Data; + +public static class SampleDataConsts +{ + public const string FoodsCategoryUniqueName = "Foods"; + public const string CakeProductUniqueName = "Cake"; +} \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Domain/Data/SampleDataSeedContributor.cs b/samples/EShopSample/aspnet-core/src/EShopSample.Domain/Data/SampleDataSeedContributor.cs new file mode 100644 index 00000000..90bf1c27 --- /dev/null +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Domain/Data/SampleDataSeedContributor.cs @@ -0,0 +1,181 @@ +using System; +using System.Threading.Tasks; +using EasyAbp.EShop.Products; +using EasyAbp.EShop.Products.Categories; +using EasyAbp.EShop.Products.ProductCategories; +using EasyAbp.EShop.Products.Products; +using EasyAbp.EShop.Stores.Settings; +using EasyAbp.EShop.Stores.Stores; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Settings; +using Volo.Abp.Uow; + +namespace EShopSample.Data; + +public class SampleDataSeedContributor : IDataSeedContributor, ITransientDependency +{ + private readonly IGuidGenerator _guidGenerator; + private readonly ICurrentTenant _currentTenant; + private readonly IStoreRepository _storeRepository; + private readonly IProductManager _productManager; + private readonly IProductRepository _productRepository; + private readonly ICategoryManager _categoryManager; + private readonly ICategoryRepository _categoryRepository; + private readonly IProductCategoryRepository _productCategoryRepository; + private readonly ISettingProvider _settingProvider; + private readonly IAttributeOptionIdsSerializer _attributeOptionIdsSerializer; + + public SampleDataSeedContributor( + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant, + IStoreRepository storeRepository, + IProductManager productManager, + IProductRepository productRepository, + ICategoryManager categoryManager, + ICategoryRepository categoryRepository, + IProductCategoryRepository productCategoryRepository, + ISettingProvider settingProvider, + IAttributeOptionIdsSerializer attributeOptionIdsSerializer) + { + _guidGenerator = guidGenerator; + _currentTenant = currentTenant; + _storeRepository = storeRepository; + _productManager = productManager; + _productRepository = productRepository; + _categoryManager = categoryManager; + _categoryRepository = categoryRepository; + _productCategoryRepository = productCategoryRepository; + _settingProvider = settingProvider; + _attributeOptionIdsSerializer = attributeOptionIdsSerializer; + } + + [UnitOfWork(true)] + public virtual async Task SeedAsync(DataSeedContext context) + { + using var changeTenant = _currentTenant.Change(context.TenantId); + + await SeedStoresAsync(); + await SeedCategoriesAsync(); + await SeedProductsAsync(); + await SeedProductCategoryMappingsAsync(); + } + + protected virtual async Task SeedProductCategoryMappingsAsync() + { + var product = await _productRepository.GetAsync( + x => x.UniqueName == SampleDataConsts.CakeProductUniqueName); + + var category = await _categoryRepository.GetAsync( + x => x.UniqueName == SampleDataConsts.FoodsCategoryUniqueName); + + if (await _productCategoryRepository.AnyAsync(x => x.ProductId == product.Id && x.CategoryId == category.Id)) + { + return; + } + + await _productCategoryRepository.InsertAsync( + new ProductCategory(_guidGenerator.Create(), _currentTenant.Id, category.Id, product.Id)); + } + + public virtual async Task SeedStoresAsync() + { + var storeName = await _settingProvider.GetOrNullAsync(StoresSettings.DefaultStoreName); + + if (await _storeRepository.AnyAsync(x => x.Name == storeName)) + { + return; + } + + await _storeRepository.InsertAsync(new Store(_guidGenerator.Create(), _currentTenant.Id, storeName), true); + } + + public virtual async Task SeedCategoriesAsync() + { + if (await _categoryRepository.AnyAsync(x => x.UniqueName == SampleDataConsts.FoodsCategoryUniqueName)) + { + return; + } + + var category = await _categoryManager.CreateAsync(null, SampleDataConsts.FoodsCategoryUniqueName, "Foods", + "Some delicious foods.", null, false); + + await _categoryRepository.InsertAsync(category, true); + } + + public virtual async Task SeedProductsAsync() + { + if (await _productRepository.AnyAsync(x => x.UniqueName == SampleDataConsts.CakeProductUniqueName)) + { + return; + } + + var defaultStore = await _storeRepository.FindDefaultStoreAsync(); + + var product = new Product( + _guidGenerator.Create(), + _currentTenant.Id, + defaultStore.Id, + ProductsConsts.DefaultProductGroupName, + null, + SampleDataConsts.CakeProductUniqueName, + "Cake", + InventoryStrategy.NoNeed, + null, + true, + false, + false, + TimeSpan.FromMinutes(15), + null, + 0); + + var attribute1 = new ProductAttribute(_guidGenerator.Create(), "Size", null, 2); + var attribute2 = new ProductAttribute(_guidGenerator.Create(), "Flavor", null, 1); + + attribute1.ProductAttributeOptions.AddRange(new[] + { + new ProductAttributeOption(_guidGenerator.Create(), "S", null, 1), + new ProductAttributeOption(_guidGenerator.Create(), "M", null, 2), + new ProductAttributeOption(_guidGenerator.Create(), "L", null, 3), + }); + + attribute2.ProductAttributeOptions.AddRange(new[] + { + new ProductAttributeOption(_guidGenerator.Create(), "Chocolate", null, 1), + new ProductAttributeOption(_guidGenerator.Create(), "Vanilla", null, 2), + }); + + product.ProductAttributes.Add(attribute1); + product.ProductAttributes.Add(attribute2); + + await _productManager.CreateAsync(product); + + var productSku1 = new ProductSku(_guidGenerator.Create(), + await _attributeOptionIdsSerializer.SerializeAsync(new[] + { attribute1.ProductAttributeOptions[0].Id, attribute2.ProductAttributeOptions[0].Id }), + null, "CNY", null, 1m, 1, 10, null, null, null); + + var productSku2 = new ProductSku(_guidGenerator.Create(), + await _attributeOptionIdsSerializer.SerializeAsync(new[] + { attribute1.ProductAttributeOptions[1].Id, attribute2.ProductAttributeOptions[0].Id }), + null, "CNY", null, 2m, 1, 10, null, null, null); + + var productSku3 = new ProductSku(_guidGenerator.Create(), + await _attributeOptionIdsSerializer.SerializeAsync(new[] + { attribute1.ProductAttributeOptions[1].Id, attribute2.ProductAttributeOptions[1].Id }), + null, "CNY", null, 3m, 1, 10, null, null, null); + + var productSku4 = new ProductSku(_guidGenerator.Create(), + await _attributeOptionIdsSerializer.SerializeAsync(new[] + { attribute1.ProductAttributeOptions[2].Id, attribute2.ProductAttributeOptions[1].Id }), + null, "CNY", null, 4m, 1, 10, null, null, null); + + await _productManager.CreateSkuAsync(product, productSku1); + await _productManager.CreateSkuAsync(product, productSku2); + await _productManager.CreateSkuAsync(product, productSku3); + await _productManager.CreateSkuAsync(product, productSku4); + } +} \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Web/EShopSample.Web.csproj b/samples/EShopSample/aspnet-core/src/EShopSample.Web/EShopSample.Web.csproj index c602502d..ecd5361c 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Web/EShopSample.Web.csproj +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Web/EShopSample.Web.csproj @@ -20,6 +20,7 @@ + diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml index 6580377f..40b4148a 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml @@ -1,16 +1,33 @@ @page @inherits EShopSample.Web.Pages.EShopSamplePage +@using EasyAbp.EShop.Orders.Orders @model EShopSample.Web.Pages.IndexModel + @section styles { - + } + @section scripts { - + + } + + +

@L["Welcome"]

@@ -19,9 +36,99 @@
- abp.io @if (!CurrentUser.IsAuthenticated) { - @L["Login"] + + @L["Login"] + + } + else + { +
+
+ + +

@L[Model.CakeProduct.DisplayName]

+
+
+
+
+
+
+
+ @L["CreateOrder"] +
+ + @if (Model.Order is not null) + { + + + @L["OrderNumber"] + @Model.Order.OrderNumber + + + @L["Product"] + @L[Model.CakeProduct.DisplayName] + + @foreach (var optionId in Model.CakeProduct.ProductSkus.First(x => x.Id == Model.Order.OrderLines[0].ProductSkuId).AttributeOptionIds) + { + var option = Model.CakeProduct.ProductAttributes.SelectMany(x => x.ProductAttributeOptions) + .Single(x => x.Id == optionId); + var attr = Model.CakeProduct.ProductAttributes.Single(x => x.ProductAttributeOptions.Contains(option)); + + @L[attr.DisplayName] + @L[option.DisplayName] + + } + + @L["TotalPrice"] + @Model.Order.ActualTotalPrice.ToString("F2") @Model.Order.Currency + + + @L["AccountBalance"] + @Model.Wallet.Balance.ToString("F2") @Model.Order.Currency + + +
+
+

+ @L["TimeToAutoCancel"] ? +

+
+
+ @L["CancelOrder"] + @L["TopUp-1-CNY"] + @L["PayForOrder"] + } +
+ + + + + # + @L["OrderNumber"] + @L["Created"] + @L["OrderStatus"] + + + + @{ + var index = Model.OrderList.TotalCount; + foreach (var order in Model.OrderList.Items) + { + + @(index--) + @order.OrderNumber + @order.CreationTime + @L[$"OrderStatus{order.OrderStatus}"] + + } + } + + + +
+
+
} \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml.cs b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml.cs index 8dcb2be4..c6d2443a 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml.cs +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.cshtml.cs @@ -1,10 +1,141 @@ -namespace EShopSample.Web.Pages +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using EasyAbp.EShop.Orders.Orders; +using EasyAbp.EShop.Orders.Orders.Dtos; +using EasyAbp.EShop.Products.Products; +using EasyAbp.EShop.Products.Products.Dtos; +using EasyAbp.EShop.Stores.Stores; +using EasyAbp.EShop.Stores.Stores.Dtos; +using EasyAbp.PaymentService.Prepayment.Accounts; +using EasyAbp.PaymentService.Prepayment.Accounts.Dtos; +using EShopSample.Data; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Users; + +namespace EShopSample.Web.Pages; + +public class IndexModel : EShopSamplePageModel { - public class IndexModel : EShopSamplePageModel + private readonly IOrderAppService _orderAppService; + private readonly IStoreAppService _storeAppService; + private readonly IProductAppService _productAppService; + private readonly IAccountAppService _accountAppService; + + public OrderDto Order { get; set; } + public PagedResultDto OrderList { get; set; } + public StoreDto Store { get; set; } + public ProductDto CakeProduct { get; set; } + public AccountDto Wallet { get; set; } + + public IndexModel( + IOrderAppService orderAppService, + IStoreAppService storeAppService, + IProductAppService productAppService, + IAccountAppService accountAppService) { - public void OnGet() + _orderAppService = orderAppService; + _storeAppService = storeAppService; + _productAppService = productAppService; + _accountAppService = accountAppService; + } + + public async Task OnGetAsync() + { + if (CurrentUser.Id is null) + { + return; + } + + OrderList = await _orderAppService.GetListAsync(new GetOrderListDto { + MaxResultCount = 5, + CustomerUserId = CurrentUser.GetId(), + Sorting = "CreationTime DESC" + }); + + Order = OrderList.Items.FirstOrDefault(x => x.OrderStatus is OrderStatus.Pending); + + Store = await _storeAppService.GetDefaultAsync(); + + CakeProduct = await _productAppService.GetByUniqueNameAsync(Store.Id, SampleDataConsts.CakeProductUniqueName); + + Wallet = (await _accountAppService.GetListAsync(new GetAccountListInput { UserId = CurrentUser.Id })).Items[0]; + } + + public string GetJsonSkuInfo() + { + var sb = new StringBuilder("["); + + foreach (var sku in CakeProduct.ProductSkus) + { + sb.Append('{'); + sb.Append("\"skus\":"); + sb.Append('{'); + + foreach (var optionId in sku.AttributeOptionIds) + { + var option = CakeProduct.ProductAttributes.SelectMany(x => x.ProductAttributeOptions) + .Single(x => x.Id == optionId); + + var attribute = CakeProduct.ProductAttributes.Single(x => x.ProductAttributeOptions.Contains(option)); + + sb.Append($"\"{L[attribute.DisplayName].Value}\":\"{L[option.DisplayName].Value}\""); + + if (optionId != sku.AttributeOptionIds.Last()) + { + sb.Append(','); + } + } + sb.Append('}'); + + sb.Append($",\"skuId\":\"{sku.Id}\""); + sb.Append($",\"skuPrice\":{sku.Price}"); + sb.Append($",\"skuCurrency\":\"{sku.Currency}\""); + + sb.Append('}'); + + if (sku != CakeProduct.ProductSkus.Last()) + { + sb.Append(','); + } + } + + sb.Append(']'); + + return sb.ToString(); + } + + public int GetSecondsToAutoCancel() + { + if (Order is null) + { + return 0; + } + + return Order.PaymentExpiration.HasValue + ? Convert.ToInt32((Order.PaymentExpiration.Value - Clock.Now).TotalSeconds) + : 0; + } + + public string GetOrderStatusColumnClass(OrderDto order) + { + if (order is null) + { + return string.Empty; } + + return order.OrderStatus switch + { + OrderStatus.Pending => "status-pending-text", + OrderStatus.Processing => "status-processing-text", + OrderStatus.Completed => "status-completed-text", + OrderStatus.Canceled => "status-canceled-text", + _ => throw new ArgumentOutOfRangeException() + }; } } \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.css b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.css index 3f73b78b..c5bd7627 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.css +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.css @@ -1,3 +1,52 @@ -body { - +#skuSelector dt { + width: 100px; + text-align: right; + font-weight: normal; + padding-top: 6px; } + +#skuSelector dl { + clear: both; + overflow: hidden; +} + +#skuSelector dl.hl { + background: #ddd; +} + +#skuSelector dt, #skuSelector dd { + float: left; + line-height: 18px; + margin-left: 10px; + margin-bottom: 0; +} + +#skuSelector button { + font-size: 14px; + font-weight: bold; + padding: 4px 4px; +} + +#skuSelector .disabled { + color: #999; + border: 1px dashed #666; +} + +#skuSelector .active { + color: red; +} + +.status-pending-text { + color: orange; +} + +.status-processing-text { + color: green; +} + +.status-completed-text { + color: blue; +} + +.status-canceled-text { +} \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.js b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.js index 46f089b9..f568a742 100644 --- a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.js +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/Index.js @@ -1,3 +1,129 @@ $(function () { - abp.log.debug('Index.js initialized!'); + const l = abp.localization.getResource('EasyAbpEShopEShopSample'); + const orderService = easyAbp.eShop.orders.orders.order; + const eShopPaymentService = easyAbp.eShop.payments.payments.payment; + const paymentService = easyAbp.paymentService.payments.payment; + const accountService = easyAbp.paymentService.prepayment.accounts.account; + + $('#CreateOrderButton').click(function (e) { + e.preventDefault(); + if (!cakeProductSkuId) return + let btn = $(this) + btn.buttonBusy(true); + orderService.create({ + storeId: storeId, + orderLines: [{ + productId: cakeProductId, + productSkuId: cakeProductSkuId, + quantity: 1 + }] + }).then(function () { + abp.message.success(l('OrderCreated')) + .then(function () { + window.location.reload(); + }) + }).catch(function () { + btn.buttonBusy(false); + }); + }); + + let redirectToOrderHistory = function () { + document.location.href = document.location.origin + '?showOrderHistory=true' + } + + $('#CancelOrderButton').click(function (e) { + e.preventDefault(); + orderService.cancel(orderId, {}) + .then(function () { + abp.message.success(l('OrderCanceled')) + .then(function () { + redirectToOrderHistory() + }) + }); + }); + + $('#TopUpButton').click(function (e) { + e.preventDefault(); + accountService.changeBalance(accountId, {changedBalance: 1.00}) + .then(function () { + abp.message.success(l('TopUpSucceeded')) + .then(function () { + window.location.reload(); + }) + }); + }); + + $('#PayForOrderButton').click(function (e) { + e.preventDefault(); + if (currentBalance < totalPrice) { + abp.message.error(l('InsufficientBalance')) + .then(function () { + window.location.reload(); + }) + return + } + eShopPaymentService.create({ + paymentMethod: "Prepayment", + orderIds: [orderId] + }).then(function () { + getPaymentIdAndPay() + }); + }); + + let getPaymentIdAndPay = function () { + orderService.get(orderId).then(function (order) { + if (!order) return + if (!order.paymentId) { + setTimeout(getPaymentIdAndPay, 500) + return + } + paymentService.pay(order.paymentId, { + extraProperties: { + "AccountId": accountId + } + }).then(function () { + abp.message.success(l('PaymentSucceeded')) + .then(function () { + redirectToOrderHistory() + }) + }) + }) + } + + let updateTimeToAutoCancel = function () { + $('#timeToAutoCancel').text(new Date(secondsToAutoCancel * 1000).toISOString().substring(11, 19)) + } + + let cancelCurrentPayment = function () { + if (!orderId) return + orderService.get(orderId).then(function (order) { + if (order.paymentId) { + paymentService.cancel(order.paymentId) + } + }) + } + + function tryGoToOrderHistoryTab() { + if (location.search.indexOf('showOrderHistory=true') < 0) return + $('#OrderHistoryTab-tab').tab("show") + history.pushState(null, null, location.origin) + // document.location.href = document.location.href.split('#')[0] + } + + let init = function () { + tryGoToOrderHistoryTab() + updateTimeToAutoCancel() + cancelCurrentPayment() + } + + init() + + let interval = setInterval(function () { + if (secondsToAutoCancel <= 0) { + clearInterval(interval) + return + } + secondsToAutoCancel-- + updateTimeToAutoCancel() + }, 1000); }); \ No newline at end of file diff --git a/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/SkuSelector.js b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/SkuSelector.js new file mode 100644 index 00000000..2770d481 --- /dev/null +++ b/samples/EShopSample/aspnet-core/src/EShopSample.Web/Pages/SkuSelector.js @@ -0,0 +1,299 @@ +// Copied from https://codepen.io/keelii/pen/RoOzgb +const l = abp.localization.getResource('EasyAbpEShopEShopSample'); + +var data = JSON.parse(skuInfo) + +var res = {} + +var spliter = '\u2299' +var r = {} +var keys = [] +var selectedCache = [] + +function combineAttr(data, keys) { + var allKeys = [] + var result = {} + + for (var i = 0; i < data.length; i++) { + var item = data[i].skus + var values = [] + + for (var j = 0; j < keys.length; j++) { + var key = keys[j] + if (!result[key]) result[key] = [] + if (result[key].indexOf(item[key]) < 0) result[key].push(item[key]) + values.push(item[key]) + } + allKeys.push({ + path: values.join(spliter), + sku: data[i].skuId, + price: data[i].skuPrice, + currency: data[i].skuCurrency + }) + } + return { + result: result, + items: allKeys + } +} + + +function render(data) { + var output = '' + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var items = data[key] + + output += '
' + output += '
' + key + ':
' + output += '
' + for (var j = 0; j < items.length; j++) { + var item = items[j] + var cName = j == 0 ? 'active' : '' + if (j == 0) { + selectedCache.push(item) + } + output += ' ' + } + output += '
' + output += '
' + } + output += '' + + $('#skuSelector').html(output) +} + +function getAllKeys(arr) { + var result = [] + for (var i = 0; i < arr.length; i++) { + result.push(arr[i].path) + } + return result +} + +function powerset(arr) { + var ps = [[]]; + for (var i = 0; i < arr.length; i++) { + for (var j = 0, len = ps.length; j < len; j++) { + ps.push(ps[j].concat(arr[i])); + } + } + return ps; +} + +function buildResult(items) { + var allKeys = getAllKeys(items) + + for (var i = 0; i < allKeys.length; i++) { + var curr = allKeys[i] + var sku = items[i].sku + var price = items[i].price + var values = curr.split(spliter) + + var allSets = powerset(values) + + for (var j = 0; j < allSets.length; j++) { + var set = allSets[j] + var key = set.join(spliter) + + if (res[key]) { + res[key].sku = items[i].sku + res[key].price = items[i].price + res[key].currency = items[i].currency + } else { + res[key] = { + sku: items[i].sku, + price: items[i].price, + currency: items[i].currency + } + } + } + } +} + +function trimSpliter(str, spliter) { + var reLeft = new RegExp('^' + spliter + '+', 'g'); + var reRight = new RegExp(spliter + '+$', 'g'); + var reSpliter = new RegExp(spliter + '+', 'g'); + return str.replace(reLeft, '') + .replace(reRight, '') + .replace(reSpliter, spliter) +} + +function getSelectedItem() { + var result = [] + $('dl[data-type]').each(function () { + var $selected = $(this).find('.active') + if ($selected.length) { + result.push($selected.val()) + } else { + result.push('') + } + }) + + return result +} + +function updateStatus(selected) { + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var data = r.result[key] + var hasActive = !!selected[i] + var copy = selected.slice() + + for (var j = 0; j < data.length; j++) { + var item = data[j] + if (selected[i] == item) continue + copy[i] = item + + var curr = trimSpliter(copy.join(spliter), spliter) + var $item = $('dl').filter('[data-type="' + key + '"]').find('[value="' + item + '"]') + + var titleStr = '[' + copy.join('-') + ']' + + if (res[curr]) { + $item.removeClass('disabled') + setTitle($item.get(0)) + } else { + $item.addClass('disabled').attr('title', titleStr + ' 无此属性搭配') + } + } + } +} + +function handleNormalClick($this) { + $this.siblings().removeClass('active') + $this.addClass('active') +} + +function handleDisableClick($this) { + var $currAttr = $this.parents('dl').eq(0) + var idx = $currAttr.data('idx') + var type = $currAttr.data('type') + var value = $this.val() + + $this.removeClass('disabled') + selectedCache[idx] = value + + console.log(selectedCache) + $('dl').not($currAttr).find('button').removeClass('active') + updateStatus(getSelectedItem()) + + for (var i = 0; i < keys.length; i++) { + var item = keys[i] + var $curr = $('dl[data-type="' + item + '"]') + if (item == type) continue + + var $lastSelected = $curr.find('button[value="' + selectedCache[i] + '"]') + + if (!$lastSelected.hasClass('disabled')) { + $lastSelected.addClass('active') + updateStatus(getSelectedItem()) + } + } + +} + +function highLightAttr() { + for (var i = 0; i < keys.length; i++) { + var key = keys[i] + var $curr = $('dl[data-type="' + key + '"]') + if ($curr.find('.active').length < 1) { + $curr.addClass('hl') + } else { + $curr.removeClass('hl') + } + } +} + +function bindEvent() { + $('#skuSelector').undelegate().delegate('button', 'click', function (e) { + var $this = $(this) + + var isActive = $this.hasClass('.active') + var isDisable = $this.hasClass('disabled') + + if (!isActive) { + handleNormalClick($this) + + if (isDisable) { + handleDisableClick($this) + } else { + selectedCache[$this.parents('dl').eq(0).data('idx')] = $this.val() + } + updateStatus(getSelectedItem()) + highLightAttr() + showResult() + } + }) + + $('button').each(function () { + var value = $(this).val() + + if (!res[value] && !$(this).hasClass('active')) { + $(this).addClass('disabled') + } + }) +} + +function showResult() { + var result = getSelectedItem() + var s = [] + + for (var i = 0; i < result.length; i++) { + var item = result[i]; + if (!!item) { + s.push(item) + } + } + + if (s.length == keys.length) { + var curr = res[s.join(spliter)] + if (curr && curr.sku) { + cakeProductSkuId = curr.sku + $('#selectedSku').html('' + l('YourChoice') + '
' + s.join('\u3000-\u3000')) + $('#selectedSkuId').html('' + l('SkuId') + '
' + curr.sku) + $('#selectedSkuPrice').html('' + l('TotalPrice') + '
' + curr.price.toFixed(2) + ' ' + curr.currency) + } + } +} + +function updateData() { + data = JSON.parse(skuInfo) + init(data) +} + +function setTitle(el) { + var title = $(el).data('title'); + if (title) $(el).attr('title', title); +} + +function setAllTitle() { + $('#skuSelector').find('button').each(setTitle) +} + +function initSkuSelector(data) { + res = {} + r = {} + keys = [] + selectedCache = [] + + for (var attr_key in data[0].skus) { + if (!data[0].skus.hasOwnProperty(attr_key)) continue; + keys.push(attr_key) + } + setAllTitle(); + + r = combineAttr(data, keys) + + render(r.result) + + buildResult(r.items) + + updateStatus(getSelectedItem()) + showResult() + + bindEvent() +} + +initSkuSelector(data) \ No newline at end of file