first commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||
@source '../../storage/framework/views/*.php';
|
||||
@source '../**/*.blade.php';
|
||||
@source '../**/*.js';
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import './bootstrap';
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
@@ -0,0 +1,138 @@
|
||||
@extends('admin.layout', [
|
||||
'title' => 'Tstore Admin · 分类管理',
|
||||
'pageTitle' => ($filters['type'] === 'plugin' ? '插件分类' : ($filters['type'] === 'theme' ? '主题分类' : '分类管理')),
|
||||
'pageSubtitle' => ($filters['type'] === 'plugin'
|
||||
? '维护插件分类,为插件 package 提供归类。'
|
||||
: ($filters['type'] === 'theme'
|
||||
? '维护主题分类,为主题 package 提供归类。'
|
||||
: '统一维护插件与主题分类,为 package 提供归类。')),
|
||||
])
|
||||
|
||||
@php
|
||||
$currentType = $filters['type'] ?? '';
|
||||
$entityLabel = $currentType === 'plugin' ? '插件分类' : ($currentType === 'theme' ? '主题分类' : '分类');
|
||||
$listHint = $currentType === 'plugin' ? '按类型和关键词筛选插件分类。'
|
||||
: ($currentType === 'theme' ? '按类型和关键词筛选主题分类。' : '按类型和关键词筛选分类列表。');
|
||||
$createHint = $currentType === 'plugin' ? '新建一个插件分类,用于给插件 package 归类。'
|
||||
: ($currentType === 'theme' ? '新建一个主题分类,用于给主题 package 归类。' : '新建一个分类,用于给 package 归类。');
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>筛选与查询</h2>
|
||||
<p>{{ $listHint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="get" class="toolbar">
|
||||
<div class="filters">
|
||||
<div class="field">
|
||||
<label>类型</label>
|
||||
<select name="type" class="select">
|
||||
<option value="">全部</option>
|
||||
<option value="plugin" @selected($filters['type']==='plugin')>插件</option>
|
||||
<option value="theme" @selected($filters['type']==='theme')>主题</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field" style="min-width:280px">
|
||||
<label>关键词</label>
|
||||
<input class="input" type="text" name="keyword" value="{{ $filters['keyword'] }}" placeholder="搜索 name / slug / description">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="submit">筛选</button>
|
||||
<a class="btn secondary" href="{{ route('webadmin.categories') }}">重置</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="split">
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>{{ $entityLabel }}列表</h2>
|
||||
<p>支持就地编辑名称、slug、类型、排序与描述。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
@forelse ($categories as $category)
|
||||
<div class="row-card">
|
||||
<form method="post" action="{{ route('webadmin.categories.update', $category->id) }}" class="grid">
|
||||
@csrf @method('PUT')
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>名称</label><input class="input" name="name" value="{{ $category->name }}" required></div>
|
||||
<div class="field"><label>Slug</label><input class="input" name="slug" value="{{ $category->slug }}" required></div>
|
||||
<div class="field"><label>类型</label>
|
||||
<select class="select" name="type">
|
||||
<option value="plugin" @selected($category->type==='plugin')>插件</option>
|
||||
<option value="theme" @selected($category->type==='theme')>主题</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field"><label>排序</label><input class="input" type="number" name="sort_order" value="{{ $category->sort_order }}"></div>
|
||||
</div>
|
||||
<div class="field"><label>描述</label><textarea name="description">{{ $category->description }}</textarea></div>
|
||||
<div class="tags">
|
||||
<span class="chip">关联扩展 {{ $category->packages_count }}</span>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn small" type="submit">保存分类</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="form-actions">
|
||||
<form method="post" action="{{ route('webadmin.categories.destroy', $category->id) }}" onsubmit="return confirm('确认删除这个分类吗?');">
|
||||
@csrf @method('DELETE')
|
||||
<button class="btn danger small" type="submit">删除分类</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="empty">当前还没有分类。</div>
|
||||
@endforelse
|
||||
</div>
|
||||
<div style="margin-top:14px">{{ $categories->links() }}</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>新建{{ $entityLabel }}</h2>
|
||||
<p>{{ $createHint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="{{ route('webadmin.categories.store') }}" class="grid">
|
||||
@csrf
|
||||
<div class="form-grid">
|
||||
<div class="field">
|
||||
<label>类型</label>
|
||||
<select class="select" name="type" required>
|
||||
<option value="plugin" @selected($currentType === 'plugin')>插件</option>
|
||||
<option value="theme" @selected($currentType === 'theme')>主题</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>排序</label>
|
||||
<input class="input" name="sort_order" type="number" value="0">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>名称</label>
|
||||
<input class="input" name="name" placeholder="SEO" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Slug</label>
|
||||
<input class="input" name="slug" placeholder="seo" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>描述</label>
|
||||
<textarea name="description" placeholder="补充说明这个分类的用途"></textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="submit">创建{{ $entityLabel }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,72 @@
|
||||
@extends('admin.layout', [
|
||||
'title' => 'Tstore Admin · 概览',
|
||||
'pageTitle' => '概览',
|
||||
'pageSubtitle' => '先看整体状态,再进入扩展管理、分类管理和版本发布流程。',
|
||||
])
|
||||
|
||||
@section('content')
|
||||
<div class="grid">
|
||||
<div class="stats">
|
||||
<div class="stat"><span class="label">扩展总数</span><span class="value">{{ $stats['packages'] }}</span><span class="hint">插件 + 主题</span></div>
|
||||
<div class="stat"><span class="label">插件</span><span class="value">{{ $stats['plugins'] }}</span><span class="hint">plugin</span></div>
|
||||
<div class="stat"><span class="label">主题</span><span class="value">{{ $stats['themes'] }}</span><span class="hint">theme</span></div>
|
||||
<div class="stat"><span class="label">版本记录</span><span class="value">{{ $stats['versions'] }}</span><span class="hint">已发布与手动录入版本</span></div>
|
||||
<div class="stat"><span class="label">下载总量</span><span class="value">{{ $stats['downloads'] }}</span><span class="hint">累计下载次数</span></div>
|
||||
<div class="stat"><span class="label">分类数</span><span class="value">{{ $stats['categories'] }}</span><span class="hint">插件 / 主题分类</span></div>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>最近更新的扩展</h2>
|
||||
<p>可以直接进入详情页编辑信息、上传 zip,或查看当前最新稳定版本。</p>
|
||||
</div>
|
||||
<a class="btn secondary small" href="{{ route('webadmin.packages') }}">查看全部</a>
|
||||
</div>
|
||||
<div class="list">
|
||||
@forelse ($recentPackages as $package)
|
||||
<div class="row-card">
|
||||
<div class="row-top">
|
||||
<div>
|
||||
<h3>{{ $package->name }}</h3>
|
||||
<p class="muted">{{ $package->type }} · {{ $package->slug }}</p>
|
||||
</div>
|
||||
<span class="chip {{ $package->status === 'published' ? 'ok' : ($package->status === 'draft' ? 'muted' : 'warn') }}">{{ $package->status }}</span>
|
||||
</div>
|
||||
<p class="muted">{{ $package->summary ?: '暂无摘要' }}</p>
|
||||
<div class="tags">
|
||||
@foreach ($package->categories as $category)
|
||||
<span class="chip">{{ $category->name }}</span>
|
||||
@endforeach
|
||||
@if ($package->latestStableVersion)
|
||||
<span class="chip ok">最新稳定版 v{{ $package->latestStableVersion->version }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a class="btn secondary small" href="{{ route('webadmin.packages.show', [$package->type, $package->slug]) }}">查看详情</a>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="empty">当前还没有扩展数据。</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>当前后台能力</h2>
|
||||
<p>这一版已经能覆盖联调闭环里最关键的管理动作。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="mini-card"><h3>扩展管理</h3><div class="muted">支持创建 package、编辑基础信息、切换发布状态。</div></div>
|
||||
<div class="mini-card"><h3>分类管理</h3><div class="muted">支持新建、编辑、删除分类,并查看分类关联的扩展数量。</div></div>
|
||||
<div class="mini-card"><h3>版本管理</h3><div class="muted">支持手动录入版本、zip 上传发布、删除历史版本。</div></div>
|
||||
<div class="mini-card"><h3>下一步建议</h3><div class="muted">可以继续补截图管理、审核日志、下载统计细分和更严格的发布校验。</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $title ?? 'Tstore Admin' }}</title>
|
||||
<style>
|
||||
:root{--bg:#f3f7fb;--panel:#ffffff;--panel-2:#f7fbff;--text:#16324f;--muted:#607489;--line:#dce6f1;--brand:#2f6ea6;--brand-2:#4f90c8;--ok:#1f8b4c;--warn:#b36b00;--danger:#c14f4f;--shadow:0 12px 30px rgba(16,36,64,.08)}
|
||||
*{box-sizing:border-box}body{margin:0;background:linear-gradient(180deg,#f6f9fc 0,#eef4f9 100%);color:var(--text);font:14px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
|
||||
a{color:inherit}.page{display:grid;grid-template-columns:260px minmax(0,1fr);min-height:100vh}
|
||||
.sidebar{background:linear-gradient(180deg,#16324f 0,#1d456b 100%);color:#fff;padding:24px 18px;position:sticky;top:0;height:100vh}
|
||||
.brand{font-size:22px;font-weight:800;letter-spacing:.02em;margin-bottom:6px}.brand-sub{font-size:12px;color:rgba(230,240,255,.72);margin-bottom:24px}
|
||||
.nav{display:grid;gap:10px}.nav a{display:flex;align-items:center;padding:11px 14px;border-radius:14px;text-decoration:none;color:rgba(241,247,255,.9);font-weight:700;background:transparent;border:1px solid transparent}.nav a.active,.nav a:hover{background:rgba(255,255,255,.10);border-color:rgba(255,255,255,.08)}
|
||||
.sidebar-foot{position:absolute;left:18px;right:18px;bottom:20px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:14px}.sidebar-foot p{margin:0;color:rgba(235,243,255,.84);font-size:12px;line-height:1.7}.sidebar-foot .muted-mini{display:block;margin-top:8px;color:rgba(224,236,251,.68);font-size:11px}
|
||||
.content{padding:28px}.topbar{display:flex;justify-content:space-between;gap:16px;align-items:center;margin-bottom:20px}.title h1{margin:0 0 4px;font-size:28px;line-height:1.15}.title p{margin:0;color:var(--muted)}
|
||||
.top-actions{display:flex;gap:10px;flex-wrap:wrap}.btn,.btn-link button{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 14px;border-radius:12px;border:1px solid transparent;background:linear-gradient(135deg,var(--brand),var(--brand-2));color:#fff;text-decoration:none;font-weight:700;cursor:pointer;box-shadow:0 10px 22px rgba(47,110,166,.18)}
|
||||
.btn.secondary{background:#fff;color:var(--brand);border-color:#cfe0f1;box-shadow:none}.btn.small,.btn-link button.small{padding:8px 12px;font-size:12px;border-radius:10px}.btn.ghost,.btn-link button.ghost{background:#fff;border-color:var(--line);color:var(--text);box-shadow:none}.btn.danger,.btn-link button.danger{background:#fff2f2;border-color:#f1cccc;color:var(--danger);box-shadow:none}
|
||||
.btn-link{display:inline}.btn-link form{display:inline}
|
||||
.flash{margin-bottom:16px;padding:12px 14px;border-radius:14px;font-weight:700}.flash.success{background:#edf9f0;color:var(--ok);border:1px solid #cfead8}.flash.error{background:#fff4f3;color:var(--danger);border:1px solid #f1d2cf}
|
||||
.panel{background:rgba(255,255,255,.92);border:1px solid var(--line);border-radius:22px;padding:18px;box-shadow:var(--shadow)}
|
||||
.grid{display:grid;gap:18px}.stats{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:16px}.stat{background:linear-gradient(180deg,#fff 0,#f9fbfd 100%);border:1px solid var(--line);border-radius:20px;padding:18px}.stat .label{display:block;font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px}.stat .value{display:block;font-size:28px;font-weight:800;line-height:1.1}.stat .hint{display:block;margin-top:8px;color:var(--muted);font-size:12px}
|
||||
.cards{display:grid;grid-template-columns:1.2fr .8fr;gap:18px}.section-title{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:14px}.section-title h2{margin:0;font-size:18px}.section-title p{margin:0;color:var(--muted);font-size:13px}
|
||||
.table-wrap{overflow:auto;border:1px solid var(--line);border-radius:18px;background:#fff}.table{width:100%;border-collapse:separate;border-spacing:0}.table th,.table td{padding:14px 16px;border-bottom:1px solid #ecf1f6;text-align:left;vertical-align:top}.table th{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;background:#f8fbfd}.table tr:hover td{background:#fbfdff}
|
||||
.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}.chip.ok{background:#edf9f0;color:var(--ok);border-color:#cfead8}.chip.warn{background:#fff8eb;color:var(--warn);border-color:#f2dfb1}.chip.muted{background:#f3f6f9;color:#667a90;border-color:#e0e7ef}
|
||||
.stack{display:grid;gap:14px}.mini-card{border:1px solid var(--line);border-radius:18px;padding:16px;background:linear-gradient(180deg,#fff 0,#fbfdff 100%)}.mini-card h3{margin:0 0 6px;font-size:16px}.muted{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,textarea{width:100%;padding:11px 13px;border:1px solid #cfdae8;border-radius:12px;background:#f9fbfd;color:var(--text);outline:none}.input:focus,.select:focus,textarea:focus{border-color:#77aee6;box-shadow:0 0 0 4px rgba(89,156,226,.14);background:#fff}textarea{min-height:96px;resize:vertical}
|
||||
.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.form-grid.full{grid-template-columns:1fr}.form-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}
|
||||
.list{display:grid;gap:12px}.row-card{border:1px solid var(--line);border-radius:18px;padding:16px;background:linear-gradient(180deg,#fff 0,#fbfdff 100%)}.row-top{display:flex;justify-content:space-between;gap:16px;align-items:flex-start}.row-top h3{margin:0;font-size:18px}.row-top p{margin:6px 0 0}.tags{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}.split{display:grid;grid-template-columns:1.2fr .8fr;gap:18px}.empty{padding:36px 18px;border:1px dashed var(--line);border-radius:18px;text-align:center;background:#fbfdff;color:var(--muted)}
|
||||
@media (max-width:1200px){.stats{grid-template-columns:repeat(3,minmax(0,1fr))}.cards,.split{grid-template-columns:1fr}}@media (max-width:860px){.page{grid-template-columns:1fr}.sidebar{position:static;height:auto}.content{padding:18px}.stats{grid-template-columns:repeat(2,minmax(0,1fr))}.form-grid{grid-template-columns:1fr}}@media (max-width:560px){.stats{grid-template-columns:1fr}.topbar{flex-direction:column;align-items:flex-start}.filters{width:100%}.field{width:100%}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">Tstore Admin</div>
|
||||
<div class="brand-sub">Laravel management console</div>
|
||||
<nav class="nav">
|
||||
<a href="{{ route('webadmin.home') }}" class="{{ request()->routeIs('webadmin.home') ? 'active' : '' }}">概览</a>
|
||||
<a href="{{ route('webadmin.packages') }}" class="{{ request()->routeIs('webadmin.packages*') && !request('type') ? 'active' : '' }}">扩展管理</a>
|
||||
<a href="{{ route('webadmin.categories') }}" class="{{ request()->routeIs('webadmin.categories*') ? 'active' : '' }}">分类管理</a>
|
||||
<a href="{{ route('webadmin.packages', ['type' => 'plugin']) }}" class="{{ request()->routeIs('webadmin.packages*') && request('type') === 'plugin' ? 'active' : '' }}">插件列表</a>
|
||||
<a href="{{ route('webadmin.packages', ['type' => 'theme']) }}" class="{{ request()->routeIs('webadmin.packages*') && request('type') === 'theme' ? 'active' : '' }}">主题列表</a>
|
||||
</nav>
|
||||
<div class="sidebar-foot">
|
||||
<p>当前后台已接入账号登录,并支持分类维护、包管理、zip 上传发布、手动版本管理和版本删除。适合先维护 package 与分类,再进入详情页发布 zip 做联调。</p>
|
||||
<span class="muted-mini">建议:先登录后台账号,维护 package 和分类,再进入详情页完成 zip 发布。</span>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<div class="topbar">
|
||||
<div class="title">
|
||||
<h1>{{ $pageTitle ?? 'Tstore Admin' }}</h1>
|
||||
<p>{{ $pageSubtitle ?? '统一管理扩展、分类、版本与发布流程。' }}</p>
|
||||
</div>
|
||||
<div class="top-actions">
|
||||
<a class="btn secondary" href="{{ route('webadmin.home') }}">后台首页</a>
|
||||
<form method="post" action="{{ route('webadmin.logout') }}">@csrf<button class="btn ghost">退出</button></form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="flash success">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="flash error">{{ session('error') }}</div>
|
||||
@endif
|
||||
@if ($errors->any())
|
||||
<div class="flash error">{{ $errors->first() }}</div>
|
||||
@endif
|
||||
|
||||
@yield('content')
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
@extends('admin.layout', [
|
||||
'title' => 'Tstore Admin · 后台登录',
|
||||
'pageTitle' => '后台登录',
|
||||
'pageSubtitle' => '使用后台账号密码登录,不再直接依赖 token。',
|
||||
])
|
||||
|
||||
@section('content')
|
||||
<div class="panel" style="max-width:620px">
|
||||
<form method="post" action="{{ route('webadmin.login.submit') }}" class="grid">
|
||||
@csrf
|
||||
<input type="hidden" name="redirect" value="{{ $redirect }}">
|
||||
<div class="field">
|
||||
<label>邮箱</label>
|
||||
<input class="input" type="email" name="email" value="{{ old('email', 'admin@tstore.local') }}" placeholder="admin@tstore.local" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>密码</label>
|
||||
<input class="input" type="password" name="password" placeholder="输入后台密码" required>
|
||||
</div>
|
||||
<label><input type="checkbox" name="remember" value="1"> 记住登录状态</label>
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="submit">进入后台</button>
|
||||
</div>
|
||||
<div class="mini-card">
|
||||
<h3>默认管理员</h3>
|
||||
<div class="muted">邮箱:admin@tstore.local</div>
|
||||
<div class="muted">密码:Admin@123456</div>
|
||||
<div class="muted">可以在 .env 中通过 ADMIN_EMAIL / ADMIN_PASSWORD 修改。</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,198 @@
|
||||
@extends('admin.layout', [
|
||||
'title' => 'Tstore Admin · 插件 / 主题管理',
|
||||
'pageTitle' => $filters['type'] === 'plugin' ? '插件列表' : ($filters['type'] === 'theme' ? '主题列表' : '扩展列表'),
|
||||
'pageSubtitle' => $filters['type'] === 'plugin'
|
||||
? '查看、筛选、维护和发布插件,统一处理插件版本与状态。'
|
||||
: ($filters['type'] === 'theme'
|
||||
? '查看、筛选、维护和发布主题,统一处理主题版本与状态。'
|
||||
: '查看全部插件与主题,统一管理扩展列表、详情页与发布版本。'),
|
||||
])
|
||||
|
||||
@php
|
||||
$currentType = $filters['type'] ?? '';
|
||||
$entityLabel = $currentType === 'plugin' ? '插件' : ($currentType === 'theme' ? '主题' : '扩展');
|
||||
$entityPluralLabel = $currentType === 'plugin' ? '插件列表' : ($currentType === 'theme' ? '主题列表' : '扩展列表');
|
||||
$newEntityLabel = $currentType === 'plugin' ? '新建插件' : ($currentType === 'theme' ? '新建主题' : '新建扩展');
|
||||
$filterHint = $currentType === 'plugin' ? '按类型、状态和关键词快速定位插件。'
|
||||
: ($currentType === 'theme' ? '按类型、状态和关键词快速定位主题。' : '按类型、状态和关键词快速定位扩展。');
|
||||
$listHint = $currentType === 'plugin' ? '支持查看插件详情、编辑元数据、上传 zip 发布版本,以及切换插件状态。'
|
||||
: ($currentType === 'theme' ? '支持查看主题详情、编辑元数据、上传 zip 发布版本,以及切换主题状态。'
|
||||
: '支持查看详情、编辑元数据、上传 zip 发布版本,以及切换扩展状态。');
|
||||
$createHint = $currentType === 'plugin' ? '先创建插件 package,再进入详情页补充版本,或直接上传 zip 发布。'
|
||||
: ($currentType === 'theme' ? '先创建主题 package,再进入详情页补充版本,或直接上传 zip 发布。'
|
||||
: '先创建 package,再进入详情页补充版本,或直接上传 zip 发布。');
|
||||
$emptyHint = '当前还没有' . $entityLabel . '。';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>筛选与查询</h2>
|
||||
<p>{{ $filterHint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="get" class="toolbar">
|
||||
<div class="filters">
|
||||
<div class="field">
|
||||
<label>类型</label>
|
||||
<select name="type" class="select">
|
||||
<option value="">全部</option>
|
||||
<option value="plugin" @selected($filters['type']==='plugin')>插件</option>
|
||||
<option value="theme" @selected($filters['type']==='theme')>主题</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>状态</label>
|
||||
<select name="status" class="select">
|
||||
<option value="">全部</option>
|
||||
<option value="draft" @selected($filters['status']==='draft')>draft</option>
|
||||
<option value="published" @selected($filters['status']==='published')>published</option>
|
||||
<option value="hidden" @selected($filters['status']==='hidden')>hidden</option>
|
||||
<option value="deprecated" @selected($filters['status']==='deprecated')>deprecated</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field" style="min-width:280px">
|
||||
<label>关键词</label>
|
||||
<input class="input" type="text" name="keyword" value="{{ $filters['keyword'] }}" placeholder="搜索 name / slug / summary">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="submit">筛选</button>
|
||||
<a class="btn secondary" href="{{ route('webadmin.packages') }}">重置</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="split">
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>{{ $entityPluralLabel }}</h2>
|
||||
<p>{{ $listHint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $entityLabel }}</th>
|
||||
<th>类型</th>
|
||||
<th>状态</th>
|
||||
<th>最新版本</th>
|
||||
<th>分类</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($packages as $package)
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ $package->name }}</strong>
|
||||
<div class="muted">{{ $package->slug }}</div>
|
||||
<div class="muted">{{ $package->summary ?: '暂无摘要' }}</div>
|
||||
</td>
|
||||
<td>{{ $package->type }}</td>
|
||||
<td><span class="chip {{ $package->status === 'published' ? 'ok' : ($package->status === 'draft' ? 'muted' : 'warn') }}">{{ $package->status }}</span></td>
|
||||
<td>{{ $package->latestStableVersion?->version ?: ($package->latest_version ?: '-') }}</td>
|
||||
<td>
|
||||
<div class="tags">
|
||||
@foreach ($package->categories as $category)
|
||||
<span class="chip">{{ $category->slug }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-actions">
|
||||
<a class="btn secondary small" href="{{ route('webadmin.packages.show', [$package->type, $package->slug]) }}">详情 / 发布</a>
|
||||
<form method="post" action="{{ route('webadmin.packages.status', [$package->type, $package->slug]) }}">
|
||||
@csrf @method('PATCH')
|
||||
<input type="hidden" name="status" value="{{ $package->status === 'published' ? 'hidden' : 'published' }}">
|
||||
<button class="btn ghost small" type="submit">{{ $package->status === 'published' ? '隐藏' : '发布' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6"><div class="empty">{{ $emptyHint }}</div></td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="margin-top:14px">{{ $packages->links() }}</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>{{ $newEntityLabel }}</h2>
|
||||
<p>{{ $createHint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="{{ route('webadmin.packages.store') }}" class="grid">
|
||||
@csrf
|
||||
<div class="form-grid">
|
||||
<div class="field">
|
||||
<label>类型</label>
|
||||
<select class="select" name="type" required>
|
||||
<option value="plugin" @selected($currentType === 'plugin')>插件</option>
|
||||
<option value="theme" @selected($currentType === 'theme')>主题</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Slug</label>
|
||||
<input class="input" name="slug" placeholder="HelloStore" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>名称</label>
|
||||
<input class="input" name="name" placeholder="Hello Store" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>状态</label>
|
||||
<select class="select" name="status">
|
||||
<option value="published">published</option>
|
||||
<option value="draft">draft</option>
|
||||
<option value="hidden">hidden</option>
|
||||
<option value="deprecated">deprecated</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>作者</label>
|
||||
<input class="input" name="author" placeholder="LT083">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>License</label>
|
||||
<input class="input" name="license" placeholder="MIT">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>摘要</label>
|
||||
<input class="input" name="summary" placeholder="一句话说明这个{{ $entityLabel }}是做什么的">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>描述</label>
|
||||
<textarea name="description" placeholder="详细描述这个{{ $entityLabel }}的用途与定位"></textarea>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="field">
|
||||
<label>主页</label>
|
||||
<input class="input" name="homepage" placeholder="https://example.com/package">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>图标 URL</label>
|
||||
<input class="input" name="icon_url" placeholder="https://example.com/icon.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>分类 slug,多个用空格分隔</label>
|
||||
<input class="input" name="categories_text" placeholder="seo performance">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="submit">创建{{ $entityLabel }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,171 @@
|
||||
@extends('admin.layout', [
|
||||
'title' => 'Tstore Admin · ' . $package->name,
|
||||
'pageTitle' => $package->name,
|
||||
'pageSubtitle' => ($package->type === 'plugin' ? '插件' : ($package->type === 'theme' ? '主题' : $package->type)) . ' · ' . $package->slug,
|
||||
])
|
||||
|
||||
@php
|
||||
$entityLabel = $package->type === 'plugin' ? '插件' : ($package->type === 'theme' ? '主题' : '扩展');
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="grid">
|
||||
<div class="split">
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>{{ $entityLabel }}信息</h2>
|
||||
<p>编辑基础元数据、分类、图标与发布状态。</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="{{ route('webadmin.packages.update', [$package->type, $package->slug]) }}" class="grid">
|
||||
@csrf @method('PUT')
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>类型</label><input class="input" name="type" value="{{ $package->type }}" readonly></div>
|
||||
<div class="field"><label>Slug</label><input class="input" name="slug" value="{{ $package->slug }}" readonly></div>
|
||||
<div class="field"><label>名称</label><input class="input" name="name" value="{{ $package->name }}" required></div>
|
||||
<div class="field"><label>状态</label>
|
||||
<select class="select" name="status">
|
||||
@foreach (['draft','published','hidden','deprecated'] as $status)
|
||||
<option value="{{ $status }}" @selected($package->status === $status)>{{ $status }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="field"><label>作者</label><input class="input" name="author" value="{{ $package->author }}"></div>
|
||||
<div class="field"><label>License</label><input class="input" name="license" value="{{ $package->license }}"></div>
|
||||
<div class="field"><label>主页</label><input class="input" name="homepage" value="{{ $package->homepage }}"></div>
|
||||
<div class="field"><label>图标 URL</label><input class="input" name="icon_url" value="{{ $package->icon_url }}"></div>
|
||||
</div>
|
||||
<div class="field"><label>摘要</label><input class="input" name="summary" value="{{ $package->summary }}"></div>
|
||||
<div class="field"><label>描述</label><textarea name="description">{{ $package->description }}</textarea></div>
|
||||
<div class="field"><label>分类 slug,多个用逗号或空格分隔</label><input class="input" name="categories_text" value="{{ $package->categories->pluck('slug')->implode(', ') }}"></div>
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="submit">保存{{ $entityLabel }}信息</button>
|
||||
<a class="btn secondary" href="{{ route('webadmin.packages', ['type' => $package->type]) }}">返回{{ $entityLabel }}列表</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>当前状态</h2>
|
||||
<p>快速查看分类、版本、下载与截图信息。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="mini-card"><h3>状态</h3><div class="tags"><span class="chip {{ $package->status === 'published' ? 'ok' : 'warn' }}">{{ $package->status }}</span><span class="chip">下载 {{ $package->download_count }}</span><span class="chip {{ $package->is_featured ? 'ok' : 'muted' }}">{{ $package->is_featured ? '推荐' : '普通' }}</span></div></div>
|
||||
<div class="mini-card"><h3>分类</h3><div class="tags">@forelse($package->categories as $category)<span class="chip">{{ $category->name }}</span>@empty<span class="muted">尚未关联分类</span>@endforelse</div></div>
|
||||
<div class="mini-card"><h3>截图</h3><div class="muted">{{ $package->screenshots->count() }} 张</div></div>
|
||||
<div class="mini-card"><h3>最新版本</h3><div class="muted">{{ $package->latest_version ?: '尚未发布' }}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="split">
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>版本记录</h2>
|
||||
<p>查看版本状态,也可以直接删除历史版本。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>版本</th>
|
||||
<th>稳定版</th>
|
||||
<th>最新</th>
|
||||
<th>兼容性</th>
|
||||
<th>发布时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($package->versions as $version)
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ $version->version }}</strong>
|
||||
<div class="muted">{{ $version->package_url ?: '未设置 package_url' }}</div>
|
||||
</td>
|
||||
<td><span class="chip {{ $version->is_stable ? 'ok' : 'warn' }}">{{ $version->is_stable ? '是' : '否' }}</span></td>
|
||||
<td><span class="chip {{ $version->is_latest ? 'ok' : 'muted' }}">{{ $version->is_latest ? '最新' : '历史' }}</span></td>
|
||||
<td>
|
||||
<div class="muted">Typecho {{ $version->typecho_min ?: '-' }} ~ {{ $version->typecho_max ?: '-' }}</div>
|
||||
<div class="muted">PHP {{ $version->php_min ?: '-' }} ~ {{ $version->php_max ?: '-' }}</div>
|
||||
</td>
|
||||
<td>{{ optional($version->published_at)->format('Y-m-d H:i') }}</td>
|
||||
<td>
|
||||
<form method="post" action="{{ route('webadmin.packages.versions.destroy', [$package->type, $package->slug, $version->id]) }}" onsubmit="return confirm('确认删除这个版本吗?');">
|
||||
@csrf @method('DELETE')
|
||||
<button class="btn danger small" type="submit">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6"><div class="empty">当前还没有版本记录。</div></td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>zip 上传发布</h2>
|
||||
<p>直接调用 publishFromZip,按 manifest 自动生成版本。</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="{{ route('webadmin.packages.publish', [$package->type, $package->slug]) }}" class="grid" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="field"><label>zip 文件</label><input class="input" type="file" name="package_file" accept=".zip" required></div>
|
||||
<div class="field"><label>发布说明(可选)</label><textarea name="changelog" placeholder="补充这次发布说明"></textarea></div>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>发布时间(可选)</label><input class="input" type="datetime-local" name="published_at"></div>
|
||||
<div class="field"><label>发布选项</label>
|
||||
<div class="tags">
|
||||
<label><input type="checkbox" name="is_stable" value="1" checked> 设为稳定版</label>
|
||||
<label><input type="checkbox" name="mark_as_latest" value="1" checked> 设为最新</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions"><button class="btn" type="submit">上传并发布</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>手动添加版本</h2>
|
||||
<p>调试阶段可以快速手动录入版本元数据。</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="{{ route('webadmin.packages.versions.store', [$package->type, $package->slug]) }}" class="grid">
|
||||
@csrf
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>版本号</label><input class="input" name="version" placeholder="1.0.0" required></div>
|
||||
<div class="field"><label>发布时间</label><input class="input" type="datetime-local" name="published_at"></div>
|
||||
<div class="field"><label>Typecho Min</label><input class="input" name="typecho_min" placeholder="1.2.0"></div>
|
||||
<div class="field"><label>Typecho Max</label><input class="input" name="typecho_max" placeholder="1.3.*"></div>
|
||||
<div class="field"><label>PHP Min</label><input class="input" name="php_min" placeholder="7.4"></div>
|
||||
<div class="field"><label>PHP Max</label><input class="input" name="php_max" placeholder="8.3"></div>
|
||||
<div class="field"><label>Package URL</label><input class="input" name="package_url" placeholder="https://..."></div>
|
||||
<div class="field"><label>Size</label><input class="input" name="package_size" type="number" placeholder="238000"></div>
|
||||
<div class="field"><label>SHA256</label><input class="input" name="sha256" placeholder="可选,默认会补 64 位 0"></div>
|
||||
<div class="field"><label>PHP Extensions</label><input class="input" name="php_extensions" placeholder="curl,json"></div>
|
||||
</div>
|
||||
<div class="field"><label>发布说明</label><textarea name="changelog" placeholder="输入更新内容"></textarea></div>
|
||||
<div class="form-grid">
|
||||
<label><input type="checkbox" name="is_stable" value="1"> 设为稳定版</label>
|
||||
<label><input type="checkbox" name="is_latest" value="1"> 设为最新版本</label>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="submit">添加版本</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user