安全(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(市场 / 开发者中心 / 审核 / 分润)
120 lines
5.0 KiB
HTML
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>
|