feat(platform): 新增对外平台入口 /platform(首页+套餐展示)

This commit is contained in:
萝卜
2026-03-14 02:20:28 +00:00
parent 9fc289d739
commit 8c373b52dc
5 changed files with 207 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Front;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PlatformController extends Controller
{
public function index(Request $request): View
{
return view('platform.index');
}
public function plans(Request $request): View
{
// 对外展示口径:仅展示“已发布 + 启用中”的套餐,避免未准备好的套餐被外部看到。
$plans = Plan::query()
->where('status', 'active')
->whereNotNull('published_at')
->orderBy('sort')
->orderByDesc('id')
->get();
return view('platform.plans', [
'plans' => $plans,
]);
}
}

View File

@@ -0,0 +1,45 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SaaSShopSaaS 电商系统</title>
<style>
body{font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif; margin:0; background:#f6f7fb; color:#111827;}
.wrap{max-width: 980px; margin:0 auto; padding:24px;}
.card{background:#fff; border:1px solid #e5e7eb; border-radius:10px; padding:20px;}
.muted{color:#6b7280; font-size:14px; line-height:1.6;}
.grid{display:grid; grid-template-columns: 1fr 1fr; gap:16px; margin-top:16px;}
.btn{display:inline-block; padding:10px 14px; border-radius:8px; border:1px solid #111827; text-decoration:none; color:#111827; font-weight:600;}
.btn-primary{background:#111827; color:#fff;}
@media (max-width: 720px){.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<h1 style="margin:0 0 8px 0;">SaaSShopSaaS 电商系统</h1>
<div class="muted">这是 SaaSShop 的对外介绍与开通入口(前期先做 A站点开通型。Blicense 授权码)后续在其它端能力更完整后再接入。</div>
<div style="margin-top:14px; display:flex; gap:10px; flex-wrap:wrap;">
<a class="btn btn-primary" href="/platform/plans">查看套餐与功能</a>
<a class="btn" href="/admin/login">进入总台管理(运营/治理)</a>
</div>
</div>
<div class="grid">
<div class="card">
<h3 style="margin:0 0 8px 0;">四端结构</h3>
<div class="muted">
总台管理(平台治理/收费) 站点后台(站点管理员) 商家后台(商家运营) 买家端(交易体验)。
</div>
</div>
<div class="card">
<h3 style="margin:0 0 8px 0;">当前优先级</h3>
<div class="muted">
先把“套餐 订阅 平台订单 生效/同步/治理”跑成收费闭环;对外平台先做到可介绍、可看套餐、可引导开通。
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,59 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SaaSShop套餐与功能</title>
<style>
body{font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif; margin:0; background:#f6f7fb; color:#111827;}
.wrap{max-width: 980px; margin:0 auto; padding:24px;}
.top{display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;}
.muted{color:#6b7280; font-size:14px; line-height:1.6;}
.grid{display:grid; grid-template-columns: repeat(3, 1fr); gap:14px; margin-top:16px;}
.card{background:#fff; border:1px solid #e5e7eb; border-radius:10px; padding:18px;}
.btn{display:inline-block; padding:8px 12px; border-radius:8px; border:1px solid #111827; text-decoration:none; color:#111827; font-weight:600;}
.btn-primary{background:#111827; color:#fff;}
.badge{display:inline-block; font-size:12px; padding:2px 8px; border-radius:999px; border:1px solid #e5e7eb; color:#374151;}
@media (max-width: 980px){.grid{grid-template-columns:1fr 1fr}}
@media (max-width: 720px){.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<div>
<h1 style="margin:0 0 6px 0;">套餐与功能</h1>
<div class="muted">仅展示「已发布 + 启用中」套餐。开通入口A站点开通型将优先在此页面逐步接入。</div>
</div>
<div style="display:flex; gap:10px;">
<a class="btn" href="/platform">返回首页</a>
<a class="btn" href="/admin/plans">运营管理套餐</a>
</div>
</div>
<div class="grid">
@forelse($plans as $p)
<div class="card">
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px;">
<h3 style="margin:0;">{{ $p->name }}</h3>
<span class="badge">{{ $p->billing_cycle }}</span>
</div>
<div class="muted" style="margin-top:6px;">套餐编码:{{ $p->code }}</div>
<div style="margin-top:10px; font-size:22px; font-weight:700;">¥{{ number_format((float) $p->price, 2) }}</div>
@if($p->description)
<div class="muted" style="margin-top:8px;">{{ $p->description }}</div>
@endif
<div style="margin-top:12px;">
<a class="btn btn-primary" href="/admin/platform-orders/create?plan_id={{ $p->id }}">我要开通/下单(暂由运营处理)</a>
</div>
<div class="muted" style="margin-top:8px;">提示:前期先跑通收费闭环与治理;自助开通会在后续版本接入。</div>
</div>
@empty
<div class="card" style="grid-column: 1 / -1;">
<div class="muted">当前暂无可对外展示的套餐(需满足:启用中 + 已发布)。</div>
</div>
@endforelse
</div>
</div>
</body>
</html>

View File

@@ -11,6 +11,7 @@ use App\Http\Controllers\Admin\ProductController as AdminProductController;
use App\Http\Controllers\Admin\MerchantController as AdminMerchantController;
use App\Http\Controllers\Admin\SiteSubscriptionController;
use App\Http\Controllers\Front\H5Controller;
use App\Http\Controllers\Front\PlatformController as FrontPlatformController;
use App\Http\Controllers\MerchantAdmin\AuthController as MerchantAdminAuthController;
use App\Http\Controllers\MerchantAdmin\DashboardController as MerchantDashboardController;
use App\Http\Controllers\MerchantAdmin\OrderController as MerchantOrderController;
@@ -36,6 +37,12 @@ Route::get('/h5', [H5Controller::class, 'index']);
Route::get('/wechat/mp', [MpController::class, 'index']);
Route::get('/wechat/mini', [MiniProgramController::class, 'index']);
// 对外平台(官网/开通入口)- 前期先承接介绍与套餐展示A站点开通型优先
Route::prefix('platform')->group(function () {
Route::get('/', [FrontPlatformController::class, 'index']);
Route::get('/plans', [FrontPlatformController::class, 'plans']);
});
Route::get('/health', function () {
$dbOk = false;
$dbMsg = '未检测';

View File

@@ -0,0 +1,65 @@
<?php
namespace Tests\Feature;
use App\Models\Plan;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PublicPlatformPlansPageTest extends TestCase
{
use RefreshDatabase;
public function test_public_platform_plans_page_should_only_show_active_and_published_plans(): void
{
Plan::query()->create([
'code' => 'pub_plan_active_published',
'name' => '对外展示套餐-启用已发布',
'billing_cycle' => 'monthly',
'price' => 99,
'list_price' => 129,
'status' => 'active',
'sort' => 10,
'description' => 'public ok',
'published_at' => now(),
]);
Plan::query()->create([
'code' => 'pub_plan_active_unpublished',
'name' => '不应展示-启用未发布',
'billing_cycle' => 'monthly',
'price' => 9,
'list_price' => 9,
'status' => 'active',
'sort' => 20,
'description' => 'no',
'published_at' => null,
]);
Plan::query()->create([
'code' => 'pub_plan_inactive_published',
'name' => '不应展示-停用已发布',
'billing_cycle' => 'monthly',
'price' => 199,
'list_price' => 199,
'status' => 'inactive',
'sort' => 30,
'description' => 'no',
'published_at' => now(),
]);
$res = $this->get('/platform/plans');
$res->assertOk();
$res->assertSee('对外展示套餐-启用已发布');
$res->assertDontSee('不应展示-启用未发布');
$res->assertDontSee('不应展示-停用已发布');
}
public function test_public_platform_index_page_should_be_accessible(): void
{
$this->get('/platform')
->assertOk()
->assertSee('SaaSShop');
}
}