mirror of https://github.com/EasyAbp/EShop.git
29 changed files with 685 additions and 1 deletions
@ -0,0 +1,34 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.Products.Dtos |
||||
|
{ |
||||
|
public class UpdateProductSkuDto |
||||
|
{ |
||||
|
[DisplayName("ProductSkuCurrency")] |
||||
|
public string Currency { get; set; } |
||||
|
|
||||
|
[DisplayName("ProductSkuOriginalPrice")] |
||||
|
public decimal OriginalPrice { get; set; } |
||||
|
|
||||
|
[DisplayName("ProductSkuPrice")] |
||||
|
public decimal Price { get; set; } |
||||
|
|
||||
|
[DisplayName("ProductSkuInventory")] |
||||
|
public int Inventory { get; set; } |
||||
|
|
||||
|
[DisplayName("ProductSkuOrderMinQuantity")] |
||||
|
public int OrderMinQuantity { get; set; } |
||||
|
|
||||
|
[DisplayName("ProductSkuProductDetailId")] |
||||
|
public Guid? ProductDetailId { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class CreateProductSkuDto : UpdateProductSkuDto |
||||
|
{ |
||||
|
[Required] |
||||
|
[DisplayName("ProductSkuSerializedAttributeOptionIds")] |
||||
|
public string SerializedAttributeOptionIds { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.Products |
||||
|
{ |
||||
|
public class ProductSkuDuplicatedException : BusinessException |
||||
|
{ |
||||
|
public ProductSkuDuplicatedException(Guid productId, string serializedAttributeOptionIds) : base( |
||||
|
message: $"Sku {serializedAttributeOptionIds} is duplicate for the product {productId}") |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.Products |
||||
|
{ |
||||
|
public interface ISerializedAttributeOptionIdsFormatter |
||||
|
{ |
||||
|
Task<string> ParseAsync(string serializedAttributeOptionIds); |
||||
|
|
||||
|
Task<string> ParseAsync(IEnumerable<Guid> attributeOptionIds); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Newtonsoft.Json; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.Products |
||||
|
{ |
||||
|
public class SerializedAttributeOptionIdsFormatter : ISerializedAttributeOptionIdsFormatter, ITransientDependency |
||||
|
{ |
||||
|
public async Task<string> ParseAsync(string serializedAttributeOptionIds) |
||||
|
{ |
||||
|
return await ParseAsync(JsonConvert.DeserializeObject<IEnumerable<Guid>>(serializedAttributeOptionIds)); |
||||
|
} |
||||
|
|
||||
|
public Task<string> ParseAsync(IEnumerable<Guid> attributeOptionIds) |
||||
|
{ |
||||
|
return Task.FromResult(JsonConvert.SerializeObject(attributeOptionIds.OrderBy(x => x))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
@page |
||||
|
@inherits EasyAbp.EShop.Products.Web.Pages.ProductsPage |
||||
|
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal |
||||
|
@model EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.CreateModalModel |
||||
|
@{ |
||||
|
Layout = null; |
||||
|
} |
||||
|
<abp-dynamic-form abp-model="ProductSku" data-ajaxForm="true" asp-page="CreateModal"> |
||||
|
<abp-modal> |
||||
|
<abp-modal-header title="@L["CreateProductSku"].Value"></abp-modal-header> |
||||
|
<abp-modal-body> |
||||
|
<abp-form-content/> |
||||
|
@foreach (var attr in Model.Attributes) |
||||
|
{ |
||||
|
<abp-select asp-for="@Model.SelectedAttributeOptionIdDict[attr.Key]" asp-items="@attr.Value" label="@L["ProductAttribute"].Value - @attr.Key" /> |
||||
|
} |
||||
|
<abp-input asp-for="StoreId" /> |
||||
|
<abp-input asp-for="ProductId" /> |
||||
|
</abp-modal-body> |
||||
|
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> |
||||
|
</abp-modal> |
||||
|
</abp-dynamic-form> |
||||
@ -0,0 +1,70 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Products.Categories; |
||||
|
using EasyAbp.EShop.Products.ProductDetails; |
||||
|
using EasyAbp.EShop.Products.ProductDetails.Dtos; |
||||
|
using EasyAbp.EShop.Products.Products; |
||||
|
using EasyAbp.EShop.Products.Products.Dtos; |
||||
|
using EasyAbp.EShop.Products.ProductTypes; |
||||
|
using EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product.ViewModels; |
||||
|
using EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.ViewModels; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.Rendering; |
||||
|
using Newtonsoft.Json; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku |
||||
|
{ |
||||
|
public class CreateModalModel : ProductsPageModel |
||||
|
{ |
||||
|
[HiddenInput] |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public Guid StoreId { get; set; } |
||||
|
|
||||
|
[HiddenInput] |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public Guid ProductId { get; set; } |
||||
|
|
||||
|
[BindProperty] |
||||
|
public CreateEditProductSkuViewModel ProductSku { get; set; } |
||||
|
|
||||
|
[BindProperty] |
||||
|
public Dictionary<string, Guid> SelectedAttributeOptionIdDict { get; set; } |
||||
|
|
||||
|
public Dictionary<string, ICollection<SelectListItem>> Attributes { get; set; } |
||||
|
|
||||
|
private readonly IProductAppService _productAppService; |
||||
|
|
||||
|
public CreateModalModel(IProductAppService productAppService) |
||||
|
{ |
||||
|
_productAppService = productAppService; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task OnGetAsync() |
||||
|
{ |
||||
|
var product = await _productAppService.GetAsync(ProductId); |
||||
|
|
||||
|
Attributes = new Dictionary<string, ICollection<SelectListItem>>(); |
||||
|
|
||||
|
foreach (var attribute in product.ProductAttributes.ToList()) |
||||
|
{ |
||||
|
Attributes.Add(attribute.DisplayName, |
||||
|
attribute.ProductAttributeOptions |
||||
|
.Select(dto => new SelectListItem(dto.DisplayName, dto.Id.ToString())).ToList()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IActionResult> OnPostAsync() |
||||
|
{ |
||||
|
var createDto = ObjectMapper.Map<CreateEditProductSkuViewModel, CreateProductSkuDto>(ProductSku); |
||||
|
|
||||
|
createDto.SerializedAttributeOptionIds = JsonConvert.SerializeObject(SelectedAttributeOptionIdDict.Values); |
||||
|
|
||||
|
await _productAppService.CreateSkuAsync(ProductId, StoreId, createDto); |
||||
|
|
||||
|
return NoContent(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
@page |
||||
|
@inherits EasyAbp.EShop.Products.Web.Pages.ProductsPage |
||||
|
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal |
||||
|
@model EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.EditModalModel |
||||
|
@{ |
||||
|
Layout = null; |
||||
|
} |
||||
|
<abp-dynamic-form abp-model="ProductSku" data-ajaxForm="true" asp-page="EditModal"> |
||||
|
<abp-modal> |
||||
|
<abp-modal-header title="@L["EditProductSku"].Value"></abp-modal-header> |
||||
|
<abp-modal-body> |
||||
|
<abp-input asp-for="Id" /> |
||||
|
<abp-form-content /> |
||||
|
</abp-modal-body> |
||||
|
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> |
||||
|
</abp-modal> |
||||
|
</abp-dynamic-form> |
||||
@ -0,0 +1,55 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Products.Products; |
||||
|
using EasyAbp.EShop.Products.Products.Dtos; |
||||
|
using EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.ViewModels; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.Rendering; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku |
||||
|
{ |
||||
|
public class EditModalModel : ProductsPageModel |
||||
|
{ |
||||
|
[HiddenInput] |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public Guid StoreId { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public Guid ProductId { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public Guid ProductSkuId { get; set; } |
||||
|
|
||||
|
[BindProperty] |
||||
|
public CreateEditProductSkuViewModel ProductSku { get; set; } |
||||
|
|
||||
|
private readonly IProductAppService _productAppService; |
||||
|
|
||||
|
public EditModalModel(IProductAppService productAppService) |
||||
|
{ |
||||
|
_productAppService = productAppService; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task OnGetAsync() |
||||
|
{ |
||||
|
var product = await _productAppService.GetAsync(ProductId); |
||||
|
|
||||
|
ProductSku = |
||||
|
ObjectMapper.Map<ProductSkuDto, CreateEditProductSkuViewModel>( |
||||
|
product.ProductSkus.Single(x => x.Id == ProductSkuId)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IActionResult> OnPostAsync() |
||||
|
{ |
||||
|
await _productAppService.UpdateSkuAsync(ProductId, ProductSkuId, StoreId, |
||||
|
ObjectMapper.Map<CreateEditProductSkuViewModel, UpdateProductSkuDto>(ProductSku)); |
||||
|
|
||||
|
return NoContent(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
@page |
||||
|
@using EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku |
||||
|
@using Volo.Abp.AspNetCore.Mvc.UI.Layout |
||||
|
@inherits EasyAbp.EShop.Products.Web.Pages.ProductsPage |
||||
|
@model IndexModel |
||||
|
@inject IPageLayout PageLayout |
||||
|
@{ |
||||
|
PageLayout.Content.Title = L["ProductSku"].Value; |
||||
|
PageLayout.Content.BreadCrumb.Add(L["Menu:Product"].Value); |
||||
|
PageLayout.Content.MenuItemName = "Product"; |
||||
|
} |
||||
|
|
||||
|
@section scripts |
||||
|
{ |
||||
|
<abp-script src="/Pages/EShop/Products/Products/ProductSku/index.js" /> |
||||
|
} |
||||
|
@section styles |
||||
|
{ |
||||
|
<abp-style src="/Pages/EShop/Products/Products/ProductSku/index.css"/> |
||||
|
} |
||||
|
<script> |
||||
|
let storeId = '@Model.StoreId'; |
||||
|
let productId = '@Model.ProductId'; |
||||
|
</script> |
||||
|
|
||||
|
<abp-card> |
||||
|
<abp-card-header> |
||||
|
<abp-row> |
||||
|
<abp-column size-md="_6"> |
||||
|
<abp-card-title>@L["ProductSku"] - @Model.ProductDisplayName</abp-card-title> |
||||
|
</abp-column> |
||||
|
<abp-column size-md="_6" class="text-right"> |
||||
|
<abp-button id="NewProductSkuButton" |
||||
|
text="@L["CreateProductSku"].Value" |
||||
|
icon="plus" |
||||
|
button-type="Primary" /> |
||||
|
</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<abp-table striped-rows="true" id="ProductSkuTable" class="nowrap"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>@L["Actions"]</th> |
||||
|
<th>@L["ProductSkuContentDescription"]</th> |
||||
|
<th>@L["ProductSkuPrice"]</th> |
||||
|
<th>@L["ProductSkuInventory"]</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
</abp-table> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
@ -0,0 +1,31 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Products.Products; |
||||
|
using EasyAbp.EShop.Stores.Stores; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku |
||||
|
{ |
||||
|
public class IndexModel : ProductsPageModel |
||||
|
{ |
||||
|
private readonly IProductAppService _productAppService; |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public Guid StoreId { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public Guid ProductId { get; set; } |
||||
|
|
||||
|
public string ProductDisplayName { get; set; } |
||||
|
|
||||
|
public IndexModel(IProductAppService productAppService) |
||||
|
{ |
||||
|
_productAppService = productAppService; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task OnGetAsync() |
||||
|
{ |
||||
|
ProductDisplayName = (await _productAppService.GetAsync(ProductId)).DisplayName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.ViewModels |
||||
|
{ |
||||
|
public class CreateEditProductSkuViewModel |
||||
|
{ |
||||
|
[Required] |
||||
|
[Display(Name = "ProductSkuCurrency")] |
||||
|
public string Currency { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[Display(Name = "ProductSkuPrice")] |
||||
|
public decimal Price { get; set; } |
||||
|
|
||||
|
[Display(Name = "ProductSkuOriginalPrice")] |
||||
|
public decimal OriginalPrice { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[Display(Name = "ProductSkuInventory")] |
||||
|
public int Inventory { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[Display(Name = "ProductSkuOrderMinQuantity")] |
||||
|
public int OrderMinQuantity { get; set; } |
||||
|
|
||||
|
[HiddenInput] |
||||
|
[Display(Name = "ProductSkuProductDetailId")] |
||||
|
public Guid? ProductDetailId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
$(function () { |
||||
|
|
||||
|
var l = abp.localization.getResource('Products'); |
||||
|
|
||||
|
var service = easyAbp.eShop.products.products.product; |
||||
|
var createModal = new abp.ModalManager(abp.appPath + 'EShop/Products/Products/ProductSku/CreateModal'); |
||||
|
var editModal = new abp.ModalManager(abp.appPath + 'EShop/Products/Products/ProductSku/EditModal'); |
||||
|
|
||||
|
var dataTable = $('#ProductSkuTable').DataTable(abp.libs.datatables.normalizeConfiguration({ |
||||
|
processing: true, |
||||
|
autoWidth: false, |
||||
|
scrollCollapse: true, |
||||
|
order: [[1, "asc"]], |
||||
|
ajax: function (requestData, callback, settings) { |
||||
|
if (callback) { |
||||
|
service.get(productId).then(function (result) { |
||||
|
callback({ |
||||
|
recordsTotal: result.productSkus.length, |
||||
|
recordsFiltered: result.productSkus.length, |
||||
|
data: fillProductSkusContentDescription(result).productSkus |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
columnDefs: [ |
||||
|
{ |
||||
|
rowAction: { |
||||
|
items: |
||||
|
[ |
||||
|
{ |
||||
|
text: l('Edit'), |
||||
|
action: function (data) { |
||||
|
editModal.open({ productId: productId, productSkuId: data.record.id, storeId: storeId }); |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
text: l('Delete'), |
||||
|
confirmMessage: function (data) { |
||||
|
return l('ProductDeletionConfirmationMessage', data.record.id); |
||||
|
}, |
||||
|
action: function (data) { |
||||
|
service.deleteSku(productId, data.record.id, storeId) |
||||
|
.then(function () { |
||||
|
abp.notify.info(l('SuccessfullyDeleted')); |
||||
|
dataTable.ajax.reload(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
{ data: "contentDescription" }, |
||||
|
{ data: "price" }, |
||||
|
{ data: "inventory" }, |
||||
|
] |
||||
|
})); |
||||
|
|
||||
|
createModal.onResult(function () { |
||||
|
dataTable.ajax.reload(); |
||||
|
}); |
||||
|
|
||||
|
editModal.onResult(function () { |
||||
|
dataTable.ajax.reload(); |
||||
|
}); |
||||
|
|
||||
|
$('#NewProductSkuButton').click(function (e) { |
||||
|
e.preventDefault(); |
||||
|
createModal.open({ storeId: storeId, productId: productId }); |
||||
|
}); |
||||
|
|
||||
|
function fillProductSkusContentDescription(product) { |
||||
|
let attributeOptionNames = []; |
||||
|
for (let i in product.productAttributes) { |
||||
|
let attribute = product.productAttributes[i]; |
||||
|
for (let j in attribute.productAttributeOptions) { |
||||
|
let option = attribute.productAttributeOptions[j]; |
||||
|
attributeOptionNames[option.id] = option.displayName; |
||||
|
} |
||||
|
} |
||||
|
for (let i in product.productSkus) { |
||||
|
let sku = product.productSkus[i]; |
||||
|
let options = []; |
||||
|
let attributeOptionIds = JSON.parse(sku.serializedAttributeOptionIds); |
||||
|
for (let j in attributeOptionIds) { |
||||
|
let optionId = attributeOptionIds[j]; |
||||
|
options.push(attributeOptionNames[optionId]); |
||||
|
} |
||||
|
sku.contentDescription = options.join(','); |
||||
|
} |
||||
|
return product; |
||||
|
} |
||||
|
}); |
||||
Loading…
Reference in new issue