feat(portal): 门户左侧栏目导航+首页视觉升级+主题正名批次-PortalSideNav模型+attachCategoryNavigation注入doCategory/doArticle三件套categoryPath/navTopId/sideNav(站点快照树推导零新增查询,home/search不注入);7主题侧栏两栏markup+窄屏横滚+顶导航祖先高亮+二级下拉当前项高亮+面包屑全路径;cms_category.home_style全链路(news_grid 16条8x2日期前缀/media_focus形态)+PortalListData.Item补source来源徽标四处装配;Banner机制gallery展示位→galleryBanners四列大图轮播(自由字符串零DDL);页脚config 12新键(footer_addresses多校区/qr_*/social_*含URL校验,footerAddressLines由PortalSiteSnapshot.of拆行派生);新增展示型皮肤包campus-glow;fresh-mint2正名fresh-mint(theme.json/头注释同步);AttachmentService scene白名单加category(单页富文本贴图免清孤儿);新增PortalSideNavIT 8+PortalHomeBlocksIT 3,CategoryServiceIT/SiteServiceIT/PortalSsrIT/ThemeContractIT等单类独跑全绿;附sql/clear_cms_data.sql全量清库脚本(清cms_*六表留平台账号/主题包/上传文件,测试数据重走用)
build / build-push (push) Successful in 1m47s
build / build-push (push) Successful in 1m47s
This commit is contained in:
@@ -26,7 +26,7 @@ public class AttachmentController {
|
||||
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
/** 上传(multipart:file 必填;articleId 可空;scene 可空 banner/link/site,B2 初步标记) */
|
||||
/** 上传(multipart:file 必填;articleId 可空;scene 可空 banner/link/site/category(category=单页栏目贴图),B2 初步标记) */
|
||||
@PostMapping("/upload")
|
||||
public AttachmentVo upload(BoaUser authUserWithDept,
|
||||
@RequestParam("siteId") Integer siteId,
|
||||
|
||||
@@ -133,6 +133,8 @@ public class PortalController {
|
||||
model.addAttribute("navCategories", site.getCategories());
|
||||
model.addAttribute("currentCategoryId", categoryId);
|
||||
model.addAttribute("category", category);
|
||||
// 栏目导航三件套(docs/11):面包屑全路径 + 顶导航祖先高亮 + 左侧兄弟侧栏
|
||||
pageRenderer.attachCategoryNavigation(site, categoryId, model);
|
||||
model.addAttribute("pageTitle", seoTitle(category, site));
|
||||
model.addAttribute("seoDescription", category.getSeoDescription());
|
||||
model.addAttribute("canonical", url.category(categoryId));
|
||||
@@ -198,6 +200,8 @@ public class PortalController {
|
||||
// 面包屑栏目名(视觉精修 D2-2):按栏目树查名,查不到(栏目已删)退"正文"
|
||||
PortalCategory detailCategory = findCategory(site, detail.getCategoryId());
|
||||
model.addAttribute("currentCategoryName", detailCategory == null ? "正文" : detailCategory.getName());
|
||||
// 栏目导航三件套(docs/11):详情页同栏目页规则;栏目已删时 path 空,模板按缺省形态渲染
|
||||
pageRenderer.attachCategoryNavigation(site, detail.getCategoryId(), model);
|
||||
// 正文上传地址渲染期补基准(B-1):详情来自 cms:article 缓存(快照跨两种暴露形态共享),
|
||||
// 拷贝后重写,避免把某一形态的绝对 URL 写回缓存
|
||||
model.addAttribute("article", detail.withContent(
|
||||
@@ -356,6 +360,7 @@ public class PortalController {
|
||||
item.setTitle(a.getTitle());
|
||||
item.setSummary(a.getSummary());
|
||||
item.setCoverImage(a.getCoverImage());
|
||||
item.setSource(a.getSource());
|
||||
item.setPublishTime(a.getPublishTime());
|
||||
item.setTopmost(a.getTopmost());
|
||||
return item;
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.yunhai.cms.enums.LinkTypeEnum;
|
||||
import com.yunhai.cms.model.portal.PortalBanner;
|
||||
import com.yunhai.cms.model.portal.PortalCategory;
|
||||
import com.yunhai.cms.model.portal.PortalListData;
|
||||
import com.yunhai.cms.model.portal.PortalSideNav;
|
||||
import com.yunhai.cms.model.portal.PortalSiteSnapshot;
|
||||
import com.yunhai.cms.repository.CmsArticleRepository;
|
||||
import com.yunhai.cms.utils.PortalUrlHelper;
|
||||
@@ -28,8 +29,9 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
public class PortalPageRenderer {
|
||||
|
||||
/** 首页每个一级栏目取 N 条 */
|
||||
/** 首页每个一级栏目取 N 条(docs/12:双栏新闻卡形态需 16 条铺满 8 行 × 2 列) */
|
||||
private static final int HOME_BLOCK_SIZE = 8;
|
||||
private static final int HOME_BLOCK_GRID_SIZE = 16;
|
||||
/** 首页置顶区取 N 条 */
|
||||
private static final int HOME_TOP_SIZE = 6;
|
||||
|
||||
@@ -46,6 +48,7 @@ public class PortalPageRenderer {
|
||||
model.addAttribute("navCategories", site.getCategories());
|
||||
List<Map<String, Object>> banners = activeBanners(site, "home", url);
|
||||
model.addAttribute("banners", banners);
|
||||
model.addAttribute("galleryBanners", activeBanners(site, "gallery", url));
|
||||
model.addAttribute("topArticles", topArticles(site.getId()));
|
||||
model.addAttribute("focusArticles", focusArticles(site.getId()));
|
||||
model.addAttribute("categoryBlocks", categoryBlocks(site.getId(), site.getCategories(), url));
|
||||
@@ -63,6 +66,71 @@ public class PortalPageRenderer {
|
||||
|
||||
// ===== 内部装配(自 PortalController 原样迁移)=====
|
||||
|
||||
/**
|
||||
* 栏目导航装配(docs/11):栏目页/详情页共用,三条模型属性——
|
||||
* categoryPath(顶级→当前路径,面包屑全路径)、navTopId(顶导航祖先高亮)、
|
||||
* sideNav(模块有二级子栏目时的左侧兄弟导航,无则不挂,模板 th:if 兜底)。
|
||||
* 全部基于站点快照树推导,零新增查询;搜索/首页等无栏目上下文的路由不调用。
|
||||
*/
|
||||
public void attachCategoryNavigation(PortalSiteSnapshot site, Integer currentCategoryId, Model model) {
|
||||
if (currentCategoryId == null) {
|
||||
return;
|
||||
}
|
||||
List<PortalCategory> path = pathFromRoot(site.getCategories(), currentCategoryId);
|
||||
model.addAttribute("categoryPath", path);
|
||||
if (path.isEmpty()) {
|
||||
return; // 栏目已删等兜底(详情页可能挂已删栏目文章),模板按缺省形态渲染
|
||||
}
|
||||
PortalCategory top = path.get(0);
|
||||
model.addAttribute("navTopId", top.getId());
|
||||
if (!top.getChildren().isEmpty()) {
|
||||
PortalSideNav nav = new PortalSideNav();
|
||||
nav.setTop(top);
|
||||
nav.setItems(top.getChildren());
|
||||
nav.setActiveId(resolveActiveId(path, currentCategoryId));
|
||||
model.addAttribute("sideNav", nav);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 侧栏高亮位:当前为二级=当前栏目;一级直点=null(侧栏头即模块名,无高亮项);
|
||||
* 三级及更深=所在二级分支(决策点②:固定列二级层,高亮当前所属分支)。
|
||||
*/
|
||||
private Integer resolveActiveId(List<PortalCategory> path, Integer currentCategoryId) {
|
||||
if (path.size() == 2) {
|
||||
return currentCategoryId;
|
||||
}
|
||||
return path.size() > 2 ? path.get(1).getId() : null;
|
||||
}
|
||||
|
||||
/** 自栏目树求 根→目标 路径(含两端);目标不存在返回空表 */
|
||||
private List<PortalCategory> pathFromRoot(List<PortalCategory> roots, Integer categoryId) {
|
||||
for (PortalCategory root : roots) {
|
||||
List<PortalCategory> hit = pathFromRootDeep(root, categoryId);
|
||||
if (hit != null) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private List<PortalCategory> pathFromRootDeep(PortalCategory node, Integer categoryId) {
|
||||
if (node.getId().equals(categoryId)) {
|
||||
List<PortalCategory> path = new ArrayList<>();
|
||||
path.add(node);
|
||||
return path;
|
||||
}
|
||||
for (PortalCategory child : node.getChildren()) {
|
||||
List<PortalCategory> sub = pathFromRootDeep(child, categoryId);
|
||||
if (sub != null) {
|
||||
sub.add(0, node);
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/** 启用中且在时间窗内的指定位轮播,并解析为完整跳转 URL */
|
||||
private List<Map<String, Object>> activeBanners(PortalSiteSnapshot site, String position,
|
||||
PortalUrlHelper url) {
|
||||
@@ -137,6 +205,7 @@ public class PortalPageRenderer {
|
||||
/**
|
||||
* 首页一级栏目区块:每栏目精确取 top N(列表型栏目才出区块)。
|
||||
* D1-2:区块聚合列表型子孙栏目文章(文章通常挂叶子栏目,父栏目区块必须显示汇总)。
|
||||
* docs/12:区块带出栏目 homeStyle,双栏新闻卡(news_grid)取 16 条铺满双栏,其余取 8 条。
|
||||
*/
|
||||
private List<Map<String, Object>> categoryBlocks(Integer siteId, List<PortalCategory> categories,
|
||||
PortalUrlHelper url) {
|
||||
@@ -145,12 +214,14 @@ public class PortalPageRenderer {
|
||||
if (category.getType() == null || category.getType() != CategoryTypeEnum.LIST.getCode()) {
|
||||
continue;
|
||||
}
|
||||
int blockSize = "news_grid".equals(category.getHomeStyle())
|
||||
? HOME_BLOCK_GRID_SIZE : HOME_BLOCK_SIZE;
|
||||
List<Integer> categoryIds = new ArrayList<>();
|
||||
collectListDescendantIds(category, categoryIds);
|
||||
List<com.yunhai.cms.entity.CmsArticle> articles = articleRepository
|
||||
.findBySiteIdAndCategoryIdInAndStatus(siteId, categoryIds,
|
||||
com.yunhai.cms.enums.ArticleStatusEnum.PUBLISHED.getCode(),
|
||||
PageRequest.of(0, HOME_BLOCK_SIZE,
|
||||
PageRequest.of(0, blockSize,
|
||||
Sort.by(Sort.Order.desc("topmost"), Sort.Order.desc("publishTime"))))
|
||||
.getContent();
|
||||
List<PortalListData.Item> items = new ArrayList<>();
|
||||
@@ -159,6 +230,7 @@ public class PortalPageRenderer {
|
||||
}
|
||||
Map<String, Object> block = new HashMap<>();
|
||||
block.put("category", category);
|
||||
block.put("homeStyle", category.getHomeStyle());
|
||||
block.put("items", items);
|
||||
block.put("moreUrl", url.category(category.getId()));
|
||||
blocks.add(block);
|
||||
@@ -182,6 +254,7 @@ public class PortalPageRenderer {
|
||||
item.setTitle(a.getTitle());
|
||||
item.setSummary(a.getSummary());
|
||||
item.setCoverImage(a.getCoverImage());
|
||||
item.setSource(a.getSource());
|
||||
item.setPublishTime(a.getPublishTime());
|
||||
item.setTopmost(a.getTopmost());
|
||||
return item;
|
||||
|
||||
Reference in New Issue
Block a user