feat(admin): 客服中心工单骨架(模型/迁移/路由/菜单/页面)

This commit is contained in:
萝卜
2026-03-15 08:44:02 +00:00
parent 0bd21f8715
commit 62d7a81df3
8 changed files with 203 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Concerns\ResolvesPlatformAdminContext;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\View\View;
class SupportTicketController extends Controller
{
use ResolvesPlatformAdminContext;
public function index(Request $request): View
{
$this->ensurePlatformAdmin($request);
// 当前阶段:先做最小骨架页,后续再接入列表/筛选/创建/指派/SLA/升级。
return view('admin.support_tickets.index');
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SupportTicket extends Model
{
protected $fillable = [
'scope',
'scope_id',
'merchant_id',
'buyer_id',
'order_id',
'platform_order_id',
'site_subscription_id',
'platform_lead_id',
'ticket_no',
'category',
'priority',
'status',
'subject',
'content',
'assigned_admin_id',
'closed_at',
];
protected $casts = [
'closed_at' => 'datetime',
];
}

View File

@@ -0,0 +1,59 @@
<?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('support_tickets', function (Blueprint $table) {
$table->id();
// scope用于多层级隔离platform/site/merchant
$table->string('scope', 20)->default('platform');
$table->unsignedBigInteger('scope_id')->nullable();
// 关联对象(用于“收费闭环/治理串联”)
$table->unsignedBigInteger('merchant_id')->nullable();
$table->unsignedBigInteger('buyer_id')->nullable();
$table->unsignedBigInteger('order_id')->nullable();
$table->unsignedBigInteger('platform_order_id')->nullable();
$table->unsignedBigInteger('site_subscription_id')->nullable();
$table->unsignedBigInteger('platform_lead_id')->nullable();
// 工单基础字段
$table->string('ticket_no', 50)->unique();
$table->string('category', 50)->default('general');
$table->string('priority', 20)->default('normal');
$table->string('status', 20)->default('open');
$table->string('subject', 200)->default('');
$table->text('content')->nullable();
// 指派与时间
$table->unsignedBigInteger('assigned_admin_id')->nullable();
$table->timestamp('closed_at')->nullable();
$table->timestamps();
$table->index(['scope', 'scope_id']);
$table->index(['merchant_id']);
$table->index(['platform_order_id']);
$table->index(['site_subscription_id']);
$table->index(['platform_lead_id']);
$table->index(['status', 'priority']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('support_tickets');
}
};

View File

@@ -43,6 +43,13 @@
</div>
</details>
<details class="topnav-item nav-group" data-role="topnav-group">
<summary class="topnav-summary nav-group-title">客服中心</summary>
<div class="topnav-dropdown">
<a href="/admin/support-tickets" class="topnav-sub nav-item nav-item--sub">工单</a>
</div>
</details>
<details class="topnav-item nav-group" data-role="topnav-group">
<summary class="topnav-summary nav-group-title">系统</summary>
<div class="topnav-dropdown">

View File

@@ -0,0 +1,16 @@
@extends('admin.layouts.app')
@section('title', '客服中心 / 工单')
@section('page_title', '客服中心 / 工单')
@section('content')
<div class="card mb-20">
<p class="muted muted-tight">客服中心(工单)骨架页:用于后续接入“对账异常/退款异常/续费缺订阅”等治理工单闭环。</p>
<p class="muted">当前阶段仅做信息架构占位与路由/权限/数据模型埋口,避免后续大改。</p>
</div>
<div class="card">
<h3>工单列表(占位)</h3>
<p class="muted">下一步将接入筛选scope/状态/优先级/关联对象、创建工单、指派、SLA、升级链路与审计。</p>
</div>
@endsection

View File

@@ -10,6 +10,7 @@ use App\Http\Controllers\Admin\ProductCategoryController as AdminProductCategory
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\Admin\SupportTicketController;
use App\Http\Controllers\Front\H5Controller;
use App\Http\Controllers\Front\PlatformController as FrontPlatformController;
use App\Http\Controllers\MerchantAdmin\AuthController as MerchantAdminAuthController;
@@ -155,6 +156,9 @@ Route::prefix('admin')->group(function () {
Route::get('/platform-leads', [\App\Http\Controllers\Admin\PlatformLeadController::class, 'index']);
Route::post('/platform-leads/{lead}/set-status', [\App\Http\Controllers\Admin\PlatformLeadController::class, 'setStatus']);
// 客服中心(工单)- 先做骨架与扩展点埋口
Route::get('/support-tickets', [SupportTicketController::class, 'index']);
});
});

View File

@@ -0,0 +1,32 @@
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AdminSupportTicketIndexPageShouldRenderTest extends TestCase
{
use RefreshDatabase;
protected function loginAsPlatformAdmin(): void
{
$this->seed();
$this->post('/admin/login', [
'email' => 'platform.admin@demo.local',
'password' => 'Platform@123456',
])->assertRedirect('/admin');
}
public function test_support_ticket_index_page_should_render(): void
{
$this->loginAsPlatformAdmin();
$res = $this->get('/admin/support-tickets');
$res->assertOk();
$res->assertSee('客服中心', false);
$res->assertSee('工单列表', false);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AdminTopNavShouldContainSupportCenterLinkTest extends TestCase
{
use RefreshDatabase;
protected function loginAsPlatformAdmin(): void
{
$this->seed();
$this->post('/admin/login', [
'email' => 'platform.admin@demo.local',
'password' => 'Platform@123456',
])->assertRedirect('/admin');
}
public function test_topnav_should_contain_support_tickets_link(): void
{
$this->loginAsPlatformAdmin();
$res = $this->get('/admin');
$res->assertOk();
$res->assertSee('客服中心', false);
$res->assertSee('/admin/support-tickets', false);
$res->assertSee('工单', false);
}
}