Files
chatroom/resources/views/changelog/index.blade.php
T

310 lines
12 KiB
PHP
Raw Normal View History

{{--
文件功能:开发日志前台独立页面(/changelog
时间轴样式,懒加载(IntersectionObserver),倒序显示
仅展示已发布的日志(is_published=1
支持 URL #vYYYY-MM-DD 锚点直跳指定版本
@extends layouts.app
--}}
@extends('layouts.app')
@section('title', '更新日志 - 飘落流星')
@section('nav-icon', '📋')
@section('nav-title', '更新日志')
@section('head')
<style>
/* 时间轴主线 */
.tl-line {
position: absolute;
left: 9px;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(to bottom, #818cf8 0%, #c4b5fd 70%, transparent 100%);
}
/* 时间轴节点圆点 */
.tl-dot {
position: absolute;
left: 0;
top: 22px;
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #818cf8;
background: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
transition: transform 0.2s, border-color 0.2s;
z-index: 1;
}
.tl-item:hover .tl-dot {
transform: scale(1.25);
border-color: #4f46e5;
}
/* Markdown 内容样式 */
.prose-log h1,
.prose-log h2,
.prose-log h3 {
font-weight: 700;
color: #1e1b4b;
margin: 1rem 0 0.5rem;
}
.prose-log h2 {
font-size: 1rem;
border-bottom: 1px solid #e0e7ff;
padding-bottom: 4px;
}
.prose-log ul {
list-style: disc;
padding-left: 1.5rem;
margin: 0.4rem 0;
}
.prose-log ol {
list-style: decimal;
padding-left: 1.5rem;
margin: 0.4rem 0;
}
.prose-log li {
margin: 0.2rem 0;
line-height: 1.6;
}
.prose-log p {
margin: 0.4rem 0;
line-height: 1.7;
}
.prose-log code {
background: #f1f5f9;
padding: 1px 5px;
border-radius: 4px;
font-size: 0.85em;
color: #7c3aed;
font-family: monospace;
}
.prose-log a {
color: #4f46e5;
text-decoration: underline;
}
.prose-log strong {
font-weight: 600;
}
.prose-log blockquote {
border-left: 3px solid #818cf8;
padding-left: 10px;
color: #64748b;
font-style: italic;
margin: 0.5rem 0;
}
</style>
@endsection
@section('content')
<div class="max-w-3xl mx-auto py-8 px-4 sm:px-6" x-data="{
items: [],
lastId: null,
hasMore: true,
loading: false,
init() {
// SSR 首屏数据注入
this.items = {{ json_encode(
$changelogs->map(
fn($l) => [
'id' => $l->id,
'version' => $l->version,
'title' => $l->title,
'type_label' => $l->type_label,
'type_color' => $l->type_color,
'content_html' => $l->content_html,
'summary' => $l->summary,
'published_at' => $l->published_at?->format('Y-m-d'),
'expanded' => false,
],
),
) }};
if (this.items.length > 0) {
this.lastId = this.items[this.items.length - 1].id;
this.hasMore = this.items.length >= 10;
} else {
this.hasMore = false;
}
// 处理 URL #v2026-02-28 锚点跳转
if (window.location.hash && window.location.hash.startsWith('#v')) {
const version = window.location.hash.slice(2);
this.$nextTick(() => {
const el = document.getElementById('v' + version);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
const item = this.items.find(i => i.version === version);
if (item) item.expanded = true;
}
});
}
},
// 懒加载更多
async loadMore() {
if (this.loading || !this.hasMore) return;
this.loading = true;
try {
const res = await fetch(`/changelog/more?after_id=${this.lastId}`);
const data = await res.json();
this.items.push(...data.items.map(i => ({ ...i, expanded: false })));
if (data.items.length > 0) this.lastId = data.items[data.items.length - 1].id;
this.hasMore = data.has_more;
} catch (e) { console.error(e); } finally { this.loading = false; }
},
}">
{{-- 页面标题区 --}}
<div class="flex items-center justify-between mb-8">
<div>
<h2 class="text-2xl font-extrabold text-gray-800 flex items-center gap-2">
📋 <span>开发日志</span>
</h2>
<p class="text-sm text-gray-500 mt-1">记录每次版本的功能新增、Bug 修复与优化改进</p>
</div>
<div class="text-right text-xs text-gray-400">
<p>按更新时间排序</p>
<p class="text-indigo-500 font-medium mt-0.5"> {{ $changelogs->count() }}+ 条日志</p>
</div>
</div>
{{-- 空状态 --}}
<template x-if="items.length === 0">
<div class="text-center py-24 text-gray-400">
<p class="text-6xl mb-4">📭</p>
<p class="text-lg font-bold text-gray-500">暂无更新日志</p>
<p class="text-sm mt-2">开发团队还没有发布任何日志,请稍后再来查看</p>
</div>
</template>
{{-- 时间轴 --}}
<div class="relative pl-10">
{{-- 时间轴主线 --}}
<div class="tl-line"></div>
<template x-for="(log, index) in items" :key="log.id">
<div class="tl-item relative mb-8" :id="'v' + log.version">
{{-- 圆点节点 --}}
<div class="tl-dot"
:class="{
'border-emerald-400': log.type_color === 'emerald',
'border-rose-400': log.type_color === 'rose',
'border-blue-400': log.type_color === 'blue',
'border-slate-400': log.type_color === 'slate',
}">
<span
x-text="log.type_color === 'emerald' ? '🆕' : log.type_color === 'rose' ? '🐛' : log.type_color === 'blue' ? '⚡' : '📌'"
class="text-xs"></span>
</div>
{{-- 日志卡片 --}}
<div
class="bg-white rounded-2xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow duration-200 overflow-hidden">
{{-- 卡片头部 --}}
<div class="px-5 py-4 border-b border-gray-50">
<div class="flex items-center gap-3 flex-wrap">
{{-- 版本号 --}}
<span class="font-mono text-lg font-black text-indigo-700 tracking-wide"
x-text="'v' + log.version"></span>
{{-- 类型标签 --}}
<span class="px-2.5 py-0.5 rounded-full text-xs font-bold"
:class="{
'bg-emerald-100 text-emerald-700': log.type_color === 'emerald',
'bg-rose-100 text-rose-700': log.type_color === 'rose',
'bg-blue-100 text-blue-700': log.type_color === 'blue',
'bg-slate-100 text-slate-600': log.type_color === 'slate',
}"
x-text="log.type_label"></span>
{{-- 发布日期 --}}
<span class="text-gray-400 text-xs ml-auto" x-text="log.published_at"></span>
</div>
{{-- 标题 --}}
<h3 class="text-base font-bold text-gray-800 mt-2 leading-snug" x-text="log.title"></h3>
</div>
{{-- 内容区 --}}
<div class="px-5 py-4">
{{-- 默认折叠:摘要 --}}
<div x-show="!log.expanded">
<p class="text-gray-600 text-sm leading-relaxed" x-text="log.summary"></p>
<button @click="log.expanded = true"
class="mt-3 text-indigo-600 hover:text-indigo-800 text-xs font-semibold hover:underline flex items-center gap-1">
展开完整内容 <span class="text-base leading-none"></span>
</button>
</div>
{{-- 展开:完整 Markdown 内容 --}}
<div x-show="log.expanded" x-transition.opacity.duration.150ms>
<div class="prose-log text-sm text-gray-700" x-html="log.content_html"></div>
<button @click="log.expanded = false"
class="mt-3 text-gray-400 hover:text-gray-600 text-xs font-semibold hover:underline flex items-center gap-1">
收起 <span class="text-base leading-none"></span>
</button>
</div>
</div>
{{-- 底部:版本锚点复制链接 --}}
<div class="px-5 py-2 bg-gray-50 border-t border-gray-100 flex items-center justify-between">
<span class="text-gray-400 text-xs">
#<span x-text="log.version"></span>
</span>
<button
@click="navigator.clipboard?.writeText(window.location.origin + '/changelog#v' + log.version).then(() => { $el.textContent = '✅ 已复制'; setTimeout(() => $el.textContent = '🔗 复制链接', 1500) })"
class="text-xs text-gray-400 hover:text-indigo-600 transition">
🔗 复制链接
</button>
</div>
</div>
</div>
</template>
{{-- 懒加载触发哨兵 --}}
<div x-show="hasMore" x-intersect.threshold.10="loadMore()" class="py-6 text-center">
<template x-if="loading">
<div class="flex items-center justify-center gap-2 text-gray-400 text-sm">
<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z">
</path>
</svg>
加载更多...
</div>
</template>
</div>
{{-- 全部加载完成提示 --}}
<div x-show="!hasMore && items.length > 0" class="text-center py-6 text-gray-400 text-sm">
<div class="inline-flex items-center gap-2">
<div class="h-px w-16 bg-gray-200"></div>
<span>以上是全部更新日志</span>
<div class="h-px w-16 bg-gray-200"></div>
</div>
</div>
</div>
</div>
@endsection