这篇文章介绍主页中中间的畅销产品展示是如何实现的。
1.打开文件 component://ecommerce/widget/CommonScreens.xml#main
<screen name="main">
<section>
<actions>
<set field="leftbarScreenName" value="leftbar"/>
<set field="rightbarScreenName" value="rightbar"/>
<set field="MainColumnStyle" value="center"/>
<set field="titleProperty" value="PageTitleMain"/>
<set field="headerItem" value="main"/>
<set field="randomSurveyGroup" value="testSurveyGroup"/>
<script location="component://ecommerce/groovyScripts/Main.groovy"/>
<script location="component://order/groovyScripts/entry/catalog/Category.groovy"/>
</actions>
<widgets>
<decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">
<decorator-section name="body">
<platform-specific>
<html><html-template location="component://ecommerce/template/Main.ftl"/></html>
</platform-specific>
</decorator-section>
</decorator-screen>
</widgets>
</section>
</screen>
<#-- Render the category page -->
<#if requestAttributes.productCategoryId?has_content>
${screens.render("component://ecommerce/widget/CatalogScreens.xml#bestSellingCategory")}
${screens.render("component://ecommerce/widget/CatalogScreens.xml#category-include")}
<#else>
<center><h2>${uiLabelMap.EcommerceNoPROMOTIONCategory}</h2></center>
</#if>
打开文件component://ecommerce/widget/CatalogScreens.xml#bestSellingCategory
<screen name="bestSellingCategory">
<section>
<widgets>
<section>
<widgets>
<!--<label style="h1" text="Popular Categories"/>-->
<include-screen name="showBestSellingCategory"/>
</widgets>
</section>
</widgets>
</section>
</screen>
<screen name="showBestSellingCategory">
<section>
<actions>
<script location="component://ecommerce/groovyScripts/catalog/BestSellingCategory.groovy"/>
</actions>
<widgets>
<platform-specific><html><html-template location="component://ecommerce/template/catalog/ShowBestSellingCategory.ftl"/></html></platform-specific>
</widgets>
</section>
</screen>
查看模板component://ecommerce/template/catalog/ShowBestSellingCategory.ftl
<#if productCategoryList?has_content>
<h1>Popular Categories</h1>
<div class="productsummary-container matrix">
<table>
<tbody>
<#list productCategoryList as childCategoryList>
<tr>
<#assign cateCount = 0/>
<#list childCategoryList as productCategory>
<#if (cateCount > 2)>
<tr>
<#assign cateCount = 0/>
</#if>
<#assign productCategoryId = productCategory.productCategoryId/>
<#assign categoryImageUrl = "/images/defaultImage.jpg">
<#assign productCategoryMembers = delegator
.findByAnd("ProductCategoryAndMember", Static["org.apache.ofbiz.base.util.UtilMisc"]
.toMap("productCategoryId", productCategoryId),
Static["org.apache.ofbiz.base.util.UtilMisc"].toList("-quantity"), false)>
<#if productCategory.categoryImageUrl?has_content>
<#assign categoryImageUrl = productCategory.categoryImageUrl/>
<#elseif productCategoryMembers?has_content>
<#assign productCategoryMember =
Static["org.apache.ofbiz.entity.util.EntityUtil"].getFirst(productCategoryMembers)/>
<#assign product = delegator.findOne("Product",
Static["org.apache.ofbiz.base.util.UtilMisc"]
.toMap("productId", productCategoryMember.productId), false)/>
<#if product.smallImageUrl?has_content>
<#assign categoryImageUrl = product.smallImageUrl/>
</#if>
</#if>
<td>
<div class="productsummary">
<div class="smallimage">
<a href="<@ofbizCatalogAltUrl productCategoryId=productCategoryId/>">
<span class="popup_link"><img alt="Small Image" src="${categoryImageUrl}"></span>
</a>
</div>
<div class="productbuy">
<a class="linktext" style="font-size:12px"
href="<@ofbizCatalogAltUrl productCategoryId=productCategoryId/>">
${productCategory.categoryName!productCategoryId}
</a>
</div>
<div class="productinfo">
<ul>
<#if productCategoryMembers??>
<#assign i = 0/>
<#list productCategoryMembers as productCategoryMember>
<#if (i > 2)>
<#if productCategoryMembers[i]?has_content>
<a class="linktext" href="<@ofbizCatalogAltUrl productCategoryId=productCategoryId/>">
<span>More...</span>
</a>
</#if>
<#break>
</#if>
<#if productCategoryMember?has_content>
<#assign product = delegator.findOne("Product",
Static["org.apache.ofbiz.base.util.UtilMisc"].toMap("productId",
productCategoryMember.productId), false)>
<li class="browsecategorytext">
<a class="linktext"
href="<@ofbizCatalogAltUrl productCategoryId="PROMOTIONS"
productId="${product.productId}"/>">
${product.productName!product.productId}
</a>
</li>
</#if>
<#assign i = i+1/>
</#list>
</#if>
</ul>
</div>
</div>
</td>
<#assign cateCount = cateCount + 1/>
</#list>
<tr/>
</#list>
</tbody>
</table>
</div>
</#if>
从上面的模板中我们可以看到<h1>Popular Categories</h1>代码。这个就是前面要展示的畅销产品。
接下来查看下是如何用groovy脚本提取数据的。
打开文件component://ecommerce/groovyScripts/Main.groovy
import org.apache.ofbiz.product.catalog.*
catalogId = CatalogWorker.getCurrentCatalogId(request)
promoCat = CatalogWorker.getCatalogPromotionsCategoryId(request, catalogId)
request.setAttribute("productCategoryId", promoCat)
打开文件component://order/groovyScripts/entry/catalog/Category.groovy
/*
* This script is also referenced by the ecommerce's screens and
* should not contain order component's specific code.
*/
import org.apache.ofbiz.base.util.*
import org.apache.ofbiz.entity.*
import org.apache.ofbiz.product.catalog.*
import org.apache.ofbiz.product.category.CategoryWorker
import org.apache.ofbiz.product.category.CategoryContentWrapper
import org.apache.ofbiz.product.store.ProductStoreWorker
detailScreen = "categorydetail"
catalogName = CatalogWorker.getCatalogName(request)
productCategoryId = request.getAttribute("productCategoryId") ?: parameters.category_id
context.productCategoryId = productCategoryId
context.productStore = ProductStoreWorker.getProductStore(request)
pageTitle = null
metaDescription = null
metaKeywords = null
category = from("ProductCategory").where("productCategoryId", productCategoryId).cache(true).queryOne()
if (category) {
if (category.detailScreen) {
detailScreen = category.detailScreen
}
categoryPageTitle = from("ProductCategoryContentAndInfo").where("productCategoryId", productCategoryId, "prodCatContentTypeId", "PAGE_TITLE").cache(true).queryList()
if (categoryPageTitle) {
pageTitle = from("ElectronicText").where("dataResourceId", categoryPageTitle.get(0).dataResourceId).cache(true).queryOne()
}
categoryMetaDescription = from("ProductCategoryContentAndInfo").where("productCategoryId", productCategoryId, "prodCatContentTypeId", "META_DESCRIPTION").cache(true).queryList()
if (categoryMetaDescription) {
metaDescription = from("ElectronicText").where("dataResourceId", categoryMetaDescription.get(0).dataResourceId).cache(true).queryOne()
}
categoryMetaKeywords = from("ProductCategoryContentAndInfo").where("productCategoryId", productCategoryId, "prodCatContentTypeId", "META_KEYWORD").cache(true).queryList()
if (categoryMetaKeywords) {
metaKeywords = from("ElectronicText").where("dataResourceId", categoryMetaKeywords.get(0).dataResourceId).cache(true).queryOne()
}
categoryContentWrapper = new CategoryContentWrapper(category, request)
categoryDescription = categoryContentWrapper.get("DESCRIPTION", "html")
if (pageTitle) {
context.title = pageTitle.textData
} else {
context.title = categoryContentWrapper.get("CATEGORY_NAME", "html")
}
if (metaDescription) {
context.metaDescription = metaDescription.textData
} else {
if (categoryDescription) {
context.metaDescription = categoryDescription
}
}
if (metaKeywords) {
context.metaKeywords = metaKeywords.textData
} else {
if (categoryDescription) {
context.metaKeywords = categoryDescription + ", " + catalogName
} else {
context.metaKeywords = catalogName
}
}
context.productCategory = category
}
// check the catalogs template path and update
templatePathPrefix = CatalogWorker.getTemplatePathPrefix(request)
if (templatePathPrefix) {
detailScreen = templatePathPrefix + detailScreen
}
context.detailScreen = detailScreen
request.setAttribute("productCategoryId", productCategoryId)
request.setAttribute("defaultViewSize", 10)
request.setAttribute("limitView", true)
这块脚本主要是展示后面的特殊产品界面的。与畅销产品展示没什么关系。
打开文件component://ecommerce/groovyScripts/catalog/BestSellingCategory.groovy
import org.apache.ofbiz.base.util.UtilValidate
import org.apache.ofbiz.base.util.UtilMisc
import org.apache.ofbiz.product.catalog.*
import org.apache.ofbiz.product.category.*
catalogId = CatalogWorker.getCurrentCatalogId(request)
bestSellerCates = []
if (UtilValidate.isNotEmpty(catalogId)) {
prodCatalogCategoryList = CatalogWorker.getProdCatalogCategories(request, catalogId, "PCCT_BEST_SELL")
if (prodCatalogCategoryList.size() > 0) {
for (int i = 0; i < prodCatalogCategoryList.size(); i++) {
prodCatalogCategory = prodCatalogCategoryList[i]
//productCategoryId = "CATALOG1_BEST_SELL"
productCategoryId = prodCatalogCategory.getString("productCategoryId")
childCategoryList = CategoryWorker.getRelatedCategoriesRet(request, "childCategoryList", productCategoryId, true)
if (childCategoryList.size() > 0) {
bestSellerCates.add(childCategoryList)
}
}
}
}
context.productCategoryList = bestSellerCates
上面这个脚本就是从数据库中提取畅销产品列表。这些数据与ShowBestSellingCategory.ftl模板结合起来,就生成了畅销产品展示界面,即参照文章开头的截图。
接下来分析前面源码,得出查询畅销产品的相关sql。
首先,看ShowBestSellingCategory.ftl文件中主要的数据参数是productCategoryList 和 childCategoryList 。
产品分类列表productCategoryList 可以查看脚本 BestSellingCategory.groovy 文件,分析源码得出查询sql。
- catalogId = CatalogWorker.getCurrentCatalogId(request)
- prodCatalogCategoryList = CatalogWorker.getProdCatalogCategories(request, catalogId, "PCCT_BEST_SELL")
查看org.apache.ofbiz.product.catalog.CatalogWorker#getProdCatalogCategories方法 (具体文件路径根据包名知道在product项目的src里)
public static List<GenericValue> getProdCatalogCategories(ServletRequest request, String prodCatalogId, String prodCatalogCategoryTypeId) {
Delegator delegator = (Delegator) request.getAttribute("delegator");
return getProdCatalogCategories(delegator, prodCatalogId, prodCatalogCategoryTypeId);
}
public static List<GenericValue> getProdCatalogCategories(Delegator delegator, String prodCatalogId, String prodCatalogCategoryTypeId) {
try {
List<GenericValue> prodCatalogCategories = EntityQuery.use(delegator).from("ProdCatalogCategory")
.where("prodCatalogId", prodCatalogId)
.orderBy("sequenceNum", "productCategoryId")
.cache(true)
.filterByDate()
.queryList();
if (UtilValidate.isNotEmpty(prodCatalogCategoryTypeId) && prodCatalogCategories != null) {
prodCatalogCategories = EntityUtil.filterByAnd(prodCatalogCategories,
UtilMisc.toMap("prodCatalogCategoryTypeId", prodCatalogCategoryTypeId));
}
return prodCatalogCategories;
} catch (GenericEntityException e) {
Debug.logError(e, "Error looking up ProdCatalogCategories for prodCatalog with id " + prodCatalogId, module);
}
return null;
}
对应的查询sql如下:
SELECT * FROM Prod_Catalog_Category WHERE prod_Catalog_Id = 'DemoCatalog' AND prod_Catalog_Category_Type_Id = 'PCCT_BEST_SELL' ORDER BY sequence_Num
所以productCategoryId 的值是 CATALOG1_BEST_SELL
childCategoryList = CategoryWorker.getRelatedCategoriesRet(request, "childCategoryList", productCategoryId, true)
查看org.apache.ofbiz.product.category.getRelatedCategoriesRet
public static List<GenericValue> getRelatedCategoriesRet(ServletRequest request, String attributeName, String parentId, boolean limitView) {
return getRelatedCategoriesRet(request, attributeName, parentId, limitView, false);
}
public static List<GenericValue> getRelatedCategoriesRet(ServletRequest request, String attributeName, String parentId, boolean limitView, boolean excludeEmpty) {
return getRelatedCategoriesRet(request, attributeName, parentId, limitView, excludeEmpty, false);
}
public static List<GenericValue> getRelatedCategoriesRet(ServletRequest request, String attributeName, String parentId, boolean limitView, boolean excludeEmpty, boolean recursive) {
Delegator delegator = (Delegator) request.getAttribute("delegator");
return getRelatedCategoriesRet(delegator, attributeName, parentId, limitView, excludeEmpty, recursive);
}
public static List<GenericValue> getRelatedCategoriesRet(Delegator delegator, String attributeName, String parentId, boolean limitView, boolean excludeEmpty, boolean recursive) {
List<GenericValue> categories = new LinkedList<GenericValue>();
if (Debug.verboseOn()) Debug.logVerbose("[CategoryWorker.getRelatedCategories] ParentID: " + parentId, module);
List<GenericValue> rollups = null;
try {
rollups = EntityQuery.use(delegator).from("ProductCategoryRollup").where("parentProductCategoryId", parentId).orderBy("sequenceNum").cache(true).queryList();
if (limitView) {
rollups = EntityUtil.filterByDate(rollups, true);
}
} catch (GenericEntityException e) {
Debug.logWarning(e.getMessage(), module);
}
if (rollups != null) {
for (GenericValue parent: rollups) {
GenericValue cv = null;
try {
cv = parent.getRelatedOne("CurrentProductCategory", true);
} catch (GenericEntityException e) {
Debug.logWarning(e.getMessage(), module);
}
if (cv != null) {
if (excludeEmpty) {
if (!isCategoryEmpty(cv)) {
categories.add(cv);
if (recursive) {
categories.addAll(getRelatedCategoriesRet(delegator, attributeName, cv.getString("productCategoryId"), limitView, excludeEmpty, recursive));
}
}
} else {
categories.add(cv);
if (recursive) {
categories.addAll(getRelatedCategoriesRet(delegator, attributeName, cv.getString("productCategoryId"), limitView, excludeEmpty, recursive));
}
}
}
}
}
return categories;
}
分析代码知道excludeEmpty和recursive的值都是false,所以对应的sql如下:
SELECT t2.* FROM Product_Category_Rollup t1
INNER JOIN product_Category t2 ON t1.product_Category_id = t2.product_Category_id
WHERE t1.parent_Product_Category_Id='CATALOG1_BEST_SELL' ORDER BY t1.sequence_Num
EntityUtil.filterByDate(rollups, true);这个就是表示数据在当前时间有效,等价条件就没写了。
查询结果如下:
数据提取在这里就完成了。接下来分析模板文件如何组织数据展示给用户。分析ShowBestSellingCategory.ftl文件。
分析界面展示小图片部分源码:
<a href="<@ofbizCatalogAltUrl productCategoryId=productCategoryId/>">
实际结果:
<a href="/ecommerce/best-sell-1-BEST-SELL-1-c">
这个转换需要研究@ofbizCatalogAltUrl ,这个暂时不管。以后有时间在研究。
<img alt="Small Image" src="${categoryImageUrl}">
对应实际结果:
<img alt="Small Image" src="/images/products/GZ-1000/small.png">
接下来分析categoryImageUrl的值的取值逻辑。
1.默认取值 "/images/defaultImage.jpg"
2.参数productCategoryMembers的值。
2.1 首先打开实体定义文件F:\个人\学习笔记\ofbiz\apache-ofbiz-16.11.02\applications\datamodel\entitydef\product-entitymodel.xml.
2.2 查看实体视图ProductCategoryAndMember的定义。
<view-entity entity-name="ProductCategoryAndMember"
package-name="org.apache.ofbiz.product.category"
title="ProductCategory And ProductCategoryMember View Entity">
<member-entity entity-alias="PC" entity-name="ProductCategory"/>
<member-entity entity-alias="PCM" entity-name="ProductCategoryMember"/>
<alias-all entity-alias="PC"/>
<alias-all entity-alias="PCM"/>
<view-link entity-alias="PC" rel-entity-alias="PCM">
<key-map field-name="productCategoryId"/>
</view-link>
<relation type="one-nofk" rel-entity-name="ProductCategoryMember">
<key-map field-name="productCategoryId"/>
<key-map field-name="productId"/>
<key-map field-name="fromDate"/>
</relation>
<relation type="one-nofk" rel-entity-name="ProductCategory">
<key-map field-name="productCategoryId"/>
</relation>
<relation type="one-nofk" rel-entity-name="Product">
<key-map field-name="productId"/>
</relation>
</view-entity>
2.3 根据源码写出对应的sql。
<#assign productCategoryMembers = delegator
.findByAnd("ProductCategoryAndMember", Static["org.apache.ofbiz.base.util.UtilMisc"]
.toMap("productCategoryId", productCategoryId),
Static["org.apache.ofbiz.base.util.UtilMisc"].toList("-quantity"), false)>
对应的sql:
SELECT PC.*,pcm.*
FROM Product_Category pc
INNER JOIN Product_Category_Member PCM ON pc.product_Category_Id = PCM.product_Category_Id
WHERE pc.product_Category_Id = 'BEST-SELL-1' ORDER BY quantity DESC
查询结果如下:
3.如果productCategory.categoryImageUrl的值存在,则categoryImageUrl的值就为productCategory.categoryImageUrl。这个之前查询结果可以看到2条记录的categoryImageUrl值都是null。所以走第4步获取categoryImageUrl的值。
4.如果productCategory.categoryImageUrl的值不存在,则通过productCategoryMembers获取categoryImageUrl的值。
获取productCategoryMembers列表第一条记录对应的产品的categoryImageUrl值。
对应的sql如下:
SELECT t.small_Image_Url ,t.* FROM product t WHERE product_id='GZ-1000'
查询结果如下:
从图中我们能看到值为 /images/products/GZ-1000/small.png 。与之前浏览器中看到的源码一致。
接下来展示该类产品的列表信息。
根据productCategoryMembers获取对应的产品实体。主要需要产品实体的product_Id与product_name字段值。
<a class="linktext"
href="<@ofbizCatalogAltUrl productCategoryId="PROMOTIONS"
productId="${product.productId}"/>">
对应的一个结果:
<a class="linktext" href="/ecommerce/nan-gismo-GZ-1001-p">
Nan Gizmo
</a>
不清楚为什么产品分类标识productCategoryId为什么写死了"PROMOTIONS",然后得到上面了结果。
@ofbizCatalogAltUrl暂时照着源码的方法用吧,以后再研究。
总结,到此整个畅销产品展示界面相关源码分析完毕。主要涉及到的查询sql有:
1.查询指定目录下畅销产品类型的所有分类
SELECT * FROM Prod_Catalog_Category WHERE prod_Catalog_Id = 'DemoCatalog' AND prod_Catalog_Category_Type_Id = 'PCCT_BEST_SELL' ORDER BY sequence_Num
2.畅销产品分类下的子分类,即所有属于畅销产品分类类型的记录。
SELECT t1.* FROM Product_Category_Rollup t1 WHERE t1.parent_Product_Category_Id='CATALOG1_BEST_SELL'
SELECT t2.* FROM Product_Category_Rollup t1
INNER JOIN product_Category t2 ON t1.product_Category_id = t2.product_Category_id
WHERE t1.parent_Product_Category_Id='CATALOG1_BEST_SELL' ORDER BY t1.sequence_Num
3.查询指定分类下的所有产品
SELECT PC.*,pcm.*
FROM Product_Category pc
INNER JOIN Product_Category_Member PCM ON pc.product_Category_Id = PCM.product_Category_Id
WHERE pc.product_Category_Id = 'BEST-SELL-1' ORDER BY quantity DESC
4.根据产品id查询产品的详细信息。
SELECT t.small_Image_Url ,t.* FROM product t WHERE product_id='GZ-1000'