diff --git a/sql/edge_seed_visual.sql b/sql/edge_seed_visual.sql index 33a56df..93cf330 100644 --- a/sql/edge_seed_visual.sql +++ b/sql/edge_seed_visual.sql @@ -21,9 +21,10 @@ WHERE code = 'demo'; UPDATE cms_banner SET status = 0 WHERE id = 5; -- 2) 院系子站(demo 的子站:header 出"专业院系"下拉;不配 public_base_url=按请求派生) +-- 主题应用:jdx→cherry-bloom(樱语粉韵)、xxgc→violet-iris(鸢尾紫韵)——两套新皮肤随种子落地可视验收 INSERT INTO cms_site (id, code, parent_site_id, sch_id, sch_name, name, theme, config_json, status, create_by, create_time) VALUES -(6, 'jdx', 1, 990001, '云海大学', '机电学院', 'default', '{"copyright": "机电学院"}', 1, 'cmssuper', NOW()), -(7, 'xxgc', 1, 990001, '云海大学', '信息工程学院', 'default', '{"copyright": "信息工程学院"}', 1, 'cmssuper', NOW()); +(6, 'jdx', 1, 990001, '云海大学', '机电学院', 'cherry-bloom', '{"copyright": "机电学院"}', 1, 'cmssuper', NOW()), +(7, 'xxgc', 1, 990001, '云海大学', '信息工程学院', 'violet-iris', '{"copyright": "信息工程学院"}', 1, 'cmssuper', NOW()); INSERT INTO cms_category (id, site_id, parent_id, name, type, link_url, content, sort_no, status, create_by, create_time) VALUES (61, 6, 0, '学院概况', 2, NULL, '

机电学院(视觉精修种子)。

', 1, 1, 'cmssuper', NOW()), (62, 6, 0, '学院新闻', 1, NULL, NULL, 2, 1, 'cmssuper', NOW()), diff --git a/src/main/resources/templates/themes/cherry-bloom/README.md b/src/main/resources/templates/themes/cherry-bloom/README.md new file mode 100644 index 0000000..ebf6e2e --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/README.md @@ -0,0 +1,35 @@ +# cherry-bloom 皮肤包 + +由 mint-breeze 派生的温柔樱粉风皮肤(docs/10 契约 §5 工作流)。**升级随版本发布,不在站点间复制目录**。 + + +## 风格定位 + +温柔、甜美,适合幼儿园与小学门户:樱花粉品牌色、大圆角 16px、卡片纯白+暖粉柔和阴影、细线顶栏+居中 logo(桌面吸顶,移动端取消)、列表卡片网格(一行 3 张)、花瓣式圆点装饰与暖白留白;无纹理、无通栏大图、无复杂动效。 + +## 数据契约(模板可取用的 model 属性全集,契约 §3) + +| 页面 | 属性 | +| --- | --- | +| 全部 | `site`(快照:config/分类树/轮播/友链组/院系子站)、`url`(PortalUrlHelper:home/category/article/asset/search/siteHome)、`themeBase`(=`themes/cherry-bloom`)、`navCategories`、`pageTitle`、`seoDescription`、`canonical`、`themeParams`(微调参数,仅 theme.json 声明过的 key)、`themeVarsCss`(服务端预构建 `:root{}` 覆盖串,无参数为空) | +| home | `banners`(时间窗内 home 位)、`topArticles`(置顶+最新 6)、`categoryBlocks`(LIST 型根栏目块,各 top 8) | +| list / search | `listData`(分页)、`category`(list)、`q` + `searchMode`(search) | +| detail | `article`、`prevArticle`、`nextArticle`、`currentCategoryId` | +| single | `category`(含 `content`)、`currentCategoryId` | + +原则:皮肤不得假设"首页一定有轮播",对空集合优雅降级。 + +## 轮播图尺寸约定(bannerSize) + +1920x560(管理端上传端按此引导裁剪,D2 决策点)。 + +## 可调参数 + +见 `theme.json` 的 `params`: + +- **color**(进 CSS 变量):`primary` 主色 / `primaryDark` 主色加深(即强调色,用于品牌文字与悬停加深)/ `primaryLight` 主色提亮 / `bg` 页面底色 / `cardBg` 卡片底色 / `border` 边框色 / `text` 正文文字 / `textLight` 次要文字。 +- **select**:`listLayout` = `grid`(默认,卡片网格一行 3 张)或 `row`(传统行列表)。 + +换色建议:主色系三值同步调整(primary / primaryDark / primaryLight),bg 与 border、primaryLight 保持同族浅色调,避免底色与卡片对比失衡。 + +优先级:`custom_css` > `themeVarsCss` > `main.css`。 diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/css/main.css b/src/main/resources/templates/themes/cherry-bloom/assets/css/main.css new file mode 100644 index 0000000..ad7c1c9 --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/assets/css/main.css @@ -0,0 +1,616 @@ +/* ============================================================ + * cherry-bloom 皮肤包样式(mint-breeze 派生,docs/10 皮肤包契约 §5) + * 温柔樱粉风:樱花粉品牌色 + 大圆角 16px + 柔和暖粉阴影 + 卡片网格列表 + * + 花瓣式圆点装饰与暖白留白;无纹理、无通栏大图、无复杂动效。 + * 轮播图设计尺寸约定:1920×560(管理端上传端按此引导裁剪)。 + * ============================================================ */ +:root { + --primary: #DB5A8B; + --primary-dark: #B23A68; + --primary-light: #FDECF3; + --text: #43333B; + --text-light: #9C828C; + --bg: #FFFAFB; + --card-bg: #FFFFFF; + --border: #F7DDE7; + --radius: 16px; + --radius-sm: 10px; + --radius-pill: 999px; + --shadow-soft: 0 6px 18px rgba(178, 58, 104, .08); + --shadow-hover: 0 12px 28px rgba(178, 58, 104, .14); + --max-width: 1200px; +} + +* { box-sizing: border-box; } +body { + margin: 0; + font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; + color: var(--text); + background: var(--bg); +} +a { color: inherit; text-decoration: none; transition: color .15s ease; } +a:hover { color: var(--primary); } +img { max-width: 100%; } +time, .item-meta { color: var(--text-light); font-size: 13px; } +.empty { color: var(--text-light); text-align: center; padding: 32px 0; } + +/* ===== 头部:细线顶栏 + 居中品牌行 + 白底导航(桌面吸顶) ===== */ +.site-header { + position: sticky; + top: 0; + z-index: 90; + background: var(--card-bg); + box-shadow: var(--shadow-soft); +} +/* 细线顶栏:院系切换在左、站内搜索在右;院系未配置时搜索仍靠右 */ +.top-bar { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 16px; + padding: 7px max(16px, calc((100% - var(--max-width)) / 2 + 16px)); + border-bottom: 1px solid var(--border); + font-size: 13px; +} +.top-bar .site-colleges { margin-right: auto; } +/* 居中品牌行:两侧几何圆点装饰(纯 CSS,无纹理) */ +.brand-row { + position: relative; + display: flex; + justify-content: center; + padding: 18px 16px 14px; +} +.brand-row::before, .brand-row::after { + content: ""; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 46px; + height: 10px; + opacity: .45; + background-image: + radial-gradient(circle, var(--primary) 2.2px, transparent 2.8px), + radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px), + radial-gradient(circle, var(--primary) 1.1px, transparent 1.7px); + background-repeat: no-repeat; + background-position: left center, center center, right center; +} +.brand-row::before { left: 6%; } +.brand-row::after { right: 6%; } +.site-brand { display: flex; align-items: center; gap: 14px; } +.site-logo { height: 52px; } +.site-name { + font-size: 28px; + font-weight: 600; + letter-spacing: .02em; + color: var(--primary-dark); +} +.site-name:hover { color: var(--primary); } +.site-search { display: flex; align-items: center; } +.site-search input { + border: 1px solid var(--border); + border-right: none; + border-radius: var(--radius-pill) 0 0 var(--radius-pill); + padding: 7px 16px; + width: 220px; + outline: none; + background: var(--card-bg); + color: var(--text); + transition: border-color .15s ease, box-shadow .15s ease; +} +.site-search input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-light); +} +.site-search button { + border: none; + background: var(--primary); + color: #fff; + padding: 7px 20px; + border-radius: 0 var(--radius-pill) var(--radius-pill) 0; + cursor: pointer; + transition: background .15s ease; +} +.site-search button:hover { background: var(--primary-dark); } + +/* ===== 专业院系下拉(院系子站切换):胶囊开关 + 圆角白卡面板 ===== */ +.site-colleges { position: relative; } +.site-colleges .colleges-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + padding: 5px 14px; + cursor: pointer; + color: var(--primary-dark); + font-weight: 600; + white-space: nowrap; + transition: border-color .15s ease, background .15s ease; +} +.site-colleges:hover .colleges-toggle { + border-color: var(--primary); + background: var(--primary-light); +} +.site-colleges .colleges-arrow { font-style: normal; font-size: 12px; color: var(--primary); } +.site-colleges .colleges-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + min-width: 200px; + max-height: 420px; + overflow-y: auto; + margin: 0; + padding: 8px 0; + list-style: none; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-hover); + z-index: 110; +} +.site-colleges:hover .colleges-menu { display: block; } +.site-colleges .colleges-menu li a { + display: block; + margin: 0 8px; + padding: 9px 12px; + border-radius: var(--radius-sm); + color: var(--text); + white-space: nowrap; +} +.site-colleges .colleges-menu li a:hover { background: var(--primary-light); color: var(--primary-dark); } +.site-colleges .colleges-menu li.active a { color: var(--primary); font-weight: 600; } + +/* ===== 主导航:白底 + 柔和文字层级,当前项圆点标记 ===== */ +.site-nav { background: var(--card-bg); border-top: 1px solid var(--border); } +.site-nav ul { + max-width: var(--max-width); + margin: 0 auto; + padding: 0 16px; + list-style: none; + display: flex; + justify-content: center; + flex-wrap: wrap; +} +.site-nav li a { + position: relative; + display: block; + padding: 13px 20px; + color: var(--text); + font-weight: 500; +} +.site-nav li a:hover { color: var(--primary); } +.site-nav li.active a { color: var(--primary); font-weight: 600; } +/* 几何圆点:当前栏目下方的小圆点 */ +.site-nav li.active > a::before { + content: ""; + position: absolute; + left: 50%; + bottom: 5px; + width: 5px; + height: 5px; + transform: translateX(-50%); + border-radius: 50%; + background: var(--primary); +} + +/* ===== 二级下拉导航:圆角白卡面板 ===== */ +.site-nav > ul > li { position: relative; } +.site-nav li.has-sub > a::after { content: " ▾"; font-size: 12px; opacity: .7; } +.site-nav ul.sub-nav { + display: none; + position: absolute; + top: 100%; + left: 0; + min-width: 168px; + max-width: none; + margin: 0; + padding: 8px 0; + list-style: none; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-hover); + z-index: 100; +} +.site-nav li:hover > .sub-nav { display: block; } +.site-nav .sub-nav li a { + margin: 0 8px; + padding: 9px 12px; + border-radius: var(--radius-sm); + color: var(--text); + font-weight: 400; + white-space: nowrap; + background: none; +} +.site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); } +.site-nav .sub-nav li.active a { color: var(--primary); font-weight: 600; } + +/* ===== 首页轮播:容器内圆角卡片(非通栏),几何圆点指示器 ===== */ +.banner { + position: relative; + width: min(var(--max-width), calc(100% - 32px)); + margin: 24px auto 0; + border-radius: var(--radius); + overflow: hidden; + box-shadow: var(--shadow-soft); +} +.banner-slides { position: relative; height: 400px; } +.banner-slide { display: none; position: absolute; inset: 0; } +.banner-slide.active { display: block; } +.banner-slide img { width: 100%; height: 100%; object-fit: cover; } +.banner-title { + position: absolute; + left: 0; right: 0; bottom: 0; + padding: 14px 20px; + color: #fff; + font-weight: 500; + background: linear-gradient(transparent, rgba(20, 63, 61, .62)); +} +.banner-dots { + position: absolute; + bottom: 14px; right: 20px; + display: flex; gap: 8px; +} +.banner-dots button { + width: 9px; height: 9px; + border-radius: 50%; + border: none; + background: rgba(255, 255, 255, .55); + cursor: pointer; + transition: background .15s ease, transform .15s ease; +} +.banner-dots button.active { background: #fff; transform: scale(1.25); } + +/* ===== 首页区块:卡片网格(头条通栏 + 栏目块一行 3 张) ===== */ +.home-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 24px; +} +.top-strip, .category-block { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 24px; +} +.top-strip { grid-column: 1 / -1; } +/* 栏目标题:几何圆点标记(环形点),标题中等粗细、层级柔和 */ +.section-title, .block-head h2 { + margin: 0 0 14px; + font-size: 19px; + font-weight: 600; + letter-spacing: .02em; + display: flex; + align-items: center; + gap: 10px; +} +.section-title::before, .block-head 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); +} +.block-head { display: flex; align-items: center; justify-content: space-between; } +.more { + flex: none; + font-size: 13px; + color: var(--text-light); + padding: 4px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + transition: color .15s ease, border-color .15s ease, background .15s ease; +} +.more:hover { + color: var(--primary-dark); + border-color: var(--primary); + background: var(--primary-light); +} +.top-strip ul, .block-list { list-style: none; margin: 0; padding: 0; } +.top-strip li, .block-list li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 0; + border-bottom: 1px dashed var(--border); +} +.top-strip li:last-child, .block-list li:last-child { border-bottom: none; } +.top-strip li a, .block-list li a { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.top-badge { + flex: none; + color: var(--primary-dark); + background: var(--primary-light); + font-size: 12px; + font-weight: 500; + padding: 2px 10px; + border-radius: var(--radius-pill); +} + +/* ===== 列表页:卡片网格(默认一行 3 张;listLayout=row 切换行列表) ===== */ +.list-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; +} +.breadcrumb { color: var(--text-light); font-size: 13px; margin-bottom: 14px; } +.breadcrumb a { margin-right: 8px; } +.breadcrumb span { margin-left: 8px; } +.page-title { + font-size: 24px; + font-weight: 600; + letter-spacing: .02em; + margin: 0 0 22px; + display: flex; + align-items: center; + gap: 10px; +} +.page-title::before { + content: ""; + flex: none; + width: 9px; + height: 9px; + border-radius: 50%; + background: var(--primary); + box-shadow: 0 0 0 3.5px var(--primary-light); +} +.article-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; +} +.article-list li { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 22px 20px; + display: flex; + flex-direction: column; + gap: 10px; + transition: transform .18s ease, box-shadow .18s ease; +} +.article-list li:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); } +.item-title { + font-size: 16px; + font-weight: 600; + line-height: 1.55; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} +.article-list li:hover .item-title { color: var(--primary); } +.item-summary { + color: var(--text-light); + font-size: 13.5px; + line-height: 1.7; + margin: 0; + flex: 1; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} +.item-meta { + display: flex; + gap: 10px; + align-items: center; + margin-top: auto; + padding-top: 10px; + border-top: 1px dashed var(--border); +} +/* 布局变体:传统行列表(themeParams listLayout=row,见 list.html classappend) */ +.list-rows .article-list { display: block; } +.list-rows .article-list li { display: block; margin-bottom: 16px; } +.list-rows .item-summary { -webkit-line-clamp: 2; flex: none; } +.list-rows .item-meta { border-top: none; padding-top: 0; margin-top: 6px; } + +/* ===== 分页:胶囊页码 ===== */ +.pagination { + display: flex; + gap: 8px; + justify-content: center; + flex-wrap: wrap; + padding: 28px 0 4px; +} +.pagination a { + min-width: 36px; + text-align: center; + padding: 6px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + background: var(--card-bg); + font-size: 14px; + transition: color .15s ease, border-color .15s ease, background .15s ease; +} +.pagination a:hover { color: var(--primary-dark); border-color: var(--primary); } +.pagination a.active { background: var(--primary); color: #fff; border-color: var(--primary); } + +/* ===== 详情/单页:整卡承载,正文舒适行距 ===== */ +.detail-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; +} +.detail-main .article { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 36px 40px; +} +.article-title { + margin: 4px 0 14px; + font-size: 26px; + font-weight: 600; + letter-spacing: .02em; + text-align: center; +} +.article-meta { + display: flex; + gap: 18px; + justify-content: center; + flex-wrap: wrap; + color: var(--text-light); + font-size: 13px; + padding-bottom: 18px; + border-bottom: 1px solid var(--border); +} +.article-content { line-height: 1.9; font-size: 16px; padding-top: 22px; word-break: break-word; } +.article-content img { height: auto; border-radius: var(--radius-sm); } +.article-content table { border-collapse: collapse; width: 100%; } +.article-content table td, .article-content table th { border: 1px solid var(--border); padding: 6px 10px; } +.article-sibling { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px; font-size: 14px; } +.article-sibling a { + flex: 1 1 320px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 14px 18px; + transition: transform .18s ease, box-shadow .18s ease, color .15s ease; +} +.article-sibling a:hover { + color: var(--primary-dark); + transform: translateY(-2px); + box-shadow: var(--shadow-hover); +} + +/* ===== 页脚:浅樱粉底 + 圆点分隔条(三态渲染逻辑不动) ===== */ +.site-footer { + margin-top: 40px; + background: var(--primary-light); + color: var(--text); + border-top: 1px solid var(--border); +} +.site-footer::before { + content: ""; + display: block; + height: 8px; + width: min(var(--max-width), calc(100% - 32px)); + margin: 18px auto 0; + background-image: radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px); + background-size: 16px 8px; + background-repeat: repeat-x; + opacity: .4; +} +.footer-links { + max-width: var(--max-width); + margin: 0 auto; + padding: 18px 16px 0; + display: flex; + flex-direction: column; + gap: 10px; +} +.links-group { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; font-size: 13px; } +.group-name { font-weight: 600; margin-right: 4px; color: var(--primary-dark); } +.links-group a { + padding: 3px 10px; + border-radius: var(--radius-pill); + transition: background .15s ease, color .15s ease; +} +.links-group a:hover { background: rgba(255, 255, 255, .75); color: var(--primary-dark); } +.footer-info { + max-width: var(--max-width); + margin: 0 auto; + padding: 14px 16px 24px; + text-align: center; + font-size: 13px; + color: var(--text-light); +} +.footer-info p { margin: 4px 0; } + +/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */ +.links-group a .link-logo { + height: 20px; + width: auto; + max-width: 64px; + object-fit: contain; + vertical-align: middle; + margin-right: 4px; + border-radius: 4px; +} + +/* ===== 平板(≤992px):网格降为两列,隐藏品牌行圆点装饰 ===== */ +@media (max-width: 992px) { + .home-main { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .article-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .brand-row::before, .brand-row::after { display: none; } +} + +/* ===== 移动端(≤768px):取消吸顶,网格单列,导航横向滑动 ===== */ +@media (max-width: 768px) { + .site-header { position: static; } + .top-bar { padding: 8px 14px; justify-content: flex-start; flex-wrap: wrap; } + .top-bar .site-colleges { margin-right: 0; } + .site-search { flex: 1; } + .site-search input { flex: 1; width: auto; min-width: 0; } + .brand-row { padding: 14px 16px 10px; } + .site-logo { height: 40px; } + .site-name { font-size: 22px; } + .site-nav ul { + justify-content: flex-start; + flex-wrap: nowrap; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .banner-slides { height: 200px; } + .home-main { grid-template-columns: 1fr; padding: 18px 14px 40px; } + .article-list { grid-template-columns: 1fr; } + .detail-main .article { padding: 22px 16px; } + .article-title { font-size: 21px; } + /* 触屏无 hover:子菜单静态平铺(缩进列表),不做浮层 */ + .site-nav ul.sub-nav { + display: block; + position: static; + border: none; + box-shadow: none; + background: transparent; + min-width: 0; + padding: 0 0 6px; + } + .site-nav .sub-nav li a { margin: 0; padding: 8px 16px 8px 34px; border-radius: 0; color: var(--text-light); } + .site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); } +} + + +/* ===== 左侧栏目导航(docs/11):有二级节点的模块,栏目/文章页左侧展示兄弟目录 ===== */ +.list-main.has-sidenav, .detail-main.has-sidenav { display: flex; align-items: flex-start; gap: 24px; } +.has-sidenav .list-body { flex: 1; min-width: 0; } +.side-nav { flex: none; width: 220px; background: var(--card-bg, #fff); border: 1px solid var(--border); overflow: hidden; } +.side-nav-head { background: var(--primary); color: #fff; font-size: 20px; font-weight: 700; text-align: center; padding: 18px 12px; letter-spacing: 2px; } +.side-nav-list { list-style: none; margin: 0; padding: 0; } +.side-nav-item + .side-nav-item { border-top: 1px dashed var(--border); } +.side-nav-item a { display: block; padding: 12px 16px; color: var(--text); text-decoration: none; font-size: 15px; transition: background .15s ease, color .15s ease; } +.side-nav-item a:hover { color: var(--primary); background: var(--bg); } +.side-nav-item.active a { background: var(--primary); color: #fff; font-weight: 600; text-decoration: none; } +/* 顶导航二级下拉当前项高亮(docs/11 顺带) */ +.site-nav ul.sub-nav li.active a { color: var(--primary); } +@media (max-width: 768px) { + .list-main.has-sidenav, .detail-main.has-sidenav { flex-direction: column; gap: 12px; } + .side-nav { width: 100%; } + .side-nav-list { display: flex; overflow-x: auto; -webkit-overflow-scrolling: touch; } + .side-nav-item { flex: none; } + .side-nav-item + .side-nav-item { border-top: none; border-left: 1px dashed var(--border); } + .side-nav-item a { white-space: nowrap; } +} +@media print { + .side-nav { display: none; } + .list-main.has-sidenav, .detail-main.has-sidenav { display: block; } +} diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/favicon.png b/src/main/resources/templates/themes/cherry-bloom/assets/favicon.png new file mode 100644 index 0000000..497cae1 Binary files /dev/null and b/src/main/resources/templates/themes/cherry-bloom/assets/favicon.png differ diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/favicon.svg b/src/main/resources/templates/themes/cherry-bloom/assets/favicon.svg new file mode 100644 index 0000000..56ed83c --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/assets/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/js/main.js b/src/main/resources/templates/themes/cherry-bloom/assets/js/main.js new file mode 100644 index 0000000..a077c85 --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/assets/js/main.js @@ -0,0 +1,40 @@ +/* cherry-bloom 皮肤包脚本:仅首页轮播自动播放与圆点切换,无任何外部依赖 */ +(function () { + var slides = document.querySelectorAll('.banner-slide'); + var dots = document.querySelectorAll('.banner-dots button'); + if (!slides.length) { + return; + } + var current = 0; + var timer = null; + + 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() { + if (slides.length > 1) { + timer = window.setInterval(function () { + show(current + 1); + }, 5000); + } + } + + dots.forEach(function (dot, index) { + dot.addEventListener('click', function () { + window.clearInterval(timer); + show(index); + start(); + }); + }); + + start(); +})(); diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/screenshot.png b/src/main/resources/templates/themes/cherry-bloom/assets/screenshot.png new file mode 100644 index 0000000..b6a7d0c Binary files /dev/null and b/src/main/resources/templates/themes/cherry-bloom/assets/screenshot.png differ diff --git a/src/main/resources/templates/themes/cherry-bloom/detail.html b/src/main/resources/templates/themes/cherry-bloom/detail.html new file mode 100644 index 0000000..8eab71f --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/detail.html @@ -0,0 +1,58 @@ + + + + +
+ +
+ + +
+ + +
+

文章标题

+ + +
正文
+
+ + +
+
+ + + + diff --git a/src/main/resources/templates/themes/cherry-bloom/fragments/footer.html b/src/main/resources/templates/themes/cherry-bloom/fragments/footer.html new file mode 100644 index 0000000..1f3f83d --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/fragments/footer.html @@ -0,0 +1,21 @@ + + + + + diff --git a/src/main/resources/templates/themes/cherry-bloom/fragments/head.html b/src/main/resources/templates/themes/cherry-bloom/fragments/head.html new file mode 100644 index 0000000..1809482 --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/fragments/head.html @@ -0,0 +1,24 @@ + + + + + + + 页面标题 + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/themes/cherry-bloom/fragments/header.html b/src/main/resources/templates/themes/cherry-bloom/fragments/header.html new file mode 100644 index 0000000..903691c --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/fragments/header.html @@ -0,0 +1,56 @@ + + + + + diff --git a/src/main/resources/templates/themes/cherry-bloom/home.html b/src/main/resources/templates/themes/cherry-bloom/home.html new file mode 100644 index 0000000..8d4f072 --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/home.html @@ -0,0 +1,68 @@ + + + + +
+ +
+ + + + + + + + + + + + + + +
+

头条推荐

+ +
+ + +
+
+

栏目名

+ 更多 > +
+ +

暂无内容

+
+
+
+ + + + + diff --git a/src/main/resources/templates/themes/cherry-bloom/list.html b/src/main/resources/templates/themes/cherry-bloom/list.html new file mode 100644 index 0000000..14c47c4 --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/list.html @@ -0,0 +1,69 @@ + + + + +
+ + +
+ + +
+ + + +

栏目名

+

搜索词

+ + +

暂无内容

+ + + +
+
+ + + + + diff --git a/src/main/resources/templates/themes/cherry-bloom/single.html b/src/main/resources/templates/themes/cherry-bloom/single.html new file mode 100644 index 0000000..65292dc --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/single.html @@ -0,0 +1,41 @@ + + + + +
+ +
+ + +
+ + +
+

栏目名

+ +
单页内容
+

暂无内容

+
+
+
+ + + + diff --git a/src/main/resources/templates/themes/cherry-bloom/theme.json b/src/main/resources/templates/themes/cherry-bloom/theme.json new file mode 100644 index 0000000..5d24a11 --- /dev/null +++ b/src/main/resources/templates/themes/cherry-bloom/theme.json @@ -0,0 +1,22 @@ +{ + "specVersion": 1, + "name": "cherry-bloom", + "title": "樱语粉韵", + "version": "1.0.0", + "author": "yunhai", + "description": "温柔樱粉风门户:细线顶栏+居中 logo 吸顶、樱花粉品牌色、大圆角 16px 卡片网格列表、花瓣式圆点装饰与暖白留白,甜美亲和,适合幼儿园与小学门户。", + "screenshot": "assets/screenshot.png", + "bannerSize": "1920x560", + "params": [ + { "key": "primary", "cssVar": "--primary", "label": "主色", "type": "color", "default": "#DB5A8B" }, + { "key": "primaryDark", "cssVar": "--primary-dark", "label": "主色加深(强调色)", "type": "color", "default": "#B23A68" }, + { "key": "primaryLight", "cssVar": "--primary-light", "label": "主色提亮", "type": "color", "default": "#FDECF3" }, + { "key": "bg", "cssVar": "--bg", "label": "页面底色", "type": "color", "default": "#FFFAFB" }, + { "key": "cardBg", "cssVar": "--card-bg", "label": "卡片底色", "type": "color", "default": "#FFFFFF" }, + { "key": "border", "cssVar": "--border", "label": "边框色", "type": "color", "default": "#F7DDE7" }, + { "key": "text", "cssVar": "--text", "label": "正文文字色", "type": "color", "default": "#43333B" }, + { "key": "textLight", "cssVar": "--text-light", "label": "次要文字色", "type": "color", "default": "#9C828C" }, + { "key": "listLayout", "label": "列表布局", "type": "select", "default": "grid", + "options": [ { "value": "grid", "label": "卡片网格(一行 3 张)" }, { "value": "row", "label": "传统行列表" } ] } + ] +} diff --git a/src/main/resources/templates/themes/violet-iris/README.md b/src/main/resources/templates/themes/violet-iris/README.md new file mode 100644 index 0000000..747398a --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/README.md @@ -0,0 +1,35 @@ +# violet-iris 皮肤包 + +由 mint-breeze 派生的雅致鸢尾紫皮肤(docs/10 契约 §5 工作流)。**升级随版本发布,不在站点间复制目录**。 + + +## 风格定位 + +沉静、书卷气,适合高校与教科研机构门户:鸢尾紫品牌色、圆角 12px、卡片纯白+淡紫柔和阴影、细线顶栏+居中 logo(桌面吸顶,移动端取消)、列表卡片网格(一行 3 张)、几何圆点装饰与大留白;无纹理、无通栏大图、无复杂动效。 + +## 数据契约(模板可取用的 model 属性全集,契约 §3) + +| 页面 | 属性 | +| --- | --- | +| 全部 | `site`(快照:config/分类树/轮播/友链组/院系子站)、`url`(PortalUrlHelper:home/category/article/asset/search/siteHome)、`themeBase`(=`themes/violet-iris`)、`navCategories`、`pageTitle`、`seoDescription`、`canonical`、`themeParams`(微调参数,仅 theme.json 声明过的 key)、`themeVarsCss`(服务端预构建 `:root{}` 覆盖串,无参数为空) | +| home | `banners`(时间窗内 home 位)、`topArticles`(置顶+最新 6)、`categoryBlocks`(LIST 型根栏目块,各 top 8) | +| list / search | `listData`(分页)、`category`(list)、`q` + `searchMode`(search) | +| detail | `article`、`prevArticle`、`nextArticle`、`currentCategoryId` | +| single | `category`(含 `content`)、`currentCategoryId` | + +原则:皮肤不得假设"首页一定有轮播",对空集合优雅降级。 + +## 轮播图尺寸约定(bannerSize) + +1920x560(管理端上传端按此引导裁剪,D2 决策点)。 + +## 可调参数 + +见 `theme.json` 的 `params`: + +- **color**(进 CSS 变量):`primary` 主色 / `primaryDark` 主色加深(即强调色,用于品牌文字与悬停加深)/ `primaryLight` 主色提亮 / `bg` 页面底色 / `cardBg` 卡片底色 / `border` 边框色 / `text` 正文文字 / `textLight` 次要文字。 +- **select**:`listLayout` = `grid`(默认,卡片网格一行 3 张)或 `row`(传统行列表)。 + +换色建议:主色系三值同步调整(primary / primaryDark / primaryLight),bg 与 border、primaryLight 保持同族浅色调,避免底色与卡片对比失衡。 + +优先级:`custom_css` > `themeVarsCss` > `main.css`。 diff --git a/src/main/resources/templates/themes/violet-iris/assets/css/main.css b/src/main/resources/templates/themes/violet-iris/assets/css/main.css new file mode 100644 index 0000000..29966fd --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/assets/css/main.css @@ -0,0 +1,616 @@ +/* ============================================================ + * violet-iris 皮肤包样式(mint-breeze 派生,docs/10 皮肤包契约 §5) + * 雅致鸢尾紫风:鸢尾紫品牌色 + 圆角 12px + 柔和紫阴影 + 卡片网格列表 + * + 几何圆点装饰与淡薰衣草留白;无纹理、无通栏大图、无复杂动效。 + * 轮播图设计尺寸约定:1920×560(管理端上传端按此引导裁剪)。 + * ============================================================ */ +:root { + --primary: #6552D9; + --primary-dark: #4A39B0; + --primary-light: #EFECFC; + --text: #332F45; + --text-light: #847E9C; + --bg: #F8F7FD; + --card-bg: #FFFFFF; + --border: #E4DFF6; + --radius: 12px; + --radius-sm: 8px; + --radius-pill: 999px; + --shadow-soft: 0 6px 18px rgba(74, 57, 176, .08); + --shadow-hover: 0 12px 28px rgba(74, 57, 176, .14); + --max-width: 1200px; +} + +* { box-sizing: border-box; } +body { + margin: 0; + font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; + color: var(--text); + background: var(--bg); +} +a { color: inherit; text-decoration: none; transition: color .15s ease; } +a:hover { color: var(--primary); } +img { max-width: 100%; } +time, .item-meta { color: var(--text-light); font-size: 13px; } +.empty { color: var(--text-light); text-align: center; padding: 32px 0; } + +/* ===== 头部:细线顶栏 + 居中品牌行 + 白底导航(桌面吸顶) ===== */ +.site-header { + position: sticky; + top: 0; + z-index: 90; + background: var(--card-bg); + box-shadow: var(--shadow-soft); +} +/* 细线顶栏:院系切换在左、站内搜索在右;院系未配置时搜索仍靠右 */ +.top-bar { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 16px; + padding: 7px max(16px, calc((100% - var(--max-width)) / 2 + 16px)); + border-bottom: 1px solid var(--border); + font-size: 13px; +} +.top-bar .site-colleges { margin-right: auto; } +/* 居中品牌行:两侧几何圆点装饰(纯 CSS,无纹理) */ +.brand-row { + position: relative; + display: flex; + justify-content: center; + padding: 18px 16px 14px; +} +.brand-row::before, .brand-row::after { + content: ""; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 46px; + height: 10px; + opacity: .45; + background-image: + radial-gradient(circle, var(--primary) 2.2px, transparent 2.8px), + radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px), + radial-gradient(circle, var(--primary) 1.1px, transparent 1.7px); + background-repeat: no-repeat; + background-position: left center, center center, right center; +} +.brand-row::before { left: 6%; } +.brand-row::after { right: 6%; } +.site-brand { display: flex; align-items: center; gap: 14px; } +.site-logo { height: 52px; } +.site-name { + font-size: 28px; + font-weight: 600; + letter-spacing: .02em; + color: var(--primary-dark); +} +.site-name:hover { color: var(--primary); } +.site-search { display: flex; align-items: center; } +.site-search input { + border: 1px solid var(--border); + border-right: none; + border-radius: var(--radius-pill) 0 0 var(--radius-pill); + padding: 7px 16px; + width: 220px; + outline: none; + background: var(--card-bg); + color: var(--text); + transition: border-color .15s ease, box-shadow .15s ease; +} +.site-search input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-light); +} +.site-search button { + border: none; + background: var(--primary); + color: #fff; + padding: 7px 20px; + border-radius: 0 var(--radius-pill) var(--radius-pill) 0; + cursor: pointer; + transition: background .15s ease; +} +.site-search button:hover { background: var(--primary-dark); } + +/* ===== 专业院系下拉(院系子站切换):胶囊开关 + 圆角白卡面板 ===== */ +.site-colleges { position: relative; } +.site-colleges .colleges-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + padding: 5px 14px; + cursor: pointer; + color: var(--primary-dark); + font-weight: 600; + white-space: nowrap; + transition: border-color .15s ease, background .15s ease; +} +.site-colleges:hover .colleges-toggle { + border-color: var(--primary); + background: var(--primary-light); +} +.site-colleges .colleges-arrow { font-style: normal; font-size: 12px; color: var(--primary); } +.site-colleges .colleges-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + min-width: 200px; + max-height: 420px; + overflow-y: auto; + margin: 0; + padding: 8px 0; + list-style: none; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-hover); + z-index: 110; +} +.site-colleges:hover .colleges-menu { display: block; } +.site-colleges .colleges-menu li a { + display: block; + margin: 0 8px; + padding: 9px 12px; + border-radius: var(--radius-sm); + color: var(--text); + white-space: nowrap; +} +.site-colleges .colleges-menu li a:hover { background: var(--primary-light); color: var(--primary-dark); } +.site-colleges .colleges-menu li.active a { color: var(--primary); font-weight: 600; } + +/* ===== 主导航:白底 + 柔和文字层级,当前项圆点标记 ===== */ +.site-nav { background: var(--card-bg); border-top: 1px solid var(--border); } +.site-nav ul { + max-width: var(--max-width); + margin: 0 auto; + padding: 0 16px; + list-style: none; + display: flex; + justify-content: center; + flex-wrap: wrap; +} +.site-nav li a { + position: relative; + display: block; + padding: 13px 20px; + color: var(--text); + font-weight: 500; +} +.site-nav li a:hover { color: var(--primary); } +.site-nav li.active a { color: var(--primary); font-weight: 600; } +/* 几何圆点:当前栏目下方的小圆点 */ +.site-nav li.active > a::before { + content: ""; + position: absolute; + left: 50%; + bottom: 5px; + width: 5px; + height: 5px; + transform: translateX(-50%); + border-radius: 50%; + background: var(--primary); +} + +/* ===== 二级下拉导航:圆角白卡面板 ===== */ +.site-nav > ul > li { position: relative; } +.site-nav li.has-sub > a::after { content: " ▾"; font-size: 12px; opacity: .7; } +.site-nav ul.sub-nav { + display: none; + position: absolute; + top: 100%; + left: 0; + min-width: 168px; + max-width: none; + margin: 0; + padding: 8px 0; + list-style: none; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-hover); + z-index: 100; +} +.site-nav li:hover > .sub-nav { display: block; } +.site-nav .sub-nav li a { + margin: 0 8px; + padding: 9px 12px; + border-radius: var(--radius-sm); + color: var(--text); + font-weight: 400; + white-space: nowrap; + background: none; +} +.site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); } +.site-nav .sub-nav li.active a { color: var(--primary); font-weight: 600; } + +/* ===== 首页轮播:容器内圆角卡片(非通栏),几何圆点指示器 ===== */ +.banner { + position: relative; + width: min(var(--max-width), calc(100% - 32px)); + margin: 24px auto 0; + border-radius: var(--radius); + overflow: hidden; + box-shadow: var(--shadow-soft); +} +.banner-slides { position: relative; height: 400px; } +.banner-slide { display: none; position: absolute; inset: 0; } +.banner-slide.active { display: block; } +.banner-slide img { width: 100%; height: 100%; object-fit: cover; } +.banner-title { + position: absolute; + left: 0; right: 0; bottom: 0; + padding: 14px 20px; + color: #fff; + font-weight: 500; + background: linear-gradient(transparent, rgba(20, 63, 61, .62)); +} +.banner-dots { + position: absolute; + bottom: 14px; right: 20px; + display: flex; gap: 8px; +} +.banner-dots button { + width: 9px; height: 9px; + border-radius: 50%; + border: none; + background: rgba(255, 255, 255, .55); + cursor: pointer; + transition: background .15s ease, transform .15s ease; +} +.banner-dots button.active { background: #fff; transform: scale(1.25); } + +/* ===== 首页区块:卡片网格(头条通栏 + 栏目块一行 3 张) ===== */ +.home-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 24px; +} +.top-strip, .category-block { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 24px; +} +.top-strip { grid-column: 1 / -1; } +/* 栏目标题:几何圆点标记(环形点),标题中等粗细、层级柔和 */ +.section-title, .block-head h2 { + margin: 0 0 14px; + font-size: 19px; + font-weight: 600; + letter-spacing: .02em; + display: flex; + align-items: center; + gap: 10px; +} +.section-title::before, .block-head 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); +} +.block-head { display: flex; align-items: center; justify-content: space-between; } +.more { + flex: none; + font-size: 13px; + color: var(--text-light); + padding: 4px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + transition: color .15s ease, border-color .15s ease, background .15s ease; +} +.more:hover { + color: var(--primary-dark); + border-color: var(--primary); + background: var(--primary-light); +} +.top-strip ul, .block-list { list-style: none; margin: 0; padding: 0; } +.top-strip li, .block-list li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 0; + border-bottom: 1px dashed var(--border); +} +.top-strip li:last-child, .block-list li:last-child { border-bottom: none; } +.top-strip li a, .block-list li a { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.top-badge { + flex: none; + color: var(--primary-dark); + background: var(--primary-light); + font-size: 12px; + font-weight: 500; + padding: 2px 10px; + border-radius: var(--radius-pill); +} + +/* ===== 列表页:卡片网格(默认一行 3 张;listLayout=row 切换行列表) ===== */ +.list-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; +} +.breadcrumb { color: var(--text-light); font-size: 13px; margin-bottom: 14px; } +.breadcrumb a { margin-right: 8px; } +.breadcrumb span { margin-left: 8px; } +.page-title { + font-size: 24px; + font-weight: 600; + letter-spacing: .02em; + margin: 0 0 22px; + display: flex; + align-items: center; + gap: 10px; +} +.page-title::before { + content: ""; + flex: none; + width: 9px; + height: 9px; + border-radius: 50%; + background: var(--primary); + box-shadow: 0 0 0 3.5px var(--primary-light); +} +.article-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; +} +.article-list li { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 22px 20px; + display: flex; + flex-direction: column; + gap: 10px; + transition: transform .18s ease, box-shadow .18s ease; +} +.article-list li:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); } +.item-title { + font-size: 16px; + font-weight: 600; + line-height: 1.55; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} +.article-list li:hover .item-title { color: var(--primary); } +.item-summary { + color: var(--text-light); + font-size: 13.5px; + line-height: 1.7; + margin: 0; + flex: 1; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} +.item-meta { + display: flex; + gap: 10px; + align-items: center; + margin-top: auto; + padding-top: 10px; + border-top: 1px dashed var(--border); +} +/* 布局变体:传统行列表(themeParams listLayout=row,见 list.html classappend) */ +.list-rows .article-list { display: block; } +.list-rows .article-list li { display: block; margin-bottom: 16px; } +.list-rows .item-summary { -webkit-line-clamp: 2; flex: none; } +.list-rows .item-meta { border-top: none; padding-top: 0; margin-top: 6px; } + +/* ===== 分页:胶囊页码 ===== */ +.pagination { + display: flex; + gap: 8px; + justify-content: center; + flex-wrap: wrap; + padding: 28px 0 4px; +} +.pagination a { + min-width: 36px; + text-align: center; + padding: 6px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + background: var(--card-bg); + font-size: 14px; + transition: color .15s ease, border-color .15s ease, background .15s ease; +} +.pagination a:hover { color: var(--primary-dark); border-color: var(--primary); } +.pagination a.active { background: var(--primary); color: #fff; border-color: var(--primary); } + +/* ===== 详情/单页:整卡承载,正文舒适行距 ===== */ +.detail-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; +} +.detail-main .article { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 36px 40px; +} +.article-title { + margin: 4px 0 14px; + font-size: 26px; + font-weight: 600; + letter-spacing: .02em; + text-align: center; +} +.article-meta { + display: flex; + gap: 18px; + justify-content: center; + flex-wrap: wrap; + color: var(--text-light); + font-size: 13px; + padding-bottom: 18px; + border-bottom: 1px solid var(--border); +} +.article-content { line-height: 1.9; font-size: 16px; padding-top: 22px; word-break: break-word; } +.article-content img { height: auto; border-radius: var(--radius-sm); } +.article-content table { border-collapse: collapse; width: 100%; } +.article-content table td, .article-content table th { border: 1px solid var(--border); padding: 6px 10px; } +.article-sibling { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px; font-size: 14px; } +.article-sibling a { + flex: 1 1 320px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 14px 18px; + transition: transform .18s ease, box-shadow .18s ease, color .15s ease; +} +.article-sibling a:hover { + color: var(--primary-dark); + transform: translateY(-2px); + box-shadow: var(--shadow-hover); +} + +/* ===== 页脚:浅鸢尾底 + 圆点分隔条(三态渲染逻辑不动) ===== */ +.site-footer { + margin-top: 40px; + background: var(--primary-light); + color: var(--text); + border-top: 1px solid var(--border); +} +.site-footer::before { + content: ""; + display: block; + height: 8px; + width: min(var(--max-width), calc(100% - 32px)); + margin: 18px auto 0; + background-image: radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px); + background-size: 16px 8px; + background-repeat: repeat-x; + opacity: .4; +} +.footer-links { + max-width: var(--max-width); + margin: 0 auto; + padding: 18px 16px 0; + display: flex; + flex-direction: column; + gap: 10px; +} +.links-group { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; font-size: 13px; } +.group-name { font-weight: 600; margin-right: 4px; color: var(--primary-dark); } +.links-group a { + padding: 3px 10px; + border-radius: var(--radius-pill); + transition: background .15s ease, color .15s ease; +} +.links-group a:hover { background: rgba(255, 255, 255, .75); color: var(--primary-dark); } +.footer-info { + max-width: var(--max-width); + margin: 0 auto; + padding: 14px 16px 24px; + text-align: center; + font-size: 13px; + color: var(--text-light); +} +.footer-info p { margin: 4px 0; } + +/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */ +.links-group a .link-logo { + height: 20px; + width: auto; + max-width: 64px; + object-fit: contain; + vertical-align: middle; + margin-right: 4px; + border-radius: 4px; +} + +/* ===== 平板(≤992px):网格降为两列,隐藏品牌行圆点装饰 ===== */ +@media (max-width: 992px) { + .home-main { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .article-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .brand-row::before, .brand-row::after { display: none; } +} + +/* ===== 移动端(≤768px):取消吸顶,网格单列,导航横向滑动 ===== */ +@media (max-width: 768px) { + .site-header { position: static; } + .top-bar { padding: 8px 14px; justify-content: flex-start; flex-wrap: wrap; } + .top-bar .site-colleges { margin-right: 0; } + .site-search { flex: 1; } + .site-search input { flex: 1; width: auto; min-width: 0; } + .brand-row { padding: 14px 16px 10px; } + .site-logo { height: 40px; } + .site-name { font-size: 22px; } + .site-nav ul { + justify-content: flex-start; + flex-wrap: nowrap; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .banner-slides { height: 200px; } + .home-main { grid-template-columns: 1fr; padding: 18px 14px 40px; } + .article-list { grid-template-columns: 1fr; } + .detail-main .article { padding: 22px 16px; } + .article-title { font-size: 21px; } + /* 触屏无 hover:子菜单静态平铺(缩进列表),不做浮层 */ + .site-nav ul.sub-nav { + display: block; + position: static; + border: none; + box-shadow: none; + background: transparent; + min-width: 0; + padding: 0 0 6px; + } + .site-nav .sub-nav li a { margin: 0; padding: 8px 16px 8px 34px; border-radius: 0; color: var(--text-light); } + .site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); } +} + + +/* ===== 左侧栏目导航(docs/11):有二级节点的模块,栏目/文章页左侧展示兄弟目录 ===== */ +.list-main.has-sidenav, .detail-main.has-sidenav { display: flex; align-items: flex-start; gap: 24px; } +.has-sidenav .list-body { flex: 1; min-width: 0; } +.side-nav { flex: none; width: 220px; background: var(--card-bg, #fff); border: 1px solid var(--border); overflow: hidden; } +.side-nav-head { background: var(--primary); color: #fff; font-size: 20px; font-weight: 700; text-align: center; padding: 18px 12px; letter-spacing: 2px; } +.side-nav-list { list-style: none; margin: 0; padding: 0; } +.side-nav-item + .side-nav-item { border-top: 1px dashed var(--border); } +.side-nav-item a { display: block; padding: 12px 16px; color: var(--text); text-decoration: none; font-size: 15px; transition: background .15s ease, color .15s ease; } +.side-nav-item a:hover { color: var(--primary); background: var(--bg); } +.side-nav-item.active a { background: var(--primary); color: #fff; font-weight: 600; text-decoration: none; } +/* 顶导航二级下拉当前项高亮(docs/11 顺带) */ +.site-nav ul.sub-nav li.active a { color: var(--primary); } +@media (max-width: 768px) { + .list-main.has-sidenav, .detail-main.has-sidenav { flex-direction: column; gap: 12px; } + .side-nav { width: 100%; } + .side-nav-list { display: flex; overflow-x: auto; -webkit-overflow-scrolling: touch; } + .side-nav-item { flex: none; } + .side-nav-item + .side-nav-item { border-top: none; border-left: 1px dashed var(--border); } + .side-nav-item a { white-space: nowrap; } +} +@media print { + .side-nav { display: none; } + .list-main.has-sidenav, .detail-main.has-sidenav { display: block; } +} diff --git a/src/main/resources/templates/themes/violet-iris/assets/favicon.png b/src/main/resources/templates/themes/violet-iris/assets/favicon.png new file mode 100644 index 0000000..497cae1 Binary files /dev/null and b/src/main/resources/templates/themes/violet-iris/assets/favicon.png differ diff --git a/src/main/resources/templates/themes/violet-iris/assets/favicon.svg b/src/main/resources/templates/themes/violet-iris/assets/favicon.svg new file mode 100644 index 0000000..eed749f --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/assets/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/templates/themes/violet-iris/assets/js/main.js b/src/main/resources/templates/themes/violet-iris/assets/js/main.js new file mode 100644 index 0000000..8261c6b --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/assets/js/main.js @@ -0,0 +1,40 @@ +/* violet-iris 皮肤包脚本:仅首页轮播自动播放与圆点切换,无任何外部依赖 */ +(function () { + var slides = document.querySelectorAll('.banner-slide'); + var dots = document.querySelectorAll('.banner-dots button'); + if (!slides.length) { + return; + } + var current = 0; + var timer = null; + + 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() { + if (slides.length > 1) { + timer = window.setInterval(function () { + show(current + 1); + }, 5000); + } + } + + dots.forEach(function (dot, index) { + dot.addEventListener('click', function () { + window.clearInterval(timer); + show(index); + start(); + }); + }); + + start(); +})(); diff --git a/src/main/resources/templates/themes/violet-iris/assets/screenshot.png b/src/main/resources/templates/themes/violet-iris/assets/screenshot.png new file mode 100644 index 0000000..b6a7d0c Binary files /dev/null and b/src/main/resources/templates/themes/violet-iris/assets/screenshot.png differ diff --git a/src/main/resources/templates/themes/violet-iris/detail.html b/src/main/resources/templates/themes/violet-iris/detail.html new file mode 100644 index 0000000..8eab71f --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/detail.html @@ -0,0 +1,58 @@ + + + + +
+ +
+ + +
+ + +
+

文章标题

+ + +
正文
+
+ + +
+
+ + + + diff --git a/src/main/resources/templates/themes/violet-iris/fragments/footer.html b/src/main/resources/templates/themes/violet-iris/fragments/footer.html new file mode 100644 index 0000000..1f3f83d --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/fragments/footer.html @@ -0,0 +1,21 @@ + + + + + diff --git a/src/main/resources/templates/themes/violet-iris/fragments/head.html b/src/main/resources/templates/themes/violet-iris/fragments/head.html new file mode 100644 index 0000000..1809482 --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/fragments/head.html @@ -0,0 +1,24 @@ + + + + + + + 页面标题 + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/themes/violet-iris/fragments/header.html b/src/main/resources/templates/themes/violet-iris/fragments/header.html new file mode 100644 index 0000000..462364e --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/fragments/header.html @@ -0,0 +1,56 @@ + + + + + diff --git a/src/main/resources/templates/themes/violet-iris/home.html b/src/main/resources/templates/themes/violet-iris/home.html new file mode 100644 index 0000000..8d4f072 --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/home.html @@ -0,0 +1,68 @@ + + + + +
+ +
+ + + + + + + + + + + + + + +
+

头条推荐

+ +
+ + +
+
+

栏目名

+ 更多 > +
+ +

暂无内容

+
+
+
+ + + + + diff --git a/src/main/resources/templates/themes/violet-iris/list.html b/src/main/resources/templates/themes/violet-iris/list.html new file mode 100644 index 0000000..14c47c4 --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/list.html @@ -0,0 +1,69 @@ + + + + +
+ + +
+ + +
+ + + +

栏目名

+

搜索词

+ + +

暂无内容

+ + + +
+
+ + + + + diff --git a/src/main/resources/templates/themes/violet-iris/single.html b/src/main/resources/templates/themes/violet-iris/single.html new file mode 100644 index 0000000..65292dc --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/single.html @@ -0,0 +1,41 @@ + + + + +
+ +
+ + +
+ + +
+

栏目名

+ +
单页内容
+

暂无内容

+
+
+
+ + + + diff --git a/src/main/resources/templates/themes/violet-iris/theme.json b/src/main/resources/templates/themes/violet-iris/theme.json new file mode 100644 index 0000000..607917f --- /dev/null +++ b/src/main/resources/templates/themes/violet-iris/theme.json @@ -0,0 +1,22 @@ +{ + "specVersion": 1, + "name": "violet-iris", + "title": "鸢尾紫韵", + "version": "1.0.0", + "author": "yunhai", + "description": "雅致鸢尾紫门户:细线顶栏+居中 logo 吸顶、鸢尾紫品牌色、圆角 12px 卡片网格列表、几何圆点装饰与淡薰衣草留白,沉静书卷气,适合高校与教科研机构。", + "screenshot": "assets/screenshot.png", + "bannerSize": "1920x560", + "params": [ + { "key": "primary", "cssVar": "--primary", "label": "主色", "type": "color", "default": "#6552D9" }, + { "key": "primaryDark", "cssVar": "--primary-dark", "label": "主色加深(强调色)", "type": "color", "default": "#4A39B0" }, + { "key": "primaryLight", "cssVar": "--primary-light", "label": "主色提亮", "type": "color", "default": "#EFECFC" }, + { "key": "bg", "cssVar": "--bg", "label": "页面底色", "type": "color", "default": "#F8F7FD" }, + { "key": "cardBg", "cssVar": "--card-bg", "label": "卡片底色", "type": "color", "default": "#FFFFFF" }, + { "key": "border", "cssVar": "--border", "label": "边框色", "type": "color", "default": "#E4DFF6" }, + { "key": "text", "cssVar": "--text", "label": "正文文字色", "type": "color", "default": "#332F45" }, + { "key": "textLight", "cssVar": "--text-light", "label": "次要文字色", "type": "color", "default": "#847E9C" }, + { "key": "listLayout", "label": "列表布局", "type": "select", "default": "grid", + "options": [ { "value": "grid", "label": "卡片网格(一行 3 张)" }, { "value": "row", "label": "传统行列表" } ] } + ] +} diff --git a/src/test/java/com/yunhai/cms/theme/ThemeRegistryTest.java b/src/test/java/com/yunhai/cms/theme/ThemeRegistryTest.java index 0c97266..c76c5f6 100644 --- a/src/test/java/com/yunhai/cms/theme/ThemeRegistryTest.java +++ b/src/test/java/com/yunhai/cms/theme/ThemeRegistryTest.java @@ -104,6 +104,24 @@ class ThemeRegistryTest { } } + @Test + @DisplayName("新增包: cherry-bloom/violet-iris 注册成功,primary 与 default 及彼此互异(拷贝忘改哨兵)") + void cherryBloomAndVioletIrisRegisteredWithDistinctPrimary() { + String defaultPrimary = registry.get(ThemeRegistry.DEFAULT_THEME).param("primary").getDefaultValue(); + java.util.Map seen = new java.util.LinkedHashMap<>(); + for (String name : new String[] {"cherry-bloom", "violet-iris"}) { + ThemeInfo info = registry.get(name); + assertThat(info).as("%s 皮肤包必须注册(清单+必备模板+截图齐全)", name).isNotNull(); + assertThat(info.getTitle()).isNotBlank(); + ThemeParam primary = info.param("primary"); + assertThat(primary).as("%s 应声明 primary 参数", name).isNotNull(); + assertThat(primary.getDefaultValue()) + .as("%s 的 primary 不得与 default 相同", name).isNotEqualTo(defaultPrimary); + seen.put(name, primary.getDefaultValue()); + } + assertThat(seen.get("cherry-bloom")).isNotEqualTo(seen.get("violet-iris")); + } + @Test @DisplayName("default 包缺失时 fail-fast(方案 §5.1,启动失败防线)") void defaultMissingFailsFast() {