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:
@@ -0,0 +1,30 @@
|
||||
# campus-glow(炫彩校园)
|
||||
|
||||
展示型皮肤包(docs/12 方案的首个落地消费方):在共享数据契约之上,为首页提供四类「炫」呈现,并大幅增强页脚个性化能力。
|
||||
|
||||
## 风格定位
|
||||
|
||||
- 主色翠绿 `#0e8a5f` + 强调橙 `#ff7a1a`(gradual 渐变与毛玻璃质感),适配综合性大学/职业院校门户的「鲜活」诉求。
|
||||
- 全部颜色可在站点 theme_params 中微调;布局类参数仅 `galleryCols`(线上展厅每行列数 3/4/5)。
|
||||
|
||||
## 首页板块形态(栏目顺序即板块顺序)
|
||||
|
||||
| 栏目 homeStyle | 呈现 | 数据 |
|
||||
| -- | -- | -- |
|
||||
| (空) | 默认卡:featured 首条 + 行式列表 | categoryBlocks |
|
||||
| `news_grid` | 双栏新闻卡:全幅背景(站点 config `news_bg_image`,未配走主题渐变)+ 半透明毛玻璃卡 + MM-DD 日期前缀双栏列表(16 条) | categoryBlocks |
|
||||
| `media_focus` | 媒体聚焦:来源徽标(文章 `source` 字段,空则只显示标题)+ 标题双栏列表 | categoryBlocks |
|
||||
| 独立板块 | 线上展厅画廊:双语文区头(config `gallery_title`/`gallery_subtitle`)+ 横向翻页轮播 | 轮播 position=`gallery`(设计尺寸 800×520) |
|
||||
|
||||
另有「要闻速递」横条(置顶文章无缝滚动,hover 暂停)与通栏轮播(1920×560)。
|
||||
|
||||
## 页脚能力(站点 config 驱动,全部有值才渲染)
|
||||
|
||||
`footer_addresses`(多校区地址,换行分行)· `qr_wechat`/`qr_weibo`(二维码,hover 放大)·
|
||||
`social_wechat`/`social_weibo`/`social_douyin`/`social_bilibili`(社媒六边形图标阵列)·
|
||||
`contact`/`copyright`/`beian`/logo/友链分组/校名底条(既有契约)· 返回顶部悬浮按钮(主题内置)。
|
||||
|
||||
## 参数取值建议
|
||||
|
||||
- 换主色:`primary`/`primaryDark`/`accent` 三者成对调整;浅色校徽建议保留页脚品牌底衬。
|
||||
- 展厅图片比例接近 800×520 时观感最佳;不足一页时画廊自动隐藏翻页按钮且不轮播。
|
||||
@@ -0,0 +1,402 @@
|
||||
/* =====================================================================
|
||||
campus-glow 炫彩校园(docs/12 展示型皮肤)
|
||||
设计令牌:theme.json color 参数经服务端聚合 :root 覆盖(优先级 custom_css > themeVarsCss > 本文件)
|
||||
===================================================================== */
|
||||
:root {
|
||||
--cg-primary: #0e8a5f;
|
||||
--cg-primary-dark: #0a6244;
|
||||
--cg-accent: #ff7a1a;
|
||||
--cg-text: #2b2f33;
|
||||
--cg-text-light: #7a8288;
|
||||
--cg-bg: #f4f7f5;
|
||||
--cg-card-bg: #ffffff;
|
||||
--cg-border: #e3e8e4;
|
||||
--cg-shadow: 0 10px 30px rgba(10, 98, 68, 0.10);
|
||||
--cg-radius: 14px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { -webkit-text-size-adjust: 100%; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
|
||||
color: var(--cg-text);
|
||||
background: var(--cg-bg);
|
||||
line-height: 1.6;
|
||||
}
|
||||
img { max-width: 100%; border: 0; display: block; }
|
||||
ul, ol, p, figure { margin: 0; padding: 0; }
|
||||
li { list-style: none; }
|
||||
a { color: inherit; text-decoration: none; transition: color .2s; }
|
||||
button { font-family: inherit; cursor: pointer; }
|
||||
|
||||
.cg-container { max-width: 1200px; margin: 0 auto; padding: 0 16px; }
|
||||
.cg-skip-link {
|
||||
position: absolute; left: -999px; top: 0; z-index: 300;
|
||||
background: var(--cg-primary); color: #fff; padding: 8px 14px; border-radius: 0 0 8px 0;
|
||||
}
|
||||
.cg-skip-link:focus { left: 0; }
|
||||
|
||||
/* ===== 顶条 ===== */
|
||||
.cg-topbar {
|
||||
background: var(--cg-primary-dark);
|
||||
color: rgba(255, 255, 255, .85);
|
||||
font-size: 13px;
|
||||
}
|
||||
.cg-topbar-inner { display: flex; justify-content: space-between; align-items: center; min-height: 34px; }
|
||||
.cg-colleges { position: relative; }
|
||||
.cg-colleges-toggle { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; padding: 4px 0; }
|
||||
.cg-colleges-arrow { font-size: 10px; transition: transform .2s; }
|
||||
.cg-colleges-menu {
|
||||
display: none; position: absolute; top: 100%; right: 0; z-index: 120;
|
||||
min-width: 200px; max-height: 420px; overflow-y: auto;
|
||||
background: #fff; color: var(--cg-text);
|
||||
border: 1px solid var(--cg-border); border-top: 2px solid var(--cg-primary);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, .12); padding: 6px 0;
|
||||
}
|
||||
.cg-colleges:hover .cg-colleges-menu, .cg-colleges:focus-within .cg-colleges-menu { display: block; }
|
||||
.cg-colleges:hover .cg-colleges-arrow { transform: rotate(180deg); }
|
||||
.cg-colleges-menu li a { display: block; padding: 7px 16px; font-size: 13px; }
|
||||
.cg-colleges-menu li a:hover { background: color-mix(in srgb, var(--cg-primary) 8%, #fff); color: var(--cg-primary); }
|
||||
.cg-colleges-menu li.active a { color: var(--cg-primary); font-weight: 600; }
|
||||
|
||||
/* ===== 品牌行 ===== */
|
||||
.cg-brand-row { display: flex; justify-content: space-between; align-items: center; padding-top: 22px; padding-bottom: 22px; gap: 16px; flex-wrap: wrap; }
|
||||
.cg-site-brand { display: flex; align-items: center; gap: 14px; min-width: 0; }
|
||||
.cg-site-logo { max-height: 56px; max-width: 220px; width: auto; }
|
||||
.cg-site-name { font-size: 26px; font-weight: 700; color: var(--cg-primary-dark); letter-spacing: 1px; }
|
||||
.cg-site-search { display: flex; }
|
||||
.cg-site-search input {
|
||||
width: 240px; height: 38px; padding: 0 14px; font-size: 14px;
|
||||
border: 2px solid var(--cg-primary); border-right: 0; border-radius: 19px 0 0 19px; outline: none;
|
||||
background: #fff; color: var(--cg-text);
|
||||
}
|
||||
.cg-site-search button {
|
||||
height: 38px; padding: 0 20px; border: 0; border-radius: 0 19px 19px 0;
|
||||
background: var(--cg-primary); color: #fff; font-size: 14px;
|
||||
}
|
||||
.cg-site-search button:hover { background: var(--cg-primary-dark); }
|
||||
|
||||
/* ===== 栏目导航 ===== */
|
||||
.cg-nav { background: linear-gradient(90deg, var(--cg-primary-dark), var(--cg-primary)); box-shadow: var(--cg-shadow); position: sticky; top: 0; z-index: 100; }
|
||||
.cg-nav > ul { display: flex; flex-wrap: wrap; }
|
||||
.cg-nav > ul > li { position: relative; }
|
||||
.cg-nav > ul > li > a {
|
||||
display: flex; align-items: center; gap: 3px; color: #fff; font-size: 16px;
|
||||
padding: 13px 22px; position: relative;
|
||||
}
|
||||
.cg-nav > ul > li > a:hover, .cg-nav > ul > li.active > a { background: rgba(255, 255, 255, .14); }
|
||||
.cg-nav > ul > li.active > a::after {
|
||||
content: ""; position: absolute; left: 22px; right: 22px; bottom: 6px; height: 3px;
|
||||
border-radius: 2px; background: var(--cg-accent);
|
||||
}
|
||||
.cg-caret { font-size: 10px; opacity: .8; }
|
||||
.cg-sub-nav {
|
||||
display: none; position: absolute; top: 100%; left: 0; z-index: 110; min-width: 176px;
|
||||
background: #fff; border: 1px solid var(--cg-border); border-top: 2px solid var(--cg-accent);
|
||||
box-shadow: 0 8px 18px rgba(0, 0, 0, .12); padding: 6px 0;
|
||||
}
|
||||
.cg-nav li:hover .cg-sub-nav, .cg-nav li:focus-within .cg-sub-nav { display: block; }
|
||||
.cg-sub-nav a { display: block; padding: 8px 16px; font-size: 14px; color: var(--cg-text); }
|
||||
.cg-sub-nav a:hover { background: color-mix(in srgb, var(--cg-primary) 8%, #fff); color: var(--cg-primary); }
|
||||
|
||||
/* ===== 轮播 ===== */
|
||||
.cg-banner { position: relative; overflow: hidden; background: var(--cg-primary-dark); }
|
||||
.cg-banner-slide { display: none; position: relative; }
|
||||
.cg-banner-slide.active { display: block; }
|
||||
.cg-banner-slide img { width: 100%; height: auto; aspect-ratio: 1920 / 560; object-fit: cover; }
|
||||
.cg-banner-cap {
|
||||
position: absolute; left: 0; right: 0; bottom: 0; padding: 42px 6% 20px;
|
||||
color: #fff; font-size: 20px; font-weight: 600; text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, .45));
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.cg-banner-arrow, .cg-gallery-arrow {
|
||||
position: absolute; top: 50%; transform: translateY(-50%); z-index: 5;
|
||||
width: 42px; height: 42px; border: 0; border-radius: 50%;
|
||||
background: rgba(0, 0, 0, .30); color: #fff; font-size: 24px; line-height: 1;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: background .2s;
|
||||
}
|
||||
.cg-banner-arrow:hover, .cg-gallery-arrow:hover { background: var(--cg-accent); }
|
||||
.cg-banner-arrow.prev, .cg-gallery-arrow.prev { left: 18px; }
|
||||
.cg-banner-arrow.next, .cg-gallery-arrow.next { right: 18px; }
|
||||
.cg-banner-dots { position: absolute; left: 0; right: 0; bottom: 14px; display: flex; justify-content: center; gap: 9px; z-index: 5; }
|
||||
.cg-banner-dots button {
|
||||
width: 10px; height: 10px; padding: 0; border: 0; border-radius: 50%;
|
||||
background: rgba(255, 255, 255, .55);
|
||||
}
|
||||
.cg-banner-dots button.active { background: var(--cg-accent); width: 24px; border-radius: 5px; }
|
||||
|
||||
/* ===== 要闻速递横条 ===== */
|
||||
.cg-ticker { background: var(--cg-card-bg); border-bottom: 1px solid var(--cg-border); }
|
||||
.cg-ticker-inner { display: flex; align-items: center; gap: 14px; }
|
||||
.cg-ticker-label {
|
||||
flex: none; display: inline-flex; align-items: center; gap: 6px;
|
||||
color: #fff; font-size: 14px; font-weight: 600; padding: 9px 16px;
|
||||
background: linear-gradient(135deg, var(--cg-accent), #ff9f4d); border-radius: 0 0 12px 12px;
|
||||
}
|
||||
.cg-ticker-viewport { overflow: hidden; flex: 1; min-width: 0; }
|
||||
.cg-ticker-track { display: inline-flex; white-space: nowrap; animation: cg-ticker-move 36s linear infinite; }
|
||||
.cg-ticker:hover .cg-ticker-track { animation-play-state: paused; }
|
||||
.cg-ticker-item { display: inline-flex; align-items: center; gap: 8px; padding: 10px 26px; font-size: 14px; color: var(--cg-text); }
|
||||
.cg-ticker-item:hover { color: var(--cg-primary); }
|
||||
.cg-ticker-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--cg-accent); flex: none; }
|
||||
@keyframes cg-ticker-move { from { transform: translateX(0); } to { transform: translateX(-50%); } }
|
||||
|
||||
/* ===== 区块通用头 ===== */
|
||||
.cg-sec-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
|
||||
.cg-sec-head h2 {
|
||||
position: relative; font-size: 26px; font-weight: 700; color: var(--cg-primary-dark);
|
||||
padding-left: 14px; letter-spacing: 1px;
|
||||
}
|
||||
.cg-sec-head h2::before {
|
||||
content: ""; position: absolute; left: 0; top: 7px; bottom: 7px; width: 5px;
|
||||
border-radius: 3px; background: linear-gradient(180deg, var(--cg-accent), var(--cg-primary));
|
||||
}
|
||||
.cg-more-circle {
|
||||
flex: none; width: 36px; height: 36px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: 2px solid var(--cg-primary); color: var(--cg-primary); background: transparent;
|
||||
transition: all .2s;
|
||||
}
|
||||
.cg-more-circle:hover { background: var(--cg-primary); color: #fff; transform: translateX(3px); }
|
||||
.cg-more-text { color: var(--cg-text-light); font-size: 14px; }
|
||||
.cg-more-text:hover { color: var(--cg-primary); }
|
||||
.cg-top-badge {
|
||||
flex: none; font-size: 12px; color: #fff; background: var(--cg-accent);
|
||||
border-radius: 4px; padding: 1px 7px; line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ===== 形态一:双栏新闻卡(全幅背景 + 毛玻璃) ===== */
|
||||
.cg-newshot { position: relative; padding: 46px 0 52px; overflow: hidden; background: linear-gradient(135deg, var(--cg-primary-dark), var(--cg-primary) 55%, #35b98a); }
|
||||
.cg-newshot-bg { position: absolute; inset: 0; background-size: cover; background-position: center; }
|
||||
.cg-newshot-bg::after { content: ""; position: absolute; inset: 0; background: linear-gradient(120deg, rgba(10, 98, 68, .55), rgba(10, 98, 68, .18)); }
|
||||
.cg-newshot .cg-container { position: relative; z-index: 2; }
|
||||
.cg-newshot-card {
|
||||
background: rgba(255, 255, 255, .88);
|
||||
-webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, .65); border-radius: var(--cg-radius);
|
||||
box-shadow: var(--cg-shadow); padding: 30px 34px 26px;
|
||||
}
|
||||
.cg-newshot-card .cg-sec-head h2 { color: var(--cg-primary); }
|
||||
.cg-news-grid { display: grid; grid-template-columns: 1fr 1fr; column-gap: 48px; row-gap: 2px; }
|
||||
.cg-news-grid li { display: flex; align-items: center; gap: 12px; padding: 10px 2px; border-bottom: 1px dashed var(--cg-border); min-width: 0; }
|
||||
.cg-news-grid li:nth-last-child(-n+2) { border-bottom: 0; }
|
||||
.cg-news-date { flex: none; color: var(--cg-accent); font-size: 15px; font-weight: 600; letter-spacing: .5px; }
|
||||
.cg-news-title { flex: 1; min-width: 0; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.cg-news-title:hover { color: var(--cg-primary); }
|
||||
|
||||
/* ===== 形态二:媒体聚焦(来源徽标 + 标题) ===== */
|
||||
.cg-media { padding: 46px 0 8px; }
|
||||
.cg-media-card {
|
||||
background: var(--cg-card-bg); border: 1px solid var(--cg-border);
|
||||
border-radius: var(--cg-radius); box-shadow: var(--cg-shadow); padding: 30px 34px 26px;
|
||||
}
|
||||
.cg-media-grid { display: grid; grid-template-columns: 1fr 1fr; column-gap: 48px; }
|
||||
.cg-media-grid li { display: flex; align-items: center; gap: 14px; padding: 13px 2px; border-bottom: 1px dashed var(--cg-border); min-width: 0; }
|
||||
.cg-media-grid li:nth-last-child(-n+2) { border-bottom: 0; }
|
||||
.cg-source-badge {
|
||||
flex: none; min-width: 88px; max-width: 140px; text-align: center;
|
||||
font-size: 13px; font-weight: 600; color: #fff; padding: 6px 10px;
|
||||
background: linear-gradient(135deg, var(--cg-accent), #ff9f4d); border-radius: 8px;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
box-shadow: 0 3px 8px rgba(255, 122, 26, .30);
|
||||
}
|
||||
.cg-media-title { flex: 1; min-width: 0; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.cg-media-title:hover { color: var(--cg-primary); }
|
||||
|
||||
/* ===== 形态三:默认卡(featured + 行式列表) ===== */
|
||||
.cg-block { padding: 40px 0 0; }
|
||||
.cg-block-card {
|
||||
background: var(--cg-card-bg); border: 1px solid var(--cg-border);
|
||||
border-radius: var(--cg-radius); box-shadow: var(--cg-shadow); padding: 26px 30px;
|
||||
}
|
||||
.cg-block-cols { display: grid; grid-template-columns: 5fr 6fr; gap: 30px; }
|
||||
.cg-block-featured { display: flex; gap: 16px; min-width: 0; }
|
||||
.cg-feat-cover { flex: none; width: 200px; border-radius: 10px; overflow: hidden; }
|
||||
.cg-feat-cover img { width: 100%; height: 140px; object-fit: cover; transition: transform .3s; }
|
||||
.cg-block-featured:hover .cg-feat-cover img { transform: scale(1.05); }
|
||||
.cg-feat-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
|
||||
.cg-feat-title { font-size: 16px; font-weight: 600; color: var(--cg-text); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.cg-block-featured:hover .cg-feat-title { color: var(--cg-primary); }
|
||||
.cg-feat-summary { font-size: 13px; color: var(--cg-text-light); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.cg-feat-meta { margin-top: auto; font-size: 13px; color: var(--cg-text-light); }
|
||||
.cg-block-list li { display: flex; align-items: center; gap: 12px; padding: 8px 2px; border-bottom: 1px dashed var(--cg-border); }
|
||||
.cg-block-list li:last-child { border-bottom: 0; }
|
||||
.cg-block-list li a { flex: 1; min-width: 0; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.cg-block-list li a:hover { color: var(--cg-primary); }
|
||||
.cg-block-list time { flex: none; font-size: 13px; color: var(--cg-text-light); }
|
||||
|
||||
/* ===== 线上展厅画廊 ===== */
|
||||
.cg-gallery { padding: 52px 0 58px; }
|
||||
.cg-gallery-head { text-align: center; margin-bottom: 26px; }
|
||||
.cg-gallery-head h2 {
|
||||
display: inline-block; position: relative; font-size: 30px; font-weight: 700;
|
||||
color: var(--cg-accent); letter-spacing: 4px; padding: 0 74px;
|
||||
}
|
||||
.cg-gallery-head h2::before, .cg-gallery-head h2::after {
|
||||
content: ""; position: absolute; top: 50%; width: 54px; height: 2px;
|
||||
background: linear-gradient(90deg, transparent, var(--cg-accent));
|
||||
}
|
||||
.cg-gallery-head h2::before { left: 0; }
|
||||
.cg-gallery-head h2::after { right: 0; transform: scaleX(-1); }
|
||||
.cg-gallery-sub { color: var(--cg-text-light); font-size: 14px; letter-spacing: 2px; margin-top: 2px; text-transform: uppercase; }
|
||||
.cg-gallery-stage { position: relative; overflow: hidden; border-radius: var(--cg-radius); }
|
||||
.cg-gallery-track { display: flex; transition: transform .45s ease; }
|
||||
.cg-gallery-item { position: relative; flex: 0 0 25%; display: block; padding: 0 8px; }
|
||||
.cg-gallery-cols-3 .cg-gallery-item { flex-basis: 33.3333%; }
|
||||
.cg-gallery-cols-4 .cg-gallery-item { flex-basis: 25%; }
|
||||
.cg-gallery-cols-5 .cg-gallery-item { flex-basis: 20%; }
|
||||
.cg-gallery-item img { width: 100%; height: 260px; object-fit: cover; border-radius: 10px; transition: transform .3s, box-shadow .3s; }
|
||||
.cg-gallery-item:hover img { transform: translateY(-4px); box-shadow: 0 14px 28px rgba(10, 98, 68, .22); }
|
||||
.cg-gallery-cap {
|
||||
position: absolute; left: 8px; right: 8px; bottom: 0; padding: 26px 12px 9px;
|
||||
color: #fff; font-size: 14px; border-radius: 0 0 10px 10px;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, .55));
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* ===== 页脚(个性化) ===== */
|
||||
.cg-footer {
|
||||
position: relative; color: rgba(255, 255, 255, .88); margin-top: 46px;
|
||||
background: linear-gradient(135deg, var(--cg-primary-dark) 0%, var(--cg-primary) 78%, #35b98a 100%);
|
||||
}
|
||||
.cg-footer::before {
|
||||
content: ""; position: absolute; inset: 0; pointer-events: none;
|
||||
background: radial-gradient(circle at 12% 20%, rgba(255, 255, 255, .10) 0, rgba(255, 255, 255, .10) 2px, transparent 3px),
|
||||
radial-gradient(circle at 88% 30%, rgba(255, 255, 255, .08) 0, rgba(255, 255, 255, .08) 2px, transparent 3px);
|
||||
background-size: 130px 130px, 170px 170px;
|
||||
}
|
||||
.cg-footer-main {
|
||||
position: relative; display: grid; grid-template-columns: 1.1fr 1.6fr auto;
|
||||
gap: 38px; padding-top: 40px; padding-bottom: 30px;
|
||||
}
|
||||
.cg-footer-logo-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
|
||||
.cg-footer-logo { max-height: 52px; max-width: 200px; width: auto; background: rgba(255, 255, 255, .92); border-radius: 8px; padding: 4px; }
|
||||
.cg-footer-site { font-size: 20px; font-weight: 700; color: #fff; }
|
||||
.cg-social { display: flex; gap: 12px; flex-wrap: wrap; }
|
||||
.cg-social-icon {
|
||||
width: 42px; height: 42px; display: flex; align-items: center; justify-content: center;
|
||||
color: #fff; background: rgba(255, 255, 255, .16);
|
||||
clip-path: polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
|
||||
transition: background .2s, transform .2s;
|
||||
}
|
||||
.cg-social-icon:hover { background: var(--cg-accent); transform: translateY(-3px); }
|
||||
.cg-footer-addr li { font-size: 14px; line-height: 2; }
|
||||
.cg-footer-line { font-size: 14px; line-height: 2; }
|
||||
.cg-footer-qr { display: flex; gap: 18px; }
|
||||
.cg-qr { text-align: center; }
|
||||
.cg-qr img {
|
||||
width: 108px; height: 108px; object-fit: contain; background: #fff; border-radius: 8px; padding: 6px;
|
||||
transition: transform .25s ease, box-shadow .25s ease; position: relative; z-index: 5;
|
||||
}
|
||||
.cg-qr:hover img { transform: scale(2.1); box-shadow: 0 12px 30px rgba(0, 0, 0, .35); }
|
||||
.cg-qr figcaption { font-size: 13px; margin-top: 7px; color: rgba(255, 255, 255, .80); }
|
||||
.cg-footer-links { position: relative; padding-bottom: 18px; }
|
||||
.cg-links-group { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; padding: 6px 0; }
|
||||
.cg-group-name { flex: none; font-size: 14px; font-weight: 600; color: #fff; }
|
||||
.cg-links-row { display: flex; flex-wrap: wrap; gap: 4px 18px; }
|
||||
.cg-links-row a { font-size: 13px; color: rgba(255, 255, 255, .82); display: inline-flex; align-items: center; gap: 6px; }
|
||||
.cg-links-row a:hover { color: #fff; }
|
||||
.cg-link-logo { max-height: 22px; max-width: 60px; }
|
||||
.cg-footer-bottom { position: relative; border-top: 1px solid rgba(255, 255, 255, .22); text-align: center; padding: 14px 16px 18px; font-size: 14px; letter-spacing: 1px; }
|
||||
|
||||
/* 返回顶部 */
|
||||
.cg-backtop {
|
||||
position: fixed; right: 26px; bottom: 90px; z-index: 150;
|
||||
width: 46px; height: 46px; border: 0; border-radius: 50%;
|
||||
background: var(--cg-primary); color: #fff; font-size: 20px;
|
||||
box-shadow: 0 8px 20px rgba(10, 98, 68, .35);
|
||||
opacity: 0; visibility: hidden; transform: translateY(14px);
|
||||
transition: all .25s ease;
|
||||
}
|
||||
.cg-backtop.visible { opacity: 1; visibility: visible; transform: translateY(0); }
|
||||
.cg-backtop:hover { background: var(--cg-accent); }
|
||||
|
||||
/* ===== 内页(列表/单页/详情共用) ===== */
|
||||
.cg-list-main { padding: 26px 16px 46px; }
|
||||
.cg-page-cols { display: grid; grid-template-columns: 232px 1fr; gap: 22px; align-items: start; }
|
||||
.cg-side-nav { background: var(--cg-card-bg); border: 1px solid var(--cg-border); border-radius: var(--cg-radius); overflow: hidden; }
|
||||
.cg-side-nav-head {
|
||||
background: linear-gradient(135deg, var(--cg-primary-dark), var(--cg-primary));
|
||||
color: #fff; font-size: 17px; font-weight: 600; padding: 13px 18px; letter-spacing: 1px;
|
||||
}
|
||||
.cg-side-nav-item a { display: block; padding: 11px 18px; font-size: 14px; border-left: 3px solid transparent; color: var(--cg-text); }
|
||||
.cg-side-nav-item a:hover { background: color-mix(in srgb, var(--cg-primary) 7%, #fff); color: var(--cg-primary); }
|
||||
.cg-side-nav-item.active a { border-left-color: var(--cg-accent); background: color-mix(in srgb, var(--cg-primary) 9%, #fff); color: var(--cg-primary); font-weight: 600; }
|
||||
.cg-page-card { background: var(--cg-card-bg); border: 1px solid var(--cg-border); border-radius: var(--cg-radius); padding: 24px 28px 30px; min-width: 0; }
|
||||
.cg-breadcrumb { font-size: 13px; color: var(--cg-text-light); margin-bottom: 8px; }
|
||||
.cg-breadcrumb a:hover { color: var(--cg-primary); }
|
||||
.cg-crumb-sep { margin: 0 8px; }
|
||||
.cg-current { color: var(--cg-primary); font-weight: 600; }
|
||||
.cg-page-title { font-size: 24px; font-weight: 700; color: var(--cg-primary-dark); margin: 6px 0 16px; padding-bottom: 12px; border-bottom: 2px solid var(--cg-border); position: relative; }
|
||||
.cg-page-title::after { content: ""; position: absolute; left: 0; bottom: -2px; width: 64px; height: 2px; background: var(--cg-accent); }
|
||||
|
||||
.cg-article-list li { border-bottom: 1px dashed var(--cg-border); }
|
||||
.cg-article-list li:last-child { border-bottom: 0; }
|
||||
.cg-article-item { display: flex; gap: 18px; padding: 16px 2px; }
|
||||
.cg-item-cover { flex: none; width: 190px; border-radius: 10px; overflow: hidden; }
|
||||
.cg-item-cover img { width: 100%; height: 122px; object-fit: cover; transition: transform .3s; }
|
||||
.cg-article-item:hover .cg-item-cover img { transform: scale(1.05); }
|
||||
.cg-item-body { min-width: 0; display: flex; flex-direction: column; gap: 7px; }
|
||||
.cg-item-title a { font-size: 16px; font-weight: 600; display: block; }
|
||||
.cg-item-title a:hover { color: var(--cg-primary); }
|
||||
.cg-item-summary { font-size: 13px; color: var(--cg-text-light); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.cg-item-meta { margin-top: auto; display: flex; align-items: center; gap: 16px; font-size: 13px; color: var(--cg-text-light); }
|
||||
|
||||
.cg-pagination { display: flex; justify-content: center; gap: 8px; margin-top: 22px; flex-wrap: wrap; }
|
||||
.cg-pagination a { min-width: 34px; height: 34px; padding: 0 10px; display: inline-flex; align-items: center; justify-content: center; font-size: 14px; border: 1px solid var(--cg-border); border-radius: 17px; color: var(--cg-text); background: var(--cg-card-bg); }
|
||||
.cg-pagination a:hover { border-color: var(--cg-primary); color: var(--cg-primary); }
|
||||
.cg-pagination a.active { background: var(--cg-primary); border-color: var(--cg-primary); color: #fff; }
|
||||
.cg-pagination a.disabled { opacity: .45; pointer-events: none; }
|
||||
|
||||
.cg-article-title { font-size: 24px; font-weight: 700; color: var(--cg-text); line-height: 1.5; margin: 10px 0 8px; }
|
||||
.cg-article-meta { display: flex; flex-wrap: wrap; gap: 6px 20px; font-size: 13px; color: var(--cg-text-light); padding-bottom: 16px; border-bottom: 1px solid var(--cg-border); }
|
||||
.cg-article-content { padding-top: 18px; font-size: 15px; line-height: 1.9; word-break: break-word; }
|
||||
.cg-article-content img { max-width: 100%; height: auto; margin: 8px auto; border-radius: 8px; }
|
||||
.cg-article-content p { margin: 0 0 12px; }
|
||||
.cg-article-sibling { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 26px; }
|
||||
.cg-article-sibling .prev, .cg-article-sibling .next { border: 1px solid var(--cg-border); border-radius: 10px; padding: 12px 16px; display: block; min-width: 0; }
|
||||
.cg-article-sibling .next { text-align: right; }
|
||||
.cg-article-sibling a:hover { border-color: var(--cg-primary); }
|
||||
.cg-sib-label { display: block; font-size: 12px; color: var(--cg-text-light); margin-bottom: 4px; }
|
||||
.cg-sib-title { display: block; font-size: 14px; color: var(--cg-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
/* ===== 空态 ===== */
|
||||
.cg-empty { text-align: center; padding: 44px 0; color: var(--cg-text-light); }
|
||||
.cg-empty-hint { font-size: 13px; margin-top: 6px; opacity: .8; }
|
||||
|
||||
/* ===== 响应式 ===== */
|
||||
@media (max-width: 992px) {
|
||||
.cg-gallery-item, .cg-gallery-cols-3 .cg-gallery-item, .cg-gallery-cols-4 .cg-gallery-item, .cg-gallery-cols-5 .cg-gallery-item { flex-basis: 50%; }
|
||||
.cg-gallery-item img { height: 210px; }
|
||||
.cg-footer-main { grid-template-columns: 1fr 1fr; }
|
||||
.cg-footer-qr { grid-column: 1 / -1; }
|
||||
.cg-block-cols { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.cg-site-name { font-size: 20px; }
|
||||
.cg-site-search input { width: 150px; }
|
||||
.cg-nav > ul > li > a { padding: 11px 13px; font-size: 14px; }
|
||||
.cg-banner-slide img { aspect-ratio: 16 / 9; }
|
||||
.cg-banner-cap { font-size: 15px; padding: 30px 5% 14px; }
|
||||
.cg-ticker-label { display: none; }
|
||||
.cg-news-grid, .cg-media-grid { grid-template-columns: 1fr; }
|
||||
.cg-news-grid li:nth-last-child(-n+2), .cg-media-grid li:nth-last-child(-n+2) { border-bottom: 1px dashed var(--cg-border); }
|
||||
.cg-news-grid li:last-child, .cg-media-grid li:last-child { border-bottom: 0; }
|
||||
.cg-newshot-card, .cg-media-card, .cg-block-card { padding: 20px 16px; }
|
||||
.cg-sec-head h2 { font-size: 21px; }
|
||||
.cg-page-cols { grid-template-columns: 1fr; }
|
||||
.cg-article-item { flex-direction: column; }
|
||||
.cg-item-cover { width: 100%; }
|
||||
.cg-item-cover img { height: 170px; }
|
||||
.cg-article-sibling { grid-template-columns: 1fr; }
|
||||
.cg-footer-main { grid-template-columns: 1fr; gap: 24px; }
|
||||
.cg-backtop { right: 14px; bottom: 70px; width: 40px; height: 40px; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.cg-gallery-item, .cg-gallery-cols-3 .cg-gallery-item, .cg-gallery-cols-4 .cg-gallery-item, .cg-gallery-cols-5 .cg-gallery-item { flex-basis: 100%; }
|
||||
.cg-gallery-item img { height: 230px; }
|
||||
.cg-qr:hover img { transform: scale(1.6); }
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 936 B |
@@ -0,0 +1,117 @@
|
||||
/* campus-glow 皮肤包脚本:顶条日期 + 首页轮播 + 线上展厅画廊翻页 + 返回顶部,无任何外部依赖 */
|
||||
(function () {
|
||||
/* 顶条日期(本地时间,YYYY年M月D日 星期X) */
|
||||
var dateEl = document.getElementById('siteDate');
|
||||
if (dateEl) {
|
||||
var now = new Date();
|
||||
var week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()];
|
||||
dateEl.textContent = now.getFullYear() + '年' + (now.getMonth() + 1) + '月'
|
||||
+ now.getDate() + '日 星期' + week;
|
||||
}
|
||||
|
||||
/* 首页轮播 */
|
||||
(function () {
|
||||
var root = document.querySelector('.cg-banner');
|
||||
if (!root) return;
|
||||
var slides = root.querySelectorAll('.cg-banner-slide');
|
||||
var dots = root.querySelectorAll('.cg-banner-dots button');
|
||||
var prevBtn = root.querySelector('.cg-banner-arrow.prev');
|
||||
var nextBtn = root.querySelector('.cg-banner-arrow.next');
|
||||
if (!slides.length) return;
|
||||
var current = 0;
|
||||
var timer = null;
|
||||
var INTERVAL = 5000;
|
||||
|
||||
function show(index) {
|
||||
slides[current].classList.remove('active');
|
||||
if (dots[current]) dots[current].classList.remove('active');
|
||||
current = (index + slides.length) % slides.length;
|
||||
slides[current].classList.add('active');
|
||||
if (dots[current]) dots[current].classList.add('active');
|
||||
}
|
||||
function start() {
|
||||
stop();
|
||||
if (slides.length > 1) timer = window.setInterval(function () { show(current + 1); }, INTERVAL);
|
||||
}
|
||||
function stop() {
|
||||
if (timer) { window.clearInterval(timer); timer = null; }
|
||||
}
|
||||
|
||||
dots.forEach(function (dot, index) {
|
||||
dot.addEventListener('click', function () { show(index); start(); });
|
||||
});
|
||||
if (prevBtn) prevBtn.addEventListener('click', function () { show(current - 1); start(); });
|
||||
if (nextBtn) nextBtn.addEventListener('click', function () { show(current + 1); start(); });
|
||||
root.addEventListener('mouseenter', stop);
|
||||
root.addEventListener('mouseleave', start);
|
||||
root.addEventListener('focusin', stop);
|
||||
root.addEventListener('focusout', start);
|
||||
start();
|
||||
})();
|
||||
|
||||
/* 线上展厅画廊:按页翻动(每页 = 当前列数),到尾页回卷 */
|
||||
(function () {
|
||||
var stage = document.querySelector('.cg-gallery-stage');
|
||||
if (!stage) return;
|
||||
var track = stage.querySelector('.cg-gallery-track');
|
||||
var items = track ? track.children : [];
|
||||
var prevBtn = stage.querySelector('.cg-gallery-arrow.prev');
|
||||
var nextBtn = stage.querySelector('.cg-gallery-arrow.next');
|
||||
if (!track || items.length < 1) return;
|
||||
var page = 0;
|
||||
var timer = null;
|
||||
var INTERVAL = 4500;
|
||||
|
||||
function perView() {
|
||||
var w = window.innerWidth;
|
||||
if (w <= 640) return 1;
|
||||
if (w <= 992) return 2;
|
||||
return parseInt(stage.getAttribute('data-cols') || '4', 10) || 4;
|
||||
}
|
||||
function pages() {
|
||||
return Math.max(1, Math.ceil(items.length / perView()));
|
||||
}
|
||||
function update() {
|
||||
if (page >= pages()) page = 0;
|
||||
if (page < 0) page = pages() - 1;
|
||||
track.style.transform = 'translateX(-' + (page * 100) + '%)';
|
||||
}
|
||||
function start() {
|
||||
stop();
|
||||
if (pages() > 1) {
|
||||
timer = window.setInterval(function () { page++; update(); }, INTERVAL);
|
||||
}
|
||||
}
|
||||
function go(delta) { page += delta; update(); start(); }
|
||||
function stop() {
|
||||
if (timer) { window.clearInterval(timer); timer = null; }
|
||||
}
|
||||
|
||||
if (prevBtn) prevBtn.addEventListener('click', function () { go(-1); });
|
||||
if (nextBtn) nextBtn.addEventListener('click', function () { go(1); });
|
||||
stage.addEventListener('mouseenter', stop);
|
||||
stage.addEventListener('mouseleave', start);
|
||||
window.addEventListener('resize', function () { page = 0; update(); });
|
||||
|
||||
update();
|
||||
start();
|
||||
})();
|
||||
|
||||
/* 返回顶部:滚动一屏后出现,点击平滑回顶 */
|
||||
(function () {
|
||||
var btn = document.querySelector('.cg-backtop');
|
||||
if (!btn) return;
|
||||
function toggle() {
|
||||
if (window.scrollY > window.innerHeight * 0.6) {
|
||||
btn.classList.add('visible');
|
||||
} else {
|
||||
btn.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
window.addEventListener('scroll', toggle, { passive: true });
|
||||
btn.addEventListener('click', function () {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
toggle();
|
||||
})();
|
||||
})();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
|
||||
<head th:replace="~{${themeBase} + '/fragments/head' :: head}"></head>
|
||||
<body>
|
||||
<header th:replace="~{${themeBase} + '/fragments/header' :: header}"></header>
|
||||
|
||||
<main class="cg-list-main cg-container" id="main-content">
|
||||
<div class="cg-page-cols">
|
||||
<!-- 左侧栏目导航(docs/11 侧栏契约):模块有二级子栏目时展示"模块名 + 全部兄弟" -->
|
||||
<aside class="cg-side-nav" th:if="${sideNav != null}" aria-label="栏目导航">
|
||||
<div class="cg-side-nav-head" th:text="${sideNav.top.name}">模块名</div>
|
||||
<ul class="cg-side-nav-list">
|
||||
<li class="cg-side-nav-item" th:each="item : ${sideNav.items}"
|
||||
th:classappend="${item.id == sideNav.activeId} ? 'active'">
|
||||
<a th:if="${item.type == 3}" th:href="${item.linkUrl}" target="_blank" rel="noopener"
|
||||
th:text="${item.name}">子栏目</a>
|
||||
<a th:unless="${item.type == 3}" th:href="${url.category(item.id)}" th:text="${item.name}">子栏目</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<div class="cg-page-card">
|
||||
<nav class="cg-breadcrumb" aria-label="面包屑">
|
||||
<a th:href="${url.home()}">首页</a>
|
||||
<th:block th:each="node : ${categoryPath}">
|
||||
<span class="cg-crumb-sep" aria-hidden="true">/</span>
|
||||
<a class="cg-crumb-parent" th:href="${url.category(node.id)}" th:text="${node.name}">栏目</a>
|
||||
</th:block>
|
||||
<span class="cg-crumb-sep" th:if="${currentCategoryId != null}" aria-hidden="true">/</span>
|
||||
<span class="cg-current">正文</span>
|
||||
</nav>
|
||||
|
||||
<article class="cg-article">
|
||||
<h1 class="cg-article-title" th:text="${article.title}">文章标题</h1>
|
||||
<div class="cg-article-meta">
|
||||
<time th:if="${article.publishTime != null}"
|
||||
th:text="${'发布时间:' + #temporals.format(article.publishTime, 'yyyy-MM-dd HH:mm')}">2026-01-01 10:00</time>
|
||||
<span th:if="${!#strings.isEmpty(article.authorName)}"
|
||||
th:text="'作者:' + ${article.authorName}">作者</span>
|
||||
<span th:if="${!#strings.isEmpty(article.source)}"
|
||||
th:text="'来源:' + ${article.source}">来源</span>
|
||||
<span th:text="'浏览量:' + ${article.viewCount}">浏览量</span>
|
||||
</div>
|
||||
<!-- 正文:入库前已经 jsoup 白名单清洗(方案 §8.5),此处允许 utext;
|
||||
其余字段一律 th:text 转义输出 -->
|
||||
<div class="cg-article-content" th:utext="${article.content}">正文</div>
|
||||
</article>
|
||||
|
||||
<nav class="cg-article-sibling" aria-label="上下篇导航">
|
||||
<a th:if="${prevArticle != null}" class="prev"
|
||||
th:href="${url.article(prevArticle.id)}">
|
||||
<span class="cg-sib-label">上一篇</span>
|
||||
<span class="cg-sib-title" th:text="${prevArticle.title}">上一篇标题</span>
|
||||
</a>
|
||||
<span class="cg-sib-none prev" th:if="${prevArticle == null}">
|
||||
<span class="cg-sib-label">上一篇</span>
|
||||
<span class="cg-sib-title">已经是最早一篇</span>
|
||||
</span>
|
||||
<a th:if="${nextArticle != null}" class="next"
|
||||
th:href="${url.article(nextArticle.id)}">
|
||||
<span class="cg-sib-label">下一篇</span>
|
||||
<span class="cg-sib-title" th:text="${nextArticle.title}">下一篇标题</span>
|
||||
</a>
|
||||
<span class="cg-sib-none next" th:if="${nextArticle == null}">
|
||||
<span class="cg-sib-label">下一篇</span>
|
||||
<span class="cg-sib-title">已是最新一篇</span>
|
||||
</span>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
<script th:src="${url.asset('js/main.js')}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<!-- 公共页脚(docs/12 个性化页脚):品牌区(logo+站名+社媒图标阵列) + 多校区地址 + 二维码(hover 放大)
|
||||
+ 联系/版权/备案 + 友链分组 + 底条校名 + 返回顶部。
|
||||
基础数据契约与 default 一致(linkGroups/contact/copyright/beian/logo/schName);
|
||||
新增消费 config 键:footer_addresses(经 site.footerAddressLines 派生)/qr_wechat/qr_weibo/
|
||||
social_wechat/social_weibo/social_douyin/social_bilibili(均有值才渲染,空站点优雅降级)。 -->
|
||||
<footer class="cg-footer" th:fragment="footer">
|
||||
<div class="cg-container cg-footer-main">
|
||||
<!-- 品牌区 -->
|
||||
<div class="cg-footer-brand">
|
||||
<div class="cg-footer-logo-row">
|
||||
<img th:if="${site.config['logo'] != null}" class="cg-footer-logo"
|
||||
th:src="${url.upload(site.config['logo'])}" th:alt="${site.name}">
|
||||
<span class="cg-footer-site" th:text="${site.name}">站点名称</span>
|
||||
</div>
|
||||
<div class="cg-social" th:if="${site.config['social_wechat'] != null
|
||||
or site.config['social_weibo'] != null
|
||||
or site.config['social_douyin'] != null
|
||||
or site.config['social_bilibili'] != null}">
|
||||
<a th:if="${site.config['social_wechat'] != null}" class="cg-social-icon"
|
||||
th:href="${site.config['social_wechat']}" target="_blank" rel="noopener" aria-label="微信公众号" th:title="微信公众号">
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" focusable="false" aria-hidden="true">
|
||||
<path fill="currentColor" d="M9.3 4C5.8 4 3 6.4 3 9.4c0 1.7.9 3.2 2.4 4.2l-.6 2 2.2-1.1c.6.2 1.3.3 2 .3h.4a5 5 0 0 1-.2-1.4c0-2.9 2.7-5.2 6-5.2h.3C14.9 5.9 12.3 4 9.3 4zM7 7.5a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8zm4.7 0a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8zM15 9.6c-3 0-5.4 2-5.4 4.5S12 18.5 15 18.5c.6 0 1.2-.1 1.7-.3l1.9 1-.5-1.8c1.3-.9 2.2-2.2 2.2-3.7 0-2.5-2.4-4.1-5.3-4.1zm-1.9 2.5a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6zm3.8 0a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a th:if="${site.config['social_weibo'] != null}" class="cg-social-icon"
|
||||
th:href="${site.config['social_weibo']}" target="_blank" rel="noopener" aria-label="微博" th:title="微博">
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" focusable="false" aria-hidden="true">
|
||||
<path fill="currentColor" d="M10.1 19.6c-3.5.3-6.6-1.3-6.9-3.6-.3-2.2 2.3-4.4 5.8-4.8 3.5-.3 6.6 1.3 6.9 3.6.3 2.3-2.3 4.5-5.8 4.8zm3.2-4.9c-.4-1.3-2-2-3.8-1.8-1.9.3-3.2 1.6-2.9 3 .3 1.3 2 2.1 3.9 1.8 1.8-.3 3.1-1.7 2.8-3zm-3.1 1.9a.7.7 0 0 1-.9.3.6.6 0 0 1-.2-.9c.2-.3.6-.4.9-.2.3.1.4.5.2.8zm.8-1a.3.3 0 0 1-.4.1c-.2 0-.3-.2-.2-.4.1-.1.3-.2.4-.1.2 0 .3.2.2.4zm3.6-7.3a3.9 3.9 0 0 0-3.6-1.1.5.5 0 0 0-.4.6c.1.3.4.5.6.4a2.9 2.9 0 0 1 3.3 3.7c-.1.3.1.5.4.6.2 0 .5-.1.6-.4.4-1.4.1-2.8-.9-3.8zm2.6-1a6.8 6.8 0 0 0-6.3-2 .5.5 0 0 0-.4.6c0 .3.3.5.6.4a5.8 5.8 0 0 1 6.7 7.4c-.1.3.1.5.3.6.3.1.6 0 .7-.3.8-2.3.3-4.9-1.6-6.7z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a th:if="${site.config['social_douyin'] != null}" class="cg-social-icon"
|
||||
th:href="${site.config['social_douyin']}" target="_blank" rel="noopener" aria-label="抖音" th:title="抖音">
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" focusable="false" aria-hidden="true">
|
||||
<path fill="currentColor" d="M16.6 3c.4 1.8 1.6 3.2 3.4 3.5v2.8c-1.3 0-2.5-.4-3.5-1.1v5.6a5.6 5.6 0 1 1-5.6-5.6c.3 0 .6 0 .9.1v2.9a2.7 2.7 0 1 0 1.9 2.6V3h2.9z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a th:if="${site.config['social_bilibili'] != null}" class="cg-social-icon"
|
||||
th:href="${site.config['social_bilibili']}" target="_blank" rel="noopener" aria-label="哔哩哔哩" th:title="哔哩哔哩">
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" focusable="false" aria-hidden="true">
|
||||
<path fill="currentColor" d="M7.2 4.3 9 6.4h6L16.8 4.3c.3-.4.9-.4 1.2-.1.3.3.4.8.1 1.2l-1 1h1.4A3.5 3.5 0 0 1 22 9.9v6.6a3.5 3.5 0 0 1-3.5 3.5h-13A3.5 3.5 0 0 1 2 16.5V9.9a3.5 3.5 0 0 1 3.5-3.5h1.4l-1-1A.9.9 0 0 1 6 4.2c.3-.3.9-.3 1.2.1zM5.5 8.2A1.7 1.7 0 0 0 3.8 9.9v6.6c0 .9.8 1.7 1.7 1.7h13c.9 0 1.7-.8 1.7-1.7V9.9c0-.9-.8-1.7-1.7-1.7h-13zm3 3.2c.5 0 .9.4.9.9v.9a.9.9 0 1 1-1.8 0v-.9c0-.5.4-.9.9-.9zm7 0c.5 0 .9.4.9.9v.9a.9.9 0 1 1-1.8 0v-.9c0-.5.4-.9.9-.9z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 地址/联系区 -->
|
||||
<div class="cg-footer-info">
|
||||
<ul class="cg-footer-addr" th:if="${!#lists.isEmpty(site.footerAddressLines)}">
|
||||
<li th:each="line : ${site.footerAddressLines}" th:text="${line}">校区地址</li>
|
||||
</ul>
|
||||
<p class="cg-footer-line" th:if="${site.config['contact'] != null}">
|
||||
<span th:text="${site.config['contact']}">联系方式</span>
|
||||
</p>
|
||||
<p class="cg-footer-line" th:if="${site.config['copyright'] != null}">
|
||||
<span th:text="${site.config['copyright']}">版权信息</span>
|
||||
</p>
|
||||
<p class="cg-footer-line" th:if="${site.config['beian'] != null}">
|
||||
<span th:text="${site.config['beian']}">备案号</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 二维码区(hover 放大预览) -->
|
||||
<div class="cg-footer-qr" th:if="${site.config['qr_wechat'] != null or site.config['qr_weibo'] != null}">
|
||||
<figure class="cg-qr" th:if="${site.config['qr_wechat'] != null}">
|
||||
<img th:src="${url.upload(site.config['qr_wechat'])}" alt="微信二维码">
|
||||
<figcaption>微信扫码</figcaption>
|
||||
</figure>
|
||||
<figure class="cg-qr" th:if="${site.config['qr_weibo'] != null}">
|
||||
<img th:src="${url.upload(site.config['qr_weibo'])}" alt="微博二维码">
|
||||
<figcaption>微博扫码</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 友链分组(无分组整栏不渲染) -->
|
||||
<div class="cg-container cg-footer-links" th:if="${!#lists.isEmpty(site.linkGroups)}">
|
||||
<div class="cg-links-group" th:each="group : ${site.linkGroups}"
|
||||
th:if="${!#lists.isEmpty(group.links)}">
|
||||
<span class="cg-group-name" th:text="${group.groupName}">分组</span>
|
||||
<p class="cg-links-row">
|
||||
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener">
|
||||
<img th:if="${link.logoUrl != null and !#strings.isEmpty(link.logoUrl)}" class="cg-link-logo"
|
||||
th:src="${url.upload(link.logoUrl)}" th:alt="${link.name}" loading="lazy">
|
||||
<span th:text="${link.name}">链接</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cg-footer-bottom">
|
||||
<p th:if="${site.schName != null and !#strings.isEmpty(site.schName)}"
|
||||
th:text="${site.schName}">学校名称</p>
|
||||
</div>
|
||||
|
||||
<button class="cg-backtop" type="button" aria-label="返回顶部" th:title="返回顶部">↑</button>
|
||||
</footer>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
|
||||
<!-- 公共 head 片段:契约与 default 一致(SEO + 主题资产 + 变量/自定义样式注入),仅换皮 -->
|
||||
<head th:fragment="head">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title th:text="${pageTitle}">页面标题</title>
|
||||
<meta name="description" th:if="${seoDescription != null and !#strings.isEmpty(seoDescription)}"
|
||||
th:content="${seoDescription}">
|
||||
<link rel="canonical" th:if="${canonical != null}" th:href="${canonical}">
|
||||
<meta property="og:title" th:if="${pageTitle != null}" th:content="${pageTitle}">
|
||||
<meta property="og:description" th:if="${seoDescription != null and !#strings.isEmpty(seoDescription)}" th:content="${seoDescription}">
|
||||
<meta property="og:image" th:if="${ogImage != null}" th:content="${ogImage}">
|
||||
<link rel="stylesheet" th:href="${url.asset('css/main.css')}">
|
||||
<style th:if="${not #strings.isEmpty(themeVarsCss)}" th:utext="${themeVarsCss}"></style>
|
||||
<style th:if="${not #strings.isEmpty(customCss)}" th:utext="${customCss}"></style>
|
||||
<link rel="icon" type="image/png"
|
||||
th:href="${site.config['favicon'] != null ? url.upload(site.config['favicon']) : url.asset('favicon.png')}">
|
||||
</head>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<!-- 公共头部:顶条(日期+专业院系) + 品牌行(校徽/站名/搜索) + 栏目导航(数据契约与 default 一致,仅换皮) -->
|
||||
<header class="cg-header" th:fragment="header">
|
||||
<a class="cg-skip-link" href="#main-content">跳到主要内容</a>
|
||||
|
||||
<div class="cg-topbar">
|
||||
<div class="cg-container cg-topbar-inner">
|
||||
<span class="cg-topbar-date" id="siteDate" th:aria-label="'今日日期'">加载日期…</span>
|
||||
<div class="cg-topbar-side" th:if="${!site.colleges.isEmpty()}"
|
||||
th:with="collegesLabel=${site.config['colleges_label'] ?: '专业院系'}">
|
||||
<div class="cg-colleges">
|
||||
<span class="cg-colleges-toggle">
|
||||
<a href="javascript:void(0)" th:text="${collegesLabel}">专业院系</a>
|
||||
<i class="cg-colleges-arrow" aria-hidden="true">▾</i>
|
||||
</span>
|
||||
<ul class="cg-colleges-menu">
|
||||
<li th:each="col : ${site.colleges}" th:classappend="${col.path == site.path} ? 'active'">
|
||||
<a th:href="${url.siteHome(col.path, col.publicBaseUrl)}"
|
||||
th:title="${col.name}" th:text="${col.name}">学院名称</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cg-container cg-brand-row">
|
||||
<div class="cg-site-brand">
|
||||
<img th:if="${site.config['logo'] != null}" class="cg-site-logo"
|
||||
th:src="${url.upload(site.config['logo'])}" th:alt="${site.name}">
|
||||
<a class="cg-site-name" th:href="${url.home()}" th:text="${site.name}">站点名称</a>
|
||||
</div>
|
||||
<form class="cg-site-search" th:action="${url.search('', null)}" method="get" role="search">
|
||||
<input type="text" name="q" placeholder="站内搜索" aria-label="站内搜索">
|
||||
<button type="submit">搜索</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<nav class="cg-nav" aria-label="栏目导航">
|
||||
<ul class="cg-container">
|
||||
<li th:classappend="${currentCategoryId == null} ? 'active'">
|
||||
<a th:href="${url.home()}">首页</a>
|
||||
</li>
|
||||
<li th:each="cat : ${navCategories}"
|
||||
th:classappend="${(currentCategoryId == cat.id ? 'active' : '') + (!cat.children.isEmpty() ? ' has-sub' : '')}">
|
||||
<a th:if="${cat.type == 3}" th:href="${cat.linkUrl}" target="_blank" rel="noopener"
|
||||
th:text="${cat.name}">栏目</a>
|
||||
<a th:unless="${cat.type == 3}" th:href="${url.category(cat.id)}"><span th:text="${cat.name}">栏目</span><i th:if="${!cat.children.isEmpty()}" class="cg-caret" aria-hidden="true">▾</i></a>
|
||||
<ul class="cg-sub-nav" th:if="${!cat.children.isEmpty()}">
|
||||
<li th:each="sub : ${cat.children}">
|
||||
<a th:if="${sub.type == 3}" th:href="${sub.linkUrl}" target="_blank" rel="noopener"
|
||||
th:text="${sub.name}">子栏目</a>
|
||||
<a th:unless="${sub.type == 3}" th:href="${url.category(sub.id)}" th:text="${sub.name}">子栏目</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</html>
|
||||
@@ -0,0 +1,176 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
|
||||
<head th:replace="~{${themeBase} + '/fragments/head' :: head}"></head>
|
||||
<body>
|
||||
<header th:replace="~{${themeBase} + '/fragments/header' :: header}"></header>
|
||||
|
||||
<main class="cg-home" id="main-content">
|
||||
<!-- 首页轮播位(cms_banner position=home;设计尺寸 1920×560,见 theme.json bannerSize) -->
|
||||
<section class="cg-banner" th:if="${!#lists.isEmpty(banners)}" aria-label="焦点图片">
|
||||
<div class="cg-banner-slides">
|
||||
<a class="cg-banner-slide" th:each="b, stat : ${banners}"
|
||||
th:classappend="${stat.first} ? 'active'"
|
||||
th:href="${b.linkUrl}" th:title="${b.title}">
|
||||
<img th:src="${b.imageUrl}" th:alt="${b.title}">
|
||||
<span class="cg-banner-cap" th:text="${b.title}">标题</span>
|
||||
</a>
|
||||
</div>
|
||||
<button class="cg-banner-arrow prev" type="button" aria-label="上一张" th:if="${#lists.size(banners) > 1}">‹</button>
|
||||
<button class="cg-banner-arrow next" type="button" aria-label="下一张" th:if="${#lists.size(banners) > 1}">›</button>
|
||||
<div class="cg-banner-dots" th:if="${#lists.size(banners) > 1}">
|
||||
<button th:each="b, stat : ${banners}" th:classappend="${stat.first} ? 'active'"
|
||||
th:aria-label="${'第' + (stat.count) + '张'}"></button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 要闻速递横条:置顶文章横向无缝滚动(hover 暂停) -->
|
||||
<section class="cg-ticker" th:if="${!#lists.isEmpty(topArticles)}" aria-label="要闻速递">
|
||||
<div class="cg-container cg-ticker-inner">
|
||||
<span class="cg-ticker-label">要闻速递</span>
|
||||
<div class="cg-ticker-viewport">
|
||||
<div class="cg-ticker-track">
|
||||
<a class="cg-ticker-item" th:each="item : ${topArticles}" th:href="${url.article(item.id)}">
|
||||
<span class="cg-ticker-dot" aria-hidden="true"></span>
|
||||
<span th:text="${item.title}">要闻标题</span>
|
||||
</a>
|
||||
<a class="cg-ticker-item" th:each="item : ${topArticles}" th:href="${url.article(item.id)}" aria-hidden="true">
|
||||
<span class="cg-ticker-dot" aria-hidden="true"></span>
|
||||
<span th:text="${item.title}">要闻标题</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 一级栏目区块(docs/12):按栏目 homeStyle 三种形态渲染,栏目顺序即首页板块顺序 -->
|
||||
<th:block th:each="block : ${categoryBlocks}">
|
||||
<!-- 形态一:双栏新闻卡(homeStyle=news_grid)——全幅背景 + 毛玻璃卡 + 双栏日期列表 -->
|
||||
<section class="cg-newshot" th:if="${block.homeStyle == 'news_grid'}"
|
||||
th:with="newsBg=${site.config['news_bg_image']}" aria-label="栏目区块">
|
||||
<div class="cg-newshot-bg" th:if="${!#strings.isEmpty(newsBg)}"
|
||||
th:style="|background-image:url(${url.upload(newsBg)})|"></div>
|
||||
<div class="cg-container">
|
||||
<div class="cg-newshot-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">校园时讯</h2>
|
||||
<a class="cg-more-circle" th:href="${block.moreUrl}" aria-label="更多">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" focusable="false" aria-hidden="true">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="cg-news-grid" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="cg-news-date" th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">09-09</span>
|
||||
<a class="cg-news-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="cg-top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态二:媒体聚焦列表(homeStyle=media_focus)——来源徽标 + 标题双栏 -->
|
||||
<section class="cg-media" th:if="${block.homeStyle == 'media_focus'}" aria-label="栏目区块">
|
||||
<div class="cg-container">
|
||||
<div class="cg-media-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">媒体聚焦</h2>
|
||||
<a class="cg-more-circle" th:href="${block.moreUrl}" aria-label="更多">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" focusable="false" aria-hidden="true">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="cg-media-grid" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="cg-source-badge" th:if="${!#strings.isEmpty(item.source)}"
|
||||
th:text="${item.source}">媒体来源</span>
|
||||
<a class="cg-media-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="cg-top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态三:默认卡(homeStyle 空)——featured 首条 + 行式列表,旧栏目保持常规呈现 -->
|
||||
<section class="cg-block" th:if="${block.homeStyle == null or block.homeStyle == ''}" aria-label="栏目区块">
|
||||
<div class="cg-container">
|
||||
<div class="cg-block-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">栏目名</h2>
|
||||
<a class="cg-more-text" th:href="${block.moreUrl}">更多 ></a>
|
||||
</div>
|
||||
<div class="cg-block-cols" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<a class="cg-block-featured" th:with="feat=${block.items[0]}" th:href="${url.article(feat.id)}">
|
||||
<span class="cg-feat-cover" th:if="${feat.coverImage != null}">
|
||||
<img th:src="${url.upload(feat.coverImage)}" th:alt="${feat.title}">
|
||||
</span>
|
||||
<span class="cg-feat-body">
|
||||
<span class="cg-feat-title" th:text="${feat.title}">头条标题</span>
|
||||
<span class="cg-feat-summary" th:if="${!#strings.isEmpty(feat.summary)}"
|
||||
th:text="${feat.summary}">摘要</span>
|
||||
<span class="cg-feat-meta">
|
||||
<time th:if="${feat.publishTime != null}"
|
||||
th:text="${#temporals.format(feat.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="cg-block-list">
|
||||
<li th:each="item, stat : ${block.items}" th:if="${stat.index > 0}">
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<time th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
|
||||
<!-- 线上展厅(cms_banner position=gallery;设计尺寸 800×520;每行列数由主题参数 galleryCols 控制) -->
|
||||
<section class="cg-gallery" th:if="${!#lists.isEmpty(galleryBanners)}" aria-label="线上展厅">
|
||||
<div class="cg-container">
|
||||
<div class="cg-gallery-head">
|
||||
<h2 th:text="${site.config['gallery_title'] ?: '线上展厅'}">线上展厅</h2>
|
||||
<p class="cg-gallery-sub" th:text="${site.config['gallery_subtitle'] ?: 'Online Exhibition Hall'}">Online Exhibition Hall</p>
|
||||
</div>
|
||||
<div class="cg-gallery-stage"
|
||||
th:classappend="${'cg-gallery-cols-' + (themeParams['galleryCols'] ?: '4')}"
|
||||
th:attr="data-cols=${themeParams['galleryCols'] ?: '4'}">
|
||||
<div class="cg-gallery-track">
|
||||
<a class="cg-gallery-item" th:each="g : ${galleryBanners}" th:href="${g.linkUrl}"
|
||||
th:title="${g.title}">
|
||||
<img th:src="${g.imageUrl}" th:alt="${g.title}">
|
||||
<span class="cg-gallery-cap" th:if="${!#strings.isEmpty(g.title)}"
|
||||
th:text="${g.title}">作品标题</span>
|
||||
</a>
|
||||
</div>
|
||||
<button class="cg-gallery-arrow prev" type="button" aria-label="上一页" th:if="${#lists.size(galleryBanners) > 1}">‹</button>
|
||||
<button class="cg-gallery-arrow next" type="button" aria-label="下一页" th:if="${#lists.size(galleryBanners) > 1}">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
<script th:src="${url.asset('js/main.js')}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
|
||||
<head th:replace="~{${themeBase} + '/fragments/head' :: head}"></head>
|
||||
<body>
|
||||
<header th:replace="~{${themeBase} + '/fragments/header' :: header}"></header>
|
||||
|
||||
<main class="cg-list-main cg-container" id="main-content">
|
||||
<div class="cg-page-cols">
|
||||
<!-- 左侧栏目导航(docs/11 侧栏契约):模块有二级子栏目时展示"模块名 + 全部兄弟" -->
|
||||
<aside class="cg-side-nav" th:if="${sideNav != null}" aria-label="栏目导航">
|
||||
<div class="cg-side-nav-head" th:text="${sideNav.top.name}">模块名</div>
|
||||
<ul class="cg-side-nav-list">
|
||||
<li class="cg-side-nav-item" th:each="item : ${sideNav.items}"
|
||||
th:classappend="${item.id == sideNav.activeId} ? 'active'">
|
||||
<a th:if="${item.type == 3}" th:href="${item.linkUrl}" target="_blank" rel="noopener"
|
||||
th:text="${item.name}">子栏目</a>
|
||||
<a th:unless="${item.type == 3}" th:href="${url.category(item.id)}" th:text="${item.name}">子栏目</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<div class="cg-page-card">
|
||||
<nav class="cg-breadcrumb" aria-label="面包屑" th:if="${searchMode != true}">
|
||||
<a th:href="${url.home()}">首页</a>
|
||||
<th:block th:each="node, st : ${categoryPath}">
|
||||
<span class="cg-crumb-sep" aria-hidden="true">/</span>
|
||||
<a th:if="${!st.last}" class="cg-crumb-parent" th:href="${url.category(node.id)}"
|
||||
th:text="${node.name}">父栏目</a>
|
||||
<span class="cg-current" th:if="${st.last}" th:text="${node.name}">栏目</span>
|
||||
</th:block>
|
||||
</nav>
|
||||
<nav class="cg-breadcrumb" aria-label="面包屑" th:if="${searchMode == true}">
|
||||
<a th:href="${url.home()}">首页</a>
|
||||
<span class="cg-crumb-sep" aria-hidden="true">/</span>
|
||||
<span class="cg-current">搜索结果</span>
|
||||
</nav>
|
||||
|
||||
<h1 class="cg-page-title" th:if="${searchMode != true}" th:text="${category.name}">栏目名</h1>
|
||||
<h1 class="cg-page-title" th:if="${searchMode == true}" th:text="'搜索:' + ${q}">搜索词</h1>
|
||||
|
||||
<ul class="cg-article-list">
|
||||
<!-- 有封面:图文行;无封面:文字行(与图文行同等用心,不造占位图) -->
|
||||
<li th:each="item : ${listData.items}">
|
||||
<div class="cg-article-item" th:if="${item.coverImage != null}">
|
||||
<a class="cg-item-cover" th:href="${url.article(item.id)}">
|
||||
<img th:src="${url.upload(item.coverImage)}" th:alt="${item.title}">
|
||||
</a>
|
||||
<div class="cg-item-body">
|
||||
<div class="cg-item-title">
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
</div>
|
||||
<p class="cg-item-summary" th:if="${!#strings.isEmpty(item.summary)}"
|
||||
th:text="${item.summary}">摘要</p>
|
||||
<div class="cg-item-meta">
|
||||
<time th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
<span th:if="${!#strings.isEmpty(item.source)}"
|
||||
th:text="'来源:' + ${item.source}">来源</span>
|
||||
<span class="cg-top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cg-article-item cg-plain" th:if="${item.coverImage == null}">
|
||||
<div class="cg-item-title">
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
</div>
|
||||
<p class="cg-item-summary" th:if="${!#strings.isEmpty(item.summary)}"
|
||||
th:text="${item.summary}">摘要</p>
|
||||
<div class="cg-item-meta">
|
||||
<time th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
<span th:if="${!#strings.isEmpty(item.source)}"
|
||||
th:text="'来源:' + ${item.source}">来源</span>
|
||||
<span class="cg-top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(listData.items)}">
|
||||
<p th:if="${searchMode == true}">未找到相关内容</p>
|
||||
<p th:if="${searchMode != true}">栏目暂无内容</p>
|
||||
<p class="cg-empty-hint" th:if="${searchMode == true}">换个关键词试试</p>
|
||||
<p class="cg-empty-hint" th:if="${searchMode != true}">内容发布后将在此展示</p>
|
||||
</div>
|
||||
|
||||
<nav class="cg-pagination" th:if="${listData.totalPages > 1}"
|
||||
th:with="cur=${listData.page}, total=${listData.totalPages}" aria-label="分页">
|
||||
<a th:classappend="${cur <= 1} ? 'disabled'"
|
||||
th:href="${searchMode == true} ? ${url.search(q, cur - 1)} : ${url.category(listData.categoryId) + '?page=' + (cur - 1)}"
|
||||
th:aria-disabled="${cur <= 1}">上一页</a>
|
||||
<a th:each="p : ${#numbers.sequence(1, total)}"
|
||||
th:classappend="${p == cur} ? 'active'"
|
||||
th:href="${searchMode == true} ? ${url.search(q, p)} : ${url.category(listData.categoryId) + '?page=' + p}"
|
||||
th:aria-label="${'第' + p + '页'}"
|
||||
th:text="${p}">1</a>
|
||||
<a th:classappend="${cur >= total} ? 'disabled'"
|
||||
th:href="${searchMode == true} ? ${url.search(q, cur + 1)} : ${url.category(listData.categoryId) + '?page=' + (cur + 1)}"
|
||||
th:aria-disabled="${cur >= total}">下一页</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
<script th:src="${url.asset('js/main.js')}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
|
||||
<head th:replace="~{${themeBase} + '/fragments/head' :: head}"></head>
|
||||
<body>
|
||||
<header th:replace="~{${themeBase} + '/fragments/header' :: header}"></header>
|
||||
|
||||
<main class="cg-list-main cg-container" id="main-content">
|
||||
<div class="cg-page-cols">
|
||||
<!-- 左侧栏目导航(docs/11 侧栏契约):模块有二级子栏目时展示"模块名 + 全部兄弟" -->
|
||||
<aside class="cg-side-nav" th:if="${sideNav != null}" aria-label="栏目导航">
|
||||
<div class="cg-side-nav-head" th:text="${sideNav.top.name}">模块名</div>
|
||||
<ul class="cg-side-nav-list">
|
||||
<li class="cg-side-nav-item" th:each="item : ${sideNav.items}"
|
||||
th:classappend="${item.id == sideNav.activeId} ? 'active'">
|
||||
<a th:if="${item.type == 3}" th:href="${item.linkUrl}" target="_blank" rel="noopener"
|
||||
th:text="${item.name}">子栏目</a>
|
||||
<a th:unless="${item.type == 3}" th:href="${url.category(item.id)}" th:text="${item.name}">子栏目</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<div class="cg-page-card">
|
||||
<nav class="cg-breadcrumb" aria-label="面包屑">
|
||||
<a th:href="${url.home()}">首页</a>
|
||||
<th:block th:each="node : ${categoryPath}">
|
||||
<span class="cg-crumb-sep" aria-hidden="true">/</span>
|
||||
<a class="cg-crumb-parent" th:href="${url.category(node.id)}" th:text="${node.name}">栏目</a>
|
||||
</th:block>
|
||||
<span class="cg-crumb-sep" th:if="${currentCategoryId != null}" aria-hidden="true">/</span>
|
||||
<span class="cg-current">正文</span>
|
||||
</nav>
|
||||
|
||||
<article class="cg-article">
|
||||
<h1 class="cg-article-title" th:text="${category.name}">栏目名</h1>
|
||||
<!-- 单页栏目内容(type=2,入库前已 jsoup 清洗) -->
|
||||
<div class="cg-article-content" th:if="${!#strings.isEmpty(category.content)}"
|
||||
th:utext="${category.content}">单页内容</div>
|
||||
<div class="cg-empty" th:if="${#strings.isEmpty(category.content)}">
|
||||
<p>页面暂无内容</p>
|
||||
<p class="cg-empty-hint">内容编辑后将在此展示</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
<script th:src="${url.asset('js/main.js')}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"specVersion": 1,
|
||||
"name": "campus-glow",
|
||||
"title": "炫彩校园",
|
||||
"version": "1.0.0",
|
||||
"author": "yunhai",
|
||||
"description": "展示型皮肤:双栏新闻卡、媒体聚焦来源标签、线上展厅画廊、个性化页脚与返回顶部,首页更炫(docs/12)。",
|
||||
"screenshot": "assets/screenshot.png",
|
||||
"bannerSize": "1920x560",
|
||||
"params": [
|
||||
{ "key": "primary", "cssVar": "--cg-primary", "label": "主色", "type": "color", "default": "#0e8a5f" },
|
||||
{ "key": "primaryDark", "cssVar": "--cg-primary-dark", "label": "主色加深", "type": "color", "default": "#0a6244" },
|
||||
{ "key": "accent", "cssVar": "--cg-accent", "label": "强调色", "type": "color", "default": "#ff7a1a" },
|
||||
{ "key": "text", "cssVar": "--cg-text", "label": "正文文字色", "type": "color", "default": "#2b2f33" },
|
||||
{ "key": "textLight", "cssVar": "--cg-text-light", "label": "次要文字色", "type": "color", "default": "#7a8288" },
|
||||
{ "key": "bg", "cssVar": "--cg-bg", "label": "页面底色", "type": "color", "default": "#f4f7f5" },
|
||||
{ "key": "cardBg", "cssVar": "--cg-card-bg", "label": "卡片底色", "type": "color", "default": "#ffffff" },
|
||||
{ "key": "border", "cssVar": "--cg-border", "label": "边框色", "type": "color", "default": "#e3e8e4" },
|
||||
{ "key": "galleryCols", "label": "展厅每行列数", "type": "select", "default": "4",
|
||||
"options": [
|
||||
{ "value": "3", "label": "3 列" },
|
||||
{ "value": "4", "label": "4 列" },
|
||||
{ "value": "5", "label": "5 列" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user