mirror of https://github.com/EasyAbp/EShop.git
11 changed files with 1013 additions and 25 deletions
@ -1,9 +1,38 @@ |
|||||
{ |
{ |
||||
"culture": "zh-Hans", |
"culture": "zh-Hans", |
||||
"texts": { |
"texts": { |
||||
"Menu:EasyAbpEShop": "EShop 商城", |
"Menu:EasyAbpEShop": "EShop 商城", |
||||
"Menu:Home": "首页", |
"Menu:Home": "首页", |
||||
"Welcome": "欢迎", |
"Welcome": "欢迎", |
||||
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io." |
"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": "取消订单" |
||||
|
} |
||||
|
} |
||||
@ -1,9 +1,38 @@ |
|||||
{ |
{ |
||||
"culture": "zh-Hant", |
"culture": "zh-Hant", |
||||
"texts": { |
"texts": { |
||||
"Menu:EasyAbpEShop": "EShop 商城", |
"Menu:EasyAbpEShop": "EShop 商城", |
||||
"Menu:Home": "首頁", |
"Menu:Home": "首頁", |
||||
"Welcome": "歡迎", |
"Welcome": "歡迎", |
||||
"LongWelcomeMessage": "歡迎來到此應用程式. 這是一個基於ABP框架的起始專案. 有關更多訊息, 請瀏覽 abp.io." |
"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": "取消訂單" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace EShopSample.Data; |
||||
|
|
||||
|
public static class SampleDataConsts |
||||
|
{ |
||||
|
public const string FoodsCategoryUniqueName = "Foods"; |
||||
|
public const string CakeProductUniqueName = "Cake"; |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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<OrderDto> 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() |
||||
|
}; |
||||
} |
} |
||||
} |
} |
||||
@ -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 { |
||||
|
} |
||||
@ -1,3 +1,129 @@ |
|||||
$(function () { |
$(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); |
||||
}); |
}); |
||||
@ -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 += '<dl data-type="' + key + '" data-idx="' + i + '">' |
||||
|
output += '<dt>' + key + ':</dt>' |
||||
|
output += '<dd>' |
||||
|
for (var j = 0; j < items.length; j++) { |
||||
|
var item = items[j] |
||||
|
var cName = j == 0 ? 'active' : '' |
||||
|
if (j == 0) { |
||||
|
selectedCache.push(item) |
||||
|
} |
||||
|
output += '<button data-title="' + item + '" class="' + cName + '" value="' + item + '">' + item + '</button> ' |
||||
|
} |
||||
|
output += '</dd>' |
||||
|
output += '</dl>' |
||||
|
} |
||||
|
output += '</dl>' |
||||
|
|
||||
|
$('#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('<b>' + l('YourChoice') + '</b><br>' + s.join('\u3000-\u3000')) |
||||
|
$('#selectedSkuId').html('<b>' + l('SkuId') + '</b><br>' + curr.sku) |
||||
|
$('#selectedSkuPrice').html('<b>' + l('TotalPrice') + '</b><br>' + 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) |
||||
Loading…
Reference in new issue