feat: 页脚栏目自定义(后台编辑器+前台渲染)+ 外链跳转确认页(/out 5秒倒计时+全站拦截)
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 外链跳转确认页工具(配合后端 GET /out?url=<encoded>):
|
||||
* - 站内路径(/ 开头)原样返回
|
||||
* - http(s) 外链 → /out?url=<encodeURIComponent(url)>(确认页 5s 倒计时后跳转)
|
||||
* - 其他协议(javascript: 等)→ 返回 '#' 并 console 警告(防 XSS 链接)
|
||||
*/
|
||||
export function safeOutUrl(url) {
|
||||
const u = String(url || '').trim();
|
||||
if (!u) return '#';
|
||||
if (u.startsWith('/')) return u; // 站内绝对/相对路径(含 /uploads、/out 本身)
|
||||
if (/^https?:\/\//i.test(u)) return '/out?url=' + encodeURIComponent(u);
|
||||
console.warn('[outlink] 不支持的链接协议,已拦截:', u);
|
||||
return '#';
|
||||
}
|
||||
|
||||
/** 判断链接是否为站内链接(/ 开头) */
|
||||
export function isInternalUrl(url) {
|
||||
return String(url || '').startsWith('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* footer_columns(JSON 字符串)解析为 [{ title, links: [{ label, url }] }];
|
||||
* 解析失败或非数组返回 [](前端回退硬编码栏目)。
|
||||
*/
|
||||
export function parseFooterColumns(raw) {
|
||||
try {
|
||||
const arr = JSON.parse(raw || '[]');
|
||||
if (!Array.isArray(arr)) return [];
|
||||
return arr
|
||||
.filter((c) => c && typeof c === 'object')
|
||||
.map((c) => ({
|
||||
title: String(c.title || ''),
|
||||
links: Array.isArray(c.links)
|
||||
? c.links
|
||||
.filter((l) => l && typeof l === 'object')
|
||||
.map((l) => ({ label: String(l.label || ''), url: String(l.url || '') }))
|
||||
: [],
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user