feat(front): 官网套餐页提交开通意向线索(PlatformLead)

This commit is contained in:
萝卜
2026-03-14 02:31:28 +00:00
parent e1822e4389
commit 249ffb3aba
7 changed files with 201 additions and 12 deletions

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Front;
use App\Http\Controllers\Controller;
use App\Models\PlatformLead;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PlatformLeadController extends Controller
{
public function store(Request $request): RedirectResponse
{
$data = $request->validate([
'name' => ['required', 'string', 'max:100'],
'mobile' => ['nullable', 'string', 'max:30'],
'email' => ['nullable', 'string', 'max:100'],
'company' => ['nullable', 'string', 'max:100'],
'plan_id' => ['nullable', 'integer', 'exists:plans,id'],
'note' => ['nullable', 'string', 'max:2000'],
'source' => ['nullable', 'string', 'max:50'],
]);
PlatformLead::query()->create([
'name' => (string) ($data['name'] ?? ''),
'mobile' => (string) ($data['mobile'] ?? ''),
'email' => (string) ($data['email'] ?? ''),
'company' => (string) ($data['company'] ?? ''),
'plan_id' => (int) ($data['plan_id'] ?? 0) ?: null,
'note' => $data['note'] ?? null,
'source' => (string) ($data['source'] ?? 'platform'),
'status' => 'new',
'meta' => [
'ip' => $request->ip(),
'user_agent' => (string) $request->userAgent(),
],
]);
return redirect('/platform/plans')
->with('success', '已收到你的开通意向,我们会尽快联系你。');
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PlatformLead extends Model
{
use HasFactory;
protected $fillable = [
'name',
'mobile',
'email',
'company',
'source',
'status',
'note',
'plan_id',
'meta',
];
protected $casts = [
'meta' => 'array',
];
}

View File

@@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('platform_leads', function (Blueprint $table) {
$table->id();
// 对外平台线索/开通意向(前期先承接 A站点开通型
$table->string('name', 100)->default('');
$table->string('mobile', 30)->default('');
$table->string('email', 100)->default('');
$table->string('company', 100)->default('');
$table->string('source', 50)->default('platform');
$table->string('status', 30)->default('new');
$table->text('note')->nullable();
// 预期套餐(可选)
$table->unsignedBigInteger('plan_id')->nullable()->index();
// 扩展字段:便于后续接入 Blicense或更复杂的开通信息
$table->json('meta')->nullable();
$table->timestamps();
$table->index(['status', 'created_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('platform_leads');
}
};

View File

@@ -22,7 +22,7 @@
<div class="grid-3"> <div class="grid-3">
@forelse($plans as $p) @forelse($plans as $p)
<div class="card"> <div class="card">
<div class="flex-between" style="gap:10px;"> <div class="flex-between gap-10">
<h3 class="h3" style="margin:0;">{{ $p->name }}</h3> <h3 class="h3" style="margin:0;">{{ $p->name }}</h3>
<span class="badge">{{ $p->billing_cycle }}</span> <span class="badge">{{ $p->billing_cycle }}</span>
</div> </div>
@@ -32,9 +32,28 @@
<div class="muted mt-8">{{ $p->description }}</div> <div class="muted mt-8">{{ $p->description }}</div>
@endif @endif
<div class="mt-12"> <div class="mt-12">
<a class="btn btn-primary" href="/admin/platform-orders/create?plan_id={{ $p->id }}">我要开通/下单(暂由运营处理)</a> <form method="post" action="/platform/leads">
@csrf
<input type="hidden" name="plan_id" value="{{ $p->id }}">
<input type="hidden" name="source" value="platform_plans">
<div class="muted">开通意向A站点开通型前期先由运营人工开通</div>
<div class="mt-8">
<input name="name" placeholder="你的姓名" required style="padding:8px 10px; border:1px solid #e5e7eb; border-radius:8px; width:100%; box-sizing:border-box;">
</div>
<div class="mt-8">
<input name="mobile" placeholder="手机号(可选)" style="padding:8px 10px; border:1px solid #e5e7eb; border-radius:8px; width:100%; box-sizing:border-box;">
</div>
<div class="mt-8">
<input name="company" placeholder="公司/团队(可选)" style="padding:8px 10px; border:1px solid #e5e7eb; border-radius:8px; width:100%; box-sizing:border-box;">
</div>
<div class="mt-8">
<button type="submit" class="btn btn-primary">提交开通意向</button>
<a class="btn" href="/admin/platform-orders/create?plan_id={{ $p->id }}">运营手工下单入口</a>
</div>
<div class="muted mt-8">提示:前期先跑通收费闭环与治理;自助开通/自助支付会在后续版本接入。</div>
</form>
</div> </div>
<div class="muted mt-8">提示:前期先跑通收费闭环与治理;自助开通会在后续版本接入。</div>
</div> </div>
@empty @empty
<div class="card" style="grid-column: 1 / -1;"> <div class="card" style="grid-column: 1 / -1;">

View File

@@ -41,6 +41,9 @@ Route::get('/wechat/mini', [MiniProgramController::class, 'index']);
Route::prefix('platform')->group(function () { Route::prefix('platform')->group(function () {
Route::get('/', [FrontPlatformController::class, 'index']); Route::get('/', [FrontPlatformController::class, 'index']);
Route::get('/plans', [FrontPlatformController::class, 'plans']); Route::get('/plans', [FrontPlatformController::class, 'plans']);
// A站点开通型前期先用“线索/开通意向”承接(后续再接自助开通/下单)
Route::post('/leads', [\App\Http\Controllers\Front\PlatformLeadController::class, 'store']);
}); });
Route::get('/health', function () { Route::get('/health', function () {

View File

@@ -0,0 +1,48 @@
<?php
namespace Tests\Feature;
use App\Models\Plan;
use App\Models\PlatformLead;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class FrontPlatformLeadSubmitTest extends TestCase
{
use RefreshDatabase;
public function test_platform_can_submit_lead_and_redirect_back_to_plans(): void
{
$this->seed();
$plan = Plan::query()->create([
'code' => 'front_lead_plan_01',
'name' => '线索提交测试套餐',
'billing_cycle' => 'monthly',
'price' => 99,
'list_price' => 99,
'status' => 'active',
'sort' => 10,
'published_at' => now(),
]);
$res = $this->post('/platform/leads', [
'name' => '张三',
'mobile' => '13800000000',
'company' => '测试公司',
'plan_id' => $plan->id,
'source' => 'platform_plans',
]);
$res->assertRedirect('/platform/plans');
$lead = PlatformLead::query()->latest('id')->first();
$this->assertNotNull($lead);
$this->assertSame('张三', $lead->name);
$this->assertSame('13800000000', $lead->mobile);
$this->assertSame('测试公司', $lead->company);
$this->assertSame($plan->id, $lead->plan_id);
$this->assertSame('platform_plans', $lead->source);
$this->assertSame('new', $lead->status);
}
}

View File

@@ -9,16 +9,21 @@ class FrontPlatformPagesUseExternalCssTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
public function test_platform_pages_should_link_platform_css_and_not_inline_style_block(): void public function test_platform_index_should_use_external_css_and_not_inline_style_block(): void
{ {
$res1 = $this->get('/platform'); $res = $this->get('/platform');
$res1->assertOk(); $res->assertOk();
$res1->assertSee('<link rel="stylesheet" href="/css/platform.css">', false);
$res1->assertDontSee('<style>', false);
$res2 = $this->get('/platform/plans'); $res->assertSee('/css/platform.css', false);
$res2->assertOk(); $res->assertDontSee('<style>', false);
$res2->assertSee('<link rel="stylesheet" href="/css/platform.css">', false); }
$res2->assertDontSee('<style>', false);
public function test_platform_plans_should_use_external_css_and_not_inline_style_block(): void
{
$res = $this->get('/platform/plans');
$res->assertOk();
$res->assertSee('/css/platform.css', false);
$res->assertDontSee('<style>', false);
} }
} }