Files
KnowledgeBase/app/Filament/Pages/SearchPage.php
Knowledge Base System acf549c43c feat: 初始化知识库系统项目
- 实现基于 Laravel 11 和 Filament 3.X 的文档管理系统
- 添加用户认证和分组管理功能
- 实现文档上传、分类和权限控制
- 集成 Word 文档自动转换为 Markdown
- 集成 Meilisearch 全文搜索引擎
- 实现文档在线预览功能
- 添加安全日志和审计功能
- 完整的简体中文界面
- 包含完整的项目文档和部署指南

技术栈:
- Laravel 11.x
- Filament 3.X
- Meilisearch 1.5+
- Pandoc 文档转换
- Redis 队列系统
- Pest PHP 测试框架
2025-12-05 14:44:44 +08:00

282 lines
8.6 KiB
PHP

<?php
namespace App\Filament\Pages;
use App\Models\Document;
use App\Models\Group;
use App\Services\DocumentSearchService;
use App\Services\DocumentService;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class SearchPage extends Page implements HasForms, HasTable
{
use InteractsWithForms;
use InteractsWithTable;
protected static ?string $navigationIcon = 'heroicon-o-magnifying-glass';
protected static string $view = 'filament.pages.search-page';
protected static ?string $navigationLabel = '搜索文档';
protected static ?string $title = '搜索文档';
protected static ?int $navigationSort = 2;
// 表单数据
public ?string $searchQuery = null;
public ?string $documentType = null;
public ?int $groupId = null;
// 搜索结果
public $searchResults = null;
public bool $hasSearched = false;
/**
* 挂载页面时的初始化
*/
public function mount(): void
{
$this->form->fill([
'searchQuery' => '',
'documentType' => null,
'groupId' => null,
]);
}
/**
* 定义搜索表单
*/
public function form(Form $form): Form
{
return $form
->schema([
TextInput::make('searchQuery')
->label('搜索关键词')
->placeholder('请输入搜索关键词...')
->required()
->maxLength(255),
Select::make('documentType')
->label('文档类型')
->placeholder('全部类型')
->options([
'global' => '全局知识库',
'dedicated' => '专用知识库',
])
->native(false),
Select::make('groupId')
->label('所属分组')
->placeholder('全部分组')
->options(Group::pluck('name', 'id'))
->searchable()
->native(false),
])
->columns(3);
}
/**
* 定义搜索结果表格
*/
public function table(Table $table): Table
{
return $table
->query($this->getTableQuery())
->columns([
TextColumn::make('title')
->label('文档标题')
->searchable()
->sortable()
->limit(50),
TextColumn::make('markdown_preview')
->label('内容片段')
->limit(100)
->wrap()
->default('暂无内容预览'),
TextColumn::make('type')
->label('文档类型')
->badge()
->formatStateUsing(fn (string $state): string => match ($state) {
'global' => '全局知识库',
'dedicated' => '专用知识库',
default => $state,
})
->color(fn (string $state): string => match ($state) {
'global' => 'success',
'dedicated' => 'info',
default => 'gray',
}),
TextColumn::make('group.name')
->label('所属分组')
->default('无')
->sortable(),
TextColumn::make('created_at')
->label('上传时间')
->dateTime('Y-m-d H:i')
->sortable(),
])
->actions([
Action::make('preview')
->label('预览')
->icon('heroicon-o-eye')
->color('info')
->modalHeading(fn (Document $record) => $record->title)
->modalContent(fn (Document $record) => view('filament.pages.document-preview-modal', [
'document' => $record,
]))
->modalWidth('7xl')
->modalSubmitAction(false)
->modalCancelActionLabel('关闭')
->visible(fn (Document $record) => $record->conversion_status === 'completed'),
Action::make('download')
->label('下载')
->icon('heroicon-o-arrow-down-tray')
->action(function (Document $record) {
try {
$documentService = app(DocumentService::class);
$user = Auth::user();
// 记录下载日志
$documentService->logDownload($record, $user);
// 返回文件下载响应
return $documentService->downloadDocument($record, $user);
} catch (\Exception $e) {
Notification::make()
->title('下载失败')
->body($e->getMessage())
->danger()
->send();
}
}),
])
->paginated([10, 25, 50, 100])
->defaultPaginationPageOption(25)
->emptyStateHeading('暂无搜索结果')
->emptyStateDescription('请输入搜索关键词并点击搜索按钮')
->emptyStateIcon('heroicon-o-magnifying-glass');
}
/**
* 获取表格查询构建器
*/
protected function getTableQuery(): Builder
{
if (!$this->hasSearched || empty($this->searchQuery)) {
// 如果还没有搜索或搜索关键词为空,返回空查询
return Document::query()->whereRaw('1 = 0');
}
// 使用 DocumentSearchService 进行搜索
$searchService = app(DocumentSearchService::class);
$user = Auth::user();
$filters = [];
if ($this->documentType) {
$filters['type'] = $this->documentType;
}
if ($this->groupId) {
$filters['group_id'] = $this->groupId;
}
// 执行搜索
$results = $searchService->search($this->searchQuery, $user, $filters);
// 获取搜索结果的 ID 列表
$documentIds = $results->pluck('id')->toArray();
// 返回包含这些 ID 的查询构建器
if (empty($documentIds)) {
return Document::query()->whereRaw('1 = 0');
}
return Document::query()
->whereIn('id', $documentIds)
->with(['group', 'uploader']);
}
/**
* 执行搜索
*/
public function search(): void
{
// 验证表单
$data = $this->form->getState();
// 检查搜索关键词是否为空
if (empty($data['searchQuery'])) {
Notification::make()
->title('请输入搜索关键词')
->warning()
->send();
return;
}
// 更新搜索参数
$this->searchQuery = $data['searchQuery'];
$this->documentType = $data['documentType'];
$this->groupId = $data['groupId'];
$this->hasSearched = true;
// 重置表格分页
$this->resetTable();
Notification::make()
->title('搜索完成')
->success()
->send();
}
/**
* 清空搜索
*/
public function clearSearch(): void
{
$this->form->fill([
'searchQuery' => '',
'documentType' => null,
'groupId' => null,
]);
$this->searchQuery = null;
$this->documentType = null;
$this->groupId = null;
$this->hasSearched = false;
$this->resetTable();
Notification::make()
->title('已清空搜索')
->success()
->send();
}
/**
* 获取页面头部操作
*/
protected function getHeaderActions(): array
{
return [];
}
}