first commit

This commit is contained in:
浪子
2026-03-19 16:44:38 +08:00
commit ff2af385b9
100 changed files with 16826 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
@extends('storefront.layout', ['title' => 'Tstore · 扩展展示站'])
@section('hero')
<div class="hero-grid">
<div class="hero-card">
<h1>精选 Typecho 插件与主题展示</h1>
<p>这里是展示前台,只负责展示站点里的插件、主题、分类、截图和版本信息,不直接暴露下载地址。</p>
<div class="hero-meta">
<div class="hero-stat"><strong>{{ $stats['packages'] }}</strong><span>已发布扩展</span></div>
<div class="hero-stat"><strong>{{ $stats['plugins'] }}</strong><span>插件</span></div>
<div class="hero-stat"><strong>{{ $stats['themes'] }}</strong><span>主题</span></div>
<div class="hero-stat"><strong>{{ $stats['categories'] }}</strong><span>分类</span></div>
</div>
</div>
<div class="hero-card">
<h3 style="margin:0 0 10px;font-size:20px">当前站点定位</h3>
<p>前台面向访客展示内容,后台则面向管理员,通过账号密码登录后维护扩展、分类、版本和 zip 发布。</p>
<div class="card-actions" style="margin-top:18px">
<a class="btn" href="{{ route('storefront.plugins') }}">查看插件</a>
<a class="btn secondary" href="{{ route('storefront.themes') }}">查看主题</a>
</div>
</div>
</div>
@endsection
@section('content')
<div class="panel">
<div class="section-head">
<div>
<h2>推荐插件</h2>
<p>展示基础信息、版本兼容性与分类,不直接提供 zip 下载地址。</p>
</div>
<a class="btn secondary" href="{{ route('storefront.plugins') }}">查看全部插件</a>
</div>
<div class="grid">
@forelse($featuredPlugins as $package)
<div class="card">
<div class="card-top">
@if($package->icon_url)
<img class="icon" src="{{ $package->icon_url }}" alt="{{ $package->name }}">
@else
<div class="icon"></div>
@endif
<div>
<div class="title">{{ $package->name }}</div>
<div class="muted">{{ $package->author ?: '未知作者' }} · {{ $package->latestStableVersion?->version ?: $package->latest_version }}</div>
</div>
</div>
<div class="summary">{{ $package->summary ?: '暂无摘要' }}</div>
<div class="tags">
@foreach($package->categories as $category)
<span class="chip">{{ $category->name }}</span>
@endforeach
</div>
<div class="card-actions">
<a class="btn secondary" href="{{ route('storefront.show', [$package->type, $package->slug]) }}">查看详情</a>
</div>
</div>
@empty
<div class="empty">当前没有推荐插件。</div>
@endforelse
</div>
</div>
<div class="panel section">
<div class="section-head">
<div>
<h2>推荐主题</h2>
<p>适合做展示站首页,突出视觉风格、分类和版本信息。</p>
</div>
<a class="btn secondary" href="{{ route('storefront.themes') }}">查看全部主题</a>
</div>
<div class="grid">
@forelse($featuredThemes as $package)
<div class="card">
<div class="card-top">
@if($package->icon_url)
<img class="icon" src="{{ $package->icon_url }}" alt="{{ $package->name }}">
@else
<div class="icon"></div>
@endif
<div>
<div class="title">{{ $package->name }}</div>
<div class="muted">{{ $package->author ?: '未知作者' }} · {{ $package->latestStableVersion?->version ?: $package->latest_version }}</div>
</div>
</div>
<div class="summary">{{ $package->summary ?: '暂无摘要' }}</div>
<div class="tags">
@foreach($package->categories as $category)
<span class="chip">{{ $category->name }}</span>
@endforeach
</div>
<div class="card-actions">
<a class="btn secondary" href="{{ route('storefront.show', [$package->type, $package->slug]) }}">查看详情</a>
</div>
</div>
@empty
<div class="empty">当前没有推荐主题。</div>
@endforelse
</div>
</div>
@endsection
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? 'Tstore' }}</title>
<style>
:root{--bg:#0f172a;--panel:#ffffff;--soft:#f5f8fc;--line:#dbe5f0;--text:#16324f;--muted:#607489;--brand:#2f6ea6;--brand-2:#61a6e3;--shadow:0 18px 48px rgba(15,23,42,.08)}
*{box-sizing:border-box}body{margin:0;font:14px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#f4f8fb;color:var(--text)}a{text-decoration:none;color:inherit}
.container{max-width:1180px;margin:0 auto;padding:0 20px}.hero{background:linear-gradient(135deg,#10233a 0%,#16324f 42%,#24527c 100%);color:#fff;padding:24px 0 84px}.nav{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:18px 0}.brand{font-size:22px;font-weight:800}.nav-links{display:flex;gap:10px;flex-wrap:wrap}.nav-links a{padding:10px 14px;border-radius:999px;color:rgba(240,247,255,.88)}.nav-links a:hover,.nav-links a.active{background:rgba(255,255,255,.12)}
.hero-grid{display:grid;grid-template-columns:1.15fr .85fr;gap:18px;align-items:center;padding-top:10px}.hero-card{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.12);border-radius:26px;padding:26px;backdrop-filter:blur(10px)}.hero h1{margin:0 0 12px;font-size:44px;line-height:1.08;letter-spacing:-.03em}.hero p{margin:0;color:rgba(230,239,249,.9);max-width:680px}.hero-meta{display:flex;gap:12px;flex-wrap:wrap;margin-top:20px}.hero-stat{min-width:120px;padding:14px 16px;border-radius:18px;background:rgba(255,255,255,.12)}.hero-stat strong{display:block;font-size:24px}.hero-stat span{display:block;font-size:12px;color:rgba(231,239,248,.78);margin-top:4px}
.shell{margin-top:-52px;padding-bottom:48px}.panel{background:#fff;border:1px solid var(--line);border-radius:24px;padding:22px;box-shadow:var(--shadow)}.section{margin-top:18px}.section-head{display:flex;justify-content:space-between;align-items:end;gap:16px;margin-bottom:14px}.section-head h2{margin:0;font-size:24px}.section-head p{margin:6px 0 0;color:var(--muted)}
.toolbar{display:flex;justify-content:space-between;gap:12px;align-items:flex-end;flex-wrap:wrap;margin-bottom:16px}.filters{display:flex;gap:10px;flex-wrap:wrap}.field{display:grid;gap:6px}.field label{font-size:12px;color:var(--muted);font-weight:700}.input,.select{padding:11px 13px;border:1px solid #cfdae8;border-radius:12px;background:#f9fbfd;min-width:180px}.input:focus,.select:focus{outline:none;border-color:#77aee6;box-shadow:0 0 0 4px rgba(89,156,226,.12);background:#fff}
.btn{display:inline-flex;align-items:center;justify-content:center;padding:11px 15px;border-radius:12px;background:linear-gradient(135deg,var(--brand),var(--brand-2));color:#fff;font-weight:700;border:none;cursor:pointer}.btn.secondary{background:#fff;color:var(--brand);border:1px solid #cfe0f1}
.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}.card{background:linear-gradient(180deg,#fff 0,#fbfdff 100%);border:1px solid var(--line);border-radius:22px;padding:18px;box-shadow:0 12px 28px rgba(15,23,42,.05)}.card-top{display:flex;gap:14px;align-items:flex-start}.icon{width:64px;height:64px;border-radius:18px;background:#edf4fb;object-fit:cover;border:1px solid #d9e6f3}.title{font-size:18px;font-weight:800;margin:0}.muted{color:var(--muted)}.summary{margin:14px 0;color:#4a6176;min-height:72px}.tags{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.chip{display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:700;background:#edf4fb;color:#2d689d;border:1px solid #d8e7f5}.card-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px}
.stats-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}.stat{padding:18px;border-radius:22px;background:linear-gradient(180deg,#ffffff 0,#f8fbfe 100%);border:1px solid var(--line)}.stat strong{display:block;font-size:30px;line-height:1.1}.stat span{display:block;margin-top:6px;color:var(--muted)}
.detail-grid{display:grid;grid-template-columns:1.2fr .8fr;gap:18px}.detail-card{background:#fff;border:1px solid var(--line);border-radius:22px;padding:20px;box-shadow:var(--shadow)}.detail-card h3{margin:0 0 12px;font-size:20px}.version-list{display:grid;gap:10px}.version{border:1px solid var(--line);border-radius:16px;padding:14px;background:#fbfdff}.version strong{font-size:16px}
.gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.shot{border:1px solid var(--line);border-radius:16px;overflow:hidden;background:#fff}.shot img{width:100%;height:180px;object-fit:cover;display:block}.shot div{padding:10px 12px;color:var(--muted);font-size:13px}
.empty{padding:36px 18px;border:1px dashed var(--line);border-radius:18px;text-align:center;background:#fbfdff;color:var(--muted)}
@media (max-width:960px){.hero-grid,.detail-grid{grid-template-columns:1fr}.grid,.stats-row{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width:640px){.grid,.stats-row,.gallery{grid-template-columns:1fr}.hero h1{font-size:34px}.shell{margin-top:-36px}.nav{flex-direction:column;align-items:flex-start}}
</style>
</head>
<body>
<header class="hero">
<div class="container">
<div class="nav">
<div class="brand"><a href="{{ route('storefront.home') }}">Tstore</a></div>
<div class="nav-links">
<a href="{{ route('storefront.home') }}" class="{{ request()->routeIs('storefront.home') ? 'active' : '' }}">首页</a>
<a href="{{ route('storefront.plugins') }}" class="{{ request()->routeIs('storefront.plugins') ? 'active' : '' }}">插件</a>
<a href="{{ route('storefront.themes') }}" class="{{ request()->routeIs('storefront.themes') ? 'active' : '' }}">主题</a>
<a href="{{ route('webadmin.login') }}">后台登录</a>
</div>
</div>
@yield('hero')
</div>
</header>
<main class="shell">
<div class="container">
@yield('content')
</div>
</main>
</body>
</html>
@@ -0,0 +1,79 @@
@extends('storefront.layout', ['title' => 'Tstore · ' . ($type === 'plugin' ? '插件' : '主题')])
@section('hero')
<div class="hero-grid">
<div class="hero-card">
<h1>{{ $type === 'plugin' ? '插件目录' : '主题目录' }}</h1>
<p>支持按分类和关键词浏览 {{ $type === 'plugin' ? '插件' : '主题' }},这里只展示信息,不直接提供下载地址。</p>
</div>
<div class="hero-card">
<form method="get" class="toolbar" style="margin:0">
<div class="filters" style="width:100%">
<div class="field" style="flex:1 1 220px">
<label>关键词</label>
<input class="input" type="text" name="keyword" value="{{ $filters['keyword'] }}" placeholder="搜索名称、slug 或摘要">
</div>
<div class="field">
<label>分类</label>
<select class="select" name="category">
<option value="">全部</option>
@foreach($categories as $category)
<option value="{{ $category->slug }}" @selected($filters['category'] === $category->slug)>{{ $category->name }}</option>
@endforeach
</select>
</div>
<div class="field">
<label>排序</label>
<select class="select" name="sort">
<option value="latest" @selected($filters['sort'] === 'latest')>最新更新</option>
<option value="name" @selected($filters['sort'] === 'name')>按名称</option>
</select>
</div>
</div>
<div class="card-actions">
<button class="btn" type="submit">筛选</button>
</div>
</form>
</div>
</div>
@endsection
@section('content')
<div class="panel">
<div class="section-head">
<div>
<h2>{{ $type === 'plugin' ? '插件列表' : '主题列表' }}</h2>
<p> {{ $packages->total() }} 项结果</p>
</div>
</div>
<div class="grid">
@forelse($packages as $package)
<div class="card">
<div class="card-top">
@if($package->icon_url)
<img class="icon" src="{{ $package->icon_url }}" alt="{{ $package->name }}">
@else
<div class="icon"></div>
@endif
<div>
<div class="title">{{ $package->name }}</div>
<div class="muted">{{ $package->author ?: '未知作者' }} · {{ $package->latestStableVersion?->version ?: $package->latest_version }}</div>
</div>
</div>
<div class="summary">{{ $package->summary ?: '暂无摘要' }}</div>
<div class="tags">
@foreach($package->categories as $category)
<span class="chip">{{ $category->name }}</span>
@endforeach
</div>
<div class="card-actions">
<a class="btn secondary" href="{{ route('storefront.show', [$package->type, $package->slug]) }}">查看详情</a>
</div>
</div>
@empty
<div class="empty">当前没有符合条件的{{ $type === 'plugin' ? '插件' : '主题' }}</div>
@endforelse
</div>
<div style="margin-top:18px">{{ $packages->links() }}</div>
</div>
@endsection
+99
View File
@@ -0,0 +1,99 @@
@extends('storefront.layout', ['title' => 'Tstore · ' . $package->name])
@section('hero')
<div class="hero-grid">
<div class="hero-card">
<h1>{{ $detail['name'] }}</h1>
<p>{{ $detail['summary'] ?: '暂无摘要' }}</p>
<div class="hero-meta">
<div class="hero-stat"><strong>{{ $detail['type'] }}</strong><span>类型</span></div>
<div class="hero-stat"><strong>{{ $detail['download_count'] }}</strong><span>累计下载</span></div>
<div class="hero-stat"><strong>{{ count($detail['versions']) }}</strong><span>版本数</span></div>
<div class="hero-stat"><strong>{{ count($detail['categories']) }}</strong><span>分类</span></div>
</div>
</div>
<div class="hero-card">
<h3 style="margin:0 0 10px;font-size:20px">说明</h3>
<p>这是前台展示页,只展示扩展信息、版本兼容性和截图,不直接暴露下载地址。需要维护请进入后台。</p>
<div class="card-actions" style="margin-top:18px">
<a class="btn secondary" href="{{ route('webadmin.login') }}">后台登录</a>
</div>
</div>
</div>
@endsection
@section('content')
<div class="detail-grid">
<div class="detail-card">
<h3>扩展说明</h3>
<div class="muted">作者:{{ $detail['author'] ?: '未知作者' }}</div>
<div class="muted">主页:{{ $detail['homepage'] ?: '未提供' }}</div>
<div class="tags">
@foreach($package->categories as $category)
<span class="chip">{{ $category->name }}</span>
@endforeach
</div>
<div style="margin-top:16px;white-space:pre-wrap">{{ $detail['description'] ?: '暂无详细描述。' }}</div>
</div>
<div class="detail-card">
<h3>版本信息</h3>
<div class="version-list">
@forelse($detail['versions'] as $version)
<div class="version">
<strong>v{{ $version['version'] }}</strong>
<div class="muted">{{ $version['is_latest'] ? '最新版本' : '历史版本' }} · {{ $version['is_stable'] ? '稳定版' : '预发布' }}</div>
<div class="muted">Typecho {{ $version['compatibility']['typecho_min'] ?: '-' }} ~ {{ $version['compatibility']['typecho_max'] ?: '-' }}</div>
<div class="muted">PHP {{ $version['compatibility']['php_min'] ?: '-' }} ~ {{ $version['compatibility']['php_max'] ?: '-' }}</div>
@if(!empty($version['changelog']))
<div style="margin-top:8px">{{ $version['changelog'] }}</div>
@endif
</div>
@empty
<div class="empty">当前没有版本记录。</div>
@endforelse
</div>
</div>
</div>
@if(count($detail['screenshots']))
<div class="panel section">
<div class="section-head">
<div>
<h2>截图展示</h2>
<p>只展示视觉效果,不提供下载入口。</p>
</div>
</div>
<div class="gallery">
@foreach($detail['screenshots'] as $shot)
<div class="shot">
<img src="{{ $shot['url'] }}" alt="{{ $shot['caption'] ?: $detail['name'] }}">
<div>{{ $shot['caption'] ?: '扩展截图' }}</div>
</div>
@endforeach
</div>
</div>
@endif
<div class="panel section">
<div class="section-head">
<div>
<h2>更多{{ $package->type === 'plugin' ? '插件' : '主题' }}</h2>
<p>继续浏览同类内容。</p>
</div>
</div>
<div class="grid">
@forelse($related as $item)
<div class="card">
<div class="title">{{ $item->name }}</div>
<div class="muted">{{ $item->summary ?: '暂无摘要' }}</div>
<div class="card-actions">
<a class="btn secondary" href="{{ route('storefront.show', [$item->type, $item->slug]) }}">查看详情</a>
</div>
</div>
@empty
<div class="empty">当前没有更多相关内容。</div>
@endforelse
</div>
</div>
@endsection