import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import '../../models/prompt_models.dart'; import '../../services/api_service/repositories/impl/admin_repository_impl.dart'; import '../../utils/logger.dart'; import '../../utils/web_theme.dart'; import '../../widgets/common/error_view.dart'; import '../../widgets/common/loading_indicator.dart'; import '../../widgets/common/master_detail_split_view.dart'; import 'widgets/enhanced_template_card.dart'; import 'widgets/template_review_dialog.dart'; import 'widgets/template_details_dialog.dart'; import 'widgets/batch_operation_dialog.dart'; import 'widgets/enhanced_template_editor.dart'; /// 增强提示词模板管理页面 /// 基于 EnhancedUserPromptTemplate 的统一管理 class EnhancedTemplatesManagementScreen extends StatefulWidget { const EnhancedTemplatesManagementScreen({Key? key}) : super(key: key); @override State createState() => _EnhancedTemplatesManagementScreenState(); } /// 增强模板管理内容主体,可以在不同布局中复用 class EnhancedTemplatesManagementBody extends StatefulWidget { const EnhancedTemplatesManagementBody({Key? key}) : super(key: key); @override State createState() => _EnhancedTemplatesManagementBodyState(); } class _EnhancedTemplatesManagementScreenState extends State with TickerProviderStateMixin { final GlobalKey<_EnhancedTemplatesManagementBodyState> _bodyKey = GlobalKey<_EnhancedTemplatesManagementBodyState>(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: WebTheme.getBackgroundColor(context), foregroundColor: WebTheme.getTextColor(context), title: Text( '公共模板管理', style: TextStyle(color: WebTheme.getTextColor(context)), ), actions: [ IconButton( onPressed: () => _bodyKey.currentState?._refreshData(), icon: Icon(Icons.refresh, color: WebTheme.getTextColor(context)), tooltip: '刷新', ), IconButton( onPressed: () => _bodyKey.currentState?._showStatistics(), icon: Icon(Icons.analytics, color: WebTheme.getTextColor(context)), tooltip: '统计信息', ), IconButton( onPressed: () => _bodyKey.currentState?._startCreate(), icon: Icon(Icons.add, color: WebTheme.getTextColor(context)), tooltip: '添加官方模板', ), ], ), backgroundColor: WebTheme.getBackgroundColor(context), body: Align( alignment: Alignment.topCenter, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 1600), child: EnhancedTemplatesManagementBody(key: _bodyKey), ), ), floatingActionButton: Column( mainAxisSize: MainAxisSize.min, children: [ FloatingActionButton( heroTag: "refresh", onPressed: () => _bodyKey.currentState?._refreshData(), tooltip: '刷新数据', child: const Icon(Icons.refresh), ), const SizedBox(height: 12), FloatingActionButton( heroTag: "add", onPressed: () => _bodyKey.currentState?._startCreate(), tooltip: '添加官方模板', child: const Icon(Icons.add), ), ], ), ); } } class _EnhancedTemplatesManagementBodyState extends State with TickerProviderStateMixin { List _templates = []; Map> _templatesByStatus = {}; Map _statistics = {}; bool _isLoading = true; String? _error; String _selectedTab = 'ALL'; List _selectedTemplates = []; bool _batchMode = false; String _searchKeyword = ''; String? _filterFeatureType; bool? _filterVerified; String _sortOption = 'LATEST'; // LATEST | MOST_USED | RATING int _pageSize = 30; int _currentPage = 1; // 右侧编辑器状态 EnhancedUserPromptTemplate? _selectedTemplate; // 选中用于编辑的模板 bool _isCreating = false; // 创建模式 late TabController _tabController; final AdminRepositoryImpl _adminRepository = AdminRepositoryImpl(); static const List _tabs = ['ALL', 'VERIFIED', 'PENDING', 'POPULAR', 'LATEST']; static const Map _tabLabels = { 'ALL': '全部模板', 'VERIFIED': '已认证', 'PENDING': '待审核', 'POPULAR': '热门模板', 'LATEST': '最新模板', }; @override void initState() { super.initState(); _tabController = TabController(length: _tabs.length, vsync: this); _tabController.addListener(_onTabChanged); _loadData(); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _onTabChanged() { if (_tabController.index >= 0 && _tabController.index < _tabs.length) { final newTab = _tabs[_tabController.index]; if (newTab != _selectedTab) { setState(() { _selectedTab = newTab; _batchMode = false; _selectedTemplates.clear(); }); _loadTemplates(); } } } Future _loadData() async { setState(() { _isLoading = true; _error = null; }); try { await Future.wait([ _loadTemplates(), _loadStatistics(), ]); } catch (e) { AppLogger.e('EnhancedTemplatesManagement', '加载增强模板数据失败', e); setState(() { _error = e.toString(); }); } finally { setState(() { _isLoading = false; }); } } Future _loadTemplates() async { try { List templates; switch (_selectedTab) { case 'VERIFIED': templates = await _adminRepository.getVerifiedEnhancedTemplates(); break; case 'PENDING': templates = await _adminRepository.getPendingEnhancedTemplates(); break; case 'POPULAR': templates = await _adminRepository.getPopularEnhancedTemplates( featureType: _filterFeatureType, limit: 20, ); break; case 'LATEST': templates = await _adminRepository.getLatestEnhancedTemplates( featureType: _filterFeatureType, limit: 20, ); break; default: templates = await _adminRepository.getAllPublicEnhancedTemplates( featureType: _filterFeatureType, ); break; } // 应用搜索过滤 if (_searchKeyword.isNotEmpty) { templates = templates.where((template) { final name = template.name.toLowerCase(); final description = (template.description ?? '').toLowerCase(); final keyword = _searchKeyword.toLowerCase(); return name.contains(keyword) || description.contains(keyword); }).toList(); } // 应用验证状态过滤 if (_filterVerified != null) { templates = templates.where((template) => template.isVerified == _filterVerified).toList(); } setState(() { _templates = templates; _templatesByStatus = _groupTemplatesByStatus(templates); }); } catch (e) { AppLogger.e('EnhancedTemplatesManagement', '加载增强模板失败', e); rethrow; } } Future _loadStatistics() async { try { final stats = await _adminRepository.getEnhancedTemplatesStatistics(); setState(() { _statistics = stats; }); } catch (e) { AppLogger.e('EnhancedTemplatesManagement', '加载统计信息失败', e); // 统计信息加载失败不影响主要功能 } } Map> _groupTemplatesByStatus( List templates) { return groupBy(templates, (template) { if (template.isVerified == true) return 'VERIFIED'; if (template.isPublic == true && template.isVerified != true) return 'PENDING'; return 'PRIVATE'; }); } void _refreshData() { _loadData(); } // 局部乐观更新:用后端返回的模板替换列表中的同ID项,并更新分组 void _applyLocalUpdate(EnhancedUserPromptTemplate updated) { setState(() { _templates = _templates.map((t) => t.id == updated.id ? updated : t).toList(); _templatesByStatus = _groupTemplatesByStatus(_templates); }); } void _showStatistics() { showDialog( context: context, builder: (context) => _StatisticsDialog(statistics: _statistics), ); } @override Widget build(BuildContext context) { if (_isLoading) { return const Center(child: LoadingIndicator()); } if (_error != null) { return ErrorView( error: _error!, onRetry: _refreshData, ); } return Column( children: [ _buildToolbar(), _buildFilterBar(), _buildTabs(), Expanded( child: MasterDetailSplitView( master: _buildTemplatesList(), detail: _buildRightDetailPane(), masterFlex: 2, detailFlex: 4, ), ), ], ); } Widget _buildToolbar() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: WebTheme.getCardColor(context), border: Border( bottom: BorderSide( color: WebTheme.getBorderColor(context), width: 1, ), ), ), child: Row( children: [ if (_batchMode) ...[ Expanded( child: Text( '已选择 ${_selectedTemplates.length} 个模板', style: TextStyle( color: WebTheme.getTextColor(context), fontWeight: FontWeight.w500, ), ), ), IconButton( onPressed: _selectedTemplates.isNotEmpty ? () => _batchReview(true) : null, icon: const Icon(Icons.check_circle), tooltip: '批量审核通过', ), IconButton( onPressed: _selectedTemplates.isNotEmpty ? () => _batchReview(false) : null, icon: const Icon(Icons.cancel), tooltip: '批量审核拒绝', ), IconButton( onPressed: _selectedTemplates.isNotEmpty ? () => _batchSetVerified(true) : null, icon: const Icon(Icons.verified), tooltip: '批量设为认证', ), IconButton( onPressed: _selectedTemplates.isNotEmpty ? () => _batchPublish(true) : null, icon: const Icon(Icons.public), tooltip: '批量发布', ), IconButton( onPressed: _selectedTemplates.isNotEmpty ? _batchExport : null, icon: const Icon(Icons.file_download), tooltip: '导出选中模板', ), IconButton( onPressed: () { setState(() { _batchMode = false; _selectedTemplates.clear(); }); }, icon: const Icon(Icons.close), tooltip: '退出批量模式', ), ] else ...[ Expanded( child: Text( '公共模板总数: ${_templates.length}', style: TextStyle( color: WebTheme.getTextColor(context), fontWeight: FontWeight.w500, ), ), ), IconButton( onPressed: _importTemplates, icon: const Icon(Icons.file_upload), tooltip: '导入模板', ), IconButton( onPressed: () { setState(() { _batchMode = true; }); }, icon: const Icon(Icons.checklist), tooltip: '批量操作', ), ], ], ), ); } Widget _buildFilterBar() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: WebTheme.getCardColor(context), border: Border( bottom: BorderSide( color: WebTheme.getBorderColor(context), width: 1, ), ), ), child: Row( children: [ // 搜索框 Expanded( flex: 2, child: TextField( decoration: InputDecoration( hintText: '搜索模板名称或描述...', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), onChanged: (value) { setState(() { _searchKeyword = value; _currentPage = 1; }); Future.delayed(const Duration(milliseconds: 400), () { if (_searchKeyword == value) { _loadTemplates(); } }); }, ), ), const SizedBox(width: 16), // 功能类型过滤 Expanded( child: DropdownButtonFormField( value: _filterFeatureType, decoration: InputDecoration( labelText: '功能类型', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), items: const [ DropdownMenuItem(value: null, child: Text('全部功能')), DropdownMenuItem(value: 'AI_CHAT', child: Text('AI聊天')), DropdownMenuItem(value: 'TEXT_EXPANSION', child: Text('文本扩写')), DropdownMenuItem(value: 'TEXT_REFACTOR', child: Text('文本润色')), DropdownMenuItem(value: 'TEXT_SUMMARY', child: Text('文本总结')), DropdownMenuItem(value: 'PROFESSIONAL_FICTION_CONTINUATION', child: Text('专业续写')), DropdownMenuItem(value: 'SCENE_BEAT_GENERATION', child: Text('场景节拍生成')), DropdownMenuItem(value: 'NOVEL_COMPOSE', child: Text('设定编排')), DropdownMenuItem(value: 'SETTING_TREE_GENERATION', child: Text('设定树生成')), ], onChanged: (value) { setState(() { _filterFeatureType = value; _currentPage = 1; }); _loadTemplates(); }, ), ), const SizedBox(width: 16), // 验证状态过滤 Expanded( child: DropdownButtonFormField( value: _filterVerified, decoration: InputDecoration( labelText: '认证状态', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), items: const [ DropdownMenuItem(value: null, child: Text('全部状态')), DropdownMenuItem(value: true, child: Text('已认证')), DropdownMenuItem(value: false, child: Text('未认证')), ], onChanged: (value) { setState(() { _filterVerified = value; _currentPage = 1; }); _loadTemplates(); }, ), ), const SizedBox(width: 16), // 排序 Expanded( child: DropdownButtonFormField( value: _sortOption, decoration: InputDecoration( labelText: '排序', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), items: const [ DropdownMenuItem(value: 'LATEST', child: Text('最新')), DropdownMenuItem(value: 'MOST_USED', child: Text('使用最多')), DropdownMenuItem(value: 'RATING', child: Text('评分最高')), ], onChanged: (value) { setState(() { _sortOption = value ?? 'LATEST'; _currentPage = 1; }); }, ), ), ], ), ); } Widget _buildTabs() { return Container( decoration: BoxDecoration( color: WebTheme.getCardColor(context), border: Border( bottom: BorderSide( color: WebTheme.getBorderColor(context), width: 1, ), ), ), child: TabBar( controller: _tabController, tabs: _tabs.map((tab) { final count = _getTabCount(tab); return Tab( text: '${_tabLabels[tab]} ($count)', ); }).toList(), isScrollable: true, labelColor: WebTheme.getTextColor(context), unselectedLabelColor: WebTheme.getTextColor(context).withOpacity(0.6), ), ); } int _getTabCount(String tab) { switch (tab) { case 'VERIFIED': return _templatesByStatus['VERIFIED']?.length ?? 0; case 'PENDING': return _templatesByStatus['PENDING']?.length ?? 0; default: return _templates.length; } } Widget _buildTemplatesList() { if (_templates.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.article_outlined, size: 64, color: WebTheme.getTextColor(context).withOpacity(0.5), ), const SizedBox(height: 16), Text( '暂无模板', style: TextStyle( color: WebTheme.getTextColor(context).withOpacity(0.7), fontSize: 16, ), ), const SizedBox(height: 8), Text( '点击右上角的加号创建第一个模板', style: TextStyle( color: WebTheme.getTextColor(context).withOpacity(0.5), fontSize: 14, ), ), ], ), ); } // 本地排序 final List sorted = List.of(_templates); switch (_sortOption) { case 'MOST_USED': sorted.sort((a, b) => (b.usageCount).compareTo(a.usageCount)); break; case 'RATING': sorted.sort((a, b) => (b.rating).compareTo(a.rating)); break; case 'LATEST': default: sorted.sort((a, b) => b.updatedAt.compareTo(a.updatedAt)); break; } // 本地分页 final visibleCount = (_currentPage * _pageSize).clamp(0, sorted.length); final items = sorted.take(visibleCount).toList(); return Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Expanded( child: ListView.builder( itemCount: items.length, itemBuilder: (context, index) { final template = items[index]; return EnhancedTemplateCard( template: template, isSelected: _selectedTemplates.contains(template.id), batchMode: _batchMode, onTap: () => _handleTemplateTap(template), onEdit: () => _openEditor(template), onDelete: () => _deleteTemplate(template), onReview: () => _reviewTemplate(template), onToggleVerified: () => _toggleVerified(template), onTogglePublish: () => _togglePublish(template), onViewStats: () => _viewTemplateStats(template), onViewDetails: () => _viewTemplateDetails(template), onDuplicate: () => _duplicateTemplate(template), onSelectionChanged: (selected) { setState(() { if (selected) { _selectedTemplates.add(template.id); } else { _selectedTemplates.remove(template.id); } }); }, ); }, ), ), if (visibleCount < sorted.length) Padding( padding: const EdgeInsets.only(top: 12), child: OutlinedButton.icon( onPressed: () { setState(() { _currentPage += 1; }); }, icon: const Icon(Icons.expand_more), label: Text('加载更多(${sorted.length - visibleCount})'), ), ), ], ), ); } void _handleTemplateTap(EnhancedUserPromptTemplate template) { if (_batchMode) { final isSelected = _selectedTemplates.contains(template.id); setState(() { if (isSelected) { _selectedTemplates.remove(template.id); } else { _selectedTemplates.add(template.id); } }); } else { _openEditor(template); } } void _openEditor(EnhancedUserPromptTemplate template) { setState(() { _selectedTemplate = template; _isCreating = false; }); } Future _duplicateTemplate(EnhancedUserPromptTemplate template) async { final controller = TextEditingController(text: '${template.name} (复制)'); final newName = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('复制模板'), content: TextField( controller: controller, decoration: const InputDecoration( labelText: '新模板名称', border: OutlineInputBorder(), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(null), child: const Text('取消'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(controller.text.trim()), child: const Text('确定'), ), ], ), ); if (newName == null || newName.isEmpty) return; try { final now = DateTime.now(); final duplicate = EnhancedUserPromptTemplate( id: '', userId: template.userId, name: newName, description: template.description, featureType: template.featureType, systemPrompt: template.systemPrompt, userPrompt: template.userPrompt, tags: List.from(template.tags), categories: List.from(template.categories), isPublic: true, shareCode: null, isFavorite: false, isDefault: false, usageCount: 0, rating: 0, ratingCount: 0, createdAt: now, updatedAt: now, lastUsedAt: null, isVerified: template.isVerified, authorId: template.authorId, version: 1, language: template.language, favoriteCount: 0, reviewedAt: null, reviewedBy: null, reviewComment: null, ); final saved = await _adminRepository.createOfficialEnhancedTemplate(duplicate); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('已复制为新模板: ${saved.name}')), ); _refreshData(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('复制失败: $e')), ); } } } Future _deleteTemplate(EnhancedUserPromptTemplate template) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('确认删除'), content: Text('确定要删除模板 "${template.name}" 吗?此操作不可撤销。'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('删除'), ), ], ), ); if (confirmed == true) { try { await _adminRepository.deleteEnhancedTemplate(template.id); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('模板 "${template.name}" 删除成功')), ); _refreshData(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('删除失败: $e')), ); } } } } void _reviewTemplate(EnhancedUserPromptTemplate template) { showDialog( context: context, builder: (context) => TemplateReviewDialog( template: template, onReview: (approved, comment) async { try { await _adminRepository.reviewEnhancedTemplate( template.id, approved, comment, ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('模板 "${template.name}" 审核${approved ? "通过" : "拒绝"}')), ); _refreshData(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('审核失败: $e')), ); } } }, ), ); } Future _toggleVerified(EnhancedUserPromptTemplate template) async { try { final newVerifiedStatus = !template.isVerified; await _adminRepository.setEnhancedTemplateVerified( template.id, newVerifiedStatus, ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('模板 "${template.name}" ${newVerifiedStatus ? "已设为认证" : "已取消认证"}'), ), ); _refreshData(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('操作失败: $e')), ); } } } Future _togglePublish(EnhancedUserPromptTemplate template) async { try { final newPublishStatus = !template.isPublic; await _adminRepository.toggleEnhancedTemplatePublish( template.id, newPublishStatus, ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('模板 "${template.name}" 已${newPublishStatus ? "发布" : "取消发布"}'), ), ); _refreshData(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('操作失败: $e')), ); } } } void _viewTemplateStats(EnhancedUserPromptTemplate template) async { try { final stats = await _adminRepository.getEnhancedTemplateStatistics(template.id); if (mounted) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('${template.name} - 统计信息'), content: SizedBox( width: 400, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildStatItem('使用次数', stats['usageCount']?.toString() ?? '0'), _buildStatItem('收藏次数', stats['favoriteCount']?.toString() ?? '0'), _buildStatItem('评分', stats['rating']?.toString() ?? '0.0'), _buildStatItem('创建时间', stats['createdAt']?.toString() ?? '未知'), _buildStatItem('最后更新', stats['updatedAt']?.toString() ?? '未知'), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('关闭'), ), ], ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('获取统计信息失败: $e')), ); } } } Widget _buildStatItem(String label, String value) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: const TextStyle(fontWeight: FontWeight.w500)), Text(value, style: const TextStyle(fontSize: 16)), ], ), ); } Future _batchReview(bool approved) async { if (_selectedTemplates.isEmpty) return; final selectedTemplateObjs = _templates .where((t) => _selectedTemplates.contains(t.id)) .toList(); if (selectedTemplateObjs.isEmpty) return; final config = approved ? BatchOperationConfig.configs[BatchOperationType.review]! : BatchOperationConfig( type: BatchOperationType.review, title: '批量拒绝审核', description: '您即将批量拒绝选中模板的审核申请。', actionColor: Colors.red, requiresComment: true, commentHint: '请说明拒绝原因', ); if (mounted) { showDialog( context: context, builder: (context) => BatchOperationDialog( operation: approved ? '审核通过' : '审核拒绝', title: config.title, description: config.description, templates: selectedTemplateObjs, actionColor: config.actionColor, requiresComment: config.requiresComment, commentHint: config.commentHint, onConfirm: (comment) async { final result = await _adminRepository.batchReviewEnhancedTemplates( _selectedTemplates, approved, ); if (mounted) { final successCount = (result['successCount'] as int?) ?? 0; final failureCount = (result['failureCount'] as int?) ?? 0; String message = '批量${approved ? "审核通过" : "审核拒绝"}完成: '; message += '成功 $successCount 个'; if (failureCount > 0) { message += ', 失败 $failureCount 个'; } ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); setState(() { _batchMode = false; _selectedTemplates.clear(); }); _refreshData(); } }, ), ); } } Future _batchSetVerified(bool verified) async { if (_selectedTemplates.isEmpty) return; final selectedTemplateObjs = _templates .where((t) => _selectedTemplates.contains(t.id)) .toList(); if (selectedTemplateObjs.isEmpty) return; final config = verified ? BatchOperationConfig.configs[BatchOperationType.verify]! : BatchOperationConfig( type: BatchOperationType.verify, title: '批量取消认证', description: '您即将取消选中模板的官方认证标识。', actionColor: Colors.grey, ); if (mounted) { showDialog( context: context, builder: (context) => BatchOperationDialog( operation: verified ? '认证' : '取消认证', title: config.title, description: config.description, templates: selectedTemplateObjs, actionColor: config.actionColor, onConfirm: (comment) async { final result = await _adminRepository.batchSetEnhancedTemplatesVerified( _selectedTemplates, verified, ); if (mounted) { final successCount = (result['successCount'] as int?) ?? 0; final failureCount = (result['failureCount'] as int?) ?? 0; String message = '批量${verified ? "认证" : "取消认证"}完成: '; message += '成功 $successCount 个'; if (failureCount > 0) { message += ', 失败 $failureCount 个'; } ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); setState(() { _batchMode = false; _selectedTemplates.clear(); }); _refreshData(); } }, ), ); } } Future _batchPublish(bool publish) async { if (_selectedTemplates.isEmpty) return; final selectedTemplateObjs = _templates .where((t) => _selectedTemplates.contains(t.id)) .toList(); if (selectedTemplateObjs.isEmpty) return; final config = publish ? BatchOperationConfig.configs[BatchOperationType.publish]! : BatchOperationConfig( type: BatchOperationType.publish, title: '批量取消发布', description: '您即将取消发布选中的模板,模板将不再对用户可见。', actionColor: Colors.grey, ); if (mounted) { showDialog( context: context, builder: (context) => BatchOperationDialog( operation: publish ? '发布' : '取消发布', title: config.title, description: config.description, templates: selectedTemplateObjs, actionColor: config.actionColor, onConfirm: (comment) async { final result = await _adminRepository.batchPublishEnhancedTemplates( _selectedTemplates, publish, ); if (mounted) { final successCount = (result['successCount'] as int?) ?? 0; final failureCount = (result['failureCount'] as int?) ?? 0; String message = '批量${publish ? "发布" : "取消发布"}完成: '; message += '成功 $successCount 个'; if (failureCount > 0) { message += ', 失败 $failureCount 个'; } ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); setState(() { _batchMode = false; _selectedTemplates.clear(); }); _refreshData(); } }, ), ); } } Future _batchExport() async { if (_selectedTemplates.isEmpty) return; final selectedTemplateObjs = _templates .where((t) => _selectedTemplates.contains(t.id)) .toList(); if (selectedTemplateObjs.isEmpty) return; final config = BatchOperationConfig.configs[BatchOperationType.export]!; if (mounted) { showDialog( context: context, builder: (context) => BatchOperationDialog( operation: '导出', title: config.title, description: config.description, templates: selectedTemplateObjs, actionColor: config.actionColor, onConfirm: (comment) async { final templates = await _adminRepository.exportEnhancedTemplates(_selectedTemplates); if (mounted) { // TODO: 实现文件下载或保存功能 ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('已导出 ${templates.length} 个模板')), ); setState(() { _batchMode = false; _selectedTemplates.clear(); }); } }, ), ); } } void _viewTemplateDetails(EnhancedUserPromptTemplate template) async { try { // 获取模板统计信息 Map? statistics = await _adminRepository.getEnhancedTemplateStatistics(template.id); if (mounted) { showDialog( context: context, builder: (context) => TemplateDetailsDialog( template: template, statistics: statistics, ), ); } } catch (e) { if (mounted) { showDialog( context: context, builder: (context) => TemplateDetailsDialog( template: template, ), ); } } } // 右侧详情/编辑区域 Widget _buildRightDetailPane() { if (_isCreating || _selectedTemplate != null) { return EnhancedTemplateEditor( key: ValueKey(_selectedTemplate?.id ?? 'creating'), template: _selectedTemplate, onCancel: () { setState(() { _selectedTemplate = null; _isCreating = false; }); }, onSaved: (saved) { final exists = _templates.any((t) => t.id == saved.id); if (exists) { _applyLocalUpdate(saved); } else { // 新建的场景,插入并重算分组 setState(() { _templates.insert(0, saved); _templatesByStatus = _groupTemplatesByStatus(_templates); }); } setState(() { _selectedTemplate = saved; _isCreating = false; }); }, ); } // 占位空视图 return Container( color: WebTheme.getSurfaceColor(context), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.description_outlined, size: 64, color: WebTheme.getTextColor(context).withOpacity(0.3)), const SizedBox(height: 12), Text( '在左侧选择一个模板查看并编辑,或点击“新增”创建', style: TextStyle(color: WebTheme.getTextColor(context).withOpacity(0.6)), ), ], ), ), ); } // 提供给父级AppBar/FAB调用:进入创建模式 void _startCreate() { setState(() { _isCreating = true; _selectedTemplate = null; }); } Future _importTemplates() async { // TODO: 实现文件选择和上传功能 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('导入功能待实现')), ); } } /// 统计信息对话框 class _StatisticsDialog extends StatelessWidget { final Map statistics; const _StatisticsDialog({required this.statistics}); @override Widget build(BuildContext context) { return AlertDialog( title: const Text('增强模板统计'), content: SizedBox( width: 400, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildStatItem('总模板数', statistics['totalTemplates']?.toString() ?? '0'), _buildStatItem('公共模板数', statistics['publicTemplates']?.toString() ?? '0'), _buildStatItem('已认证模板', statistics['verifiedTemplates']?.toString() ?? '0'), _buildStatItem('总使用次数', statistics['totalUsage']?.toString() ?? '0'), _buildStatItem('总收藏次数', statistics['totalFavorites']?.toString() ?? '0'), _buildStatItem('平均评分', statistics['averageRating']?.toString() ?? '0.0'), const SizedBox(height: 16), const Text('按功能类型分布:', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), if (statistics['byFeatureType'] is Map) ...(statistics['byFeatureType'] as Map).entries.map( (entry) => Padding( padding: const EdgeInsets.only(left: 16, bottom: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(entry.key), Text(entry.value.toString()), ], ), ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('关闭'), ), ], ); } Widget _buildStatItem(String label, String value) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: const TextStyle(fontWeight: FontWeight.w500)), Text(value, style: const TextStyle(fontSize: 16)), ], ), ); } }