Files
chatroom/resources/views/changelog/index.blade.php
lkddi 5f30220609 feat: 任命/撤销通知系统 + 用户名片UI优化
- 任命/撤销事件增加 type 字段区分类型
- 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息
- 撤销:灰色弹窗 + 灰色系统消息,无礼花
- 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏
- 系统消息加随机鼓励语(各5条轮换)
- ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds)
- 用户名片折叠优化:管理员视野、职务履历均可折叠
- 管理操作 + 职务操作合并为「🔧 管理操作」折叠区
- 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
2026-02-28 23:44:38 +08:00

310 lines
12 KiB
PHP
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.
{{--
文件功能:开发日志前台独立页面(/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