前端极简黑白重设计,并纳入客户端注册、站点设置与后台相关改动

- storefront 全站改为极简黑白配色:直角、细线分隔、卡片 hover 浅底、深色模式(跟随系统+手动切换)
- 抽出 storefront/partials/card 复用卡片
- 一并提交此前暂存的客户端注册 API、站点设置、后台页面与测试

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
浪子
2026-09-22 08:35:49 +08:00
parent 62cffc6f5f
commit 04a2034975
41 changed files with 3447 additions and 658 deletions
+113 -77
View File
@@ -1,78 +1,87 @@
@extends('admin.layout', [
'title' => 'Tstore Admin · 插件 / 主题管理',
'pageTitle' => $filters['type'] === 'plugin' ? '插件列表' : ($filters['type'] === 'theme' ? '主题列表' : '扩展列表'),
'pageSubtitle' => $filters['type'] === 'plugin'
? '查看、筛选、维护和发布插件,统一处理插件版本与状态。'
: ($filters['type'] === 'theme'
? '查看、筛选、维护和发布主题,统一处理主题版本与状态。'
: '查看全部插件与主题,统一管理扩展列表、详情页与发布版本。'),
])
@php
$statusLabels = [
'draft' => '草稿',
'published' => '发布中',
'hidden' => '隐藏',
'deprecated' => '弃用',
];
$statusTones = [
'draft' => 'muted',
'published' => 'ok',
'hidden' => 'warn',
'deprecated' => 'warn',
];
$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 . '。';
$newEntityLabel = '新建' . $entityLabel;
$currentStatusLabel = $filters['status'] !== '' ? ($statusLabels[$filters['status']] ?? $filters['status']) : '全部状态';
@endphp
@extends('admin.layout', [
'title' => 'Tstore Admin · 扩展管理',
'pageTitle' => $entityPluralLabel,
'pageSubtitle' => '统一维护扩展元数据、分类关联、发布状态与版本入口。',
'pageBadge' => 'Packages',
])
@section('content')
<div class="grid">
<div class="panel">
<section class="stats">
<div class="stat"><span class="label">当前结果</span><span class="value">{{ $packages->total() }}</span><span class="hint">符合筛选条件的{{ $entityLabel }}</span></div>
<div class="stat"><span class="label">当前类型</span><span class="value">{{ $currentType === '' ? '全部' : strtoupper($currentType) }}</span><span class="hint">{{ $entityLabel }}视角</span></div>
<div class="stat"><span class="label">当前状态</span><span class="value">{{ $currentStatusLabel }}</span><span class="hint">已应用状态筛选</span></div>
<div class="stat"><span class="label">页码</span><span class="value">{{ $packages->currentPage() }}/{{ max($packages->lastPage(), 1) }}</span><span class="hint">分页浏览</span></div>
</section>
<section class="panel">
<div class="section-title">
<div>
<h2>筛选与查询</h2>
<p>{{ $filterHint }}</p>
<p>先缩小范围,再进入详情页处理版本发布或资料修改。</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>
<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>
@foreach ($statusLabels as $status => $label)
<option value="{{ $status }}" @selected($filters['status'] === $status)>{{ $label }}</option>
@endforeach
</select>
</div>
<div class="field" style="min-width:280px">
<div class="field" style="flex:1 1 280px">
<label>关键词</label>
<input class="input" type="text" name="keyword" value="{{ $filters['keyword'] }}" placeholder="搜索 name / slug / summary">
<input class="input" type="text" name="keyword" value="{{ $filters['keyword'] }}" placeholder="搜索名称、slug 或摘要">
</div>
</div>
<div class="form-actions">
<button class="btn" type="submit">筛选</button>
<button class="btn" type="submit">应用筛选</button>
<a class="btn secondary" href="{{ route('webadmin.packages') }}">重置</a>
</div>
</form>
</div>
</section>
<div class="split">
<div class="panel">
<div class="stack">
<section class="panel">
<div class="section-title">
<div>
<h2>{{ $entityPluralLabel }}</h2>
<p>{{ $listHint }}</p>
<p>保持列表信息密度,重点展示状态、当前版本、分类和最近更新时间。</p>
</div>
</div>
<div class="table-wrap">
<table class="table">
<thead>
@@ -82,6 +91,7 @@
<th>状态</th>
<th>最新版本</th>
<th>分类</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
@@ -91,108 +101,134 @@
<td>
<strong>{{ $package->name }}</strong>
<div class="muted">{{ $package->slug }}</div>
<div class="muted">{{ $package->summary ?: '暂无摘要' }}</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>{{ strtoupper($package->type) }}</td>
<td><span class="chip {{ $statusTones[$package->status] ?? 'muted' }}">{{ $statusLabels[$package->status] ?? $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
@forelse ($package->categories as $category)
<span class="chip">{{ $category->name }}</span>
@empty
<span class="muted">未分类</span>
@endforelse
</div>
</td>
<td>{{ optional($package->updated_at)->format('Y-m-d H:i') ?: '-' }}</td>
<td>
<div class="form-actions">
<a class="btn secondary small" href="{{ route('webadmin.packages.show', [$package->type, $package->slug]) }}">详情 / 发布</a>
<a class="btn ghost 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')
@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>
<button class="btn secondary small" type="submit">{{ $package->status === 'published' ? '隐藏' : '发布' }}</button>
</form>
<form method="post" action="{{ route('webadmin.packages.destroy', [$package->type, $package->slug]) }}" onsubmit="return confirm('确认删除这个{{ $entityLabel }}吗?版本、截图和分类关联都会一起删除。');">
@csrf
@method('DELETE')
<button class="btn danger small" type="submit">删除</button>
</form>
</div>
</td>
</tr>
@empty
<tr><td colspan="6"><div class="empty">{{ $emptyHint }}</div></td></tr>
<tr>
<td colspan="7">
<div class="empty">当前还没有{{ $entityLabel }}。</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div style="margin-top:14px">{{ $packages->links() }}</div>
</div>
<div class="panel">
{{ $packages->links() }}
</section>
<section class="panel soft">
<div class="section-title">
<div>
<h2>{{ $newEntityLabel }}</h2>
<p>{{ $createHint }}</p>
<p>先创建扩展,再进入详情页继续补充版本、截图和发布信息。</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>
<option value="plugin" @selected(old('type', $currentType ?: 'plugin') === 'plugin')>插件</option>
<option value="theme" @selected(old('type', $currentType) === 'theme')>主题</option>
</select>
</div>
<div class="field">
<label>Slug</label>
<input class="input" name="slug" placeholder="HelloStore" required>
<input class="input" name="slug" value="{{ old('slug') }}" placeholder="HelloStore" required>
</div>
<div class="field">
<label>名称</label>
<input class="input" name="name" placeholder="Hello Store" required>
<input class="input" name="name" value="{{ old('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>
@foreach ($statusLabels as $status => $label)
<option value="{{ $status }}" @selected(old('status', 'published') === $status)>{{ $label }}</option>
@endforeach
</select>
</div>
<div class="field">
<label>作者</label>
<input class="input" name="author" placeholder="LT083">
<input class="input" name="author" value="{{ old('author') }}" placeholder="LT083">
</div>
<div class="field">
<label>License</label>
<input class="input" name="license" placeholder="MIT">
<input class="input" name="license" value="{{ old('license') }}" placeholder="MIT">
</div>
<div class="field">
<label>排序</label>
<input class="input" type="number" name="sort_order" value="{{ old('sort_order', 0) }}">
</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">
<input class="input" name="homepage" value="{{ old('homepage') }}" placeholder="https://example.com/package">
</div>
</div>
<div class="field">
<label>分类 slug,多个用空格分隔</label>
<input class="input" name="categories_text" placeholder="seo performance">
<label>图标 URL</label>
<input class="input" name="icon_url" value="{{ old('icon_url') }}" placeholder="https://example.com/icon.png">
</div>
<div class="field">
<label>摘要</label>
<input class="input" name="summary" value="{{ old('summary') }}" placeholder="一句话说明这个{{ $entityLabel }}是做什么的">
</div>
<div class="field">
<label>描述</label>
<textarea name="description" placeholder="补充更完整的定位与功能说明">{{ old('description') }}</textarea>
</div>
<div class="field">
<label>分类名称或 slug,优先用逗号分隔</label>
<input class="input" name="categories_text" value="{{ old('categories_text') }}" placeholder="性能优化, seo">
</div>
<div class="checkbox-row">
<label><input type="checkbox" name="is_featured" value="1" @checked(old('is_featured'))> 设为推荐扩展</label>
</div>
<div class="form-actions">
<button class="btn" type="submit">创建{{ $entityLabel }}</button>
</div>
</form>
</div>
</section>
</div>
</div>
@endsection
+344 -90
View File
@@ -1,82 +1,257 @@
@php
$typeLabel = $package->type === 'plugin' ? '插件' : '主题';
$statusLabels = [
'draft' => '草稿',
'published' => '发布中',
'hidden' => '隐藏',
'deprecated' => '弃用',
];
$statusTone = [
'draft' => 'muted',
'published' => 'ok',
'hidden' => 'warn',
'deprecated' => 'warn',
];
$currentVersion = $package->versions->firstWhere('is_latest', true)
?: $package->versions->firstWhere('is_stable', true)
?: $package->versions->first();
@endphp
@extends('admin.layout', [
'title' => 'Tstore Admin · ' . $package->name,
'pageTitle' => $package->name,
'pageSubtitle' => ($package->type === 'plugin' ? '插件' : ($package->type === 'theme' ? '主题' : $package->type)) . ' · ' . $package->slug,
'pageSubtitle' => $typeLabel . ' · ' . $package->slug,
'pageBadge' => ucfirst($package->type),
])
@php
$entityLabel = $package->type === 'plugin' ? '插件' : ($package->type === 'theme' ? '主题' : '扩展');
@endphp
@section('content')
<div class="grid">
<section class="stats">
<div class="stat"><span class="label">扩展类型</span><span class="value">{{ strtoupper($package->type) }}</span><span class="hint">{{ $typeLabel }} package</span></div>
<div class="stat"><span class="label">当前状态</span><span class="value">{{ $statusLabels[$package->status] ?? $package->status }}</span><span class="hint">可在下方直接调整</span></div>
<div class="stat"><span class="label">当前主版本</span><span class="value">{{ $currentVersion?->version ?: '-' }}</span><span class="hint">最新或稳定版本</span></div>
<div class="stat"><span class="label">版本数量</span><span class="value">{{ $package->versions->count() }}</span><span class="hint">历史记录总数</span></div>
<div class="stat"><span class="label">截图数量</span><span class="value">{{ $package->screenshots->count() }}</span><span class="hint">当前展示素材</span></div>
<div class="stat"><span class="label">下载次数</span><span class="value">{{ $package->download_count }}</span><span class="hint">累计下载总量</span></div>
</section>
<div class="split">
<div class="panel">
<div class="section-title">
<div>
<h2>{{ $entityLabel }}信息</h2>
<p>编辑基础元数据、分类、图标与发布状态。</p>
<section class="panel">
<div class="entity-head">
<div class="entity-icon">
@if ($package->icon_url)
<img src="{{ $package->icon_url }}" alt="{{ $package->name }}">
@else
{{ $package->type === 'plugin' ? 'P' : 'T' }}
@endif
</div>
<div class="entity-copy">
<h2>基础资料</h2>
<p>这里维护扩展展示信息、排序、推荐状态以及分类关联。</p>
</div>
</div>
<form method="post" action="{{ route('webadmin.packages.update', [$package->type, $package->slug]) }}" class="grid">
@csrf @method('PUT')
@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>
<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="{{ old('name', $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>
@foreach ($statusLabels as $status => $label)
<option value="{{ $status }}" @selected(old('status', $package->status) === $status)>{{ $label }}</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 class="field">
<label>作者</label>
<input class="input" name="author" value="{{ old('author', $package->author) }}">
</div>
<div class="field">
<label>License</label>
<input class="input" name="license" value="{{ old('license', $package->license) }}">
</div>
<div class="field">
<label>排序</label>
<input class="input" type="number" name="sort_order" value="{{ old('sort_order', $package->sort_order ?? 0) }}">
</div>
<div class="field">
<label>主页</label>
<input class="input" name="homepage" value="{{ old('homepage', $package->homepage) }}">
</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="field">
<label>图标 URL</label>
<input class="input" name="icon_url" value="{{ old('icon_url', $package->icon_url) }}">
</div>
<div class="field">
<label>摘要</label>
<input class="input" name="summary" value="{{ old('summary', $package->summary) }}">
</div>
<div class="field">
<label>描述</label>
<textarea name="description">{{ old('description', $package->description) }}</textarea>
</div>
<div class="field">
<label>分类名称或 slug,优先用逗号分隔</label>
<input class="input" name="categories_text" value="{{ old('categories_text', $package->categories->map(fn ($category) => $category->name ?: $category->slug)->implode(', ')) }}">
</div>
<div class="checkbox-row">
<label><input type="checkbox" name="is_featured" value="1" @checked(old('is_featured', $package->is_featured))> 设为推荐扩展</label>
</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>
<button class="btn" type="submit">保存{{ $typeLabel }}信息</button>
<a class="btn ghost" href="{{ route('webadmin.packages', ['type' => $package->type]) }}">返回{{ $typeLabel }}列表</a>
</div>
</form>
</div>
<div class="panel">
<div class="section-title">
<div>
<h2>当前状态</h2>
<p>快速查看分类、版本、下载与截图信息。</p>
<div class="form-actions" style="margin-top:16px">
<form method="post" action="{{ route('webadmin.packages.destroy', [$package->type, $package->slug]) }}" onsubmit="return confirm('确认删除这个{{ $typeLabel }}吗?版本、截图和分类关联都会一起删除。');">
@csrf
@method('DELETE')
<button class="btn danger" type="submit">删除{{ $typeLabel }}</button>
</form>
</div>
</section>
<div class="stack">
<section class="panel soft">
<div class="section-title">
<div>
<h2>当前资料</h2>
<p>快速核对基础信息、时间戳和前台展示状态。</p>
</div>
</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 class="kv-grid">
<div class="kv-card">
<span class="kv-label">Slug</span>
<span class="kv-value">{{ $package->slug }}</span>
</div>
<div class="kv-card">
<span class="kv-label">状态</span>
<span class="kv-value"><span class="chip {{ $statusTone[$package->status] ?? 'muted' }}">{{ $statusLabels[$package->status] ?? $package->status }}</span></span>
</div>
<div class="kv-card">
<span class="kv-label">作者</span>
<span class="kv-value">{{ $package->author ?: '未填写' }}</span>
</div>
<div class="kv-card">
<span class="kv-label">主页</span>
<span class="kv-value">{{ $package->homepage ?: '未填写' }}</span>
</div>
<div class="kv-card">
<span class="kv-label">创建时间</span>
<span class="kv-value">{{ optional($package->created_at)->format('Y-m-d H:i') ?: '-' }}</span>
</div>
<div class="kv-card">
<span class="kv-label">更新时间</span>
<span class="kv-value">{{ optional($package->updated_at)->format('Y-m-d H:i') ?: '-' }}</span>
</div>
</div>
<div class="tags">
@foreach ($package->categories as $category)
<span class="chip">{{ $category->name }}</span>
@endforeach
@if ($package->categories->isEmpty())
<span class="muted">尚未关联分类</span>
@endif
<span class="chip {{ $package->is_featured ? 'ok' : 'muted' }}">{{ $package->is_featured ? '推荐扩展' : '普通扩展' }}</span>
</div>
</section>
<section class="panel soft">
<div class="section-title">
<div>
<h2>截图管理</h2>
<p>手动录入详情页预览截图,第一张会优先作为前台主预览展示。</p>
</div>
</div>
<form method="post" action="{{ route('webadmin.packages.screenshots.store', [$package->type, $package->slug]) }}" class="grid">
@csrf
<div class="field">
<label>图片 URL</label>
<input class="input" name="image_url" value="{{ old('image_url') }}" placeholder="https://example.com/screenshots/home.webp" required>
</div>
<div class="field">
<label>截图说明</label>
<input class="input" name="caption" value="{{ old('caption') }}" placeholder="首页、设置页、文章页等">
</div>
<div class="field">
<label>排序</label>
<input class="input" type="number" name="sort_order" value="{{ old('sort_order', 0) }}">
</div>
<div class="form-actions">
<button class="btn" type="submit">添加截图</button>
</div>
</form>
@if ($package->screenshots->isNotEmpty())
<div class="preview-grid" style="margin-top:18px">
@foreach ($package->screenshots as $shot)
<article class="preview-card">
<figure class="preview-shot">
<img src="{{ $shot->image_url }}" alt="{{ $shot->caption ?: $package->name }}">
<figcaption>{{ $shot->caption ?: $package->name . ' 截图' }}</figcaption>
</figure>
<div class="preview-tools">
<span class="chip muted">排序 {{ $shot->sort_order }}</span>
<form method="post" action="{{ route('webadmin.packages.screenshots.destroy', [$package->type, $package->slug, $shot->id]) }}" onsubmit="return confirm('确认删除这张截图吗?');">
@csrf
@method('DELETE')
<button class="btn danger small" type="submit">删除</button>
</form>
</div>
</article>
@endforeach
</div>
@else
<div class="empty">当前还没有截图。</div>
@endif
</section>
</div>
</div>
<div class="split">
<div class="panel">
<section class="panel">
<div class="section-title">
<div>
<h2>版本记录</h2>
<p>查看版本状态,也可以直接删除历史版本。</p>
<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>
<th>操作</th>
@@ -86,94 +261,173 @@
@forelse ($package->versions as $version)
<tr>
<td>
<strong>{{ $version->version }}</strong>
<div class="muted">{{ $version->package_url ?: '未设置 package_url' }}</div>
<strong>v{{ $version->version }}</strong>
<div class="muted">{{ $version->package_url ?: '未生成下载地址' }}</div>
</td>
<td>
<div class="tags" style="margin-top:0">
<span class="chip {{ $version->is_stable ? 'ok' : 'warn' }}">{{ $version->is_stable ? '稳定版' : '预发布' }}</span>
<span class="chip {{ $version->is_latest ? 'ok' : 'muted' }}">{{ $version->is_latest ? '最新' : '历史' }}</span>
</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>{{ 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')
@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>
<tr>
<td colspan="5">
<div class="empty">当前还没有版本记录。</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</section>
<div class="panel">
<section class="panel soft">
<div class="section-title">
<div>
<h2>zip 上传发布</h2>
<p>优先读取 zip 根目录的 manifest.json;如果没有 manifest.json,则使用下方手动填写的版本和兼容性字段。</p>
<h2>zip 发布</h2>
<p>上传 zip 后补充版本字段,服务端会校验目录结构并生成可供 API 返回的版本元数据。</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>版本号(无 manifest 时必填)</label><input class="input" name="version" value="{{ old('version') }}" placeholder="1.0.0"></div>
<div class="field"><label>发布说明(可选)</label><textarea name="changelog" placeholder="补充这次发布说明">{{ old('changelog') }}</textarea></div>
<div class="form-grid">
<div class="field"><label>Typecho Min</label><input class="input" name="typecho_min" value="{{ old('typecho_min', '1.2.0') }}" placeholder="1.2.0"></div>
<div class="field"><label>Typecho Max</label><input class="input" name="typecho_max" value="{{ old('typecho_max') }}" placeholder="1.3.*"></div>
<div class="field"><label>PHP Min</label><input class="input" name="php_min" value="{{ old('php_min', '7.4') }}" placeholder="7.4"></div>
<div class="field"><label>PHP Max</label><input class="input" name="php_max" value="{{ old('php_max') }}" placeholder="8.3"></div>
<div class="field"><label>PHP Extensions</label><input class="input" name="php_extensions" value="{{ is_array(old('php_extensions')) ? implode(',', old('php_extensions')) : old('php_extensions') }}" placeholder="curl,json"></div>
<div class="field"><label>发布时间(可选)</label><input class="input" type="datetime-local" name="published_at" value="{{ old('published_at') }}"></div>
<div class="field">
<label>zip 文件</label>
<input class="input" type="file" name="package_file" accept=".zip" required>
</div>
<div class="form-grid">
<div class="field"><label>发布选项</label>
<div class="tags">
<label><input type="checkbox" name="is_stable" value="1" @checked(old('is_stable', 1))> 设为稳定版</label>
<label><input type="checkbox" name="mark_as_latest" value="1" @checked(old('mark_as_latest', 1))> 设为最新</label>
</div>
<div class="field">
<label>版本号</label>
<input class="input" name="version" value="{{ old('version') }}" placeholder="如 zip 不含 manifest,可在这里手动填写 1.0.0">
</div>
<div class="field">
<label>发布说明</label>
<textarea name="changelog" placeholder="补充这次发布的主要变化">{{ old('changelog') }}</textarea>
</div>
<div class="form-grid compact">
<div class="field">
<label>Typecho Min</label>
<input class="input" name="typecho_min" value="{{ old('typecho_min', '1.2.0') }}" placeholder="1.2.0">
</div>
<div class="field">
<label>Typecho Max</label>
<input class="input" name="typecho_max" value="{{ old('typecho_max') }}" placeholder="1.3.*">
</div>
<div class="field">
<label>PHP Min</label>
<input class="input" name="php_min" value="{{ old('php_min', '7.4') }}" placeholder="7.4">
</div>
<div class="field">
<label>PHP Max</label>
<input class="input" name="php_max" value="{{ old('php_max') }}" placeholder="8.3">
</div>
<div class="field">
<label>PHP Extensions</label>
<input class="input" name="php_extensions" value="{{ is_array(old('php_extensions')) ? implode(',', old('php_extensions')) : old('php_extensions') }}" placeholder="curl,json">
</div>
<div class="field">
<label>发布时间</label>
<input class="input" type="datetime-local" name="published_at" value="{{ old('published_at') }}">
</div>
</div>
<div class="form-actions"><button class="btn" type="submit">上传并发布</button></div>
<div class="checkbox-row">
<label><input type="checkbox" name="is_stable" value="1" @checked(old('is_stable', 1))> 设为稳定版</label>
<label><input type="checkbox" name="mark_as_latest" value="1" @checked(old('mark_as_latest', 1))> 设为最新版本</label>
</div>
<div class="form-actions">
<button class="btn" type="submit">上传并发布</button>
</div>
</form>
</div>
</section>
</div>
<div class="panel">
<section class="panel">
<div class="section-title">
<div>
<h2>手动添加版本</h2>
<p>调试阶段可以快速手动录入版本元数据。</p>
<p>调试或补录历史版本时,可以直接写入元数据而不经过 zip 发布流程。</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 class="form-grid compact">
<div class="field">
<label>版本号</label>
<input class="input" name="version" value="{{ old('version') }}" placeholder="1.0.0" required>
</div>
<div class="field">
<label>发布时间</label>
<input class="input" type="datetime-local" name="published_at" value="{{ old('published_at') }}">
</div>
<div class="field">
<label>Typecho Min</label>
<input class="input" name="typecho_min" value="{{ old('typecho_min') }}" placeholder="1.2.0">
</div>
<div class="field">
<label>Typecho Max</label>
<input class="input" name="typecho_max" value="{{ old('typecho_max') }}" placeholder="1.3.*">
</div>
<div class="field">
<label>PHP Min</label>
<input class="input" name="php_min" value="{{ old('php_min') }}" placeholder="7.4">
</div>
<div class="field">
<label>PHP Max</label>
<input class="input" name="php_max" value="{{ old('php_max') }}" placeholder="8.3">
</div>
<div class="field">
<label>Package URL</label>
<input class="input" name="package_url" value="{{ old('package_url') }}" placeholder="https://...">
</div>
<div class="field">
<label>Size</label>
<input class="input" type="number" name="package_size" value="{{ old('package_size') }}" placeholder="238000">
</div>
<div class="field">
<label>SHA256</label>
<input class="input" name="sha256" value="{{ old('sha256') }}" placeholder="可选,默认会补 64 位 0">
</div>
<div class="field">
<label>PHP Extensions</label>
<input class="input" name="php_extensions" value="{{ old('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 class="field">
<label>发布说明</label>
<textarea name="changelog" placeholder="输入更新内容">{{ old('changelog') }}</textarea>
</div>
<div class="checkbox-row">
<label><input type="checkbox" name="is_stable" value="1" @checked(old('is_stable'))> 设为稳定版</label>
<label><input type="checkbox" name="is_latest" value="1" @checked(old('is_latest'))> 设为最新版本</label>
</div>
<div class="form-actions">
<button class="btn" type="submit">添加版本</button>
</div>
</form>
</div>
</section>
</div>
@endsection