use BackedEnum;
use UnitEnum;
// 导航图标(必须是 BackedEnum|string|null)
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
// 导航组(必须是 UnitEnum|string|null)
protected static string|UnitEnum|null $navigationGroup = '题库系统';
// 导航标签
protected static ?string $navigationLabel = '题库管理';
// 导航排序
protected static ?int $navigationSort = 2;
// 错误:使用了 ?string
protected static ?string $navigationGroup = '题库系统';
// 正确:使用 string|UnitEnum|null
protected static string|UnitEnum|null $navigationGroup = '题库系统';
use Livewire\Attributes\Computed;
/**
* 计算属性:从 API 获取题目列表
*/
#[Computed]
public function questions(): array
{
$service = app(QuestionServiceApi::class);
// ... 逻辑
return $response['data'] ?? [];
}
/**
* 计算属性:分页信息
*/
#[Computed]
public function meta(): array
{
// ... 逻辑
return $response['meta'] ?? [];
}
/**
* 计算属性:统计数据
*/
#[Computed]
public function statistics(): array
{
// ... 逻辑
return $service->getStatistics();
}
use Livewire\Attributes\On;
/**
* 刷新数据
*/
#[On('refresh-data')]
public function refreshData(): void
{
$this->resetCache();
// ...
}
/**
* AI 生成题目
*/
#[On('ai-generate')]
public function aiGenerate(): void
{
// ...
}
/**
* 搜索更新处理
*/
public function updatedSearch(): void
{
$this->currentPage = 1;
}
/**
* 知识点筛选更新处理
*/
public function updatedSelectedKpCode(): void
{
$this->currentPage = 1;
}
双向绑定:
{{-- 搜索框 --}}
<x-filament::input
type="text"
wire:model.live.debounce.300ms="search"
placeholder="输入题目内容、答案或编号"
/>
{{-- 知识点筛选 --}}
<x-filament::input
type="text"
wire:model.live="selectedKpCode"
placeholder="如:KP1001"
/>
{{-- 难度筛选 --}}
<x-filament::input
type="text"
wire:model.live="selectedDifficulty"
placeholder="0.3/0.6/0.85"
/>
{{-- 每页数量 --}}
<x-filament::input
type="number"
wire:model.live="perPage"
min="10"
max="100"
step="5"
/>
点击事件:
{{-- AI 生成题目按钮 --}}
<x-filament::button
icon="heroicon-m-sparkles"
color="success"
wire:click="$dispatch('ai-generate')"
>
AI 生成题目
</x-filament::button>
{{-- 刷新按钮 --}}
<x-filament::button
icon="heroicon-m-arrow-path"
color="warning"
wire:click="$dispatch('refresh-data')"
>
刷新
</x-filament::button>
{{-- 上一页 --}}
<button
type="button"
wire:click="previousPage"
@disabled($this->currentPage <= 1)
>
上一页
</button>
{{-- 下一页 --}}
<button
type="button"
wire:click="nextPage"
@disabled($this->currentPage >= ($this->meta['total_pages'] ?? 1))
>
下一页
</button>
加载状态:
{{-- 表格加载状态 --}}
<div class="overflow-x-auto" wire:loading.class="opacity-50">
<!-- 表格内容 -->
</div>
{{-- 加载指示器 --}}
<div wire:loading class="fixed top-4 right-4 bg-primary-600 text-white px-4 py-2 rounded-lg shadow-lg flex items-center gap-2 z-50">
<div class="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></div>
<span>加载中...</span>
</div>
{{-- 调用 compute 属性 --}}
{{ $this->questions }}
{{-- 统计数据 --}}
{{ $this->statistics['total'] ?? 0 }}
{{ $this->statistics['by_difficulty']['0.3'] ?? 0 }}
{{-- 分页信息 --}}
{{ $this->meta['total'] ?? 0 }}
{{ $this->meta['total_pages'] ?? 0 }}
{{-- 调用方法 --}}
@foreach($this->getPages() as $page)
{{ $page }}
@endforeach
{{-- 页面容器 --}}
<x-filament-pages::page>
{{-- 区块组件 --}}
<x-filament::section>
<!-- 内容 -->
</x-filament::section>
{{-- 输入框组件 --}}
<x-filament::input.wrapper>
<x-filament::input type="text" />
</x-filament::input.wrapper>
{{-- 按钮组件 --}}
<x-filament::button color="success" icon="heroicon-m-sparkles">
AI 生成题目
</x-filament::button>
public int $currentPage = 1;
public int $perPage = 25;
/**
* 跳转到指定页
*/
public function gotoPage(int $page): void
{
$this->currentPage = $page;
}
/**
* 上一页
*/
public function previousPage(): void
{
if ($this->currentPage > 1) {
$this->currentPage--;
}
}
/**
* 下一页
*/
public function nextPage(): void
{
if ($this->currentPage < ($this->meta['total_pages'] ?? 1)) {
$this->currentPage++;
}
}
/**
* 获取页码数组(用于分页器)
*/
public function getPages(): array
{
$totalPages = $this->meta['total_pages'] ?? 1;
$currentPage = $this->currentPage;
$pages = [];
$start = max(1, $currentPage - 2);
$end = min($totalPages, $currentPage + 2);
for ($i = $start; $i <= $end; $i++) {
$pages[] = $i;
}
return $pages;
}
{{-- 分页按钮 --}}
@foreach($this->getPages() as $page)
<button
type="button"
class="px-3 py-1 text-sm border rounded {{ $page === $this->currentPage ? 'bg-primary-50 text-primary-700 border-primary-300' : 'hover:bg-gray-50' }}"
wire:click="gotoPage({{ $page }})"
>
{{ $page }}
</button>
@endforeach
class QuestionServiceApi
{
public function __construct(
protected string $baseUrl = '',
protected int $timeout = 10,
protected int $cacheTtl = 300,
) {
// 初始化
}
/**
* 获取所有题目(分页)
*/
public function listQuestions(int $page = 1, int $perPage = 50, array $filters = []): array
{
// 实现
}
/**
* 语义搜索题目
*/
public function searchQuestions(string $query, int $limit = 20): array
{
// 实现
}
/**
* 获取题目统计信息
*/
public function getStatistics(): array
{
// 实现
}
}
try {
$response = $this->request('GET', '/questions', $query);
return $response;
} catch (\Exception $e) {
\Log::error('Failed to fetch questions: ' . $e->getMessage());
return ['data' => [], 'meta' => []];
}
return Cache::remember(
$cacheKey,
now()->addSeconds($this->cacheTtl),
function () use ($page, $perPage, $filters): array {
// API 调用逻辑
}
);
✅ 正确的类型声明
string|BackedEnum|nullstring|UnitEnum|null?string✅ Livewire 3 特性
#[On])updated* 方法)wire:model)wire:click)✅ Filament 组件
$dispatch)✅ 分页实现
✅ 性能优化
✅ 错误处理
#[On] 装饰器处理事件wire:model.live 实现双向绑定wire:click 处理点击事件wire:loading 显示加载状态cd /Volumes/T9/code/math/apis/KnowledgeServic && docker compose up -d
cd /Volumes/T9/code/math/apis/QuestionBankService && docker compose up -d
cd /Volumes/T9/code/math/apis/FilamentAdmin && herd serve
URL: http://filament-admin.test/admin
导航:题库系统 → 题库管理
严格按照 Filament 3 和 Livewire 3 规范开发的 QuestionManagement 功能现已完全符合标准,包括:
可以立即使用!
创建日期:2025-11-15 版本:v2.0 状态:✅ 完全符合规范