Files
YwxAppThink/addon/haonav/view/frontend/index/detail.html
T
yangwx 18101cf11a feat: 上线前安全加固与发布工程化;appmall 由插件迁移为独立应用
安全(P0)
- 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、
  ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签
  (ywxapp.framework_package_verify,默认开启)
- JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret()
  校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET
- /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验
- 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流
- TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive
- 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php)
- 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis
- paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG

工程化 / 交付
- 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本
- 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过
- 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障
- .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess

结构
- appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
2026-10-22 00:53:39 +08:00

120 lines
5.0 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{$info.title|default='网址详情'} - {$seo.title|default='网址导航'}</title>
{notempty name="info"}
<meta name="keywords" content="{$info.keywords|default=''}">
<meta name="description" content="{$info.description|default=''}">
<link rel="canonical" href="{$siteUrl|default=''}/haonav/site/{$info.id}.html">
{/notempty}
{notempty name="jsonld"}<script type="application/ld+json">{$jsonld|raw}</script>{/notempty}
{include file="public/styles" /}
<link rel="stylesheet" href="/static/haonav/css/nav-page-detail.css">
</head>
<body>
{assign name="navActive" value="" /}
{assign name="navSearch" value="" /}
{assign name="navUser" value="1" /}
{include file="public/navbar" /}
<div class="detail-container">
<img src="{$info.show_icon}" class="website-icon-large" id="websiteIcon" alt="{$info.title}">
<h1 class="website-name-large" id="websiteName">{$info.title}</h1>
<div class="rating-box" id="ratingBox" data-id="{$info.id}">
<div class="stars" id="rateStars">
<span class="star" data-score="1">★</span>
<span class="star" data-score="2">★</span>
<span class="star" data-score="3">★</span>
<span class="star" data-score="4">★</span>
<span class="star" data-score="5">★</span>
</div>
<div class="rating-meta">
<span class="rating-score" id="ratingScore">{$info.rating|default='0.0'}</span>
<span class="rating-count" id="ratingCount">{$info.rating_count|default='0'} 人评分</span>
</div>
</div>
<p class="rating-tip" id="ratingTip">点击星星即可评分(需登录)</p>
<p class="website-desc-large" id="websiteDesc">{$info.description | default='暂无描述'}</p>
<!-- 广告位:详情页内联 -->
{notempty name="ads.detail_inline"}
<div class="ad-box ad-detail-inline">
<span class="ad-label">广告</span>
{volist name="ads.detail_inline" id="ad"}
{if $ad.type == 1}
<a class="ad-item" href="/haonav/ad/click?id={$ad.id}" target="_blank" rel="nofollow">
<img src="{$ad.image}" alt="{$ad.title}" loading="lazy">
</a>
{else/}
{$ad.code|raw}
{/if}
{/volist}
</div>
{/notempty}
<img src="/haonav/snapshot.html?id={$info.id}" alt="网站预览" loading="lazy"
style="width:100%;border-radius:12px;margin:0 0 22px;display:none;box-shadow:0 4px 16px rgba(0,0,0,.15);"
onload="if(this.naturalWidth>120){this.style.display='block';}">
<div class="countdown-box">
<div class="countdown-title">⏳ 正在为您跳转到目标网站</div>
<p class="countdown-number" id="countdown">3</p>
</div>
<div class="action-buttons">
<a href="{$info.outbound_url | default='/'}" class="btn btn-primary" id="skipBtn">立即跳转</a>
<a href="index.html" class="btn btn-secondary">返回首页</a>
<button class="btn btn-secondary js-qr" data-url="{$info.url}" style="cursor:pointer;">分享二维码</button>
</div>
<p class="info-text">⚠️ 温馨提示:如果不想等待,可点击「立即跳转」。如果跳转失败,请检查网络或直接访问目标网址。</p>
</div>
<div class="qr-mask" id="qrMask">
<div class="qr-box">
<img id="qrImg" src="" alt="二维码">
<p id="qrText"></p>
<span class="close">关闭</span>
</div>
</div>
<!-- 登录 / 注册 弹层由全局组件 auth.js 自动注入 -->
<script src="/static/haonav/js/nav-core.js"></script>
<script src="/static/haonav/js/nav-theme.js"></script>
<script src="/static/haonav/js/nav-festival.js"></script>
<script src="/static/haonav/js/nav-fav.js"></script>
<script src="/static/haonav/js/nav-rating.js"></script>
<script src="/static/haonav/js/nav-widgets.js"></script>
<script src="/static/haonav/js/auth.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
var url = document.getElementById('skipBtn').href;
if (!url) { return; }
var countdown = 3;
var el = document.getElementById('countdown');
var timer = setInterval(function () {
countdown--;
el.textContent = countdown;
if (countdown <= 0) {
clearInterval(timer);
window.location.href = url;
}
}, 1000);
document.getElementById('skipBtn').addEventListener('click', function (e) {
e.preventDefault();
clearInterval(timer);
window.location.href = url;
});
});
</script>
</body>
</html>