Orchard商城模块(Commerce)设计与后台部分
前言:使用CMS開發網站為目標,編寫一個擴展性比較好的商城模塊。
首先是整體流程圖,大概介紹功能與設計。
?
接下來我們逐個模塊功能介紹。
一。商品管理模塊
商品模塊中可發布需要在線售賣的商品 (套餐商品) ?
1.1 添加一個商品
1. 商品正常價,與當前促銷價, ?(不填寫促銷價,將按照正常價計算 ?。)
2.是否為虛擬商品 (虛擬商品將不需要填寫收貨地址, 如果購物車上所有商品均為虛擬商品,則不需填寫收貨地址,如果有一個非虛擬商品,仍需填寫)
?
?以下商品實體類
namespace Aivics.Commerce.Models {/// <summary>/// 商品對象/// </summary>[OrchardFeature("Aivics.Commerce")]public class ProductPart : ContentPart<ProductPartRecord>, IProduct {/// <summary>/// 商品SKU/// </summary>[Required]public string Sku{get { return Retrieve(r => r.Sku); }set { Store(r => r.Sku, value); }}/// <summary>/// 價格/// </summary>[Required]public double Price{get { return Retrieve(r => r.Price); }set { Store(r => r.Price, value); }}/// <summary>/// 折扣價/// </summary>public double DiscountPrice{get { return Retrieve(r => r.DiscountPrice, -1); }set { Store(r => r.DiscountPrice, value); }}/// <summary>/// 數字商品、虛擬商品(沒有物流)/// </summary>public bool IsDigital{get { return Retrieve(r => r.IsDigital); }set { Store(r => r.IsDigital, value); }}/// <summary>/// 物流費用 -不填寫時將使用物流費用模板進行計算/// </summary>public double? ShippingCost{get { return Retrieve(r => r.ShippingCost); }set { Store(r => r.ShippingCost, value); }}/// <summary>/// 中獎/// </summary>public double Weight{get { return Retrieve(r => r.Weight); }set { Store(r => r.Weight, value); }}/// <summary>/// 規格/// </summary>public string Size{get { return Retrieve(r => r.Size); }set { Store(r => r.Size, value); }}/// <summary>/// 庫存/// </summary>public int Inventory{get { return Retrieve(r => r.Inventory); }set { Store(r => r.Inventory, value); }}/// <summary>/// 超過庫存警告信息/// </summary>public string OutOfStockMessage{get { return Retrieve(r => r.OutOfStockMessage); }set { Store(r => r.OutOfStockMessage, value); }}/// <summary>/// 允許超庫存購買/// </summary>public bool AllowBackOrder{get { return Retrieve(r => r.AllowBackOrder); }set { Store(r => r.AllowBackOrder, value); }}/// <summary>/// 覆蓋階梯價格,/// </summary>public bool OverrideTieredPricing{get { return Retrieve(r => r.OverrideTieredPricing); }set { Store(r => r.OverrideTieredPricing, value); }}/// <summary>/// 價格階梯,(折扣邏輯)/// </summary>public IEnumerable<PriceTier> PriceTiers{get{var rawTiers = Retrieve<string>("PriceTiers");return PriceTier.DeserializePriceTiers(rawTiers);}set{var serializedTiers = PriceTier.SerializePriceTiers(value);Store("PriceTiers", serializedTiers ?? "");}}/// <summary>/// 最小起訂數/// </summary>public int MinimumOrderQuantity{get{var minimumOrderQuantity = Retrieve(r => r.MinimumOrderQuantity);return minimumOrderQuantity > 1 ? minimumOrderQuantity : 1;}set{var minimumOrderQuantity = value > 1 ? value : 1;Store(r => r.MinimumOrderQuantity, minimumOrderQuantity);}}/// <summary>/// 是否要求必須登陸后購買/// </summary>public bool AuthenticationRequired{get { return Retrieve(r => r.AuthenticationRequired); }set { Store(r => r.AuthenticationRequired, value); }}} }2. ?套餐商品類 (目前UI菜單中不公布,此功能與流程調試中)
/// <summary>/// 產品套餐/// </summary>public class BundlePart : ContentPart<BundlePartRecord>{public IEnumerable<int> ProductIds{get { return Record.Products.Select(p => p.ContentItemRecord.Id); }}public IEnumerable<ProductQuantity> ProductQuantities{get{returnRecord.Products.Select(p => new ProductQuantity{Quantity = p.Quantity,ProductId = p.ContentItemRecord.Id});}}}二。物流計費方案模塊
說明: 該模塊為商品所需運費自動匹配計算的功能, 如果商品中指定了【運費】金額,則不從此處計算運費。 ?
目前支持 【重量計算規則】和【大小規格計算】
有效區域應為 所有省份, 目前僅提供幾個周邊省份(測試數據)
?
實體的相關代碼
namespace Aivics.Commerce.Models {/// <summary>/// 基于重量的物流計費/// </summary>public class WeightBasedShippingMethodPart : ContentPart<WeightBasedShippingMethodPartRecord>,IShippingMethod{public string Name{get { return Retrieve(r => r.Name); }set { Store(r => r.Name, value); }}public string ShippingCompany{get { return Retrieve(r => r.ShippingCompany); }set { Store(r => r.ShippingCompany, value); }}public double Price{get { return Retrieve(r => r.Price); }set { Store(r => r.Price, value); }}public string IncludedShippingAreas{get { return Retrieve(r => r.IncludedShippingAreas); }set { Store(r => r.IncludedShippingAreas, value); }}public string ExcludedShippingAreas{get { return Retrieve(r => r.ExcludedShippingAreas); }set { Store(r => r.ExcludedShippingAreas, value); }}public double? MinimumWeight{get { return Retrieve(r => r.MinimumWeight); }set { Store(r => r.MinimumWeight, value); }}public double? MaximumWeight{get { return Retrieve(r => r.MaximumWeight); }set { Store(r => r.MaximumWeight, value); }} // Set to double.PositiveInfinity (the default) for unlimited weight rangespublic IEnumerable<ShippingOption> ComputePrice(IEnumerable<ShoppingCartQuantityProduct> productQuantities,IEnumerable<IShippingMethod> shippingMethods,string country,string zipCode,IWorkContextAccessor workContextAccessor){var quantities = productQuantities.ToList();var fixedCost = quantities.Where(pq => pq.Product.ShippingCost != null && pq.Product.ShippingCost >= 0 && !pq.Product.IsDigital).Sum(pq => pq.Quantity * (double)pq.Product.ShippingCost);var weight = quantities.Where(pq => (pq.Product.ShippingCost == null || pq.Product.ShippingCost < 0) && !pq.Product.IsDigital).Sum(pq => pq.Quantity * pq.Product.Weight);if (weight.CompareTo(0) == 0){yield return GetOption(fixedCost);}else if (weight >= MinimumWeight && weight <= MaximumWeight){yield return GetOption(fixedCost + Price);}}private ShippingOption GetOption(double price){return new ShippingOption{Description = Name,Price = price,IncludedShippingAreas =IncludedShippingAreas == null? new string[] { }: IncludedShippingAreas.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),ExcludedShippingAreas =ExcludedShippingAreas == null? new string[] { }: ExcludedShippingAreas.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)};}} }三。商品階梯價格
說明:可設置一個全局的階梯價格表, (滿立減的規則, 同時商品可以對此進行覆蓋)
如果需要做定期的促銷類活動,則可使用下面一個模塊。。。。
public class TieredPriceProvider : ITieredPriceProvider{private readonly IWorkContextAccessor _wca;public TieredPriceProvider(IWorkContextAccessor wca){_wca = wca;}public ShoppingCartQuantityProduct GetTieredPrice(ShoppingCartQuantityProduct quantityProduct){var priceTiers = GetPriceTiers(quantityProduct.Product);var priceTier = priceTiers != null ? priceTiers.Where(t => t.Quantity <= quantityProduct.Quantity).OrderByDescending(t => t.Quantity).Take(1).SingleOrDefault() : null;if (priceTier != null){quantityProduct.Price = (double)priceTier.Price;}return quantityProduct;}public IEnumerable<PriceTier> GetPriceTiers(ProductPart product){var productSettings = _wca.GetContext().CurrentSite.As<ProductSettingsPart>();IEnumerable<PriceTier> priceTiers = null;List<PriceTier> adjustedPriceTiers = new List<PriceTier>();if (productSettings.AllowProductOverrides && product.OverrideTieredPricing){priceTiers = product.PriceTiers;}else if (productSettings.DefineSiteDefaults && (!productSettings.AllowProductOverrides || !product.OverrideTieredPricing)){priceTiers = productSettings.PriceTiers;}if (priceTiers == null)return priceTiers;foreach (var tier in priceTiers){var adjustedPrice = tier.Price;if (tier.Price == null && tier.PricePercent != null){adjustedPrice = product.Price * (double)tier.PricePercent / 100;}adjustedPriceTiers.Add(new PriceTier{Price = adjustedPrice,Quantity = tier.Quantity,PricePercent = tier.PricePercent});}return adjustedPriceTiers.OrderBy(t => t.Quantity);}}四。促銷模塊
說明:目前提供一個促銷模塊規則, 主要為滿立減活動等適用。 ?
1.可設置折扣的比例(9折等)和固定的折扣金額
2.指定參與的時間
3.可參加此活動的角色(高級會員)
4.有效數量區間?
5.URL匹配可以設置與指定特定的商品參加。。
?
?
主要代碼如下:
目前繼承自IPromotion類,之后相關其他活動促銷規則,可從此直接繼承。(如買10贈1活動設置等)
public class Discount : IPromotion{private readonly IWorkContextAccessor _wca;private readonly IClock _clock;public Discount(IWorkContextAccessor wca, IClock clock){_wca = wca;_clock = clock;}public DiscountPart DiscountPart { get; set; }public IContent ContentItem { get { return DiscountPart.ContentItem; } }public string Name { get { return DiscountPart == null ? "Discount" : DiscountPart.Name; } }public bool Applies(ShoppingCartQuantityProduct quantityProduct, IEnumerable<ShoppingCartQuantityProduct> cartProducts){if (DiscountPart == null) return false;var now = _clock.UtcNow;if (DiscountPart.StartDate != null && DiscountPart.StartDate > now) return false;if (DiscountPart.EndDate != null && DiscountPart.EndDate < now) return false;if (DiscountPart.StartQuantity != null &&DiscountPart.StartQuantity > quantityProduct.Quantity)return false;if (DiscountPart.EndQuantity != null &&DiscountPart.EndQuantity < quantityProduct.Quantity)return false;if (!string.IsNullOrWhiteSpace(DiscountPart.Pattern) || !string.IsNullOrWhiteSpace(DiscountPart.ExclusionPattern)){string path = null;if (DiscountPart.DisplayUrlResolver != null){path = DiscountPart.DisplayUrlResolver(quantityProduct.Product);}else if (_wca.GetContext().HttpContext != null){var urlHelper = new UrlHelper(_wca.GetContext().HttpContext.Request.RequestContext);path = urlHelper.ItemDisplayUrl(quantityProduct.Product);}else{var autoroutePart = quantityProduct.Product.As<AutoroutePart>();if (autoroutePart != null){path = "/" + autoroutePart.Path; }}if (path == null) return false;if (!string.IsNullOrWhiteSpace(DiscountPart.Pattern)){var patternExpression = new Regex(DiscountPart.Pattern, RegexOptions.Singleline | RegexOptions.IgnoreCase);if (!patternExpression.IsMatch(path))return false;}if (!string.IsNullOrWhiteSpace(DiscountPart.ExclusionPattern)){var exclusionPatternExpression = new Regex(DiscountPart.ExclusionPattern,RegexOptions.Singleline | RegexOptions.IgnoreCase);if (exclusionPatternExpression.IsMatch(path))return false;}}if (DiscountPart.Roles.Any()){var user = _wca.GetContext().CurrentUser;if (!user.Has<IUserRoles>()) return false;var roles = user.As<IUserRoles>().Roles;if (!roles.Any(r => DiscountPart.Roles.Contains(r))) return false;}return true;}public ShoppingCartQuantityProduct Apply(ShoppingCartQuantityProduct quantityProduct, IEnumerable<ShoppingCartQuantityProduct> cartProducts){if (DiscountPart == null) return quantityProduct;var comment = DiscountPart.Comment; var percent = DiscountPart.DiscountPercent;if (percent != null){return new ShoppingCartQuantityProduct(quantityProduct.Quantity, quantityProduct.Product, quantityProduct.AttributeIdsToValues){Comment = comment,Price = Math.Round(quantityProduct.Price * (1 - ((double)percent / 100)), 2),Promotion = DiscountPart};}var discount = DiscountPart.Discount;if (discount != null){return new ShoppingCartQuantityProduct(quantityProduct.Quantity, quantityProduct.Product, quantityProduct.AttributeIdsToValues){Comment = comment,Price = Math.Round(Math.Max(0, quantityProduct.Price - (double)discount), 2),Promotion = DiscountPart};}return quantityProduct;}}五。商品擴展模塊
主要為解決需要 用戶確定附屬配置的 商品 。 ?用戶可在選擇了主商品的基礎上, 選擇額外配置, 不同的配置將決定追加的金額不同。?
(這個功能需與購物車整合體現)
namespace Aivics.Commerce.Models {/// <summary>/// 商品擴展插件對象 用戶可選擇不同的插件,需支付額外的插件價格/// </summary>public class ProductAttributePart : ContentPart<ProductAttributePartRecord>{public IEnumerable<ProductAttributeValue> AttributeValues{get{return ProductAttributeValue.DeserializeAttributeValues(AttributeValuesString);}set{AttributeValuesString = ProductAttributeValue.SerializeAttributeValues(value);}}/// <summary>/// 排序號/// </summary>[DisplayName("Sort Order")]public int SortOrder{get { return Retrieve(r => r.SortOrder); }set { Store(r => r.SortOrder, value); }}/// <summary>/// 顯示名/// </summary>[DisplayName("Display Name")]public string DisplayName{get { return Retrieve(r => r.DisplayName); }set { Store(r => r.DisplayName, value); }}/// <summary>/// 設置信息/// </summary>internal string AttributeValuesString{get{return Retrieve(r => r.AttributeValues);}set{Store(r => r.AttributeValues, value);}}} }?
六。訂單管理
查看所有用戶下單等。
?
?
?
?
七。購物車模塊設置
除了domain/cart為進入購物車頁面外, ?購物車模塊已經做成widget. 可以做到layout的其中一個位置固定。
?
?購物車相關代碼:
[OrchardFeature("Aivics.Commerce")]public class ShoppingCart : IShoppingCart{private readonly IContentManager _contentManager;private readonly IShoppingCartStorage _cartStorage;private readonly IPriceService _priceService;private readonly IEnumerable<IProductAttributesDriver> _attributesDrivers;private readonly INotifier _notifier;private IEnumerable<ShoppingCartQuantityProduct> _products;public ShoppingCart(IContentManager contentManager,IShoppingCartStorage cartStorage,IPriceService priceService,IEnumerable<IProductAttributesDriver> attributesDrivers,INotifier notifier){_contentManager = contentManager;_cartStorage = cartStorage;_priceService = priceService;_attributesDrivers = attributesDrivers;_notifier = notifier;T = NullLocalizer.Instance;}public Localizer T { get; set; }public IEnumerable<ShoppingCartItem> Items{get { return ItemsInternal.AsReadOnly(); }}private List<ShoppingCartItem> ItemsInternal{get{return _cartStorage.Retrieve();}}/// <summary>/// 添加商品至購物車中,目前將商品存放在session中/// </summary>/// <param name="productId"></param>/// <param name="quantity"></param>/// <param name="attributeIdsToValues"></param>public void Add(int productId, int quantity = 1, IDictionary<int, ProductAttributeValueExtended> attributeIdsToValues = null){if (!ValidateAttributes(productId, attributeIdsToValues)){// 將該商品添加到購物車時,該商品擴展屬性不正確(或后臺有更新,或前臺數據結構異常)。_notifier.Warning(T("Couldn't add this product because of invalid attributes. Please refresh the page and try again."));return;}var item = FindCartItem(productId, attributeIdsToValues);if (item != null){item.Quantity += quantity;}else{ItemsInternal.Insert(0, new ShoppingCartItem(productId, quantity, attributeIdsToValues));}_products = null;}/// <summary>/// 查找一個商品, 可以通過商品id直接從查詢,或者也需同時傳遞擴展屬性進行匹配/// </summary>/// <param name="productId"></param>/// <param name="attributeIdsToValues"></param>/// <returns></returns>public ShoppingCartItem FindCartItem(int productId, IDictionary<int, ProductAttributeValueExtended> attributeIdsToValues = null){if (attributeIdsToValues == null || attributeIdsToValues.Count == 0){return Items.FirstOrDefault(i => i.ProductId == productId&& (i.AttributeIdsToValues == null || i.AttributeIdsToValues.Count == 0));}return Items.FirstOrDefault(i => i.ProductId == productId&& i.AttributeIdsToValues != null&& i.AttributeIdsToValues.Count == attributeIdsToValues.Count&& i.AttributeIdsToValues.All(attributeIdsToValues.Contains));}/// <summary>/// 驗證該商品擴展屬性是否正確(或后臺有更新,或前臺數據結構異常)。/// </summary>/// <param name="productId"></param>/// <param name="attributeIdsToValues"></param>/// <returns></returns>private bool ValidateAttributes(int productId, IDictionary<int, ProductAttributeValueExtended> attributeIdsToValues){if (_attributesDrivers == null ||attributeIdsToValues == null ||!attributeIdsToValues.Any()) return true;var product = _contentManager.Get(productId);return _attributesDrivers.All(d => d.ValidateAttributes(product, attributeIdsToValues));}/// <summary>/// 批量添加商品至購物車/// </summary>/// <param name="items"></param>public void AddRange(IEnumerable<ShoppingCartItem> items){foreach (var item in items){Add(item.ProductId, item.Quantity, item.AttributeIdsToValues);}}/// <summary>/// 移除購物車商品/// </summary>/// <param name="productId"></param>/// <param name="attributeIdsToValues"></param>public void Remove(int productId, IDictionary<int, ProductAttributeValueExtended> attributeIdsToValues = null){var item = FindCartItem(productId, attributeIdsToValues);if (item == null) return;ItemsInternal.Remove(item);_products = null;}/// <summary>/// 獲取目前選購的商品 (1.數量>0, 2. 重新從服務器匹配商品,避免商品被刪除,臟數據。/// </summary>/// <returns></returns>public IEnumerable<ShoppingCartQuantityProduct> GetProducts(){if (_products != null) return _products;//從session中獲得所有保存的商品ID (用戶購物車)var ids = Items.Select(x => x.ProductId);var productParts =_contentManager.GetMany<ProductPart>(ids, VersionOptions.Published,new QueryHints().ExpandParts<TitlePart, ProductPart, AutoroutePart>()).ToArray();var productPartIds = productParts.Select(p => p.Id);//保證Session中存儲的ID是 服務器端 在用的Product對象 (排除掉未發布,刪除等,避免臟數據)var shoppingCartQuantities =(from item in Itemswhere productPartIds.Contains(item.ProductId) && item.Quantity > 0select new ShoppingCartQuantityProduct(item.Quantity, productParts.First(p => p.Id == item.ProductId), item.AttributeIdsToValues)) //使用ShoppingCartQuantityProduct,完善的購物車商品字段等信息 .ToList();//返回所有>0選購條數的商品return _products = shoppingCartQuantities.Select(q => _priceService.GetDiscountedPrice(q, shoppingCartQuantities)).Where(q => q.Quantity > 0).ToList();}/// <summary>/// 更新購物車數據,刪除數量為0的數據 *疑問點*/// </summary>public void UpdateItems(){ItemsInternal.RemoveAll(x => x.Quantity <= 0);_products = null;}/// <summary>/// 獲得總價格/// </summary>/// <returns></returns>public double Subtotal(){return Math.Round(GetProducts().Sum(pq => Math.Round(pq.Price * pq.Quantity + pq.LinePriceAdjustment, 2)), 2);}/// <summary>/// 總價/// </summary>/// <param name="subTotal"></param>/// <returns></returns>public double Total(double subTotal = 0){if (subTotal.Equals(0)){subTotal = Subtotal();}return subTotal;}/// <summary>/// 購買總數/// </summary>/// <returns></returns>public double ItemCount(){return Items.Sum(x => x.Quantity);}/// <summary>/// 清空/// </summary>public void Clear(){_products = null;ItemsInternal.Clear();UpdateItems();}}目前購物車使用?Session存儲。
?
八。商品列表,詳情
商品的列表與詳情我們完全可以借助于orchard本身的模塊實現, ?簡單介紹。。具體可查詢 orchard?Projection 等相關?
一。創建篩選 (可理解為創建分頁查詢與篩選語句等。。)
二。創建Projection或者Projection Widget
?
轉載于:https://www.cnblogs.com/cuiyangMJ/p/5903875.html
總結
以上是生活随笔為你收集整理的Orchard商城模块(Commerce)设计与后台部分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 姑娘姑娘,太阳太阳
- 下一篇: js文章QQ空间分享