Filament-Livewire 规范开发说明.md 9.8 KB

Filament + Livewire 规范开发说明

✅ 已按照规范完成的修改

1. PHP 类型声明(QuestionManagement.php)

✅ 正确的类型注解

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 = '题库系统';

2. Livewire 3 特性使用

✅ Compute 属性(替代原有属性)

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;
}

3. 视图层规范(question-management.blade.php)

✅ Livewire 指令使用

双向绑定

{{-- 搜索框 --}}
<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 属性调用

{{-- 调用 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

4. Filament 组件使用

✅ 正确的组件调用

{{-- 页面容器 --}}
<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>

5. 分页实现规范

✅ 分页属性

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

6. API 客户端规范

✅ 服务类设计

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 调用逻辑
    }
);

🎯 最佳实践总结

✅ 已实现的功能

  1. ✅ 正确的类型声明

    • navigationIcon: string|BackedEnum|null
    • navigationGroup: string|UnitEnum|null
    • navigationLabel: ?string
  2. ✅ Livewire 3 特性

    • Compute 属性(替代原有属性)
    • 事件监听器(#[On]
    • 响应式更新(updated* 方法)
    • 双向绑定(wire:model
    • 点击事件(wire:click
  3. ✅ Filament 组件

    • 使用官方组件
    • 正确的事件分发($dispatch
    • 加载状态指示
  4. ✅ 分页实现

    • 自定义分页逻辑
    • 上一页/下一页
    • 页码跳转
    • 动态页码显示
  5. ✅ 性能优化

    • 缓存机制
    • 防抖搜索(300ms)
    • 计算属性缓存
  6. ✅ 错误处理

    • 异常捕获
    • 日志记录
    • 用户友好提示

📋 编码规范检查清单

  • 使用正确的类型注解
  • 使用 Livewire 3 特性
  • 使用 Compute 属性替代原有属性
  • 使用 #[On] 装饰器处理事件
  • 使用 wire:model.live 实现双向绑定
  • 使用 wire:click 处理点击事件
  • 使用 wire:loading 显示加载状态
  • 使用 Filament 官方组件
  • 实现防抖搜索
  • 添加错误处理
  • 添加日志记录
  • 使用缓存优化性能
  • 遵循 PSR-12 编码规范
  • 添加 PHPDoc 注释

🚀 使用方法

启动服务

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
导航:题库系统 → 题库管理

功能说明

  1. 实时搜索:输入关键词即时筛选结果
  2. 多维筛选:按知识点、难度、每页数量筛选
  3. 分页浏览:支持上一页/下一页/页码跳转
  4. 统计展示:显示题目总数和各难度分布
  5. 加载状态:实时显示数据加载进度
  6. 操作按钮:AI 生成、刷新、搜索等功能

📚 参考资料

✨ 总结

严格按照 Filament 3 和 Livewire 3 规范开发的 QuestionManagement 功能现已完全符合标准,包括:

  1. ✅ 正确的 PHP 类型声明
  2. ✅ 规范的 Livewire 特性使用
  3. ✅ 完善的交互体验
  4. ✅ 高效的性能表现
  5. ✅ 清晰的代码结构

可以立即使用!


创建日期:2025-11-15 版本:v2.0 状态:✅ 完全符合规范