Files
YwxAppThink/public/static/docs/js/docs.js
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

288 lines
8.0 KiB
JavaScript
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.
/**
* YwxApp 文档中心前台脚本
*
* 提供:目录折叠、移动端抽屉、代码复制、右侧锚点滚动高亮、版本切换。
* 纯原生实现,不依赖任何库。
*/
(function () {
'use strict';
/**
* 前台路由前缀(layout.html 注入):
* 路径模式为 '/docs';域名绑定/独立入口模式为 ''。
* 老缓存页面未注入时回退 '/docs'。
*/
var BASE = (typeof window.DOCS_BASE === 'string' && window.DOCS_BASE !== '')
? window.DOCS_BASE : '/docs';
/**
* 从当前 pathname 解析 [项目, 版本, 文档] 段:
* 绑定模式 /<项目>/<版本>/<文档>,路径模式 /docs/<项目>/... 自动剥掉前缀段。
*/
function parsePathParts() {
var parts = window.location.pathname.split('/').filter(Boolean);
var head = BASE.replace(/^\//, '');
if (head && parts[0] === head) {
parts = parts.slice(1);
}
return parts;
}
document.addEventListener('DOMContentLoaded', function () {
initTreeToggle();
initMobileDrawer();
initCodeCopy();
initTocScrollSpy();
initVersionSwitch();
scrollActiveIntoView();
});
/**
* 侧边目录展开与折叠
*/
function initTreeToggle() {
var tree = document.querySelector('.dc-tree');
if (!tree) {
return;
}
tree.addEventListener('click', function (e) {
var toggle = e.target.closest('.dc-tree-toggle');
var dirLink = e.target.closest('.dc-tree-link.is-dir');
var trigger = toggle || dirLink;
if (!trigger) {
return;
}
var li = trigger.closest('li');
if (li) {
li.classList.toggle('is-open');
}
});
}
/**
* 移动端侧边栏抽屉
*/
function initMobileDrawer() {
var btn = document.getElementById('dcMenuToggle');
var sidebar = document.getElementById('dcSidebar');
var mask = document.getElementById('dcMask');
if (!btn || !sidebar) {
return;
}
function close() {
sidebar.classList.remove('is-open');
if (mask) {
mask.classList.remove('is-show');
}
}
btn.addEventListener('click', function () {
sidebar.classList.toggle('is-open');
if (mask) {
mask.classList.toggle('is-show', sidebar.classList.contains('is-open'));
}
});
if (mask) {
mask.addEventListener('click', close);
}
// 点击目录项后自动收起抽屉
sidebar.addEventListener('click', function (e) {
if (e.target.closest('a.dc-tree-link') && window.innerWidth <= 860) {
close();
}
});
window.addEventListener('resize', function () {
if (window.innerWidth > 860) {
close();
}
});
}
/**
* 代码块复制按钮
*/
function initCodeCopy() {
var blocks = document.querySelectorAll('.dc-content pre');
Array.prototype.forEach.call(blocks, function (pre) {
var btn = document.createElement('button');
btn.className = 'dc-copy-btn';
btn.type = 'button';
btn.textContent = '复制';
btn.addEventListener('click', function () {
var code = pre.querySelector('code');
var text = code ? code.innerText : pre.innerText;
copyText(text, function (ok) {
btn.textContent = ok ? '已复制' : '复制失败';
btn.classList.toggle('is-done', ok);
setTimeout(function () {
btn.textContent = '复制';
btn.classList.remove('is-done');
}, 1600);
});
});
pre.appendChild(btn);
});
}
/**
* 复制文本到剪贴板,兼容不支持 Clipboard API 的环境
*
* @param {string} text 待复制文本
* @param {Function} cb 回调,参数为是否成功
*/
function copyText(text, cb) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(function () {
cb(true);
}).catch(function () {
cb(fallbackCopy(text));
});
return;
}
cb(fallbackCopy(text));
}
/**
* 兜底复制方案
*
* @param {string} text 待复制文本
* @returns {boolean}
*/
function fallbackCopy(text) {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
var ok = false;
try {
ok = document.execCommand('copy');
} catch (err) {
ok = false;
}
document.body.removeChild(ta);
return ok;
}
/**
* 右侧锚点滚动高亮
*/
function initTocScrollSpy() {
var toc = document.getElementById('dcToc');
var content = document.getElementById('dcContent');
if (!toc || !content) {
return;
}
var links = Array.prototype.slice.call(toc.querySelectorAll('a[data-anchor]'));
if (!links.length) {
return;
}
var headings = links.map(function (link) {
return document.getElementById(link.getAttribute('data-anchor'));
}).filter(Boolean);
if (!headings.length) {
return;
}
var offset = 90;
var ticking = false;
function update() {
var current = 0;
for (var i = 0; i < headings.length; i++) {
if (headings[i].getBoundingClientRect().top <= offset) {
current = i;
} else {
break;
}
}
links.forEach(function (link, index) {
link.classList.toggle('is-active', index === current);
});
ticking = false;
}
window.addEventListener('scroll', function () {
if (!ticking) {
window.requestAnimationFrame(update);
ticking = true;
}
}, { passive: true });
update();
}
/**
* 版本切换:保持当前文档路径,仅替换版本段
*/
function initVersionSwitch() {
var select = document.getElementById('dcVersionSelect');
if (!select) {
return;
}
select.addEventListener('change', function () {
var opt = select.options[select.selectedIndex];
var isDefault = opt.getAttribute('data-default') === '1';
// 地址形如 <BASE>/<项目>[/<版本>]/<文档>(BASE 由访问模式决定)
var parts = parsePathParts();
if (parts.length < 1) {
return;
}
var project = parts[0];
var docName = parts[parts.length - 1];
// 只有项目段时说明还未进入具体文档
if (parts.length === 1) {
window.location.href = BASE + '/' + project;
return;
}
window.location.href = isDefault
? BASE + '/' + project + '/' + docName
: BASE + '/' + project + '/' + select.value + '/' + docName;
});
}
/**
* 让当前选中的目录项进入可视区域
*/
function scrollActiveIntoView() {
var active = document.querySelector('.dc-tree-item.is-active');
var sidebar = document.getElementById('dcSidebar');
if (!active || !sidebar) {
return;
}
var top = active.offsetTop - sidebar.clientHeight / 2;
if (top > 0) {
sidebar.scrollTop = top;
}
}
})();