150 lines
4.7 KiB
TypeScript
150 lines
4.7 KiB
TypeScript
// 格式化相对时间
|
|
export function formatRelativeTime(date: string | Date): string {
|
|
const now = new Date();
|
|
const target = new Date(date);
|
|
const diffMs = now.getTime() - target.getTime();
|
|
const diffSeconds = Math.floor(diffMs / 1000);
|
|
const diffMinutes = Math.floor(diffSeconds / 60);
|
|
const diffHours = Math.floor(diffMinutes / 60);
|
|
const diffDays = Math.floor(diffHours / 24);
|
|
const diffMonths = Math.floor(diffDays / 30);
|
|
const diffYears = Math.floor(diffDays / 365);
|
|
|
|
if (diffYears > 0) return `${diffYears}年前`;
|
|
if (diffMonths > 0) return `${diffMonths}月前`;
|
|
if (diffDays > 0) return `${diffDays}天前`;
|
|
if (diffHours > 0) return `${diffHours}小时前`;
|
|
if (diffMinutes > 0) return `${diffMinutes}分钟前`;
|
|
return "刚刚";
|
|
}
|
|
|
|
// 格式化时间显示 (mm:ss)
|
|
export function formatTime(seconds: number): string {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = Math.floor(seconds % 60);
|
|
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
}
|
|
|
|
// 处理评论文本中的表情占位符
|
|
export function parseCommentText(
|
|
text: string,
|
|
): (string | { type: "emoji"; name: string })[] {
|
|
const parts: (string | { type: "emoji"; name: string })[] = [];
|
|
const regex = /\[([^\]]+)\]/g;
|
|
let lastIndex = 0;
|
|
let match: RegExpExecArray | null;
|
|
|
|
while ((match = regex.exec(text)) !== null) {
|
|
// 添加表情前的文本
|
|
if (match.index > lastIndex) {
|
|
parts.push(text.slice(lastIndex, match.index));
|
|
}
|
|
// 添加表情
|
|
parts.push({ type: "emoji", name: match[1] });
|
|
lastIndex = regex.lastIndex;
|
|
}
|
|
|
|
// 添加剩余文本
|
|
if (lastIndex < text.length) {
|
|
parts.push(text.slice(lastIndex));
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
|
|
// 从 localStorage 获取数值
|
|
export function getNumberFromStorage(
|
|
key: string,
|
|
defaultValue: number,
|
|
): number {
|
|
if (typeof window === "undefined") return defaultValue;
|
|
const saved = localStorage.getItem(key);
|
|
if (!saved) return defaultValue;
|
|
const parsed = parseFloat(saved);
|
|
return Number.isNaN(parsed) ? defaultValue : parsed;
|
|
}
|
|
|
|
// 从 localStorage 获取字符串
|
|
export function getStringFromStorage(
|
|
key: string,
|
|
defaultValue: string,
|
|
): string {
|
|
if (typeof window === "undefined") return defaultValue;
|
|
return localStorage.getItem(key) || defaultValue;
|
|
}
|
|
|
|
// 保存到 localStorage
|
|
export function saveToStorage(key: string, value: string | number): void {
|
|
if (typeof window === "undefined") return;
|
|
localStorage.setItem(key, value.toString());
|
|
}
|
|
|
|
// 稳定的绝对时间格式(与 locale 无关),用于避免 SSR/CSR 水合不一致
|
|
// 输出示例:2025-10-20 08:23:05 UTC
|
|
export function formatAbsoluteUTC(date: string | Date): string {
|
|
const d = new Date(date);
|
|
const Y = d.getUTCFullYear();
|
|
const M = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
const D = String(d.getUTCDate()).padStart(2, "0");
|
|
const h = String(d.getUTCHours()).padStart(2, "0");
|
|
const m = String(d.getUTCMinutes()).padStart(2, "0");
|
|
const s = String(d.getUTCSeconds()).padStart(2, "0");
|
|
return `${Y}-${M}-${D} ${h}:${m}:${s} UTC`;
|
|
}
|
|
|
|
// 主动释放 HTMLMediaElement 解码缓冲与网络流,防止 SPA 连续切页导致内存/硬件解码器泄漏
|
|
export function releaseMediaElement(
|
|
el: HTMLMediaElement | null | undefined,
|
|
): void {
|
|
if (!el) return;
|
|
try {
|
|
el.pause();
|
|
el.removeAttribute("src");
|
|
el.load();
|
|
} catch {}
|
|
}
|
|
|
|
const PROGRESS_STORAGE_PREFIX = "aweme_progress_";
|
|
const PROGRESS_TTL_MS = 5 * 60 * 1000;
|
|
const MAX_PROGRESS_ENTRIES = 30;
|
|
|
|
// 清理过期或超量的视频播放进度缓存,避免长期浏览堆积 localStorage
|
|
export function cleanupExpiredProgressStorage(): void {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
const now = Date.now();
|
|
const validEntries: { key: string; timestamp: number }[] = [];
|
|
|
|
for (let i = localStorage.length - 1; i >= 0; i--) {
|
|
const key = localStorage.key(i);
|
|
if (!key || !key.startsWith(PROGRESS_STORAGE_PREFIX)) continue;
|
|
|
|
const raw = localStorage.getItem(key);
|
|
if (!raw) {
|
|
localStorage.removeItem(key);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(raw) as { timestamp?: unknown };
|
|
const timestamp =
|
|
typeof parsed.timestamp === "number" ? parsed.timestamp : 0;
|
|
if (now - timestamp >= PROGRESS_TTL_MS) {
|
|
localStorage.removeItem(key);
|
|
} else {
|
|
validEntries.push({ key, timestamp });
|
|
}
|
|
} catch {
|
|
localStorage.removeItem(key);
|
|
}
|
|
}
|
|
|
|
if (validEntries.length > MAX_PROGRESS_ENTRIES) {
|
|
validEntries.sort((a, b) => b.timestamp - a.timestamp);
|
|
for (let i = MAX_PROGRESS_ENTRIES; i < validEntries.length; i++) {
|
|
localStorage.removeItem(validEntries[i].key);
|
|
}
|
|
}
|
|
} catch {}
|
|
}
|