Files
TripLog/templates/layout.php
T
laoyang 43171597e2 给静态资源加版本号,修掉「服务端更新了但界面没变」
现象:CSS 更新并上线后,用户打开站点看不到任何变化,怀疑没部署。
实测服务器上的 style.css 已是新版(与工作树逐字节一致),真实浏览器
直连线上也拿得到新文件,所以问题在客户端缓存:

  · 页面 HTML 带 no-store,每次都重新渲染 → HTML 一直是新的;
  · 但 /assets/*.css|js 的响应没有 Cache-Control / Expires,浏览器只好
    退回自己的「启发式缓存」——在它自行估算的新鲜期内根本不发请求;
  · 而 <link href="/assets/style.css"> 的地址从不改变,文件换了浏览器
    也无从得知。于是出现「HTML 新的、CSS 旧的」,界面看起来毫无变化。

修法:新增 asset() 辅助函数,拼上 mtime + 文件大小做 cache busting,
文件名不变而内容一变、地址就变,浏览器只能重新拉取,普通刷新即生效。
版本号同时带上文件大小,挡住「mtime 没变但内容变了」这种漏网情况;
取不到文件信息时不加版本号,退化成原行为,不影响可用性。

已验证:线上 HTML 现在引用 /assets/style.css?v=…;真实浏览器直连线上
三个视口(1920/1280/390)均无横向溢出,资源全部 200;1920px 下顶栏品牌
与正文左边缘同为 420px,头像 26×26(源图 400×400)。
2026-09-23 07:06:36 +08:00

135 lines
5.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
$__isLoggedIn = \App\Auth::check();
$__user = $__isLoggedIn ? \App\Auth::user() : null;
$__isAdmin = $__isLoggedIn && \App\Auth::isAdmin();
$__name = (string)($__user['name'] ?? ($_SESSION['user_name'] ?? ''));
// 用 media_avatar() 解析后再判断,不能拿原始值判断:
// 外站地址(https://…)会被 media_avatar() 拒掉返回空串,若还按非空处理,
// 就会渲染出 <img src=""> —— 一个破图占位。
$__avatar = media_avatar((string)($__user['avatar'] ?? ''));
$__initial = $__name !== '' ? mb_substr($__name, 0, 1) : '·';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title><?= e($pageTitle ?? '家庭出行记录') ?></title>
<?php /* 静态资源一律走 asset():不带版本号的话,浏览器会一直用缓存里的旧 CSS */ ?>
<link rel="stylesheet" href="<?= e(asset('assets/style.css')) ?>">
<link rel="stylesheet" href="<?= e(asset('assets/vendor/cropper/cropper.min.css')) ?>">
<script src="<?= e(asset('assets/vendor/cropper/cropper.min.js')) ?>"></script>
<script>
window._AMapSecurityConfig = {
securityJsCode: '<?= e(app_config()['amap']['security_js'] ?? '') ?>',
};
window.__AMAP_LOADED = false;
window.__AMAP_ERROR = null;
</script>
<script src="https://webapi.amap.com/maps?v=2.0&key=<?= e(app_config()['amap']['key'] ?? '') ?>&plugin=AMap.AutoComplete"
onload="window.__AMAP_LOADED = true;"
onerror="window.__AMAP_ERROR = 'script load failed';">
</script>
</head>
<body>
<?php if ($__isLoggedIn): ?>
<header class="topbar">
<a href="<?= url('activities/index') ?>" class="brand">
<span class="brand-mark">🧭</span>
<span class="brand-text">家庭出行记录</span>
</a>
<div class="topbar-right">
<a href="<?= url('activities/create') ?>" class="btn-primary btn-sm topbar-new">+ 新建活动</a>
<?php /* 原生 details 做下拉:不依赖 JS,键盘可用,点外部失焦由 JS 兜底 */ ?>
<details class="menu" id="user-menu">
<summary class="menu-trigger" aria-label="打开菜单">
<?php if ($__avatar !== ''): ?>
<img class="avatar-xs" src="<?= e($__avatar) ?>" alt="">
<?php else: ?>
<span class="avatar-xs avatar-initial"><?= e($__initial) ?></span>
<?php endif; ?>
<span class="menu-name"><?= e($__name) ?></span>
<span class="menu-caret" aria-hidden="true">▾</span>
</summary>
<div class="menu-panel">
<div class="menu-head">
<div class="menu-head-name"><?= e($__name) ?></div>
<div class="menu-head-role"><?= $__isAdmin ? '管理员' : '家庭成员' ?></div>
</div>
<a class="menu-item" href="<?= url('activities/index') ?>">
<span class="menu-icon">📋</span> 活动列表
</a>
<a class="menu-item" href="<?= url('activities/create') ?>">
<span class="menu-icon">➕</span> 新建活动
</a>
<?php if (\App\Auth::id()): ?>
<a class="menu-item" href="<?= url('users/edit', ['id' => \App\Auth::id()]) ?>">
<span class="menu-icon">🙋</span> 我的资料
</a>
<?php endif; ?>
<?php if ($__isAdmin): ?>
<div class="menu-sep">管理</div>
<a class="menu-item" href="<?= url('travel_modes/index') ?>">
<span class="menu-icon">🚄</span> 出行方式
</a>
<a class="menu-item" href="<?= url('vehicles/index') ?>">
<span class="menu-icon">🚗</span> 车辆
</a>
<a class="menu-item" href="<?= url('node_types/index') ?>">
<span class="menu-icon">🏷️</span> 节点类型
</a>
<a class="menu-item" href="<?= url('role_tags/index') ?>">
<span class="menu-icon">🎭</span> 角色标签
</a>
<a class="menu-item" href="<?= url('users/index') ?>">
<span class="menu-icon">👥</span> 用户管理
</a>
<a class="menu-item" href="<?= url('settings/index') ?>">
<span class="menu-icon">🗄️</span> 存储设置
</a>
<?php endif; ?>
<div class="menu-sep"></div>
<a class="menu-item menu-item-danger" href="<?= url('auth/logout') ?>">
<span class="menu-icon">🚪</span> 退出登录
</a>
</div>
</details>
</div>
</header>
<?php endif; ?>
<main class="container<?= $__isLoggedIn ? '' : ' container-guest' ?>">
<?php if ($err = flash('error')): ?>
<div class="alert"><?= e($err) ?></div>
<?php endif; ?>
<?php require $content; ?>
</main>
<?php if ($__isLoggedIn): ?>
<footer class="footer">家庭出行记录</footer>
<script>
// 原生 details 不会因为点击面板外部而收起,这里补一句(不依赖它也能用)
document.addEventListener('click', function (ev) {
var menu = document.getElementById('user-menu');
if (menu && menu.open && !menu.contains(ev.target)) {
menu.open = false;
}
});
// Esc 收起
document.addEventListener('keydown', function (ev) {
var menu = document.getElementById('user-menu');
if (ev.key === 'Escape' && menu && menu.open) {
menu.open = false;
}
});
</script>
<?php endif; ?>
</body>
</html>