gongxiangp2/components/UpdateLogModal.tsx
chenpei 29115dddb8 反编译恢复源码:阶段1 - 核心API和工具库
- 反编译 app/api/materials/route.ts (素材列表和删除API)
- 反编译 app/api/materials/stats/route.ts (统计API)
- 新增 lib/material-log.ts (素材操作日志工具)
- 新增 lib/utils.ts (URL规范化工具)

这是从生产系统2.2.0版本编译代码反编译恢复的第一批文件
2025-12-20 18:10:47 +08:00

424 lines
16 KiB
TypeScript
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.

'use client'
import { useEffect, useState } from 'react'
// 更新日志配置 - 每次有新功能或优化时更新这个版本号
const CURRENT_VERSION = '2.2.1'
const STORAGE_KEY = 'update_log_viewed_version_v221_r1'
interface UpdateItem {
type: 'feature' | 'improvement' | 'fix' // 新功能、优化、修复
title: string
description: string
}
interface UpdateLog {
version: string
date: string
title: string
items: UpdateItem[]
}
// 更新日志内容 - 通俗易懂,面向客户
// 注意:只有经过测试验证的功能才会添加到更新记录中
const UPDATE_LOGS: UpdateLog[] = [
{
version: '2.2.1',
date: '2025-12-09',
title: '系统更新',
items: [
{
type: 'feature',
title: '网络诊断功能',
description: '新增网络诊断功能,支持下载和上传测速,自动检测存储服务连通性,结果可复制分享,方便排查网络问题。'
},
{
type: 'improvement',
title: '下载体验大幅提升',
description: '大文件下载支持断点续传,下载更稳定、速度更快。下载后下载数立即更新,无需刷新页面即可看到最新数据,排行榜面板也会自动刷新。'
},
{
type: 'improvement',
title: '收藏功能体验优化',
description: '收藏/取消收藏操作后显示气泡提示,操作反馈更清晰。收藏状态在所有素材列表中正确显示,方便快速识别已收藏内容。'
},
{
type: 'improvement',
title: '素材卡片头像显示优化',
description: '作者头像尺寸增大,改为圆角方形样式,显示更清晰。优化显示逻辑,头像完整显示不被裁切,同时保持卡片高度不变。'
},
{
type: 'improvement',
title: '个人中心卡片更清晰',
description: '个人中心改为上下大卡片,分开展示"我的素材""我的收藏",信息一目了然。'
},
{
type: 'improvement',
title: '榜单悬浮窗体验优化',
description: '支持拖动、关闭后出现可拖动的迷你按钮可再打开,位置会记忆。拖拽无延迟,拖动更流畅。'
},
{
type: 'improvement',
title: '头像加载更稳定',
description: '优化头像加载与处理,解决头像不显示/加载慢的问题,统一压缩为 WebP 格式,优先加载。'
},
{
type: 'improvement',
title: '转码任务自动恢复',
description: '部署中断后自动恢复转码任务,清理残留文件,避免存储浪费。恢复时保持队列顺序,确保转码任务按顺序执行。'
},
{
type: 'fix',
title: '登录退出后能正常回到登录页',
description: '修正退出后偶尔跳到 localhost 的问题,现在会正确返回登录页。'
}
]
},
{
version: '2.2.0',
date: '2025-12-08',
title: '系统更新',
items: [
{
type: 'feature',
title: '个人中心上线',
description: '新增“我的素材 / 我的收藏”两个入口,卡片支持预览与视频弹窗,常用信息更聚合。'
},
{
type: 'feature',
title: '收藏(书签)按钮',
description: '卡片新增“收藏/取消收藏”书签按钮,和点赞清晰区分;收藏列表可在个人中心查看。'
},
{
type: 'feature',
title: '回收站与批量操作(管理员)',
description: '删除默认进入回收站;支持批量恢复与永久删除,关键操作留痕,误删可挽回。'
},
{
type: 'feature',
title: '头像上传与即时刷新',
description: '头像选择-预览-上传一体化,提交后标题栏头像即时更新,无需整页刷新。'
},
{
type: 'feature',
title: '一键复制分享链接',
description: '卡片操作区支持复制分享链接,便于把素材快速发给同事协作。'
},
{
type: 'improvement',
title: '收藏按钮样式与反馈优化',
description: '采用书签样式,未收藏/已收藏状态对比明显;悬停与点击反馈更清晰。'
},
{
type: 'improvement',
title: '转码失败一键重试',
description: '在卡片上即可重试转码,结合实时进度提示,处理更直观。'
},
{
type: 'improvement',
title: '预览视频编码与清晰度升级',
description: '预览视频改用 H.265 (HEVC) 编码,并提升分辨率与质量;画质更清晰。由于分辨率与质量目标提升,网络带宽占用与转码耗时会相应增加(弱网环境建议暂停自动播放)。'
},
{
type: 'fix',
title: '首页卡片稳定性修复',
description: '修复极端情况下预览区不渲染的问题,提升卡片加载稳定性。'
}
]
},
{
version: '2.1.6',
date: '2025-12-06',
title: '系统更新',
items: [
{
type: 'feature',
title: '素材类型标签',
description: '现在每个素材卡片的右上角都会显示素材类型标签(如"3DMAX"、"视频"等),不同颜色区分,让您一眼就能看出是什么类型的素材。'
},
{
type: 'feature',
title: '素材ID快速复制',
description: '素材卡片底部现在会显示完整的素材ID,点击ID就能一键复制到剪贴板,再也不用手动选择复制了!复制成功后会显示提示,非常方便。'
},
{
type: 'feature',
title: '视频素材卡片预览',
description: '视频素材在首页卡片上会自动播放10秒预览片段,转码完成后会显示预览视频而不是静态封面图。还会显示视频时长和转码进度,让您实时了解处理状态。'
},
{
type: 'improvement',
title: '视频转码速度大幅提升',
description: '优化了存储服务连接方式,视频转码速度更快了!之前转码可能会卡住的问题已经解决,现在转码过程更加稳定可靠。'
},
{
type: 'feature',
title: '新增"更新记录"菜单',
description: '在头像下拉菜单中新增了"更新记录"入口,您可以随时查看系统所有的历史更新,了解系统都做了哪些改进。'
},
{
type: 'improvement',
title: '优化复制提示位置',
description: '复制素材ID时的提示消息现在显示在页面顶部居中位置,不会再和标题栏重叠了,提示更加清晰明显。'
}
]
},
{
version: '2.1.5',
date: '2025-12-06',
title: '系统更新',
items: [
{
type: 'feature',
title: '视频转码实时进度显示',
description: '现在上传视频后,可以实时看到转码进度了!进度条会准确显示当前转码状态,让您清楚知道视频处理到哪一步了。'
},
{
type: 'improvement',
title: '视频上传稳定性大幅提升',
description: '优化了视频上传机制,即使网络不稳定也能可靠完成上传。系统会自动重试,确保您的视频素材不会丢失。'
},
{
type: 'improvement',
title: '视频预览播放更流畅',
description: '优化了视频预览播放体验,加载速度更快,播放更流畅。现在可以更顺畅地查看和预览视频素材了。'
},
{
type: 'fix',
title: '修复视频转码卡顿问题',
description: '修复了视频转码过程中可能出现的卡顿问题,现在转码过程更加稳定可靠,不会中途失败。'
},
{
type: 'feature',
title: '更新日志弹窗',
description: '新增了更新日志功能,每次系统有新功能或优化时,会自动弹出更新说明,让您及时了解系统改进。'
}
]
}
]
interface UpdateLogModalProps {
isOpen: boolean
onClose: () => void
updateLog?: UpdateLog // 可选,如果不传则显示所有历史记录
showAllHistory?: boolean // 是否显示所有历史记录
}
function UpdateLogModal({ isOpen, onClose, updateLog, showAllHistory = false }: UpdateLogModalProps) {
useEffect(() => {
if (isOpen) {
// 阻止背景滚动
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = 'unset'
}
return () => {
document.body.style.overflow = 'unset'
}
}, [isOpen])
if (!isOpen) return null
// 如果showAllHistory为true,显示所有历史记录;否则只显示指定的updateLog
const logsToShow = showAllHistory ? UPDATE_LOGS : (updateLog ? [updateLog] : [])
// 如果没有可显示的内容,不渲染弹窗
if (logsToShow.length === 0) return null
const getTypeIcon = (type: string) => {
switch (type) {
case 'feature':
return '✨'
case 'improvement':
return '🚀'
case 'fix':
return '🔧'
default:
return '📝'
}
}
const getTypeColor = (type: string) => {
switch (type) {
case 'feature':
return 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800'
case 'improvement':
return 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800'
case 'fix':
return 'bg-orange-50 dark:bg-orange-900/20 border-orange-200 dark:border-orange-800'
default:
return 'bg-gray-50 dark:bg-gray-800 border-gray-200 dark:border-gray-700'
}
}
return (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
{/* 背景遮罩 */}
<div
className="absolute inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm transition-opacity"
onClick={onClose}
/>
{/* 弹窗内容 */}
<div className="relative w-full max-w-2xl max-h-[85vh] bg-white dark:bg-gray-800 rounded-2xl shadow-apple-lg overflow-hidden transform transition-all flex flex-col">
{/* 头部 */}
<div className="flex items-center justify-between px-6 py-5 border-b border-gray-200 dark:border-gray-700 bg-gradient-to-r from-gray-50 to-white dark:from-gray-800 dark:to-gray-900 flex-shrink-0">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
</svg>
</div>
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{showAllHistory ? '更新记录' : updateLog?.title || '系统更新'}
</h2>
<p className="text-sm text-gray-500 dark:text-gray-400">
{showAllHistory ? '查看所有历史更新' : `版本 ${updateLog?.version || ''} · ${updateLog?.date || ''}`}
</p>
</div>
</div>
<button
onClick={onClose}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
aria-label="关闭"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 内容区域 */}
<div className="px-6 py-6 overflow-y-auto flex-1 min-h-0">
<div className="space-y-6">
{logsToShow.map((log, logIndex) => (
<div key={logIndex} className="space-y-4">
{/* 版本标题(仅在显示所有历史时显示) */}
{showAllHistory && (
<div className="sticky top-0 bg-white dark:bg-gray-800 pb-2 border-b border-gray-200 dark:border-gray-700 z-10 mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
版本 {log.version}
</h3>
<p className="text-xs text-gray-500 dark:text-gray-400">
{log.date}
</p>
</div>
)}
{/* 更新项列表 */}
<div className="space-y-4">
{log.items.map((item, itemIndex) => (
<div
key={itemIndex}
className={`p-4 rounded-xl border ${getTypeColor(item.type)} transition-all hover:shadow-md`}
>
<div className="flex items-start gap-3">
<span className="text-2xl flex-shrink-0">{getTypeIcon(item.type)}</span>
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-gray-900 dark:text-white mb-1.5">
{item.title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-300 leading-relaxed">
{item.description}
</p>
</div>
</div>
</div>
))}
</div>
</div>
))}
</div>
</div>
{/* 底部按钮 */}
<div className="px-6 py-5 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/50 flex-shrink-0">
<button
onClick={onClose}
className="w-full py-3 px-6 bg-gray-900 dark:bg-gray-700 text-white rounded-xl font-medium hover:bg-gray-800 dark:hover:bg-gray-600 active:scale-[0.98] transition-all duration-200 shadow-apple hover:shadow-apple-lg"
>
知道了
</button>
</div>
</div>
</div>
)
}
// Hook: 检查是否需要显示更新弹窗
export function useUpdateLog() {
const [showModal, setShowModal] = useState(false)
const [currentLog, setCurrentLog] = useState<UpdateLog | null>(null)
const [showAllHistory, setShowAllHistory] = useState(false)
useEffect(() => {
// 监听自定义事件,用于手动触发显示更新日志
const handleShowUpdateLog = (event: CustomEvent) => {
if (event.detail?.log) {
setCurrentLog(event.detail.log)
setShowAllHistory(false)
setShowModal(true)
} else if (event.detail?.showAll) {
setCurrentLog(null)
setShowAllHistory(true)
setShowModal(true)
}
}
window.addEventListener('showUpdateLog', handleShowUpdateLog as EventListener)
// 检查是否已查看过当前版本(仅在首次加载时自动弹出)
// 注意:只有经过测试验证的功能更新才会添加到UPDATE_LOGS中
const viewedVersion = localStorage.getItem(STORAGE_KEY)
if (viewedVersion !== CURRENT_VERSION) {
// 找到当前版本的更新日志
const log = UPDATE_LOGS.find(log => log.version === CURRENT_VERSION)
if (log) {
setCurrentLog(log)
setShowAllHistory(false)
setShowModal(true)
}
}
return () => {
window.removeEventListener('showUpdateLog', handleShowUpdateLog as EventListener)
}
}, [])
const handleClose = () => {
setShowModal(false)
// 如果显示的是当前版本,标记已查看
if (!showAllHistory && currentLog?.version === CURRENT_VERSION) {
localStorage.setItem(STORAGE_KEY, CURRENT_VERSION)
}
}
return {
showModal,
currentLog,
showAllHistory,
handleClose,
}
}
// 更新日志管理器 - 用于手动触发显示更新日志
export function showUpdateLog(version?: string) {
if (version === 'all' || version === undefined) {
// 显示所有历史记录
const event = new CustomEvent('showUpdateLog', { detail: { showAll: true } })
window.dispatchEvent(event)
} else {
// 显示指定版本
const log = UPDATE_LOGS.find(log => log.version === version)
if (log) {
const event = new CustomEvent('showUpdateLog', { detail: { log } })
window.dispatchEvent(event)
}
}
}
export default UpdateLogModal