feat(theme)!: 下线粉/紫两包,改以学院蓝为基座交付 academic-navy/campus-azure 两套高校官网风皮肤
build / build-push (push) Successful in 51s

线上 10027 首页配了 home_layout 后走 themes/common 通用骨架,派生皮肤只换了页头页脚,
正文仍是骨架内置蓝灰卡(外加 cherry-bloom 的粉紫配色),用户判定"完全不能用"。

- 新增 academic-navy(学府藏蓝,an-)/campus-azure(青蓝校园,cz-):以 scholar-blue 为基座,
  各自自带 fragments/home-blocks.html 与 home.html 分发,news_grid/media_focus/compact/
  image_wall/card/ticker/gallery/links 八种区块形态全部由皮肤渲染,未知类型才回落通用骨架
- 两套皮肤 :root 把私有变量别名到 --primary/--text/--card-bg/--border/--radius 契约变量;
  中文行锁 nowrap+min-width:0+ellipsis,grid 轨道用 minmax(0,1fr) 防窄视口压塌
- 新闻背景带 .an-sec-bg/.cz-sec-bg 压 z-index:0、卡片抬到 1:绝对定位层默认画在普通流之上,
  不抬起会整张卡片泛成底图色(无头截图实测)
- common 骨架空态守卫改包 th:block(th:replace 优先级 100 先于 th:if 300)
- 删除 cherry-bloom/violet-iris;sql/upgrade_theme_academic_skins.sql 幂等改指存量站点
  (站点缓存 CacheNames.SITE TTL 30 分钟,改库后需重启或在管理端重存站点)
- 测试:AcademicSkinContractTest(区块自带/分发/CJK 行/变量别名/主色必须学术蓝/背景带层级/
  预览图不得沿用基座)、HomeBlocksCssContractTest、PortalHomeLayoutIT 端到端断言零 plb- 输出;
  全量 383 项通过
This commit is contained in:
chenxing
2026-09-19 20:21:30 +08:00
parent 52433f06a2
commit d2df12b91e
65 changed files with 5498 additions and 2118 deletions
@@ -0,0 +1,190 @@
/* academic-navy 主题脚本:顶栏日期 + 轮播(圆点/箭头/触摸) + 导航吸顶反蓝 + 移动端抽屉与二级手风琴。
与 default 同源逻辑,无任何外部依赖。 */
(function () {
'use strict';
/* ---------- 顶栏日期(本地时间,YYYY年M月D日 星期X) ---------- */
var dateEl = document.getElementById('siteDate');
if (dateEl) {
var now = new Date();
var week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()];
dateEl.textContent = now.getFullYear() + '年' + (now.getMonth() + 1) + '月'
+ now.getDate() + '日 星期' + week;
}
/* ---------- 导航吸顶反蓝(桌面端;≤768 不吸顶) ---------- */
var nav = document.getElementById('anNav');
var DESKTOP_MIN = 769;
var navBaseTop = 0;
var holder = null;
function measureNavTop() {
unlockNav();
navBaseTop = nav.getBoundingClientRect().top + window.pageYOffset;
}
function lockNav() {
if (!nav.classList.contains('an-nav--fixed')) {
holder = document.createElement('div');
holder.className = 'an-nav-holder';
holder.style.height = nav.offsetHeight + 'px';
nav.parentNode.insertBefore(holder, nav);
nav.classList.add('an-nav--fixed');
} else if (holder) {
holder.style.height = nav.offsetHeight + 'px';
}
}
function unlockNav() {
nav.classList.remove('an-nav--fixed');
if (holder && holder.parentNode) {
holder.parentNode.removeChild(holder);
}
holder = null;
}
function onScrollNav() {
if (window.innerWidth < DESKTOP_MIN) {
unlockNav();
return;
}
if (window.pageYOffset > navBaseTop) {
lockNav();
} else {
unlockNav();
}
}
if (nav) {
measureNavTop();
window.addEventListener('scroll', onScrollNav, { passive: true });
var resizeTimer = null;
window.addEventListener('resize', function () {
if (resizeTimer) { window.clearTimeout(resizeTimer); }
resizeTimer = window.setTimeout(function () {
measureNavTop();
onScrollNav();
}, 150);
});
}
/* ---------- 移动端抽屉 + 二级手风琴 ---------- */
var navToggle = document.querySelector('.an-nav-toggle');
var navList = document.querySelector('.an-nav-list');
if (navToggle && navList) {
navToggle.addEventListener('click', function () {
var open = navList.classList.toggle('an-open');
navToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
});
}
/* 窄屏下:点击带二级的项先展开面板,再点才跳转;宽屏交还给 CSS hover */
if (navList) {
navList.addEventListener('click', function (e) {
if (window.innerWidth >= DESKTOP_MIN) { return; }
var link = e.target.closest ? e.target.closest('a') : null;
if (!link) { return; }
var li = link.parentElement;
if (!li || !li.classList.contains('has-sub')) { return; }
var sub = li.querySelector('.an-sub');
if (!sub) { return; }
if (!li.classList.contains('an-sub-open')) {
e.preventDefault();
li.classList.add('an-sub-open');
}
});
}
/* ---------- 首页轮播 ---------- */
var root = document.querySelector('.an-banner');
var slides = document.querySelectorAll('.an-banner-slide');
var dots = document.querySelectorAll('.an-banner-dots button');
var prevBtn = document.querySelector('.an-banner-arrow.prev');
var nextBtn = document.querySelector('.an-banner-arrow.next');
if (!slides.length || !root) {
return;
}
var current = 0;
var timer = null;
var INTERVAL = 5000;
function show(index) {
slides[current].classList.remove('active');
if (dots[current]) {
dots[current].classList.remove('active');
}
current = (index + slides.length) % slides.length;
slides[current].classList.add('active');
if (dots[current]) {
dots[current].classList.add('active');
}
}
function start() {
stop();
if (slides.length > 1) {
timer = window.setInterval(function () {
show(current + 1);
}, INTERVAL);
}
}
function stop() {
if (timer) {
window.clearInterval(timer);
timer = null;
}
}
dots.forEach(function (dot, index) {
dot.addEventListener('click', function () {
show(index);
start();
});
});
if (prevBtn) {
prevBtn.addEventListener('click', function () {
show(current - 1);
start();
});
}
if (nextBtn) {
nextBtn.addEventListener('click', function () {
show(current + 1);
start();
});
}
root.addEventListener('mouseenter', stop);
root.addEventListener('mouseleave', start);
root.addEventListener('focusin', stop);
root.addEventListener('focusout', start);
root.setAttribute('tabindex', '-1');
root.addEventListener('keydown', function (e) {
if (e.key === 'ArrowLeft') {
show(current - 1);
start();
} else if (e.key === 'ArrowRight') {
show(current + 1);
start();
}
});
/* 触摸滑动切页 */
var touchX = null;
root.addEventListener('touchstart', function (e) {
touchX = e.changedTouches[0].clientX;
stop();
}, { passive: true });
root.addEventListener('touchend', function (e) {
if (touchX === null) { return; }
var dx = e.changedTouches[0].clientX - touchX;
if (Math.abs(dx) > 40) {
show(dx < 0 ? current + 1 : current - 1);
}
touchX = null;
start();
}, { passive: true });
start();
})();