feat(theme): S6 薄荷沁风主题改造——自研 home-blocks 10 形态+2 新区块、svc/guide 页、三列网格区块显式跨行
build / build-push (push) Successful in 55s
build / build-push (push) Successful in 55s
- fragments/home-blocks.html 新建 238 行 11 fragment(8 形态 + quick_links/notice_bar + empty), 坑①空态守卫写在外层 th:block、坑②新增可选键一律 block['key'] 括号访问 - home.html 双层 th:switch(case 在父、replace 在子,坑④),未知类型仍回落 common :: render - assets/css/main.css 617→1123 行:.home-main 三列网格下每个区块根类显式 grid-column:1/-1(坑③最高风险点) - service.html/guide.html 新建(poly-blue 平移换皮,绑定面零差异);theme.json params 9→10 新增 headerStyle - fragments/header.html 1 行接 themeParams['headerStyle'] - 其余 10 套包零改动;后端全量 412/412 绿(skip.tests=false,3307 侧车库)
This commit is contained in:
@@ -615,3 +615,509 @@ time, .item-meta { color: var(--text-light); font-size: 13px; }
|
||||
.side-nav { display: none; }
|
||||
.list-main.has-sidenav, .detail-main.has-sidenav { display: block; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* 首页布局模式区块(home_layout 落库后由 fragments/home-blocks.html 自持渲染)
|
||||
* 手册 §A.2 · P-A2。三条硬约束:
|
||||
* ① 本包 .home-main 是三列网格(第「首页区块」节),自研区块不再由 common 兜住
|
||||
* grid-column → **每个区块根类必须显式跨满整行**,否则塌成三分之一列(§2.5 坑③)。
|
||||
* ② 网格轨道一律 minmax(0,1fr),中文长标题才不会被 min-content 撑破窄视口。
|
||||
* ③ 颜色只用本包已有令牌;唯一的自持色是公告条 warn 三件套(皮肤契约没有告警色变量,
|
||||
* 与 themes/common 骨架的 --plb-warn-* 同一处置),且只写在公告条规则内、不进 :root。
|
||||
* ============================================================ */
|
||||
|
||||
/* 区块根:卡片本体(news_grid / card / compact / media_focus / image_wall / gallery / links / quick_links) */
|
||||
.mb-block {
|
||||
grid-column: 1 / -1;
|
||||
min-width: 0;
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-soft);
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
}
|
||||
.mb-block-inner { position: relative; }
|
||||
|
||||
/* 站点 news_bg_image:本包卡片不透明,整幅铺图会看不见,故按"薄荷水印"处理成低透明衬底,正文浮于其上 */
|
||||
.mb-sec-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: var(--radius);
|
||||
opacity: .12;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 双栏要闻:左 3 份头条、右 2 份紧凑行 */
|
||||
.mb-split {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
|
||||
gap: 24px;
|
||||
align-items: start;
|
||||
}
|
||||
.mb-featured > a { display: block; color: inherit; text-decoration: none; }
|
||||
.mb-featured-body { display: block; min-width: 0; }
|
||||
.mb-cover {
|
||||
display: block;
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.mb-cover img { width: 100%; height: 240px; object-fit: cover; display: block; transition: transform .4s ease; }
|
||||
.mb-featured:hover .mb-cover img { transform: scale(1.03); }
|
||||
/* 默认卡形态的头条:左图右文(整宽) */
|
||||
.mb-featured--row > a { display: flex; align-items: flex-start; gap: 18px; }
|
||||
.mb-cover--row { flex: none; width: 34%; }
|
||||
.mb-cover--row img { height: 178px; margin: 0; }
|
||||
|
||||
/* 元信息行:置顶徽标 / 来源 / 日期(复用 .top-badge 语汇)
|
||||
必须限定在 .mb-block 内:.item-meta 是本页族 list.html 在用的既有类,
|
||||
不限定就会顺手改掉列表页(列表页不在 P-A2 范围内)。 */
|
||||
.mb-block .item-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
.mb-src {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
color: var(--primary-dark);
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 1px 9px;
|
||||
}
|
||||
.mb-date { flex: none; font-size: 13px; color: var(--text-light); }
|
||||
|
||||
/* 日期药丸(compact 形态):本包圆角语言下的"日期方块" */
|
||||
.mb-date-chip {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 52px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--primary-light);
|
||||
color: var(--primary-dark);
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mb-date-chip b { font-size: 18px; font-weight: 600; }
|
||||
.mb-date-chip i { font-style: normal; font-size: 11px; opacity: .8; }
|
||||
|
||||
/* 媒体聚焦双列:columns 断点处降单列(本包无 hover 依赖,纯排布) */
|
||||
.mb-list--two { columns: 2; column-gap: 28px; }
|
||||
.mb-list--two > li { break-inside: avoid; }
|
||||
|
||||
/* 图片墙 / 线上展厅:四列格 + 底部渐变题注(渐变端点用主色深变量,不写死色值) */
|
||||
.mb-wall {
|
||||
list-style: none;
|
||||
margin: 16px 0 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
.mb-wall-item {
|
||||
position: relative;
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
.mb-wall-item a { display: block; }
|
||||
.mb-wall-item img { width: 100%; height: 150px; object-fit: cover; display: block; transition: transform .5s ease; }
|
||||
.mb-wall-item:hover img { transform: scale(1.05); }
|
||||
.mb-wall-cap {
|
||||
position: absolute;
|
||||
left: 0; right: 0; bottom: 0;
|
||||
padding: 26px 10px 9px;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
background: linear-gradient(transparent, var(--primary-dark));
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.mb-gallery-sub {
|
||||
margin: -6px 0 4px;
|
||||
font-size: 12px;
|
||||
letter-spacing: .18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
/* 要闻速递横条:flex 项自动最小尺寸 = min-content,中文可按字断行 →
|
||||
不锁 nowrap / 不让出收缩权就塌成一字一列(与 HomeBlocksCssContractTest 钉骨架的三条同形) */
|
||||
.mb-ticker {
|
||||
grid-column: 1 / -1;
|
||||
min-width: 0;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
border-radius: var(--radius);
|
||||
padding: 11px 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.mb-ticker-inner { display: flex; align-items: center; gap: 14px; min-width: 0; }
|
||||
.mb-ticker-label {
|
||||
flex: none;
|
||||
font-weight: 600;
|
||||
padding: 2px 12px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(255, 255, 255, .18);
|
||||
}
|
||||
.mb-ticker-list {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 22px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mb-ticker-item {
|
||||
display: block;
|
||||
flex: none;
|
||||
max-width: 320px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
.mb-ticker-item:hover { text-decoration: underline; text-underline-offset: 3px; }
|
||||
|
||||
/* 快捷通道宫格:六列 → 三列 → 两列 */
|
||||
.mb-quick-grid {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.mb-quick-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 52px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
transition: color .15s ease, border-color .15s ease, background .15s ease, transform .15s ease;
|
||||
}
|
||||
.mb-quick-link:hover {
|
||||
color: var(--primary-dark);
|
||||
border-color: var(--primary);
|
||||
background: var(--primary-light);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 友情链接分组 */
|
||||
.mb-links {
|
||||
list-style: none;
|
||||
margin: 0 0 12px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
.mb-links li { flex: none; }
|
||||
.mb-links a {
|
||||
display: inline-block;
|
||||
padding: 4px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
transition: color .15s ease, border-color .15s ease, background .15s ease;
|
||||
}
|
||||
.mb-links a:hover { color: var(--primary-dark); border-color: var(--primary); background: var(--primary-light); }
|
||||
|
||||
/* 公告条:整幅横条(非卡片),info 走主色、warn 走自持暖色三件套 */
|
||||
.mb-notice-bar {
|
||||
--mb-warn-fg: #B45309;
|
||||
--mb-warn-bg: #FFF7E6;
|
||||
--mb-warn-border: #FFD591;
|
||||
grid-column: 1 / -1;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: var(--radius);
|
||||
background: var(--primary-light);
|
||||
padding: 12px 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.mb-notice-inner { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; min-width: 0; }
|
||||
.mb-notice-tag {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: var(--primary);
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 2px 12px;
|
||||
}
|
||||
.mb-notice-link {
|
||||
color: var(--primary-dark);
|
||||
text-decoration: none;
|
||||
overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.mb-notice-link:hover { text-decoration: underline; text-underline-offset: 3px; }
|
||||
.mb-notice-text { color: var(--text); min-width: 0; }
|
||||
.mb-notice--info { border-color: var(--primary); background: var(--primary-light); }
|
||||
.mb-notice--warn { border-color: var(--mb-warn-border); background: var(--mb-warn-bg); }
|
||||
.mb-notice--warn .mb-notice-tag { color: var(--mb-warn-fg); background: var(--mb-warn-bg); border: 1px solid var(--mb-warn-border); }
|
||||
.mb-notice--warn .mb-notice-link { color: var(--mb-warn-fg); }
|
||||
|
||||
/* 空态(.empty 已给居中淡字与留白,这里只补两行的间距与提示字级) */
|
||||
.mb-empty p { margin: 0; }
|
||||
.mb-empty .mb-empty-hint { margin-top: 6px; font-size: 12px; }
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.mb-split { grid-template-columns: minmax(0, 1fr); }
|
||||
.mb-wall { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.mb-list--two { columns: 1; }
|
||||
.mb-ticker-item { max-width: 180px; }
|
||||
.mb-quick-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.mb-block { padding: 18px; }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.mb-quick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.mb-featured--row > a { flex-direction: column; }
|
||||
.mb-cover--row { width: 100%; }
|
||||
.mb-notice-inner { flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 服务中心 / 事项指南(service.html · guide.html,svc 页族)
|
||||
* 页壳/面包屑/页题/分页沿用既有 .list-main / .breadcrumb / .page-title / .pagination,
|
||||
* 这里只补事项特有的件。
|
||||
* ============================================================ */
|
||||
.mb-page-card {
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-soft);
|
||||
padding: 24px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mb-svc-search { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 18px; }
|
||||
.mb-svc-search input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 9px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
}
|
||||
.mb-svc-search button {
|
||||
flex: none;
|
||||
padding: 9px 22px;
|
||||
border: none;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background .15s ease;
|
||||
}
|
||||
.mb-svc-search button:hover { background: var(--primary-dark); }
|
||||
|
||||
.mb-svc-filters {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 10px 14px;
|
||||
margin-bottom: 18px;
|
||||
background: var(--bg);
|
||||
}
|
||||
.mb-svc-filter-group { display: flex; align-items: flex-start; gap: 12px; padding: 6px 0; }
|
||||
.mb-svc-filter-group + .mb-svc-filter-group { border-top: 1px dashed var(--border); }
|
||||
.mb-svc-filter-label { flex: none; font-size: 13px; font-weight: 600; color: var(--text); padding-top: 3px; }
|
||||
.mb-svc-filter-options { display: flex; flex-wrap: wrap; gap: 8px; min-width: 0; }
|
||||
.mb-chip {
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
text-decoration: none;
|
||||
padding: 2px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--card-bg);
|
||||
transition: color .15s ease, border-color .15s ease, background .15s ease;
|
||||
}
|
||||
.mb-chip:hover { color: var(--primary-dark); border-color: var(--primary); }
|
||||
.mb-chip.active { color: #fff; background: var(--primary); border-color: var(--primary); }
|
||||
.mb-svc-clear { font-size: 13px; color: var(--primary-dark); text-decoration: none; align-self: center; }
|
||||
.mb-svc-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; font-size: 13px; color: var(--text-light); }
|
||||
|
||||
/* 事项卡网格:三列 → 两列 → 单列(轨道 minmax(0,1fr),中文不撑破) */
|
||||
.mb-svc-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
.mb-svc-item {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-soft);
|
||||
padding: 16px;
|
||||
min-width: 0;
|
||||
transition: transform .18s ease, box-shadow .18s ease;
|
||||
}
|
||||
.mb-svc-item:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); }
|
||||
.mb-svc-item a { display: flex; gap: 14px; align-items: flex-start; color: inherit; text-decoration: none; }
|
||||
.mb-svc-item-cover { flex: none; width: 96px; height: 68px; border-radius: var(--radius-sm); overflow: hidden; }
|
||||
.mb-svc-item-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.mb-svc-item-body { flex: 1; min-width: 0; }
|
||||
.mb-svc-item-title { display: block; }
|
||||
.mb-svc-item-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.mb-svc-item:hover .mb-svc-item-name { color: var(--primary); }
|
||||
.mb-svc-item-summary {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.mb-svc-item-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; font-size: 12px; color: var(--text-light); }
|
||||
.mb-tag {
|
||||
font-size: 12px;
|
||||
color: var(--primary-dark);
|
||||
background: var(--primary-light);
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 1px 9px;
|
||||
}
|
||||
/* 可在线办理:本包没有第二品牌色,用描边区分而不是引入新色相 */
|
||||
.mb-tag.online { color: var(--primary-dark); background: transparent; border: 1px solid var(--primary); }
|
||||
|
||||
.mb-visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
margin: -1px; padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.mb-svc-guide, .mb-svc-guide-body, .mb-svc-unavailable { min-width: 0; }
|
||||
.mb-svc-guide .article-meta { justify-content: flex-start; }
|
||||
.mb-svc-unavailable { padding: 40px 0; }
|
||||
.mb-svc-apply { display: flex; align-items: center; gap: 14px; margin: 18px 0; }
|
||||
.mb-svc-apply-btn {
|
||||
display: inline-block;
|
||||
padding: 10px 30px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
text-decoration: none;
|
||||
transition: background .15s ease, transform .15s ease;
|
||||
}
|
||||
.mb-svc-apply-btn:hover { background: var(--primary-dark); transform: translateY(-2px); }
|
||||
.mb-svc-apply-hint { font-size: 13px; color: var(--text-light); }
|
||||
.mb-svc-summary { font-size: 15px; color: var(--text-light); line-height: 1.8; margin: 0 0 18px; }
|
||||
|
||||
.mb-svc-info { width: 100%; border-collapse: collapse; margin: 6px 0 18px; font-size: 14px; }
|
||||
.mb-svc-info th, .mb-svc-info td { border: 1px solid var(--border); padding: 10px 14px; text-align: left; vertical-align: top; }
|
||||
.mb-svc-info th { width: 132px; background: var(--bg); color: var(--text-light); font-weight: 500; }
|
||||
|
||||
.mb-svc-section { margin-top: 22px; }
|
||||
.mb-svc-section h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.mb-svc-section h2::before {
|
||||
content: "";
|
||||
flex: none;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
box-shadow: 0 0 0 3.5px var(--primary-light);
|
||||
}
|
||||
.mb-svc-attachments ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.mb-svc-attachments a { color: var(--primary-dark); font-size: 14px; text-decoration: none; }
|
||||
.mb-svc-attachments a:hover { text-decoration: underline; text-underline-offset: 3px; }
|
||||
.mb-svc-att-size { font-size: 12px; color: var(--text-light); margin-left: 8px; }
|
||||
|
||||
.mb-svc-links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; padding-top: 16px; border-top: 1px dashed var(--border); }
|
||||
.mb-svc-links a {
|
||||
font-size: 14px;
|
||||
color: var(--primary-dark);
|
||||
padding: 4px 16px;
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: var(--radius-pill);
|
||||
text-decoration: none;
|
||||
transition: background .15s ease, color .15s ease;
|
||||
}
|
||||
.mb-svc-links a:hover { background: var(--primary); color: #fff; }
|
||||
|
||||
.mb-svc-related { margin-top: 28px; }
|
||||
.mb-svc-related-title { font-size: 18px; font-weight: 600; margin-bottom: 12px; }
|
||||
.mb-svc-related-list { list-style: none; margin: 0; padding: 0; }
|
||||
.mb-svc-related-list > li + li { border-top: 1px dashed var(--border); }
|
||||
.mb-svc-related-list a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 0;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.mb-svc-related-name { flex: 1; min-width: 0; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.mb-svc-related-list a:hover .mb-svc-related-name { color: var(--primary); }
|
||||
.mb-svc-related-meta { flex: none; display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-light); }
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.mb-svc-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.mb-svc-list { grid-template-columns: minmax(0, 1fr); }
|
||||
.mb-page-card { padding: 18px 16px; }
|
||||
.mb-svc-info th { width: 92px; }
|
||||
.mb-svc-item a { flex-direction: column; }
|
||||
.mb-svc-item-cover { width: 100%; height: 150px; }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* headerStyle=plain(theme.json select 参数):收起细线顶栏、压低居中品牌行
|
||||
* ============================================================ */
|
||||
.header-plain .top-bar { display: none; }
|
||||
.header-plain .brand-row { padding-top: 12px; padding-bottom: 10px; }
|
||||
.header-plain .site-logo { height: 40px; }
|
||||
.header-plain .site-name { font-size: 24px; }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<!-- 公共头部(mint-breeze):细线顶栏(院系切换+搜索)→ 居中品牌行 → 白底导航;th:* 数据绑定与 blue-white 基准逐字一致 -->
|
||||
<header class="site-header" th:fragment="header">
|
||||
<!-- 公共头部(mint-breeze):细线顶栏(院系切换+搜索)→ 居中品牌行 → 白底导航;th:* 数据绑定与 blue-white 基准逐字一致
|
||||
headerStyle=plain(theme.json select 参数)时收起细线顶栏、压低品牌行——root class 切换,CSS「头部样式参数」节消费。 -->
|
||||
<header class="site-header" th:fragment="header"
|
||||
th:classappend="${themeParams['headerStyle'] == 'plain'} ? 'header-plain'">
|
||||
<div class="top-bar">
|
||||
<!-- 专业院系下拉:顶层站取其子站、院系子站取兄弟站(含自身);无子站不渲染;文案经 config_json.colleges_label 覆盖 -->
|
||||
<div class="site-colleges" th:if="${!site.colleges.isEmpty()}"
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
<!--
|
||||
mint-breeze(薄荷沁风)首页区块 fragment(v2 手册 §A.2 · P-A2,复用 S5 定型模式):
|
||||
站点配了 home_layout 时,区块正文必须由本文件渲染,不能落到 themes/common 的通用骨架——
|
||||
否则换肤只换了页头页脚,首页正文仍是骨架内置蓝灰卡(线上事故根因,见手册 §2.2)。
|
||||
命名习惯:本包(blue-white 派生)全站类名无前缀,故**直接复用既有语汇**
|
||||
(block-head / block-list / more / top-badge / empty / group-name / links-group / item-title / item-summary),
|
||||
只有"本包此前不存在"的区块排布类才加 mb- 前缀(与 common 骨架的 plb- 区分),
|
||||
避免同一视觉件在一份 CSS 里出现两套规则。
|
||||
契约:block 为渲染端装配的同构 Map(type/title/category.name/homeStyle/items/moreUrl/background/
|
||||
quickLinks/noticeText/noticeUrl/noticeLevel/subtitle/linkGroups),与回落模式 categoryBlocks 的块字段兼容;
|
||||
区块标题一律取 category.name(baseBlock 已把 title 合成进 category.name,两种模式同构)。
|
||||
三条铁律(手册 §2.5):Map 可选键一律 block['key'] 括号访问(坑②,点号访问缺键抛错);
|
||||
引用 empty 的空态守卫必须包在 th:block 外层(坑①,th:replace 优先级 100 先于 th:if 300 求值);
|
||||
本包 .home-main 是三列网格,每个区块根类必须自带 grid-column:1/-1(坑③,见 main.css 第 11 节)。
|
||||
-->
|
||||
<!-- 形态一:双栏要闻(homeStyle=news_grid)——左 3 份大图文头条、右 2 份紧凑行;区块背景可取站点 news_bg_image -->
|
||||
<section class="mb-block" th:fragment="news_grid(block)" aria-label="栏目区块"
|
||||
th:with="newsBg=${block['background'] != null ? (block['background'] ? site.config['news_bg_image'] : null) : site.config['news_bg_image']}">
|
||||
<div class="mb-sec-bg" th:if="${!#strings.isEmpty(newsBg)}"
|
||||
th:style="|background-image:url(${url.upload(newsBg)})|"></div>
|
||||
<div class="mb-block-inner"
|
||||
th:with="lead=${!#lists.isEmpty(block.items) ? block.items[0] : null},
|
||||
rest=${#lists.size(block.items) > 1 ? block.items.subList(1, block.items.size()) : null}">
|
||||
<div class="block-head">
|
||||
<h2 th:text="${block.category.name}">校园要闻</h2>
|
||||
<a class="more" th:href="${block.moreUrl}">更多 ></a>
|
||||
</div>
|
||||
<div class="mb-split" th:if="${lead != null}">
|
||||
<div class="mb-featured">
|
||||
<a th:href="${url.article(lead.id)}">
|
||||
<span class="mb-cover" th:if="${lead.coverImage != null}">
|
||||
<img th:src="${url.upload(lead.coverImage)}" th:alt="${lead.title}" loading="lazy">
|
||||
</span>
|
||||
<span class="mb-featured-body">
|
||||
<span class="item-title" th:text="${lead.title}">头条标题</span>
|
||||
<span class="item-summary" th:if="${!#strings.isEmpty(lead.summary)}"
|
||||
th:text="${lead.summary}">摘要</span>
|
||||
<span class="item-meta">
|
||||
<span class="top-badge" th:if="${lead.topmost == 1}">置顶</span>
|
||||
<span class="mb-src" th:if="${!#strings.isEmpty(lead.source)}" th:text="${lead.source}">来源</span>
|
||||
<time th:if="${lead.publishTime != null}"
|
||||
th:text="${#temporals.format(lead.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="block-list" th:if="${rest != null}">
|
||||
<li th:each="item : ${rest}">
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
<time class="mb-date" th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<th:block th:if="${#lists.isEmpty(block.items)}">
|
||||
<div th:replace="~{'themes/mint-breeze/fragments/home-blocks' :: empty(${block})}"></div>
|
||||
</th:block>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态二:默认卡(homeStyle 未设/未知)——整宽头条(左图右文)+ 行式列表 -->
|
||||
<section class="mb-block" th:fragment="card(block)" aria-label="栏目区块">
|
||||
<div class="block-head">
|
||||
<h2 th:text="${block.category.name}">栏目名</h2>
|
||||
<a class="more" th:href="${block.moreUrl}">更多 ></a>
|
||||
</div>
|
||||
<div class="mb-featured mb-featured--row" th:if="${!#lists.isEmpty(block.items)}" th:with="feat=${block.items[0]}">
|
||||
<a th:href="${url.article(feat.id)}">
|
||||
<span class="mb-cover mb-cover--row" th:if="${feat.coverImage != null}">
|
||||
<img th:src="${url.upload(feat.coverImage)}" th:alt="${feat.title}" loading="lazy">
|
||||
</span>
|
||||
<span class="mb-featured-body">
|
||||
<span class="item-title" th:text="${feat.title}">头条标题</span>
|
||||
<span class="item-summary" th:if="${!#strings.isEmpty(feat.summary)}" th:text="${feat.summary}">摘要</span>
|
||||
<span class="item-meta">
|
||||
<span class="top-badge" th:if="${feat.topmost == 1}">置顶</span>
|
||||
<span class="mb-src" th:if="${!#strings.isEmpty(feat.source)}" th:text="${feat.source}">来源</span>
|
||||
<time th:if="${feat.publishTime != null}"
|
||||
th:text="${#temporals.format(feat.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="block-list" th:if="${#lists.size(block.items) > 1}">
|
||||
<li th:each="item, stat : ${block.items}" th:if="${stat.index > 0}">
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
<time class="mb-date" th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
<th:block th:if="${#lists.isEmpty(block.items)}">
|
||||
<div th:replace="~{'themes/mint-breeze/fragments/home-blocks' :: empty(${block})}"></div>
|
||||
</th:block>
|
||||
</section>
|
||||
|
||||
<!-- 形态三:日期方块行(homeStyle=compact)——圆角药丸日期块 + 标题(本包圆角 12px 语言的紧凑行) -->
|
||||
<section class="mb-block" th:fragment="compact(block)" aria-label="栏目区块">
|
||||
<div class="block-head">
|
||||
<h2 th:text="${block.category.name}">通知公告</h2>
|
||||
<a class="more" th:href="${block.moreUrl}">更多 ></a>
|
||||
</div>
|
||||
<ul class="block-list" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="mb-date-chip" th:if="${item.publishTime != null}" th:with="d=${item.publishTime}">
|
||||
<b th:text="${#temporals.format(d, 'dd')}">01</b>
|
||||
<i th:text="${#temporals.format(d, 'yyyy-MM')}">2026-01</i>
|
||||
</span>
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</li>
|
||||
</ul>
|
||||
<th:block th:if="${#lists.isEmpty(block.items)}">
|
||||
<div th:replace="~{'themes/mint-breeze/fragments/home-blocks' :: empty(${block})}"></div>
|
||||
</th:block>
|
||||
</section>
|
||||
|
||||
<!-- 形态四:媒体聚焦(homeStyle=media_focus)——来源徽标 + 双列行式 -->
|
||||
<section class="mb-block" th:fragment="media_focus(block)" aria-label="栏目区块">
|
||||
<div class="block-head">
|
||||
<h2 th:text="${block.category.name}">媒体聚焦</h2>
|
||||
<a class="more" th:href="${block.moreUrl}">更多 ></a>
|
||||
</div>
|
||||
<ul class="block-list mb-list--two" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="mb-src" th:if="${!#strings.isEmpty(item.source)}" th:text="${item.source}">媒体来源</span>
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<time class="mb-date" th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
<th:block th:if="${#lists.isEmpty(block.items)}">
|
||||
<div th:replace="~{'themes/mint-breeze/fragments/home-blocks' :: empty(${block})}"></div>
|
||||
</th:block>
|
||||
</section>
|
||||
|
||||
<!-- 形态五:图片墙(homeStyle=image_wall)——仅取有封面的条目,四列格 + 底部渐变题注 -->
|
||||
<section class="mb-block" th:fragment="image_wall(block)" aria-label="栏目区块">
|
||||
<div class="block-head">
|
||||
<h2 th:text="${block.category.name}">图片新闻</h2>
|
||||
<a class="more" th:href="${block.moreUrl}">更多 ></a>
|
||||
</div>
|
||||
<ul class="mb-wall" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li class="mb-wall-item" th:each="item : ${block.items}" th:if="${item.coverImage != null}">
|
||||
<a th:href="${url.article(item.id)}" th:title="${item.title}">
|
||||
<img th:src="${url.upload(item.coverImage)}" th:alt="${item.title}" loading="lazy">
|
||||
<span class="mb-wall-cap" th:text="${item.title}">图片标题</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<th:block th:if="${#lists.isEmpty(block.items)}">
|
||||
<div th:replace="~{'themes/mint-breeze/fragments/home-blocks' :: empty(${block})}"></div>
|
||||
</th:block>
|
||||
</section>
|
||||
|
||||
<!-- 布局模式:要闻速递横条(block.items 由渲染端按置顶+最新装配)——单行不换行,中文不塌成一字一列 -->
|
||||
<section class="mb-ticker" th:fragment="ticker(block)" aria-label="要闻速递"
|
||||
th:if="${!#lists.isEmpty(block.items)}">
|
||||
<div class="mb-ticker-inner">
|
||||
<span class="mb-ticker-label" th:text="${block.title}">要闻速递</span>
|
||||
<ul class="mb-ticker-list">
|
||||
<li th:each="item : ${block.items}">
|
||||
<a class="mb-ticker-item" th:href="${url.article(item.id)}" th:text="${item.title}">要闻标题</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 布局模式:线上展厅(block.items = 轮播位 gallery)——四列图墙 + 英文副标题 -->
|
||||
<section class="mb-block" th:fragment="gallery(block)" aria-label="线上展厅"
|
||||
th:if="${!#lists.isEmpty(block.items)}">
|
||||
<div class="block-head">
|
||||
<h2 th:text="${block.title}">线上展厅</h2>
|
||||
</div>
|
||||
<p class="mb-gallery-sub" th:if="${!#strings.isEmpty(block['subtitle'])}" th:text="${block['subtitle']}">
|
||||
Online Exhibition Hall
|
||||
</p>
|
||||
<ul class="mb-wall">
|
||||
<li class="mb-wall-item" th:each="g : ${block.items}">
|
||||
<a th:href="${g.linkUrl}" th:title="${g.title}">
|
||||
<img th:src="${g.imageUrl}" th:alt="${g.title}" loading="lazy">
|
||||
<span class="mb-wall-cap" th:if="${!#strings.isEmpty(g.title)}" th:text="${g.title}">作品标题</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- 布局模式:友情链接分组(block.linkGroups 由渲染端按 group 过滤)——分组标题走既有 .group-name,链接胶囊复用 .links-group 语汇 -->
|
||||
<section class="mb-block" th:fragment="links(block)" aria-label="友情链接">
|
||||
<div class="block-head">
|
||||
<h2 th:text="${block.title}">友情链接</h2>
|
||||
</div>
|
||||
<th:block th:each="grp : ${block.linkGroups}">
|
||||
<p class="group-name" th:if="${!#strings.isEmpty(grp.groupName)}" th:text="${grp.groupName}">分组</p>
|
||||
<ul class="mb-links">
|
||||
<li th:each="l : ${grp.links}">
|
||||
<a th:href="${l.linkUrl}" th:text="${l.name}" target="_blank" rel="noopener">链接名</a>
|
||||
</li>
|
||||
</ul>
|
||||
</th:block>
|
||||
</section>
|
||||
|
||||
<!-- 布局模式:快捷通道(quick_links,手册 §2.8 类型 A)——区块内联入口数组,不读友情链接表。
|
||||
渲染端已把 name 空/url 非法的项丢弃,这里只消费 block['quickLinks']=[{name,url}](括号访问,坑②) -->
|
||||
<section class="mb-block" th:fragment="quick_links(block)" aria-label="快捷通道">
|
||||
<div class="block-head">
|
||||
<h2 th:text="${block.title}">快捷通道</h2>
|
||||
</div>
|
||||
<ul class="mb-quick-grid" th:if="${!#lists.isEmpty(block['quickLinks'])}">
|
||||
<li th:each="l : ${block['quickLinks']}">
|
||||
<a class="mb-quick-link" th:href="${l.url}" th:text="${l.name}">入口名</a>
|
||||
</li>
|
||||
</ul>
|
||||
<th:block th:if="${#lists.isEmpty(block['quickLinks'])}">
|
||||
<div th:replace="~{'themes/mint-breeze/fragments/home-blocks' :: empty(${block})}"></div>
|
||||
</th:block>
|
||||
</section>
|
||||
|
||||
<!-- 布局模式:公告条(notice_bar,手册 §2.8 类型 B)——单条手填通告,非滚动非文章列表。
|
||||
noticeLevel 渲染端已归一为 info|warn;noticeUrl 非法时为空串 → 退化成纯文本 -->
|
||||
<div class="mb-notice-bar" th:fragment="notice_bar(block)" role="note"
|
||||
th:if="${!#strings.isEmpty(block['noticeText'])}"
|
||||
th:classappend="${block['noticeLevel'] == 'warn' ? 'mb-notice--warn' : 'mb-notice--info'}">
|
||||
<div class="mb-notice-inner">
|
||||
<span class="mb-notice-tag" th:text="${block.title}">公告</span>
|
||||
<a class="mb-notice-link" th:if="${!#strings.isEmpty(block['noticeUrl'])}"
|
||||
th:href="${block['noticeUrl']}" th:text="${block['noticeText']}">公告正文</a>
|
||||
<span class="mb-notice-text" th:unless="${!#strings.isEmpty(block['noticeUrl'])}"
|
||||
th:text="${block['noticeText']}">公告正文</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空态(各形态引用时守卫必须包在 th:block 外层:th:replace 优先级 100 先于 th:if 300 求值) -->
|
||||
<div class="empty mb-empty" th:fragment="empty(block)">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="mb-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
@@ -0,0 +1,132 @@
|
||||
<!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>
|
||||
|
||||
<!-- 事项指南页(事项库 §6.2 契约 · P-A2 换皮):item / attachments[] / relatedItems[] /
|
||||
faqCategoryUrl·noticeCategoryUrl·ruleCategoryUrl(可空)/ applyUrl(可空,空则不渲染"我要办理")。
|
||||
三个富文本字段已在保存期过 jsoup 白名单、渲染期补 URL 基准,此处允许 utext。
|
||||
svcUnavailable=true(P1-4 停机降级):只渲染骨架+空态,不渲染事项内容与"我要办理"。
|
||||
标题/元信息/正文沿用本包既有 .article-title / .article-meta / .article-content(数据绑定与 default 同构,仅换皮)。 -->
|
||||
<main class="list-main" id="main-content">
|
||||
<div class="mb-page-card mb-svc-guide">
|
||||
<nav class="breadcrumb" aria-label="面包屑">
|
||||
<a th:href="${url.home()}">首页</a>
|
||||
<a th:href="${url.serviceList()}">服务中心</a>
|
||||
<span th:text="${svcUnavailable} ? '服务中心' : ${item.title}">事项名称</span>
|
||||
</nav>
|
||||
|
||||
<!-- P1-4 停机空态:svc-hall 不可达时降级渲染(200),不出 500 错误页 -->
|
||||
<div class="empty mb-empty mb-svc-unavailable" th:if="${svcUnavailable}">
|
||||
<p>事项服务暂时不可用,请稍后再试</p>
|
||||
<p class="mb-empty-hint">事项数据服务暂不可达,稍后刷新即可恢复</p>
|
||||
</div>
|
||||
|
||||
<article class="mb-svc-guide-body" th:if="${!svcUnavailable}">
|
||||
<h1 class="article-title" th:text="${item.title}">事项名称</h1>
|
||||
<div class="article-meta">
|
||||
<span th:if="${!#strings.isEmpty(item.deptName)}"
|
||||
th:text="${'办理部门:' + item.deptName}">办理部门</span>
|
||||
<span th:text="${item.handleMode == 1 ? '线上办理' : (item.handleMode == 2 ? '线下办理' : '线上线下均可办理')}">办理方式</span>
|
||||
<time th:if="${item.publishTime != null}"
|
||||
th:text="${'发布时间:' + #temporals.format(item.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
<span th:text="${'浏览量:' + item.viewCount}">浏览量</span>
|
||||
</div>
|
||||
|
||||
<!-- 我要办理:三态(手填外链 > svc_hall_base 生成 > 不渲染),按钮外链新窗口打开 -->
|
||||
<div class="mb-svc-apply" th:if="${applyUrl != null}">
|
||||
<a class="mb-svc-apply-btn" th:href="${applyUrl}" target="_blank" rel="noopener">我要办理</a>
|
||||
<span class="mb-svc-apply-hint">进入服务大厅在线办理</span>
|
||||
</div>
|
||||
|
||||
<p class="mb-svc-summary" th:if="${!#strings.isEmpty(item.summary)}" th:text="${item.summary}">简介</p>
|
||||
|
||||
<table class="mb-svc-info">
|
||||
<caption class="mb-visually-hidden">事项基本信息</caption>
|
||||
<tbody>
|
||||
<tr th:if="${!#strings.isEmpty(item.objectName)}">
|
||||
<th scope="row">服务对象</th>
|
||||
<td th:text="${item.objectName}">服务对象</td>
|
||||
</tr>
|
||||
<tr th:if="${!#strings.isEmpty(item.categoryName)}">
|
||||
<th scope="row">事项类别</th>
|
||||
<td th:text="${item.categoryName}">事项类别</td>
|
||||
</tr>
|
||||
<tr th:if="${!#strings.isEmpty(item.handlePlace)}">
|
||||
<th scope="row">办理地点</th>
|
||||
<td th:text="${item.handlePlace}">办理地点</td>
|
||||
</tr>
|
||||
<tr th:if="${!#strings.isEmpty(item.handleTimeLimit)}">
|
||||
<th scope="row">办理时限</th>
|
||||
<td th:text="${item.handleTimeLimit}">办理时限</td>
|
||||
</tr>
|
||||
<tr th:if="${!#strings.isEmpty(item.promiseTimeLimit)}">
|
||||
<th scope="row">承诺时限</th>
|
||||
<td th:text="${item.promiseTimeLimit}">承诺时限</td>
|
||||
</tr>
|
||||
<tr th:if="${!#strings.isEmpty(item.contactPhone)}">
|
||||
<th scope="row">咨询电话</th>
|
||||
<td th:text="${item.contactPhone}">咨询电话</td>
|
||||
</tr>
|
||||
<tr th:if="${!#strings.isEmpty(item.supervisePhone)}">
|
||||
<th scope="row">监督投诉</th>
|
||||
<td th:text="${item.supervisePhone}">监督投诉电话</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<section class="mb-svc-section" th:if="${!#strings.isEmpty(item.processHtml)}">
|
||||
<h2>办理流程</h2>
|
||||
<div class="article-content" th:utext="${item.processHtml}">办理流程</div>
|
||||
</section>
|
||||
<section class="mb-svc-section" th:if="${!#strings.isEmpty(item.materialHtml)}">
|
||||
<h2>所需材料</h2>
|
||||
<div class="article-content" th:utext="${item.materialHtml}">所需材料</div>
|
||||
</section>
|
||||
<section class="mb-svc-section" th:if="${!#strings.isEmpty(item.noticeHtml)}">
|
||||
<h2>注意事项</h2>
|
||||
<div class="article-content" th:utext="${item.noticeHtml}">注意事项</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-svc-section mb-svc-attachments" th:if="${!#lists.isEmpty(attachments)}">
|
||||
<h2>相关材料下载</h2>
|
||||
<ul>
|
||||
<li th:each="att : ${attachments}">
|
||||
<a th:href="${url.upload(att.webUrl)}" th:download
|
||||
th:text="${att.fileName}">材料文件</a>
|
||||
<span class="mb-svc-att-size" th:if="${att.fileSize != null}"
|
||||
th:text="${#numbers.formatDecimal(att.fileSize / 1024.0, 1, 1) + ' KB'}">12.3 KB</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<nav class="mb-svc-links" th:if="${faqCategoryUrl != null or noticeCategoryUrl != null or ruleCategoryUrl != null}">
|
||||
<a th:if="${faqCategoryUrl != null}" th:href="${faqCategoryUrl}">常见问题</a>
|
||||
<a th:if="${noticeCategoryUrl != null}" th:href="${noticeCategoryUrl}">公告信息</a>
|
||||
<a th:if="${ruleCategoryUrl != null}" th:href="${ruleCategoryUrl}">文件规定</a>
|
||||
</nav>
|
||||
</article>
|
||||
|
||||
<aside class="mb-svc-related" th:if="${!svcUnavailable and !#lists.isEmpty(relatedItems)}">
|
||||
<h2 class="mb-svc-related-title">相关服务</h2>
|
||||
<ul class="mb-svc-related-list">
|
||||
<li th:each="rel : ${relatedItems}">
|
||||
<a th:href="${rel.url}">
|
||||
<span class="mb-svc-related-name" th:text="${rel.title}">相关事项</span>
|
||||
<span class="mb-svc-related-meta">
|
||||
<span th:if="${!#strings.isEmpty(rel.deptName)}" th:text="${rel.deptName}">部门</span>
|
||||
<span class="mb-tag"
|
||||
th:text="${rel.handleMode == 1 ? '线上' : (rel.handleMode == 2 ? '线下' : '线上线下')}">方式</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
<script th:src="${url.asset('js/main.js')}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -21,11 +21,31 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染(common 兜底骨架,本主题不另设形态) -->
|
||||
<!-- 首页布局模式(docs/16 §4.4 · 手册 §A.2):home_layout 落库后按区块序列渲染。
|
||||
本包为 A 档:8 形态 + quick_links/notice_bar 全部自持(fragments/home-blocks.html),
|
||||
只有白名单外的未知类型才落 themes/common 兜底骨架(故 common 的 :: style 仍要引入)。
|
||||
注意坑④:th:replace(优先级 100)先于 th:case(300)执行——case 必须放父元素、replace 放子元素,
|
||||
否则未命中的 case 分支也会求值 fragment 表达式(缺键 Map 访问直接抛错整页 500)。 -->
|
||||
<th:block th:if="${layoutBlocks != null}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: style}" />
|
||||
<th:block th:each="block : ${layoutBlocks}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" />
|
||||
<th:block th:switch="${block.type}">
|
||||
<th:block th:case="'article_list'">
|
||||
<th:block th:switch="${block.homeStyle}">
|
||||
<th:block th:case="'news_grid'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: news_grid(${block})}" /></th:block>
|
||||
<th:block th:case="'media_focus'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: media_focus(${block})}" /></th:block>
|
||||
<th:block th:case="'compact'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: compact(${block})}" /></th:block>
|
||||
<th:block th:case="'image_wall'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: image_wall(${block})}" /></th:block>
|
||||
<th:block th:case="*"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: card(${block})}" /></th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
<th:block th:case="'ticker'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: ticker(${block})}" /></th:block>
|
||||
<th:block th:case="'gallery'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: gallery(${block})}" /></th:block>
|
||||
<th:block th:case="'links'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: links(${block})}" /></th:block>
|
||||
<th:block th:case="'quick_links'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: quick_links(${block})}" /></th:block>
|
||||
<th:block th:case="'notice_bar'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: notice_bar(${block})}" /></th:block>
|
||||
<th:block th:case="*"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" /></th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<!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>
|
||||
|
||||
<!-- 服务中心(事项库 §6.1 契约 · P-A2 换皮):serviceList / serviceFilters / activeFilters / q;
|
||||
筛选与分页 URL 全部后端预生成(facets[].url / pageUrls[].url),模板禁止手拼 query。
|
||||
页壳/面包屑/页题/分页直接复用本包既有 .list-main / .breadcrumb / .page-title / .pagination,
|
||||
只有事项特有的件用 mb- 前缀(数据绑定与 default、poly-blue 同构,仅换皮)。 -->
|
||||
<main class="list-main" id="main-content">
|
||||
<div class="mb-page-card">
|
||||
<nav class="breadcrumb" aria-label="面包屑">
|
||||
<a th:href="${url.home()}">首页</a>
|
||||
<span>服务中心</span>
|
||||
</nav>
|
||||
|
||||
<h1 class="page-title">服务中心</h1>
|
||||
|
||||
<!-- P1-4 停机空态:svc-hall 不可达时降级渲染(200),不出 500 错误页 -->
|
||||
<div class="empty mb-empty mb-svc-unavailable" th:if="${svcUnavailable}">
|
||||
<p>事项服务暂时不可用,请稍后再试</p>
|
||||
<p class="mb-empty-hint">事项数据服务暂不可达,稍后刷新即可恢复</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-svc-filters"
|
||||
th:if="${!svcUnavailable and (!#lists.isEmpty(serviceFilters.objects) or !#lists.isEmpty(serviceFilters.categories) or !#lists.isEmpty(serviceFilters.depts))}">
|
||||
<div class="mb-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.objects)}">
|
||||
<span class="mb-svc-filter-label">服务对象</span>
|
||||
<div class="mb-svc-filter-options">
|
||||
<a class="mb-chip" th:each="opt : ${serviceFilters.objects}"
|
||||
th:classappend="${opt.active} ? 'active'" th:href="${opt.url}">
|
||||
<span th:text="${opt.name}">对象</span><em th:text="${opt.count}">0</em>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.categories)}">
|
||||
<span class="mb-svc-filter-label">事项类别</span>
|
||||
<div class="mb-svc-filter-options">
|
||||
<a class="mb-chip" th:each="opt : ${serviceFilters.categories}"
|
||||
th:classappend="${opt.active} ? 'active'" th:href="${opt.url}">
|
||||
<span th:text="${opt.name}">类别</span><em th:text="${opt.count}">0</em>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.depts)}">
|
||||
<span class="mb-svc-filter-label">服务部门</span>
|
||||
<div class="mb-svc-filter-options">
|
||||
<a class="mb-chip" th:each="opt : ${serviceFilters.depts}"
|
||||
th:classappend="${opt.active} ? 'active'" th:href="${opt.url}">
|
||||
<span th:text="${opt.name}">部门</span><em th:text="${opt.count}">0</em>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-svc-toolbar" th:if="${!svcUnavailable}">
|
||||
<form class="mb-svc-search" th:action="${url.serviceList()}" method="get" role="search">
|
||||
<input type="search" name="q" th:value="${q}" placeholder="搜索事项名称 / 简介" maxlength="64">
|
||||
<button type="submit">搜索</button>
|
||||
</form>
|
||||
<a class="mb-svc-clear" th:if="${activeFilters.hasAny}" th:href="${activeFilters.clearUrl}">清除筛选</a>
|
||||
</div>
|
||||
|
||||
<ul class="mb-svc-list" th:if="${!svcUnavailable}">
|
||||
<li th:each="it : ${serviceList.items}">
|
||||
<a class="mb-svc-item" th:href="${url.serviceItem(it.id)}">
|
||||
<span class="mb-svc-item-cover" th:if="${it.coverImage != null}">
|
||||
<img th:src="${url.upload(it.coverImage)}" th:alt="${it.title}">
|
||||
</span>
|
||||
<span class="mb-svc-item-body">
|
||||
<span class="mb-svc-item-title">
|
||||
<span class="mb-svc-item-name" th:text="${it.title}">事项名称</span>
|
||||
<span class="mb-tag online" th:if="${it.onlineFlag == 1}">可在线办理</span>
|
||||
<span class="mb-tag"
|
||||
th:text="${it.handleMode == 1 ? '线上办理' : (it.handleMode == 2 ? '线下办理' : '线上线下')}">办理方式</span>
|
||||
</span>
|
||||
<span class="mb-svc-item-summary" th:if="${!#strings.isEmpty(it.summary)}"
|
||||
th:text="${it.summary}">简介</span>
|
||||
<span class="mb-svc-item-meta">
|
||||
<span th:if="${!#strings.isEmpty(it.deptName)}" th:text="${it.deptName}">部门</span>
|
||||
<span th:if="${!#strings.isEmpty(it.categoryName)}" th:text="${it.categoryName}">类别</span>
|
||||
<time th:if="${it.publishTime != null}"
|
||||
th:text="${#temporals.format(it.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="empty mb-empty" th:if="${!svcUnavailable and #lists.isEmpty(serviceList.items)}">
|
||||
<p th:if="${activeFilters.hasAny}">未找到符合条件的服务事项</p>
|
||||
<p th:if="${!activeFilters.hasAny}">服务中心暂未上架事项</p>
|
||||
<p class="mb-empty-hint" th:if="${activeFilters.hasAny}">换个筛选条件或关键词试试</p>
|
||||
<p class="mb-empty-hint" th:if="${!activeFilters.hasAny}">事项发布后将在此展示</p>
|
||||
</div>
|
||||
|
||||
<!-- 分页:URL 由后端预生成(携带当前筛选,pageUrls[].active 标记当前页);停机态 totalPages=0 自然隐藏 -->
|
||||
<nav class="pagination" th:if="${!svcUnavailable and serviceList.totalPages > 1}" aria-label="分页">
|
||||
<a th:each="p : ${serviceList.pageUrls}"
|
||||
th:classappend="${p.active} ? 'active'"
|
||||
th:href="${p.url}"
|
||||
th:aria-label="${'第' + p.page + '页'}"
|
||||
th:aria-current="${p.active} ? 'page'"
|
||||
th:text="${p.page}">1</a>
|
||||
</nav>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
<script th:src="${url.asset('js/main.js')}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -17,6 +17,11 @@
|
||||
{ "key": "text", "cssVar": "--text", "label": "正文文字色", "type": "color", "default": "#2F3E3C" },
|
||||
{ "key": "textLight", "cssVar": "--text-light", "label": "次要文字色", "type": "color", "default": "#7D8F8C" },
|
||||
{ "key": "listLayout", "label": "列表布局", "type": "select", "default": "grid",
|
||||
"options": [ { "value": "grid", "label": "卡片网格(一行 3 张)" }, { "value": "row", "label": "传统行列表" } ] }
|
||||
"options": [ { "value": "grid", "label": "卡片网格(一行 3 张)" }, { "value": "row", "label": "传统行列表" } ] },
|
||||
{ "key": "headerStyle", "label": "头部样式", "type": "select", "default": "banner",
|
||||
"options": [
|
||||
{ "value": "banner", "label": "细线顶栏+居中品牌行+白底导航(完整薄荷风)" },
|
||||
{ "value": "plain", "label": "简洁头部(收起细线顶栏、压低品牌行)" }
|
||||
] }
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user