schema([ Select::make('series_id') ->label('教材系列') ->options(function () { $series = static::getApiService()->getTextbookSeries(); $options = []; foreach ($series['data'] as $s) { $displayName = $s['name']; if (!empty($s['publisher'])) { $displayName .= ' (' . $s['publisher'] . ')'; } $options[$s['id']] = $displayName; } return $options; }) ->required() ->searchable() ->preload(), Select::make('stage') ->label('学段') ->options([ 'primary' => '小学', 'junior' => '初中', 'senior' => '高中', ]) ->default('junior') ->required() ->reactive(), Select::make('schooling_system') ->label('学制') ->options([ '63' => '六三学制', '54' => '五四学制', ]) ->default('63') ->visible(fn ($get): bool => in_array($get('stage'), ['primary', 'junior'])), TextInput::make('grade') ->label('年级') ->numeric() ->helperText('小学1-6、初中7-9、高中10-12'), Select::make('semester') ->label('学期') ->options([ 1 => '上册', 2 => '下册', ]) ->visible(fn ($get): bool => in_array($get('stage'), ['primary', 'junior'])), Select::make('naming_scheme') ->label('命名体系') ->options([ 'new' => '新体系', 'old' => '旧体系', ]) ->visible(fn ($get): bool => $get('stage') === 'senior') ->reactive(), Select::make('track') ->label('版本') ->options([ 'A' => 'A版', 'B' => 'B版', ]) ->visible(fn ($get): bool => $get('stage') === 'senior' && $get('naming_scheme') === 'new'), Select::make('module_type') ->label('模块类型') ->options([ 'compulsory' => '必修', 'selective_compulsory' => '选择性必修', 'elective' => '选修', ]) ->visible(fn ($get): bool => $get('stage') === 'senior'), TextInput::make('volume_no') ->label('册次') ->numeric() ->helperText('如:1、2、3') ->visible(fn ($get): bool => $get('stage') === 'senior' && $get('naming_scheme') === 'new'), TextInput::make('legacy_code') ->label('旧体系编码') ->placeholder('如:必修1、选修1-1') ->visible(fn ($get): bool => $get('stage') === 'senior' && $get('naming_scheme') === 'old'), TextInput::make('curriculum_standard_year') ->label('课标年代') ->numeric() ->helperText('义务教育:2011/2022,高中:2017'), TextInput::make('curriculum_revision_year') ->label('修订年份') ->numeric() ->helperText('高中:2020'), TextInput::make('approval_year') ->label('审定年份') ->numeric() ->helperText('如:2024'), TextInput::make('edition_label') ->label('版次标识') ->placeholder('如:2024秋版、修订版'), TextInput::make('isbn') ->label('ISBN') ->maxLength(32), FileUpload::make('cover_path') ->label('封面图片') ->image() ->imageEditor() ->imageResizeTargetWidth(400) ->imageResizeTargetHeight(600) ->imageCropAspectRatio('2:3') ->imagePreviewHeight('200') ->directory('textbook-covers') ->maxSize(5120) // 5MB ->hint('支持 JPG、PNG、WebP 格式,最大 5MB') ->preserveFilenames() ->multiple(false) ->downloadable(false) ->afterStateHydrated(function ($component, $state) { // 确保状态是字符串或 null,而不是数组 if (is_array($state)) { $component->state(!empty($state) ? $state[0] : null); } }), TextInput::make('official_title') ->label('官方书名') ->maxLength(512) ->helperText('自动生成,可手动覆盖'), TextInput::make('display_title') ->label('展示名称') ->maxLength(512) ->helperText('站内显示名称'), Textarea::make('aliases') ->label('别名') ->helperText('JSON 格式,如:["别名1", "别名2"]') ->formatStateUsing(fn ($state) => is_array($state) ? json_encode($state, JSON_UNESCAPED_UNICODE) : $state) ->dehydrateStateUsing(fn ($state) => is_string($state) ? json_decode($state, true) : $state) ->columnSpanFull(), Select::make('status') ->label('状态') ->options([ 'draft' => '草稿', 'published' => '已发布', 'archived' => '已归档', ]) ->default('draft') ->required(), Textarea::make('meta') ->label('扩展信息') ->placeholder('JSON 格式') ->formatStateUsing(fn ($state) => is_array($state) ? json_encode($state, JSON_UNESCAPED_UNICODE) : $state) ->dehydrateStateUsing(fn ($state) => is_string($state) ? json_decode($state, true) : $state) ->columnSpanFull(), ]); } public static function table(Tables\Table $table): Tables\Table { return $table ->columns([ ImageColumn::make('cover_path') ->label('封面') ->square() ->defaultImageUrl(url('/images/no-image.png')) ->disk('public') ->size(60), TextColumn::make('series.name') ->label('系列') ->searchable() ->sortable(), BadgeColumn::make('stage') ->label('学段') ->formatStateUsing(function ($state): string { // 确保返回字符串,即使输入是数组 $state = is_array($state) ? ($state[0] ?? '') : $state; return match ((string) $state) { 'primary' => '小学', 'junior' => '初中', 'senior' => '高中', default => (string) $state, }; }) ->color('success'), TextColumn::make('grade') ->label('年级') ->sortable(), TextColumn::make('semester') ->label('学期') ->formatStateUsing(function ($state): string { // 确保返回字符串,即使输入是数组 $state = is_array($state) ? ($state[0] ?? null) : $state; return match ((int) $state) { 1 => '上册', 2 => '下册', default => '', }; }), TextColumn::make('official_title') ->label('官方书名') ->searchable() ->wrap(), BadgeColumn::make('status') ->label('状态') ->formatStateUsing(function ($state): string { // 确保返回字符串,即使输入是数组 $state = is_array($state) ? ($state[0] ?? '') : $state; return match ((string) $state) { 'draft' => '草稿', 'published' => '已发布', 'archived' => '已归档', default => (string) $state, }; }) ->color(function ($state): string { // 确保返回字符串,即使输入是数组 $state = is_array($state) ? ($state[0] ?? '') : $state; return match ((string) $state) { 'draft' => 'warning', 'published' => 'success', 'archived' => 'gray', default => 'gray', }; }), TextColumn::make('approval_year') ->label('审定年份') ->sortable(), TextColumn::make('created_at') ->label('创建时间') ->dateTime('Y-m-d H:i') ->sortable() ->toggleable(), ]) ->filters([ Tables\Filters\SelectFilter::make('stage') ->label('学段') ->options([ 'primary' => '小学', 'junior' => '初中', 'senior' => '高中', ]) ->query(function ($query, $data) { if ($data['value']) { // API 过滤 return $query; } }), Tables\Filters\SelectFilter::make('status') ->label('状态') ->options([ 'draft' => '草稿', 'published' => '已发布', 'archived' => '已归档', ]) ->query(function ($query, $data) { if ($data['value']) { // API 过滤 return $query; } }), ]) ->actions([ EditAction::make() ->label('编辑'), DeleteAction::make() ->label('删除'), Action::make('view_catalog') ->label('查看目录') ->icon('heroicon-o-list-bullet') ->url(fn(Model $record): string => route('filament.admin.resources.textbook-catalogs.index', ['tableFilters[textbook_id][value]' => $record->id]) ), ]) ->bulkActions([ \Filament\Actions\BulkActionGroup::make([ \Filament\Actions\DeleteBulkAction::make() ->label('批量删除'), ]), ]) ->defaultSort('id', 'desc') ->paginated([10, 25, 50, 100]) ->poll('30s'); } public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder { // 返回空查询,实际数据通过 API 获取 return parent::getEloquentQuery()->whereRaw('1=0'); } public static function resolveRecordRouteBinding(int | string $key, ?\Closure $modifyQuery = null): ?\Illuminate\Database\Eloquent\Model { $record = static::getApiService()->getTextbook((int) $key); if (!$record) { return null; } $model = new \App\Models\Textbook($record); $model->exists = true; $model->id = $record['id']; return $model; } public static function getPages(): array { return [ 'index' => Pages\ManageTextbooks::route('/'), 'create' => Pages\CreateTextbook::route('/create'), 'edit' => Pages\EditTextbook::route('/{record}/edit'), ]; } public static function canViewAny(): bool { // 临时允许所有用户查看,等待权限系统完善 return true; } public static function getHeaderActions(): array { return [ \Filament\Actions\Action::make('import_excel') ->label('Excel导入') ->icon('heroicon-o-document-arrow-up') ->color('success') ->url(fn(): string => route('filament.admin.pages.textbook-excel-import-page') ), ]; } public static function canCreate(): bool { // 临时允许所有用户创建,等待权限系统完善 return true; } public static function canEdit(Model $record): bool { // 临时允许所有用户编辑,等待权限系统完善 return true; } public static function canDelete(Model $record): bool { // 临时允许所有用户删除,等待权限系统完善 return true; } public static function canDeleteAny(): bool { // 临时允许所有用户批量删除,等待权限系统完善 return true; } }