mirror of https://github.com/EasyAbp/EShop.git
11 changed files with 1013 additions and 25 deletions
@ -1,9 +1,38 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Menu:EasyAbpEShop": "EShop 商城", |
|||
"Menu:Home": "首页", |
|||
"Welcome": "欢迎", |
|||
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io." |
|||
} |
|||
} |
|||
"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": "取消订单" |
|||
} |
|||
} |
|||
@ -1,9 +1,38 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"Menu:EasyAbpEShop": "EShop 商城", |
|||
"Menu:Home": "首頁", |
|||
"Welcome": "歡迎", |
|||
"LongWelcomeMessage": "歡迎來到此應用程式. 這是一個基於ABP框架的起始專案. 有關更多訊息, 請瀏覽 abp.io." |
|||
} |
|||
} |
|||
"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": "取消訂單" |
|||
} |
|||
} |
|||
@ -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 () { |
|||
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