feat(theme): S7 炫彩校园主题补齐——3 形态自研+2 新区块+svc/guide 页族,A 档三包整项自审收口
build / build-push (push) Successful in 49s
build / build-push (push) Successful in 49s
- fragments/home-blocks.html 150→280(纯追加 130 行):news_grid/media_focus/image_wall 三形态 由委派 common::plb* 改为本包自持,并补 quick_links/notice_bar;空态用内联 div.cg-empty (与 scholar-blue/mint-breeze 的 ::empty 子片段策略分裂,记 R-37 未统一) - home.html 分派改双层 th:switch(case 父 / replace 子,坑④)并引入 common :: style(R-32 本包实例关闭) - assets/css/main.css 402→656(纯追加 254 行,0 删):新节全部 .cg-svc-*/.cg-chip/.cg-tag/.cg-sec-card 等全新类名,未重写任何既有选择器;坑③ 在本包论证不适用(.cg-home 非网格,全幅 section 天然占满) - 新建 service.html(135)/guide.html(134):poly-blue 页族换皮,绑定面 diff 0 差异 - theme.json 零改动(P-A3 步骤 6「不动参数」,实测 9 参保持) - 后端全量 412/412 + 前端 160/160 绿;本次 S10 复跑后端 412、前端 166 绿
This commit is contained in:
@@ -400,3 +400,257 @@ button { font-family: inherit; cursor: pointer; }
|
|||||||
.cg-gallery-item img { height: 230px; }
|
.cg-gallery-item img { height: 230px; }
|
||||||
.cg-qr:hover img { transform: scale(1.6); }
|
.cg-qr:hover img { transform: scale(1.6); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
首页布局模式区块补齐(v2 手册 §A.3 · P-A3,S7)
|
||||||
|
对应 fragments/home-blocks.html 新增的 5 个 fragment:compact / image_wall / links / quick_links / notice_bar。
|
||||||
|
本包三条硬约束说明:
|
||||||
|
· §2.5 坑③(全宽区块根补 grid-column)在本包**不适用**:.cg-home 无任何布局规则(非网格),
|
||||||
|
区块用"全幅 section > .cg-container"惯用结构天然占满整行——补 grid-column 只会留死声明。
|
||||||
|
· 网格轨道一律 minmax(0,1fr)(本包 .cg-news-grid/.cg-media-grid 基线用的是裸 1fr,
|
||||||
|
新节按记忆教训改用 minmax,避免中文 min-content 撑破窄视口;不动既有两行以守住"已有形态不改")。
|
||||||
|
· 颜色只用本包既有令牌;#fff 与 color-mix() 是本包既有习惯(顶栏/导航/侧栏已如此写),未新增色值。
|
||||||
|
===================================================================== */
|
||||||
|
|
||||||
|
/* 区块壳:与 .cg-block-card / .cg-media-card 同值(本包习惯是各形态各写一份,不合并选择器) */
|
||||||
|
.cg-compact, .cg-wall, .cg-friend, .cg-quick { padding: 40px 0 0; }
|
||||||
|
.cg-sec-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 形态四 compact:圆角日期块 + 单行标题 */
|
||||||
|
.cg-compact-list { display: grid; grid-template-columns: 1fr 1fr; column-gap: 48px; }
|
||||||
|
.cg-compact-list li {
|
||||||
|
display: flex; align-items: center; gap: 14px; min-width: 0;
|
||||||
|
padding: 11px 2px; border-bottom: 1px dashed var(--cg-border);
|
||||||
|
}
|
||||||
|
.cg-compact-list li:nth-last-child(-n+2) { border-bottom: 0; }
|
||||||
|
.cg-date-chip {
|
||||||
|
flex: none; display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
|
||||||
|
width: 56px; line-height: 1.25; color: #fff; border-radius: 10px;
|
||||||
|
background: linear-gradient(135deg, var(--cg-primary), var(--cg-primary-dark));
|
||||||
|
box-shadow: 0 3px 8px rgba(10, 98, 68, .22);
|
||||||
|
}
|
||||||
|
.cg-date-chip b { font-size: 20px; font-weight: 700; }
|
||||||
|
.cg-date-chip i { font-style: normal; font-size: 11px; opacity: .82; letter-spacing: .5px; }
|
||||||
|
.cg-compact-title {
|
||||||
|
flex: 1; min-width: 0; font-size: 15px; color: var(--cg-text);
|
||||||
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.cg-compact-title:hover { color: var(--cg-primary); }
|
||||||
|
|
||||||
|
/* 形态五 image_wall:四列图墙 + 底部渐变题注(渐变写法同 .cg-gallery-cap,色值为该规则既有) */
|
||||||
|
.cg-wall-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
|
||||||
|
.cg-wall-item { position: relative; border-radius: 10px; overflow: hidden; }
|
||||||
|
.cg-wall-item a { display: block; }
|
||||||
|
.cg-wall-item img {
|
||||||
|
width: 100%; height: 156px; object-fit: cover;
|
||||||
|
transition: transform .3s ease, box-shadow .3s ease;
|
||||||
|
}
|
||||||
|
.cg-wall-item:hover img { transform: translateY(-4px); box-shadow: 0 14px 28px rgba(10, 98, 68, .22); }
|
||||||
|
.cg-wall-cap {
|
||||||
|
position: absolute; left: 0; right: 0; bottom: 0; padding: 26px 12px 9px;
|
||||||
|
color: #fff; font-size: 14px;
|
||||||
|
background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, .55));
|
||||||
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 形态六 links:友情链接分组(分组标题带强调色竖条;logo 用页脚同款 .cg-link-logo) */
|
||||||
|
.cg-friend-group {
|
||||||
|
margin: 16px 0 8px; padding-left: 10px; font-size: 14px; font-weight: 600;
|
||||||
|
color: var(--cg-primary-dark); border-left: 3px solid var(--cg-accent);
|
||||||
|
}
|
||||||
|
.cg-friend-group:first-of-type { margin-top: 0; }
|
||||||
|
.cg-friend-links { display: flex; flex-wrap: wrap; gap: 8px 12px; }
|
||||||
|
.cg-friend-links li { flex: none; }
|
||||||
|
.cg-friend-links a {
|
||||||
|
display: inline-flex; align-items: center; gap: 6px; padding: 5px 14px;
|
||||||
|
font-size: 13px; color: var(--cg-text); background: var(--cg-bg);
|
||||||
|
border: 1px solid var(--cg-border); border-radius: 999px; transition: color .2s, border-color .2s;
|
||||||
|
}
|
||||||
|
.cg-friend-links a:hover { color: var(--cg-primary); border-color: var(--cg-primary); }
|
||||||
|
|
||||||
|
/* 新区块一 quick_links:六列宫格 → 三列 → 两列(入口名居中,悬停走本包渐变语言) */
|
||||||
|
.cg-quick-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; }
|
||||||
|
.cg-quick-link {
|
||||||
|
display: flex; align-items: center; justify-content: center; min-height: 58px; padding: 8px 10px;
|
||||||
|
font-size: 14px; text-align: center; color: var(--cg-text);
|
||||||
|
background: var(--cg-bg); border: 1px solid var(--cg-border); border-radius: 10px;
|
||||||
|
transition: color .2s, background .2s, transform .2s, box-shadow .2s;
|
||||||
|
}
|
||||||
|
.cg-quick-link:hover {
|
||||||
|
color: #fff; background: linear-gradient(135deg, var(--cg-primary), var(--cg-primary-dark));
|
||||||
|
border-color: var(--cg-primary); transform: translateY(-3px); box-shadow: var(--cg-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 新区块二 notice_bar:整幅横条(与 .cg-ticker 同层级,不是卡片)。
|
||||||
|
info 用主色浅底、warn 用强调色浅底——本包有 --cg-accent 令牌,故不需要像 mint-breeze 那样自持 warn 三件套。 */
|
||||||
|
.cg-notice { border-top: 1px solid var(--cg-border); border-bottom: 1px solid var(--cg-border); background: var(--cg-card-bg); }
|
||||||
|
.cg-notice-inner { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; padding-top: 12px; padding-bottom: 12px; font-size: 14px; }
|
||||||
|
.cg-notice-tag {
|
||||||
|
flex: none; font-size: 13px; font-weight: 600; color: #fff; padding: 4px 14px; border-radius: 0 0 10px 10px;
|
||||||
|
background: linear-gradient(135deg, var(--cg-primary), var(--cg-primary-dark));
|
||||||
|
}
|
||||||
|
.cg-notice-link, .cg-notice-text { flex: 1; min-width: 0; color: var(--cg-text); }
|
||||||
|
.cg-notice-link { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.cg-notice-link:hover { color: var(--cg-primary); text-decoration: underline; text-underline-offset: 3px; }
|
||||||
|
.cg-notice-info { border-top-color: color-mix(in srgb, var(--cg-primary) 30%, var(--cg-border)); background: color-mix(in srgb, var(--cg-primary) 7%, var(--cg-card-bg)); }
|
||||||
|
.cg-notice-warn { border-top-color: color-mix(in srgb, var(--cg-accent) 45%, var(--cg-border)); background: color-mix(in srgb, var(--cg-accent) 9%, var(--cg-card-bg)); }
|
||||||
|
.cg-notice-warn .cg-notice-tag { background: linear-gradient(135deg, var(--cg-accent), #ff9f4d); }
|
||||||
|
.cg-notice-warn .cg-notice-link:hover { color: var(--cg-accent); }
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.cg-sec-card { padding: 20px 18px; }
|
||||||
|
.cg-wall-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.cg-quick-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cg-compact-list { grid-template-columns: 1fr; }
|
||||||
|
.cg-compact-list li:nth-last-child(-n+2) { border-bottom: 1px dashed var(--cg-border); }
|
||||||
|
.cg-compact-list li:last-child { border-bottom: 0; }
|
||||||
|
.cg-compact, .cg-wall, .cg-friend, .cg-quick { padding-top: 26px; }
|
||||||
|
.cg-quick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.cg-notice-inner { flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||||
|
.cg-notice-link { white-space: normal; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
服务中心 / 事项指南(v2 手册 §A.3 · P-A3,S7 新建 service.html + guide.html)
|
||||||
|
页壳、面包屑、页题、分页、空态、标题/元信息/正文全部复用本包既有
|
||||||
|
.cg-list-main / .cg-page-card / .cg-breadcrumb / .cg-page-title / .cg-pagination /
|
||||||
|
.cg-empty / .cg-article-title / .cg-article-meta / .cg-article-content,
|
||||||
|
本节只补事项特有的件;色值只用本包令牌(#fff、#ff9f4d 为本包既有习惯)。
|
||||||
|
===================================================================== */
|
||||||
|
|
||||||
|
/* 筛选面板:分组行 + 胶囊选项(active 走本包主色渐变) */
|
||||||
|
.cg-svc-filters {
|
||||||
|
border: 1px solid var(--cg-border); border-radius: 10px;
|
||||||
|
background: var(--cg-bg); padding: 8px 14px; margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
.cg-svc-filter-group { display: flex; align-items: flex-start; gap: 12px; padding: 7px 0; }
|
||||||
|
.cg-svc-filter-group + .cg-svc-filter-group { border-top: 1px dashed var(--cg-border); }
|
||||||
|
.cg-svc-filter-label { flex: none; min-width: 60px; font-size: 13px; font-weight: 600; color: var(--cg-primary-dark); padding-top: 3px; }
|
||||||
|
.cg-svc-filter-options { display: flex; flex-wrap: wrap; gap: 8px; min-width: 0; }
|
||||||
|
.cg-chip {
|
||||||
|
display: inline-flex; align-items: center; gap: 5px; padding: 2px 12px;
|
||||||
|
font-size: 13px; color: var(--cg-text-light);
|
||||||
|
background: var(--cg-card-bg); border: 1px solid var(--cg-border); border-radius: 999px;
|
||||||
|
transition: color .2s, border-color .2s, background .2s;
|
||||||
|
}
|
||||||
|
.cg-chip em { font-style: normal; font-size: 12px; opacity: .75; }
|
||||||
|
.cg-chip:hover { color: var(--cg-primary); border-color: var(--cg-primary); }
|
||||||
|
.cg-chip.active {
|
||||||
|
color: #fff; border-color: var(--cg-primary);
|
||||||
|
background: linear-gradient(135deg, var(--cg-primary), var(--cg-primary-dark));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工具条:搜索框(圆角同 .cg-site-search,但方角贴合卡片)+ 清除筛选 */
|
||||||
|
.cg-svc-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
|
||||||
|
.cg-svc-search { display: flex; flex: 1; min-width: 0; }
|
||||||
|
.cg-svc-search input {
|
||||||
|
flex: 1; min-width: 0; padding: 9px 14px; font-size: 14px; font-family: inherit;
|
||||||
|
border: 1px solid var(--cg-border); border-right: 0; border-radius: 8px 0 0 8px;
|
||||||
|
background: var(--cg-bg); color: var(--cg-text); outline: none;
|
||||||
|
}
|
||||||
|
.cg-svc-search input:focus { border-color: var(--cg-primary); background: var(--cg-card-bg); }
|
||||||
|
.cg-svc-search button {
|
||||||
|
flex: none; padding: 9px 24px; border: 0; border-radius: 0 8px 8px 0;
|
||||||
|
background: var(--cg-primary); color: #fff; font-size: 14px; transition: background .2s;
|
||||||
|
}
|
||||||
|
.cg-svc-search button:hover { background: var(--cg-primary-dark); }
|
||||||
|
.cg-svc-clear { flex: none; font-size: 13px; color: var(--cg-primary); }
|
||||||
|
.cg-svc-clear:hover { color: var(--cg-accent); text-decoration: underline; text-underline-offset: 3px; }
|
||||||
|
|
||||||
|
/* 事项卡网格:三列 → 两列 → 单列;轨道 minmax(0,·) 防中文 min-content 撑破窄视口 */
|
||||||
|
.cg-svc-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
|
||||||
|
.cg-svc-item {
|
||||||
|
display: flex; gap: 14px; align-items: flex-start; min-width: 0; padding: 16px;
|
||||||
|
background: var(--cg-card-bg); border: 1px solid var(--cg-border); border-radius: 10px;
|
||||||
|
transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
|
||||||
|
}
|
||||||
|
.cg-svc-item:hover { transform: translateY(-3px); border-color: var(--cg-primary); box-shadow: var(--cg-shadow); }
|
||||||
|
.cg-svc-item-cover { flex: none; width: 108px; border-radius: 8px; overflow: hidden; }
|
||||||
|
.cg-svc-item-cover img { width: 100%; height: 72px; object-fit: cover; }
|
||||||
|
.cg-svc-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
|
||||||
|
.cg-svc-item-title { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; min-width: 0; }
|
||||||
|
.cg-svc-item-name {
|
||||||
|
font-size: 15px; font-weight: 600; color: var(--cg-text); line-height: 1.5;
|
||||||
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
||||||
|
}
|
||||||
|
.cg-svc-item:hover .cg-svc-item-name { color: var(--cg-primary); }
|
||||||
|
.cg-svc-item-summary {
|
||||||
|
font-size: 13px; color: var(--cg-text-light);
|
||||||
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
||||||
|
}
|
||||||
|
.cg-svc-item-meta { margin-top: auto; display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 12px; color: var(--cg-text-light); }
|
||||||
|
|
||||||
|
/* 标签:办理方式用主色浅底;"可在线办理"是第二优先级信息,用强调色描边区分而不是引入新色相 */
|
||||||
|
.cg-tag {
|
||||||
|
flex: none; font-size: 12px; line-height: 1.6; padding: 1px 9px; border-radius: 4px;
|
||||||
|
color: var(--cg-primary); background: color-mix(in srgb, var(--cg-primary) 10%, var(--cg-card-bg));
|
||||||
|
}
|
||||||
|
.cg-tag.online { color: var(--cg-accent); background: transparent; border: 1px solid color-mix(in srgb, var(--cg-accent) 55%, transparent); }
|
||||||
|
|
||||||
|
/* 指南页:办理按钮、基本信息表、章节题、附件、跳转条、相关服务 */
|
||||||
|
.cg-svc-apply { display: flex; align-items: center; gap: 14px; margin: 18px 0; }
|
||||||
|
.cg-svc-apply-btn {
|
||||||
|
display: inline-block; padding: 10px 32px; font-size: 15px; color: #fff;
|
||||||
|
border-radius: 999px; background: linear-gradient(135deg, var(--cg-primary), var(--cg-primary-dark));
|
||||||
|
box-shadow: 0 6px 16px rgba(10, 98, 68, .26); transition: transform .2s ease, box-shadow .2s ease;
|
||||||
|
}
|
||||||
|
.cg-svc-apply-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 22px rgba(10, 98, 68, .34); }
|
||||||
|
.cg-svc-apply-hint { font-size: 13px; color: var(--cg-text-light); }
|
||||||
|
.cg-svc-summary { font-size: 15px; color: var(--cg-text-light); line-height: 1.85; margin-bottom: 18px; }
|
||||||
|
|
||||||
|
.cg-svc-info { width: 100%; border-collapse: collapse; margin-bottom: 18px; font-size: 14px; }
|
||||||
|
.cg-svc-info th, .cg-svc-info td { border: 1px solid var(--cg-border); padding: 10px 14px; text-align: left; vertical-align: top; }
|
||||||
|
.cg-svc-info th { width: 132px; font-weight: 500; color: var(--cg-text-light); background: var(--cg-bg); }
|
||||||
|
|
||||||
|
.cg-svc-section { margin-top: 22px; }
|
||||||
|
.cg-svc-section h2 {
|
||||||
|
font-size: 18px; font-weight: 600; color: var(--cg-primary-dark);
|
||||||
|
padding-left: 12px; margin-bottom: 10px; border-left: 4px solid var(--cg-accent);
|
||||||
|
}
|
||||||
|
.cg-svc-section .cg-article-content { padding-top: 0; }
|
||||||
|
.cg-svc-attachments > ul { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
.cg-svc-attachments a { font-size: 14px; color: var(--cg-primary-dark); }
|
||||||
|
.cg-svc-attachments a:hover { color: var(--cg-primary); text-decoration: underline; text-underline-offset: 3px; }
|
||||||
|
.cg-svc-att-size { margin-left: 8px; font-size: 12px; color: var(--cg-text-light); }
|
||||||
|
|
||||||
|
.cg-svc-links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; padding-top: 18px; border-top: 1px dashed var(--cg-border); }
|
||||||
|
.cg-svc-links a {
|
||||||
|
padding: 4px 18px; font-size: 14px; color: var(--cg-primary);
|
||||||
|
border: 1px solid var(--cg-primary); border-radius: 999px; transition: color .2s, background .2s;
|
||||||
|
}
|
||||||
|
.cg-svc-links a:hover { color: #fff; background: var(--cg-primary); }
|
||||||
|
|
||||||
|
.cg-svc-related { margin-top: 30px; padding: 18px 20px; border: 1px solid var(--cg-border); border-radius: 10px; background: var(--cg-bg); }
|
||||||
|
.cg-svc-related-title { font-size: 17px; font-weight: 600; color: var(--cg-primary-dark); margin-bottom: 6px; }
|
||||||
|
.cg-svc-related-list > li + li { border-top: 1px dashed var(--cg-border); }
|
||||||
|
.cg-svc-related-list a { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 0; min-width: 0; }
|
||||||
|
.cg-svc-related-name { flex: 1; min-width: 0; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.cg-svc-related-list a:hover .cg-svc-related-name { color: var(--cg-primary); }
|
||||||
|
.cg-svc-related-meta { flex: none; display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: var(--cg-text-light); }
|
||||||
|
|
||||||
|
/* 表格 caption 仅面向读屏(本包 .cg-skip-link 是跳转链接专用,语义不同,不复用) */
|
||||||
|
.cg-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.cg-svc-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cg-svc-list { grid-template-columns: minmax(0, 1fr); }
|
||||||
|
.cg-svc-toolbar { flex-direction: column; align-items: stretch; }
|
||||||
|
.cg-svc-clear { align-self: flex-start; }
|
||||||
|
.cg-svc-info th { width: 92px; padding: 9px 10px; }
|
||||||
|
.cg-svc-info td { padding: 9px 10px; }
|
||||||
|
.cg-svc-item { flex-direction: column; }
|
||||||
|
.cg-svc-item-cover { width: 100%; }
|
||||||
|
.cg-svc-item-cover img { height: 158px; }
|
||||||
|
.cg-svc-apply { flex-wrap: wrap; gap: 8px; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -148,3 +148,133 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- ===== 以下 5 个 fragment 为 v2 手册 §A.3 · P-A3 补齐(S7)=====
|
||||||
|
补齐原因:本包原先只有 5 个自持形态,compact/image_wall/links 三处直接借 themes/common 的 plb 骨架
|
||||||
|
(首页换肤只换页头页脚、正文落骨架 = 手册 §2.2 线上事故根因),quick_links/notice_bar 两个新区块类型则完全没有。
|
||||||
|
上面 5 个形态保持原样未重写;本段沿用本包既有惯例:
|
||||||
|
· 结构 = 全幅 <section class="cg-形态名"> > .cg-container > 卡本体(故本包 main 不是网格,§2.5 坑③ 不适用,见 audit-A.md A3 证据)
|
||||||
|
· 空态 = 内联 <div class="cg-empty"> + th:if 同元素(本包 news_grid / media_focus / card 三形态既有写法如此;不引用 :: empty fragment,
|
||||||
|
因而不新增 fragment 名,S4 门禁 validateHomeBlockFragmentRefs 的"引用 ⊆ 声明"仍成立);
|
||||||
|
links 与 scholar-blue / mint-breeze 定型同构(分组遍历,不设空态卡);notice_bar 无文本时整条隐藏(本包 ticker / gallery 既有"无数据即隐藏"写法)
|
||||||
|
· 可选 Map 键一律 block['key'] 括号访问(坑②)
|
||||||
|
-->
|
||||||
|
<!-- 形态四:日期方块行(homeStyle=compact)——圆角日期块 + 单行标题(原借 common :: plbCompact) -->
|
||||||
|
<section class="cg-compact" th:fragment="compact(block)" aria-label="栏目区块">
|
||||||
|
<div class="cg-container">
|
||||||
|
<div class="cg-sec-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-compact-list" th:if="${!#lists.isEmpty(block.items)}">
|
||||||
|
<li th:each="item : ${block.items}">
|
||||||
|
<span class="cg-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 class="cg-compact-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=image_wall)——四列格 + 底部渐变题注,仅取有封面的条目(原借 common :: plbImageWall) -->
|
||||||
|
<section class="cg-wall" th:fragment="image_wall(block)" aria-label="栏目区块">
|
||||||
|
<div class="cg-container">
|
||||||
|
<div class="cg-sec-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-wall-grid" th:if="${!#lists.isEmpty(block.items)}">
|
||||||
|
<li class="cg-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="cg-wall-cap" th:text="${item.title}">图片标题</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||||
|
<p>栏目暂无内容</p>
|
||||||
|
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 形态六:友情链接分组(type=links,block.linkGroups 由渲染端按 group 过滤)——原借 common :: plbLinks。
|
||||||
|
语义与 quick_links 分开:links 读友情链接表(带 logo),quick_links 读区块内联数组(手册 §2.8 类型 A)。 -->
|
||||||
|
<section class="cg-friend" th:fragment="links(block)" aria-label="友情链接">
|
||||||
|
<div class="cg-container">
|
||||||
|
<div class="cg-sec-card">
|
||||||
|
<div class="cg-sec-head">
|
||||||
|
<h2 th:text="${block.title}">友情链接</h2>
|
||||||
|
</div>
|
||||||
|
<th:block th:each="grp : ${block.linkGroups}">
|
||||||
|
<p class="cg-friend-group" th:if="${!#strings.isEmpty(grp.groupName)}" th:text="${grp.groupName}">分组</p>
|
||||||
|
<ul class="cg-friend-links">
|
||||||
|
<li th:each="l : ${grp.links}">
|
||||||
|
<a th:href="${l.linkUrl}" target="_blank" rel="noopener">
|
||||||
|
<img th:if="${l.logoUrl != null and !#strings.isEmpty(l.logoUrl)}" class="cg-link-logo"
|
||||||
|
th:src="${url.upload(l.logoUrl)}" th:alt="${l.name}" loading="lazy">
|
||||||
|
<span th:text="${l.name}">链接名</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</th:block>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 新区块一:快捷通道(quick_links,手册 §2.8 类型 A)——区块内联入口数组,不读友情链接表。
|
||||||
|
渲染端已丢弃 name 空 / url 非法项(PortalPageRenderer:477-500),模板只判 #lists.isEmpty -->
|
||||||
|
<section class="cg-quick" th:fragment="quick_links(block)" aria-label="快捷通道">
|
||||||
|
<div class="cg-container">
|
||||||
|
<div class="cg-sec-card">
|
||||||
|
<div class="cg-sec-head">
|
||||||
|
<h2 th:text="${block.title}">快捷通道</h2>
|
||||||
|
<a class="cg-more-text" th:if="${block.moreUrl != null}" th:href="${block.moreUrl}">更多 ></a>
|
||||||
|
</div>
|
||||||
|
<ul class="cg-quick-grid" th:if="${!#lists.isEmpty(block['quickLinks'])}">
|
||||||
|
<li th:each="l : ${block['quickLinks']}">
|
||||||
|
<a class="cg-quick-link" th:href="${l.url}" th:text="${l.name}">入口名</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="cg-empty" th:if="${#lists.isEmpty(block['quickLinks'])}">
|
||||||
|
<p>暂无可用入口</p>
|
||||||
|
<p class="cg-empty-hint">请在首页布局中配置快捷通道</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 新区块二:公告条(notice_bar,手册 §2.8 类型 B)——站点单条手填通告,非滚动非文章列表。
|
||||||
|
整幅横条(与本包 .cg-ticker 同层级,不是卡片);noticeLevel 渲染端已归一 info|warn;noticeUrl 非法时为空串 → 退化纯文本 -->
|
||||||
|
<div class="cg-notice" th:fragment="notice_bar(block)" role="note"
|
||||||
|
th:if="${!#strings.isEmpty(block['noticeText'])}"
|
||||||
|
th:classappend="${block['noticeLevel'] == 'warn' ? 'cg-notice-warn' : 'cg-notice-info'}">
|
||||||
|
<div class="cg-container cg-notice-inner">
|
||||||
|
<span class="cg-notice-tag" th:text="${block.title}">公告</span>
|
||||||
|
<a class="cg-notice-link" th:if="${!#strings.isEmpty(block['noticeUrl'])}"
|
||||||
|
th:href="${block['noticeUrl']}" th:text="${block['noticeText']}">公告正文</a>
|
||||||
|
<span class="cg-notice-text" th:unless="${!#strings.isEmpty(block['noticeUrl'])}"
|
||||||
|
th:text="${block['noticeText']}">公告正文</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<!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-A3 换皮):item / attachments[] / relatedItems[] /
|
||||||
|
faqCategoryUrl·noticeCategoryUrl·ruleCategoryUrl(可空)/ applyUrl(可空,空则不渲染"我要办理")。
|
||||||
|
三个富文本字段已在保存期过 jsoup 白名单、渲染期补 URL 基准,此处允许 utext。
|
||||||
|
svcUnavailable=true(P1-4 停机降级):只渲染骨架+空态,不渲染事项内容与"我要办理"。
|
||||||
|
标题/元信息/正文沿用本包既有 .cg-article-title / .cg-article-meta / .cg-article-content,
|
||||||
|
数据绑定与 default、poly-blue 同构,仅换皮。 -->
|
||||||
|
<main class="cg-list-main cg-container" id="main-content">
|
||||||
|
<div class="cg-page-card">
|
||||||
|
<nav class="cg-breadcrumb" aria-label="面包屑">
|
||||||
|
<a th:href="${url.home()}">首页</a>
|
||||||
|
<span class="cg-crumb-sep" aria-hidden="true">/</span>
|
||||||
|
<a th:href="${url.serviceList()}">服务中心</a>
|
||||||
|
<span class="cg-crumb-sep" aria-hidden="true">/</span>
|
||||||
|
<span class="cg-current" th:text="${svcUnavailable} ? '服务中心' : ${item.title}">事项名称</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- P1-4 停机空态:svc-hall 不可达时降级渲染(200),不出 500 错误页 -->
|
||||||
|
<div class="cg-empty" th:if="${svcUnavailable}">
|
||||||
|
<p>事项服务暂时不可用,请稍后再试</p>
|
||||||
|
<p class="cg-empty-hint">事项数据服务暂不可达,稍后刷新即可恢复</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article th:if="${!svcUnavailable}">
|
||||||
|
<h1 class="cg-article-title" th:text="${item.title}">事项名称</h1>
|
||||||
|
<div class="cg-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="cg-svc-apply" th:if="${applyUrl != null}">
|
||||||
|
<a class="cg-svc-apply-btn" th:href="${applyUrl}" target="_blank" rel="noopener">我要办理</a>
|
||||||
|
<span class="cg-svc-apply-hint">进入服务大厅在线办理</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="cg-svc-summary" th:if="${!#strings.isEmpty(item.summary)}" th:text="${item.summary}">简介</p>
|
||||||
|
|
||||||
|
<table class="cg-svc-info">
|
||||||
|
<caption class="cg-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="cg-svc-section" th:if="${!#strings.isEmpty(item.processHtml)}">
|
||||||
|
<h2>办理流程</h2>
|
||||||
|
<div class="cg-article-content" th:utext="${item.processHtml}">办理流程</div>
|
||||||
|
</section>
|
||||||
|
<section class="cg-svc-section" th:if="${!#strings.isEmpty(item.materialHtml)}">
|
||||||
|
<h2>所需材料</h2>
|
||||||
|
<div class="cg-article-content" th:utext="${item.materialHtml}">所需材料</div>
|
||||||
|
</section>
|
||||||
|
<section class="cg-svc-section" th:if="${!#strings.isEmpty(item.noticeHtml)}">
|
||||||
|
<h2>注意事项</h2>
|
||||||
|
<div class="cg-article-content" th:utext="${item.noticeHtml}">注意事项</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="cg-svc-section cg-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="cg-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="cg-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="cg-svc-related" th:if="${!svcUnavailable and !#lists.isEmpty(relatedItems)}">
|
||||||
|
<h2 class="cg-svc-related-title">相关服务</h2>
|
||||||
|
<ul class="cg-svc-related-list">
|
||||||
|
<li th:each="rel : ${relatedItems}">
|
||||||
|
<a th:href="${rel.url}">
|
||||||
|
<span class="cg-svc-related-name" th:text="${rel.title}">相关事项</span>
|
||||||
|
<span class="cg-svc-related-meta">
|
||||||
|
<span th:if="${!#strings.isEmpty(rel.deptName)}" th:text="${rel.deptName}">部门</span>
|
||||||
|
<span class="cg-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>
|
||||||
@@ -23,26 +23,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染。
|
<!-- 首页布局模式(docs/16 §4.4 · v2 手册 §A.3 P-A3):home_layout 落库后按区块序列渲染。
|
||||||
article_list 三种形态走主题自有 fragment(compact/image_wall 与未实现类型落 common 兜底骨架)。
|
本包为 A 档:8 形态 + quick_links/notice_bar 全部自持(fragments/home-blocks.html),
|
||||||
注意:th:replace(优先级100)先于 th:case(300)执行——case 必须放父元素、replace 放子元素 -->
|
compact/image_wall/links 三处不再借 themes/common 的 plb 骨架(借骨架 = 换肤只换页头页脚,手册 §2.2 事故根因);
|
||||||
<th:block th:if="${layoutBlocks != null}" th:each="block : ${layoutBlocks}">
|
只有白名单外的未知类型才落 common 兜底骨架——故 common 的 :: style 样式段必须引入(R-32:本包此前缺这段,兜底区块裸奔)。
|
||||||
|
注意坑④:th:replace(优先级 100)先于 th:case(300)执行——case 必须放父元素、replace 放子元素。 -->
|
||||||
|
<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:switch="${block.type}">
|
<th:block th:switch="${block.type}">
|
||||||
<th:block th:case="'article_list'">
|
<th:block th:case="'article_list'">
|
||||||
<th:block th:switch="${block.homeStyle}">
|
<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="'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="'media_focus'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: media_focus(${block})}" /></th:block>
|
||||||
<th:block th:case="'compact'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbCompact(${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="~{'themes/common/fragments/home-blocks' :: plbImageWall(${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:case="*"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: card(${block})}" /></th:block>
|
||||||
</th: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="'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="'gallery'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: gallery(${block})}" /></th:block>
|
||||||
<th:block th:case="'links'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbLinks(${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:case="*"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" /></th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染(布局模式下渲染端数据源已清空,双保险不漏段) -->
|
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染(布局模式下渲染端数据源已清空,双保险不漏段) -->
|
||||||
<th:block th:unless="${layoutBlocks != null}">
|
<th:block th:unless="${layoutBlocks != null}">
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<!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-A3 换皮):serviceList / serviceFilters / activeFilters / q;
|
||||||
|
筛选与分页 URL 全部后端预生成(facets[].url / pageUrls[].url),模板禁止手拼 query。
|
||||||
|
页壳/面包屑/页题/分页/空态沿用本包既有 .cg-list-main / .cg-page-card / .cg-breadcrumb /
|
||||||
|
.cg-page-title / .cg-pagination / .cg-empty;无侧栏,故不进 .cg-page-cols 网格。
|
||||||
|
数据绑定与 default、poly-blue 同构,仅换皮。 -->
|
||||||
|
<main class="cg-list-main cg-container" id="main-content">
|
||||||
|
<div class="cg-page-card">
|
||||||
|
<nav class="cg-breadcrumb" aria-label="面包屑">
|
||||||
|
<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">服务中心</h1>
|
||||||
|
|
||||||
|
<!-- P1-4 停机空态:svc-hall 不可达时降级渲染(200),不出 500 错误页 -->
|
||||||
|
<div class="cg-empty" th:if="${svcUnavailable}">
|
||||||
|
<p>事项服务暂时不可用,请稍后再试</p>
|
||||||
|
<p class="cg-empty-hint">事项数据服务暂不可达,稍后刷新即可恢复</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cg-svc-filters"
|
||||||
|
th:if="${!svcUnavailable and (!#lists.isEmpty(serviceFilters.objects) or !#lists.isEmpty(serviceFilters.categories) or !#lists.isEmpty(serviceFilters.depts))}">
|
||||||
|
<div class="cg-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.objects)}">
|
||||||
|
<span class="cg-svc-filter-label">服务对象</span>
|
||||||
|
<div class="cg-svc-filter-options">
|
||||||
|
<a class="cg-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="cg-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.categories)}">
|
||||||
|
<span class="cg-svc-filter-label">事项类别</span>
|
||||||
|
<div class="cg-svc-filter-options">
|
||||||
|
<a class="cg-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="cg-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.depts)}">
|
||||||
|
<span class="cg-svc-filter-label">服务部门</span>
|
||||||
|
<div class="cg-svc-filter-options">
|
||||||
|
<a class="cg-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="cg-svc-toolbar" th:if="${!svcUnavailable}">
|
||||||
|
<form class="cg-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="cg-svc-clear" th:if="${activeFilters.hasAny}" th:href="${activeFilters.clearUrl}">清除筛选</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="cg-svc-list" th:if="${!svcUnavailable}">
|
||||||
|
<li th:each="it : ${serviceList.items}">
|
||||||
|
<a class="cg-svc-item" th:href="${url.serviceItem(it.id)}">
|
||||||
|
<span class="cg-svc-item-cover" th:if="${it.coverImage != null}">
|
||||||
|
<img th:src="${url.upload(it.coverImage)}" th:alt="${it.title}">
|
||||||
|
</span>
|
||||||
|
<span class="cg-svc-item-body">
|
||||||
|
<span class="cg-svc-item-title">
|
||||||
|
<span class="cg-svc-item-name" th:text="${it.title}">事项名称</span>
|
||||||
|
<span class="cg-tag online" th:if="${it.onlineFlag == 1}">可在线办理</span>
|
||||||
|
<span class="cg-tag"
|
||||||
|
th:text="${it.handleMode == 1 ? '线上办理' : (it.handleMode == 2 ? '线下办理' : '线上线下')}">办理方式</span>
|
||||||
|
</span>
|
||||||
|
<span class="cg-svc-item-summary" th:if="${!#strings.isEmpty(it.summary)}"
|
||||||
|
th:text="${it.summary}">简介</span>
|
||||||
|
<span class="cg-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="cg-empty" th:if="${!svcUnavailable and #lists.isEmpty(serviceList.items)}">
|
||||||
|
<p th:if="${activeFilters.hasAny}">未找到符合条件的服务事项</p>
|
||||||
|
<p th:if="${!activeFilters.hasAny}">服务中心暂未上架事项</p>
|
||||||
|
<p class="cg-empty-hint" th:if="${activeFilters.hasAny}">换个筛选条件或关键词试试</p>
|
||||||
|
<p class="cg-empty-hint" th:if="${!activeFilters.hasAny}">事项发布后将在此展示</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页:URL 由后端预生成(携带当前筛选,pageUrls[].active 标记当前页);停机态 totalPages=0 自然隐藏 -->
|
||||||
|
<nav class="cg-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>
|
||||||
Reference in New Issue
Block a user