diff --git a/app/Blog/Controllers/ArchiveController.php b/app/Blog/Controllers/ArchiveController.php index 38dd3b3..8cebf9e 100644 --- a/app/Blog/Controllers/ArchiveController.php +++ b/app/Blog/Controllers/ArchiveController.php @@ -14,8 +14,14 @@ class ArchiveController extends Controller { $posts = Post::published()->orderByDesc('published_at')->get(['id', 'title', 'slug', 'published_at']); - $archives = $posts->groupBy(fn (Post $post) => $post->published_at->format('Y')) - ->map(fn ($yearPosts) => $yearPosts->groupBy(fn (Post $post) => $post->published_at->format('m'))); + // 年 → 月(1-12) → 日 → 文章,用于按年/月/日索引 + $archives = []; + foreach ($posts as $post) { + $year = $post->published_at->format('Y'); + $month = (int) $post->published_at->format('m'); + $day = (int) $post->published_at->format('d'); + $archives[$year][$month][$day][] = $post; + } return theme_view('archives', compact('archives')); } diff --git a/app/Blog/Support/HomeViewMode.php b/app/Blog/Support/HomeViewMode.php index 6cd94e7..df39546 100644 --- a/app/Blog/Support/HomeViewMode.php +++ b/app/Blog/Support/HomeViewMode.php @@ -15,6 +15,8 @@ class HomeViewMode public const FULL = 'full'; + public const CARD = 'card'; + public const COOKIE = 'll_home_view'; /** @@ -25,12 +27,13 @@ class HomeViewMode return [ self::LIST => '列表', self::FULL => '标准', + self::CARD => '图文', ]; } public static function isValid(mixed $value): bool { - return is_string($value) && in_array($value, [self::LIST, self::FULL], true); + return is_string($value) && in_array($value, [self::LIST, self::FULL, self::CARD], true); } public static function resolve(?Request $request = null): string diff --git a/app/Blog/Support/PageCacheMiddleware.php b/app/Blog/Support/PageCacheMiddleware.php index 96c84e2..e31977f 100644 --- a/app/Blog/Support/PageCacheMiddleware.php +++ b/app/Blog/Support/PageCacheMiddleware.php @@ -41,11 +41,12 @@ class PageCacheMiddleware $response = $next($request); if ($response->getStatusCode() === 200) { + $body = $response->getContent(); Cache::put($key, [ - 'body' => $response->getContent(), + 'body' => $body, 'type' => $response->headers->get('Content-Type') ?: 'text/html; charset=utf-8', ], $ttl); - self::rememberKey($key, $request->fullUrl()); + self::rememberKey($key, $request->fullUrl(), strlen($body), $ttl); } return $response; @@ -89,7 +90,7 @@ class PageCacheMiddleware } /** - * @return list + * @return list */ public static function entries(): array { @@ -100,6 +101,8 @@ class PageCacheMiddleware 'key' => (string) $key, 'url' => is_array($meta) ? (string) ($meta['url'] ?? $key) : (string) $key, 'cached_at' => is_array($meta) ? (string) ($meta['cached_at'] ?? '') : '', + 'size' => is_array($meta) ? (int) ($meta['size'] ?? 0) : 0, + 'expires_at' => is_array($meta) ? (string) ($meta['expires_at'] ?? '') : '', ]; } @@ -122,7 +125,7 @@ class PageCacheMiddleware Cache::put($key, (int) Cache::get($key, 0) + 1, now()->addDay()); } - private static function rememberKey(string $key, ?string $url = null): void + private static function rememberKey(string $key, ?string $url = null, int $size = 0, int $ttl = 0): void { $keys = (array) Cache::get(self::TAG.'_keys', []); $previous = $keys[$key] ?? []; @@ -130,6 +133,8 @@ class PageCacheMiddleware $keys[$key] = [ 'url' => $url ?: (is_array($previous) ? (string) ($previous['url'] ?? '') : ''), 'cached_at' => now()->toDateTimeString(), + 'size' => $size, + 'expires_at' => $ttl > 0 ? now()->addSeconds($ttl)->toDateTimeString() : '', ]; Cache::put(self::TAG.'_keys', $keys, now()->addDay()); diff --git a/app/Console/Commands/SablogImport.php b/app/Console/Commands/SablogImport.php index c685a44..8ada18d 100644 --- a/app/Console/Commands/SablogImport.php +++ b/app/Console/Commands/SablogImport.php @@ -215,6 +215,7 @@ class SablogImport extends Command { $count = 0; foreach ($this->rows('categories') as $row) { + $row['name'] = html_entity_decode((string) $row['name'], ENT_QUOTES | ENT_HTML5, 'UTF-8'); $slug = SlugGenerator::make($row['name'], 'categories', 'slug', ignoreId: (int) $row['cid'], fallback: 'category-'.$row['cid']); DB::table('categories')->updateOrInsert(['id' => (int) $row['cid']], [ 'name' => $row['name'], @@ -291,7 +292,7 @@ class SablogImport extends Command DB::table('posts')->updateOrInsert(['id' => (int) $row['articleid']], [ 'category_id' => $categoryId, 'user_id' => (int) $row['uid'] ?: null, - 'title' => $row['title'], + 'title' => html_entity_decode((string) $row['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'slug' => $slug, 'excerpt' => $row['description'] ?: null, 'content' => $row['content'], @@ -323,6 +324,7 @@ class SablogImport extends Command // 而不是 sablog_tags.aids(那个映射是错的/过时的) foreach ($this->rows('articles') as $row) { $keywords = trim((string) ($row['keywords'] ?? '')); + $keywords = html_entity_decode($keywords, ENT_QUOTES | ENT_HTML5, 'UTF-8'); if ($keywords === '') { continue; } @@ -371,7 +373,7 @@ class SablogImport extends Command foreach ($this->rows('comments') as $row) { DB::table('comments')->updateOrInsert(['id' => (int) $row['commentid']], [ 'post_id' => (int) $row['articleid'], - 'author_name' => $row['author'] ?: '匿名', + 'author_name' => html_entity_decode((string) ($row['author'] ?: '匿名'), ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'author_url' => $row['url'] ?: null, 'content' => $row['content'], 'ip' => $row['ipaddress'] ?: null, @@ -389,7 +391,7 @@ class SablogImport extends Command $count = 0; foreach ($this->rows('links') as $row) { DB::table('links')->updateOrInsert(['id' => (int) $row['linkid']], [ - 'name' => $row['name'], + 'name' => html_entity_decode((string) $row['name'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'url' => $row['url'], 'note' => $row['note'] ?: null, 'display_order' => (int) $row['displayorder'], diff --git a/app/Filament/Pages/ThemeInject.php b/app/Filament/Pages/ThemeInject.php index 1c62ed0..c90ce87 100644 --- a/app/Filament/Pages/ThemeInject.php +++ b/app/Filament/Pages/ThemeInject.php @@ -8,7 +8,6 @@ use App\Blog\Support\ThemeInjector; use App\Blog\Support\ThemeManager; use App\Models\Setting; use Filament\Actions\Action; -use Filament\Forms\Components\KeyValue; use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; @@ -38,9 +37,15 @@ class ThemeInject extends Page public function mount(): void { + $vars = app(ThemeManager::class)->variables(); + $varRows = []; + foreach ($vars as $key => $value) { + $varRows[] = ['key' => (string) $key, 'value' => (string) $value]; + } + $this->form->fill([ 'inject' => $this->normalizeBlocks(Setting::get('inject.blocks', [])), - 'theme_vars' => app(ThemeManager::class)->variables(), + 'theme_vars' => $varRows, ]); } @@ -80,11 +85,23 @@ class ThemeInject extends Page $tabs[] = Tab::make('主题变量(stylevar)') ->schema([ - KeyValue::make('theme_vars') + Repeater::make('theme_vars') ->label('当前主题变量:视图用 {{ theme(\'var\', \'key\') }} 引用') - ->keyLabel('变量名') - ->valueLabel('内容(HTML)') + ->schema([ + TextInput::make('key') + ->label('变量名') + ->required(), + Textarea::make('value') + ->label('内容(HTML)') + ->rows(4), + ]) + ->columns(1) + ->defaultItems(0) + ->collapsible() + ->itemLabel(fn (array $state): ?string => $state['key'] ?? null) ->reorderable() + ->reorderableWithButtons() + ->addActionLabel('添加变量') ->columnSpanFull(), ]); @@ -118,7 +135,16 @@ class ThemeInject extends Page } Setting::set('inject.blocks', $blocks); - Setting::set('theme_vars_'.app(ThemeManager::class)->active(), $data['theme_vars'] ?? []); + + $vars = []; + foreach ($data['theme_vars'] ?? [] as $row) { + $key = trim((string) ($row['key'] ?? '')); + if ($key === '') { + continue; + } + $vars[$key] = (string) ($row['value'] ?? ''); + } + Setting::set('theme_vars_'.app(ThemeManager::class)->active(), $vars); \App\Blog\Support\PageCacheMiddleware::flush(); Notification::make()->title('注入内容已保存')->success()->send(); diff --git a/app/Livewire/LegacySyncButton.php b/app/Livewire/LegacySyncButton.php new file mode 100644 index 0000000..2c0ae9e --- /dev/null +++ b/app/Livewire/LegacySyncButton.php @@ -0,0 +1,61 @@ +running = true; + + $src = config('blog.sablog_source'); + + try { + Artisan::call('sablog:import', [ + '--host' => $src['host'], + '--port' => $src['port'], + '--database' => $src['database'], + '--username' => $src['username'], + '--password' => $src['password'], + '--prefix' => $src['prefix'], + ]); + $output = trim(Artisan::output()); + + PageCacheMiddleware::flush(); + + Notification::make() + ->title('旧站数据同步完成') + ->body(Str::afterLast($output, '报告:') ?: $output) + ->success() + ->persistent() + ->send(); + } catch (\Throwable $e) { + Notification::make() + ->title('同步失败') + ->body($e->getMessage()) + ->danger() + ->persistent() + ->send(); + } + + $this->running = false; + } + + public function render(): \Illuminate\Contracts\View\View + { + return view('livewire.legacy-sync-button'); + } +} diff --git a/app/Models/Post.php b/app/Models/Post.php index 850577e..a9e4d30 100644 --- a/app/Models/Post.php +++ b/app/Models/Post.php @@ -133,6 +133,35 @@ class Post extends Model implements HasMedia return $cover->getUrl($conversion) ?: $cover->getUrl(); } + /** + * 封面回退链:显式封面 → 附件/正文里的第一张图。 + * 都没有时返回 null,由调用方决定兜底(占位图或不显示)。 + */ + public function coverOrFirstImageUrl(): ?string + { + if ($url = $this->coverImageUrl()) { + return $url; + } + + // 附件里的第一张图片 + $attachment = $this->getMedia('attachments') + ->first(fn ($m) => str_starts_with((string) $m->mime_type, 'image/')); + if ($attachment) { + return $attachment->getUrl(); + } + + // 正文 HTML/Markdown 里的第一张图片地址 + $content = (string) $this->content; + if (preg_match('/]+src=["\']([^"\']+)["\']/i', $content, $m)) { + return $m[1]; + } + if (preg_match('/!\[[^\]]*\]\((https?:\/\/[^)\s]+)[^)]*\)/i', $content, $m)) { + return $m[1]; + } + + return null; + } + public function comments(): HasMany { return $this->hasMany(Comment::class); diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 86df3ea..83558a5 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -48,41 +48,6 @@ class AdminPanelProvider extends PanelProvider ->sidebarWidth('13rem') ->maxContentWidth(\Filament\Support\Enums\Width::Full) ->userMenuItems([ - // 工具:同步旧站数据(sablog 增量导入,约 1 分钟) - \Filament\Actions\Action::make('sablog-import') - ->label('同步旧站数据') - ->icon('heroicon-o-arrow-path') - ->color('info') - ->requiresConfirmation() - ->modalDescription('从旧 sablog 库增量导入文章/评论/标签等(幂等,不会产生重复),约需 1 分钟,期间请不要重复点击。') - ->action(function (): void { - $src = config('blog.sablog_source'); - try { - \Illuminate\Support\Facades\Artisan::call('sablog:import', [ - '--host' => $src['host'], - '--port' => $src['port'], - '--database' => $src['database'], - '--username' => $src['username'], - '--password' => $src['password'], - '--prefix' => $src['prefix'], - ]); - $output = trim(\Illuminate\Support\Facades\Artisan::output()); - \App\Blog\Support\PageCacheMiddleware::flush(); - \Filament\Notifications\Notification::make() - ->title('旧站数据同步完成') - ->body(\Illuminate\Support\Str::afterLast($output, '报告:') ?: $output) - ->success() - ->persistent() - ->send(); - } catch (\Throwable $e) { - \Filament\Notifications\Notification::make() - ->title('同步失败') - ->body($e->getMessage()) - ->danger() - ->persistent() - ->send(); - } - }), // 工具:清空缓存(Livewire action,即时通知) \Filament\Actions\Action::make('flush-page-cache') ->label('清空前台缓存') diff --git a/public/themes/sablog/style.css b/public/themes/sablog/style.css index 80e10b9..cd43a49 100644 --- a/public/themes/sablog/style.css +++ b/public/themes/sablog/style.css @@ -102,6 +102,90 @@ a:hover { padding: 18px 20px; margin-bottom: 16px; } +/* 列表模式:纯标题列表,紧凑 */ +.post-list-list .post-card { + padding: 8px 20px; + margin-bottom: 0; + border-bottom: none; +} +.post-list-list .post-card:last-child { + border-bottom: 1px solid #d8dfe6; + margin-bottom: 16px; +} +.post-list-list .post-card .post-title { + font-size: 15px; + margin-bottom: 2px; + display: inline; +} +.post-list-list .post-card .post-meta { + display: inline; + margin-left: 10px; +} +/* 图文模式:卡片网格 */ +.post-list-card { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-bottom: 16px; +} +.post-list-card .post-card { + margin-bottom: 0; + padding: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} +.post-list-card .post-cover { + display: block; + aspect-ratio: 16 / 10; + background: #eef2f5; + overflow: hidden; +} +.post-list-card .post-cover img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.post-cover-placeholder { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding: 12px; + color: #7a8a99; + font-size: 14px; + text-align: center; + background: linear-gradient(135deg, #eef2f5 0%, #dde7ee 100%); +} +.post-list-card .post-card-body { + padding: 12px 14px; + flex: 1; +} +.post-list-card .post-title { + font-size: 15px; + margin-bottom: 6px; +} +.post-list-card .post-excerpt { + font-size: 13px; + color: #666; + margin-bottom: 8px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +@media (max-width: 1024px) { + .post-list-card { + grid-template-columns: repeat(2, 1fr); + } +} +@media (max-width: 640px) { + .post-list-card { + grid-template-columns: 1fr; + } +} + .post-card.sticky { border-left: 4px solid #f60; } @@ -347,7 +431,7 @@ a:hover { /* 兜底页面(归档/标签云/友链/评论/登录等使用默认布局) */ .container.main-layout { - max-width: 960px; + max-width: 1380px; margin: 0 auto; } .main-layout { @@ -452,6 +536,63 @@ a:hover { margin: 4px 0 10px 20px; } +/* 年/月/日索引归档 */ +.archive-months { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + margin-bottom: 18px; +} +.archive-month { + border: 1px solid #e5e5e5; + border-radius: 4px; + padding: 8px 10px; + background: #fafafa; +} +.archive-month.is-empty { + opacity: .45; + background: transparent; +} +.archive-month-title { + display: block; + font-size: 13px; + font-weight: bold; + margin-bottom: 4px; +} +.archive-month.has-posts .archive-month-title:hover { + text-decoration: underline; +} +.archive-month-title .count { + color: #999; + font-weight: normal; + font-size: 12px; +} +.archive-days { + display: flex; + flex-wrap: wrap; + gap: 4px; +} +.archive-day { + display: inline-block; + min-width: 22px; + text-align: center; + font-size: 12px; + line-height: 20px; + border-radius: 3px; + background: #e8f0f8; + color: #1c62b0; + text-decoration: none; +} +.archive-day:hover { + background: #1c62b0; + color: #fff; +} +@media (max-width: 768px) { + .archive-months { + grid-template-columns: repeat(2, 1fr); + } +} + .links-list { list-style: none; } diff --git a/resources/views/archives.blade.php b/resources/views/archives.blade.php index 12e2b52..8aec03e 100644 --- a/resources/views/archives.blade.php +++ b/resources/views/archives.blade.php @@ -8,20 +8,32 @@ @include('partials.theme-inject', ['point' => 'content_top']) @forelse($archives as $year => $months) -

{{ $year }} 年

-
    - @foreach($months as $month => $monthPosts) -
  • - {{ (int) $month }} 月 - ({{ $monthPosts->count() }} 篇) -
      - @foreach($monthPosts as $post) -
    • {{ $post->title }}
    • - @endforeach -
    -
  • - @endforeach -
+
+

{{ $year }} 年

+
+ @foreach(range(1, 12) as $month) + @php $days = $months[$month] ?? []; @endphp +
+ @if(count($days)) + + {{ $month }} 月 ({{ collect($days)->flatten(1)->count() }}) + +
+ @foreach($days as $day => $dayPosts) + @if(count($dayPosts) === 1) + {{ $day }} + @else + {{ $day }} + @endif + @endforeach +
+ @else + {{ $month }} 月 + @endif +
+ @endforeach +
+
@empty
暂无归档
@endforelse diff --git a/resources/views/filament/hooks/topbar-tools.blade.php b/resources/views/filament/hooks/topbar-tools.blade.php index a74e4b6..6669b44 100644 --- a/resources/views/filament/hooks/topbar-tools.blade.php +++ b/resources/views/filament/hooks/topbar-tools.blade.php @@ -31,4 +31,6 @@ > 写文章 + + diff --git a/resources/views/filament/pages/page-cache.blade.php b/resources/views/filament/pages/page-cache.blade.php index 08d22fb..5a38ddc 100644 --- a/resources/views/filament/pages/page-cache.blade.php +++ b/resources/views/filament/pages/page-cache.blade.php @@ -31,7 +31,9 @@ 地址 + 大小 缓存时间 + 过期时间 操作 @@ -43,7 +45,9 @@ {{ $entry['url'] }} + {{ $entry['size'] ? number_format($entry['size'] / 1024, 1).' KB' : '—' }} {{ $entry['cached_at'] ?: '—' }} + {{ $entry['expires_at'] ?: '—' }} +

+ 共 {{ count($this->entries) }} 条,合计 {{ number_format(collect($this->entries)->sum('size') / 1024 / 1024, 2) }} MB。 + 缓存说明:只缓存未登录访客 GET 的 200 页面;登录用户、评论提交等动态请求不缓存;浏览量统计不受影响。 +

@endif diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index efc71c7..fbba20f 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -14,11 +14,13 @@ @include('partials.view-mode-switcher') @include('partials.theme-inject', ['point' => 'content_top']) - @forelse($posts as $post) - @include('partials.post-card', ['post' => $post]) - @empty -
暂无文章
- @endforelse +
+ @forelse($posts as $post) + @include('partials.post-card', ['post' => $post]) + @empty +
暂无文章
+ @endforelse +
@include('partials.pagination', ['paginator' => $posts]) @include('partials.theme-inject', ['point' => 'content_bottom']) diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php index 5ca4a58..9c7184e 100644 --- a/resources/views/list.blade.php +++ b/resources/views/list.blade.php @@ -27,11 +27,13 @@ @include('partials.view-mode-switcher') @include('partials.theme-inject', ['point' => 'content_top']) - @forelse($posts as $post) - @include('partials.post-card', ['post' => $post]) - @empty -
该分类暂无文章
- @endforelse +
+ @forelse($posts as $post) + @include('partials.post-card', ['post' => $post]) + @empty +
该分类暂无文章
+ @endforelse +
@include('partials.pagination', ['paginator' => $posts]) @include('partials.theme-inject', ['point' => 'content_bottom']) diff --git a/resources/views/livewire/legacy-sync-button.blade.php b/resources/views/livewire/legacy-sync-button.blade.php new file mode 100644 index 0000000..3e6ed9f --- /dev/null +++ b/resources/views/livewire/legacy-sync-button.blade.php @@ -0,0 +1,13 @@ +
+ + 同步旧站 + 同步中… + +
diff --git a/resources/views/partials/post-card.blade.php b/resources/views/partials/post-card.blade.php index e84d803..adf55fe 100644 --- a/resources/views/partials/post-card.blade.php +++ b/resources/views/partials/post-card.blade.php @@ -4,38 +4,78 @@ && blank($post->read_password) && empty(($post->meta ?? [])['members_only']); @endphp -
- @if($url = $post->coverImageUrl()) +@if($viewMode === \App\Blog\Support\HomeViewMode::CARD) +
- {{ $post->title }} + @if($url = $post->coverOrFirstImageUrl()) + {{ $post->title }} + @else + {{ \Illuminate\Support\Str::limit($post->title, 24, '') }} + @endif - @endif -

- @if($label = $post->stickyLabel()){{ $label }}@endif - {{ $post->title }} -

-
+@elseif($viewMode === \App\Blog\Support\HomeViewMode::LIST) +
+

+ @if($label = $post->stickyLabel()){{ $label }}@endif + {{ $post->title }} +

+ +
+@else +
+ @if($url = $post->coverImageUrl()) + + {{ $post->title }} + @endif - · 阅读 {{ $post->views }} - · 评论 {{ $post->comment_count }} - - @if($showFull) -
- {!! app(\App\Blog\Services\PostContentRenderer::class)->render($post) !!} +

+ @if($label = $post->stickyLabel()){{ $label }}@endif + {{ $post->title }} +

+ - @else -
- {{ \Illuminate\Support\Str::limit($post->excerpt_or_fallback, 200) }} + @if($showFull) +
+ {!! app(\App\Blog\Services\PostContentRenderer::class)->render($post) !!} +
+ @else +
+ {{ \Illuminate\Support\Str::limit($post->excerpt_or_fallback, 200) }} +
+ @endif + - @endif - -
+
+@endif diff --git a/resources/views/partials/view-mode-switcher.blade.php b/resources/views/partials/view-mode-switcher.blade.php index 5982a60..ddc26b6 100644 --- a/resources/views/partials/view-mode-switcher.blade.php +++ b/resources/views/partials/view-mode-switcher.blade.php @@ -6,4 +6,6 @@ 标准 | 列表 + | + 图文 diff --git a/resources/views/search.blade.php b/resources/views/search.blade.php index c4d7779..e0d535d 100644 --- a/resources/views/search.blade.php +++ b/resources/views/search.blade.php @@ -13,11 +13,13 @@ @include('partials.view-mode-switcher') @include('partials.theme-inject', ['point' => 'content_top']) - @forelse($posts as $post) - @include('partials.post-card', ['post' => $post]) - @empty -
没有找到相关文章
- @endforelse +
+ @forelse($posts as $post) + @include('partials.post-card', ['post' => $post]) + @empty +
没有找到相关文章
+ @endforelse +
@include('partials.pagination', ['paginator' => $posts]) @include('partials.theme-inject', ['point' => 'content_bottom']) diff --git a/resources/views/tag.blade.php b/resources/views/tag.blade.php index d5a7ca0..5549bdd 100644 --- a/resources/views/tag.blade.php +++ b/resources/views/tag.blade.php @@ -9,11 +9,13 @@ @include('partials.view-mode-switcher') @include('partials.theme-inject', ['point' => 'content_top']) - @forelse($posts as $post) - @include('partials.post-card', ['post' => $post]) - @empty -
该标签暂无文章
- @endforelse +
+ @forelse($posts as $post) + @include('partials.post-card', ['post' => $post]) + @empty +
该标签暂无文章
+ @endforelse +
@include('partials.pagination', ['paginator' => $posts]) @include('partials.theme-inject', ['point' => 'content_bottom']) diff --git a/themes/sablog/assets/style.css b/themes/sablog/assets/style.css index 03ed1c5..cd43a49 100644 --- a/themes/sablog/assets/style.css +++ b/themes/sablog/assets/style.css @@ -102,6 +102,90 @@ a:hover { padding: 18px 20px; margin-bottom: 16px; } +/* 列表模式:纯标题列表,紧凑 */ +.post-list-list .post-card { + padding: 8px 20px; + margin-bottom: 0; + border-bottom: none; +} +.post-list-list .post-card:last-child { + border-bottom: 1px solid #d8dfe6; + margin-bottom: 16px; +} +.post-list-list .post-card .post-title { + font-size: 15px; + margin-bottom: 2px; + display: inline; +} +.post-list-list .post-card .post-meta { + display: inline; + margin-left: 10px; +} +/* 图文模式:卡片网格 */ +.post-list-card { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-bottom: 16px; +} +.post-list-card .post-card { + margin-bottom: 0; + padding: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} +.post-list-card .post-cover { + display: block; + aspect-ratio: 16 / 10; + background: #eef2f5; + overflow: hidden; +} +.post-list-card .post-cover img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.post-cover-placeholder { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding: 12px; + color: #7a8a99; + font-size: 14px; + text-align: center; + background: linear-gradient(135deg, #eef2f5 0%, #dde7ee 100%); +} +.post-list-card .post-card-body { + padding: 12px 14px; + flex: 1; +} +.post-list-card .post-title { + font-size: 15px; + margin-bottom: 6px; +} +.post-list-card .post-excerpt { + font-size: 13px; + color: #666; + margin-bottom: 8px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +@media (max-width: 1024px) { + .post-list-card { + grid-template-columns: repeat(2, 1fr); + } +} +@media (max-width: 640px) { + .post-list-card { + grid-template-columns: 1fr; + } +} + .post-card.sticky { border-left: 4px solid #f60; } @@ -452,6 +536,63 @@ a:hover { margin: 4px 0 10px 20px; } +/* 年/月/日索引归档 */ +.archive-months { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + margin-bottom: 18px; +} +.archive-month { + border: 1px solid #e5e5e5; + border-radius: 4px; + padding: 8px 10px; + background: #fafafa; +} +.archive-month.is-empty { + opacity: .45; + background: transparent; +} +.archive-month-title { + display: block; + font-size: 13px; + font-weight: bold; + margin-bottom: 4px; +} +.archive-month.has-posts .archive-month-title:hover { + text-decoration: underline; +} +.archive-month-title .count { + color: #999; + font-weight: normal; + font-size: 12px; +} +.archive-days { + display: flex; + flex-wrap: wrap; + gap: 4px; +} +.archive-day { + display: inline-block; + min-width: 22px; + text-align: center; + font-size: 12px; + line-height: 20px; + border-radius: 3px; + background: #e8f0f8; + color: #1c62b0; + text-decoration: none; +} +.archive-day:hover { + background: #1c62b0; + color: #fff; +} +@media (max-width: 768px) { + .archive-months { + grid-template-columns: repeat(2, 1fr); + } +} + .links-list { list-style: none; } diff --git a/themes/sablog/views/index.blade.php b/themes/sablog/views/index.blade.php index bfec637..2ffe36c 100644 --- a/themes/sablog/views/index.blade.php +++ b/themes/sablog/views/index.blade.php @@ -14,11 +14,13 @@ @include('partials.view-mode-switcher') @include('partials.theme-inject', ['point' => 'content_top']) - @forelse($posts as $post) - @include('partials.post-card', ['post' => $post]) - @empty -
暂无文章
- @endforelse +
+ @forelse($posts as $post) + @include('partials.post-card', ['post' => $post]) + @empty +
暂无文章
+ @endforelse +
@include('partials.pagination', ['paginator' => $posts]) @include('partials.theme-inject', ['point' => 'content_bottom']) diff --git a/themes/sablog/views/list.blade.php b/themes/sablog/views/list.blade.php index 02bfe76..952d9b0 100644 --- a/themes/sablog/views/list.blade.php +++ b/themes/sablog/views/list.blade.php @@ -27,11 +27,13 @@ @include('partials.view-mode-switcher') @include('partials.theme-inject', ['point' => 'content_top']) - @forelse($posts as $post) - @include('partials.post-card', ['post' => $post]) - @empty -
该分类暂无文章
- @endforelse +
+ @forelse($posts as $post) + @include('partials.post-card', ['post' => $post]) + @empty +
该分类暂无文章
+ @endforelse +
@include('partials.pagination', ['paginator' => $posts]) @include('partials.theme-inject', ['point' => 'content_bottom']) diff --git a/themes/sablog/views/search.blade.php b/themes/sablog/views/search.blade.php index 77eec8b..440d68f 100644 --- a/themes/sablog/views/search.blade.php +++ b/themes/sablog/views/search.blade.php @@ -13,11 +13,13 @@ @include('partials.view-mode-switcher') @include('partials.theme-inject', ['point' => 'content_top']) - @forelse($posts as $post) - @include('partials.post-card', ['post' => $post]) - @empty -
没有找到相关文章
- @endforelse +
+ @forelse($posts as $post) + @include('partials.post-card', ['post' => $post]) + @empty +
没有找到相关文章
+ @endforelse +
@include('partials.pagination', ['paginator' => $posts]) @include('partials.theme-inject', ['point' => 'content_bottom']) diff --git a/themes/sablog/views/tag.blade.php b/themes/sablog/views/tag.blade.php index 4cf0182..9498270 100644 --- a/themes/sablog/views/tag.blade.php +++ b/themes/sablog/views/tag.blade.php @@ -9,11 +9,13 @@ @include('partials.view-mode-switcher') @include('partials.theme-inject', ['point' => 'content_top']) - @forelse($posts as $post) - @include('partials.post-card', ['post' => $post]) - @empty -
该标签暂无文章
- @endforelse +
+ @forelse($posts as $post) + @include('partials.post-card', ['post' => $post]) + @empty +
该标签暂无文章
+ @endforelse +
@include('partials.pagination', ['paginator' => $posts]) @include('partials.theme-inject', ['point' => 'content_bottom'])