安全(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(市场 / 开发者中心 / 审核 / 分润)
162 lines
6.4 KiB
CSS
162 lines
6.4 KiB
CSS
/* ==========================================================================
|
||
nav-theme.css 主导航 dropdown + 主题配色面板
|
||
- 全页面共享(所有前台页面顶部 navbar 都用这套 dropdown)
|
||
- 不依赖 layui.dropdown 模块(项目不引入 layui.dropdown 时也能用)
|
||
========================================================================== */
|
||
|
||
/* ============== 主导航按钮(替代原横排 .nav-links) ============== */
|
||
|
||
.nav-main-btn {
|
||
background: var(--nav-tag-bg);
|
||
color: var(--nav-card-text);
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 9px 16px;
|
||
border-radius: 22px;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
transition: all .3s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
white-space: nowrap;
|
||
font-family: inherit;
|
||
}
|
||
.nav-main-btn:hover { background: var(--nav-primary); color: #fff; }
|
||
.nav-main-btn .caret { font-size: 11px; opacity: .75; transition: transform .3s; display: inline-block; }
|
||
.nav-main-btn[aria-expanded="true"] { background: var(--nav-primary); color: #fff; }
|
||
.nav-main-btn[aria-expanded="true"] .caret { transform: rotate(180deg); }
|
||
|
||
/* ============== 主导航下拉面板 ============== */
|
||
|
||
.main-nav-panel {
|
||
position: absolute;
|
||
z-index: 999999;
|
||
background: var(--nav-card);
|
||
border: 1px solid var(--nav-border);
|
||
border-radius: 12px;
|
||
box-shadow: 0 8px 30px rgba(0,0,0,.18);
|
||
min-width: 200px;
|
||
padding: 6px 0;
|
||
display: none;
|
||
margin: 0;
|
||
}
|
||
.main-nav-panel.show { display: block; animation: navFadeIn .15s ease-out; }
|
||
.main-nav-panel ul { list-style: none; margin: 0; padding: 0; }
|
||
.main-nav-panel li a {
|
||
display: block;
|
||
padding: 10px 18px;
|
||
color: var(--nav-card-text);
|
||
text-decoration: none;
|
||
font-size: 14px;
|
||
transition: background .15s, color .15s;
|
||
white-space: nowrap;
|
||
}
|
||
.main-nav-panel li a:hover, .main-nav-panel li.active a {
|
||
background: var(--nav-primary);
|
||
color: #fff;
|
||
}
|
||
|
||
/* ============== 主题配色面板 ============== */
|
||
|
||
.theme-popover {
|
||
position: absolute;
|
||
z-index: 999999;
|
||
display: none;
|
||
}
|
||
.theme-popover.show { display: block; animation: navFadeIn .15s ease-out; }
|
||
.theme-panel { padding: 14px 16px; min-width: 260px; max-width: 320px; color: var(--nav-card-text); background: var(--nav-card); border: 1px solid var(--nav-border); border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,.18); }
|
||
.theme-panel h4 {
|
||
font-size: 12px; color: var(--nav-muted); margin: 0 0 8px; font-weight: 600;
|
||
letter-spacing: .5px; text-transform: uppercase;
|
||
}
|
||
.theme-panel .group + .group { margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--nav-border); }
|
||
.theme-panel .seg { display: flex; gap: 6px; flex-wrap: wrap; }
|
||
.theme-panel .seg-btn {
|
||
cursor: pointer; border: 2px solid transparent; padding: 6px 12px;
|
||
border-radius: 18px; background: var(--nav-tag-bg); color: var(--nav-card-text);
|
||
font-size: 13px; transition: all .2s; font-family: inherit;
|
||
}
|
||
.theme-panel .seg-btn:hover { filter: brightness(1.05); }
|
||
.theme-panel .seg-btn.active {
|
||
border-color: var(--nav-primary); background: var(--nav-primary); color: #fff;
|
||
}
|
||
.theme-panel .swatch {
|
||
cursor: pointer; border: 2px solid transparent; padding: 0;
|
||
width: 28px; height: 28px; border-radius: 50%; display: inline-block;
|
||
background-clip: padding-box; transition: transform .2s;
|
||
box-shadow: 0 1px 4px rgba(0,0,0,.15);
|
||
}
|
||
.theme-panel .swatch:hover { transform: scale(1.15); }
|
||
.theme-panel .swatch.active {
|
||
border-color: #fff; outline: 2px solid var(--nav-primary); outline-offset: 1px;
|
||
}
|
||
|
||
/* 触发按钮激活态 */
|
||
.icon-btn[aria-expanded="true"] { background: var(--nav-primary); color: #fff; transform: translateY(-2px); }
|
||
|
||
/* 主题面板底部「恢复默认」按钮 */
|
||
.theme-panel .reset-row {
|
||
margin-top: 14px; padding-top: 12px;
|
||
border-top: 1px dashed var(--nav-border);
|
||
text-align: right;
|
||
}
|
||
.theme-panel .reset-btn {
|
||
cursor: pointer; border: none; padding: 6px 14px;
|
||
border-radius: 16px; background: transparent;
|
||
color: var(--nav-muted); font-size: 12px; font-family: inherit;
|
||
transition: color .2s, background .2s;
|
||
}
|
||
.theme-panel .reset-btn:hover { color: var(--nav-primary); background: var(--nav-tag-bg); }
|
||
|
||
/* ============== 背景图片遮罩层(nav-theme.js img: 模式) ==============
|
||
* body.nav-bg-img 建立层叠上下文,::before 半透明遮罩盖在背景图之上、
|
||
* 页面内容之下,保证卡片文字可读;强度由 --nav-bg-mask(后台可配 0-90%)控制。 */
|
||
body.nav-bg-img { position: relative; z-index: 0; }
|
||
body.nav-bg-img::before {
|
||
content: '';
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: -1;
|
||
pointer-events: none;
|
||
background: rgba(255, 255, 255, var(--nav-bg-mask, .35));
|
||
transition: background .3s;
|
||
}
|
||
[data-theme="dark"] body.nav-bg-img::before {
|
||
/* 暗色模式下遮罩改为压暗,而非提亮 */
|
||
background: rgba(15, 17, 26, calc(var(--nav-bg-mask, .35) + .1));
|
||
}
|
||
|
||
/* ============== 面板:自定义图片背景输入行 ============== */
|
||
|
||
.theme-panel .bg-url-row { display: flex; gap: 6px; margin-top: 8px; }
|
||
.theme-panel .bg-url-row input {
|
||
flex: 1; min-width: 0; padding: 6px 10px; font-size: 12px; font-family: inherit;
|
||
border: 1px solid var(--nav-border); border-radius: 8px; outline: none;
|
||
background: var(--nav-tag-bg); color: var(--nav-card-text); transition: border-color .2s;
|
||
}
|
||
.theme-panel .bg-url-row input:focus { border-color: var(--nav-primary); }
|
||
.theme-panel .bg-url-row .bg-apply-btn {
|
||
cursor: pointer; flex-shrink: 0; padding: 6px 12px; font-size: 12px; font-family: inherit;
|
||
border: none; border-radius: 8px; background: var(--nav-primary); color: #fff;
|
||
transition: filter .2s;
|
||
}
|
||
.theme-panel .bg-url-row .bg-apply-btn:hover { filter: brightness(1.1); }
|
||
|
||
/* ============== 下拉面板出场动画 ============== */
|
||
|
||
@keyframes navFadeIn {
|
||
from { opacity: 0; transform: translateY(-4px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
/* ============== 小屏响应式 ============== */
|
||
|
||
@media (max-width: 768px) {
|
||
.nav-main-btn { font-size: 14px; padding: 8px 14px; }
|
||
}
|
||
@media (max-width: 480px) {
|
||
.nav-main-btn { font-size: 13px; padding: 7px 12px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; }
|
||
/* 小屏:面板宽度保护(不超出可视区,按钮对齐右沿) */
|
||
.main-nav-panel, .theme-popover .theme-panel { max-width: calc(100vw - 16px); right: 8px !important; left: 8px !important; width: auto !important; }
|
||
} |