import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../../models/prompt_models.dart'; import '../../../utils/web_theme.dart'; import '../../../widgets/common/dialog_container.dart'; import '../../../widgets/common/dialog_header.dart'; /// 模板详情查看对话框 class TemplateDetailsDialog extends StatefulWidget { final EnhancedUserPromptTemplate template; final Map? statistics; const TemplateDetailsDialog({ Key? key, required this.template, this.statistics, }) : super(key: key); @override State createState() => _TemplateDetailsDialogState(); } class _TemplateDetailsDialogState extends State with TickerProviderStateMixin { late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return DialogContainer( maxWidth: 900, height: 700, child: Column( children: [ DialogHeader( title: '模板详情 - ${widget.template.name}', onClose: () => Navigator.of(context).pop(), ), _buildTabs(), Expanded( child: TabBarView( controller: _tabController, children: [ _buildBasicInfoTab(), _buildContentTab(), _buildStatisticsTab(), ], ), ), ], ), ); } Widget _buildTabs() { return Container( decoration: BoxDecoration( border: Border( bottom: BorderSide( color: WebTheme.getBorderColor(context), ), ), ), child: TabBar( controller: _tabController, labelColor: WebTheme.getTextColor(context), unselectedLabelColor: WebTheme.getTextColor(context).withOpacity(0.6), indicatorColor: WebTheme.getPrimaryColor(context), tabs: const [ Tab( icon: Icon(Icons.info), text: '基础信息', ), Tab( icon: Icon(Icons.code), text: '提示词内容', ), Tab( icon: Icon(Icons.analytics), text: '统计信息', ), ], ), ); } Widget _buildBasicInfoTab() { return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildInfoSection(), const SizedBox(height: 24), _buildStatusSection(), const SizedBox(height: 24), _buildTagsSection(), const SizedBox(height: 24), _buildMetadataSection(), ], ), ); } Widget _buildInfoSection() { return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.info, size: 20), const SizedBox(width: 8), Text( '基本信息', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), ], ), const SizedBox(height: 16), _buildInfoRow('模板名称', widget.template.name), _buildInfoRow('模板ID', widget.template.id), _buildInfoRow('功能类型', _getFeatureTypeLabel(widget.template.featureType.toApiString())), _buildInfoRow('语言', _getLanguageLabel(widget.template.language)), _buildInfoRow('版本', (widget.template.version ?? 1).toString()), if (widget.template.description?.isNotEmpty == true) _buildInfoRow('描述', widget.template.description!, maxLines: 3), _buildInfoRow('作者ID', widget.template.authorId ?? '未知'), _buildInfoRow('创建时间', _formatDateTime(widget.template.createdAt)), _buildInfoRow('更新时间', _formatDateTime(widget.template.updatedAt)), ], ), ), ); } Widget _buildStatusSection() { return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.flag, size: 20), const SizedBox(width: 8), Text( '状态信息', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), ], ), const SizedBox(height: 16), Wrap( spacing: 12, runSpacing: 12, children: [ _buildStatusChip( label: '公开状态', value: widget.template.isPublic == true ? '已发布' : '私有', color: widget.template.isPublic == true ? Colors.green : Colors.grey, ), _buildStatusChip( label: '认证状态', value: widget.template.isVerified == true ? '已认证' : '未认证', color: widget.template.isVerified == true ? Colors.blue : Colors.grey, ), _buildStatusChip( label: '评分', value: widget.template.rating > 0 ? widget.template.rating.toStringAsFixed(1) : '无评分', color: _getRatingColor(widget.template.rating), ), ], ), ], ), ), ); } Widget _buildTagsSection() { if (widget.template.tags.isEmpty) { return const SizedBox.shrink(); } return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.label, size: 20), const SizedBox(width: 8), Text( '标签', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), ], ), const SizedBox(height: 16), Wrap( spacing: 8, runSpacing: 8, children: widget.template.tags.map((tag) => _buildTag(tag)).toList(), ), ], ), ), ); } Widget _buildMetadataSection() { return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.data_object, size: 20), const SizedBox(width: 8), Text( '元数据', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), ], ), const SizedBox(height: 16), _buildInfoRow('使用次数', widget.template.usageCount.toString()), _buildInfoRow('收藏次数', (widget.template.favoriteCount ?? 0).toString()), if (widget.template.reviewedAt != null) _buildInfoRow('审核时间', _formatDateTime(widget.template.reviewedAt)), if (widget.template.reviewedBy?.isNotEmpty == true) _buildInfoRow('审核人', widget.template.reviewedBy!), if (widget.template.reviewComment?.isNotEmpty == true) _buildInfoRow('审核备注', widget.template.reviewComment!, maxLines: 2), ], ), ), ); } Widget _buildContentTab() { return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 系统提示词部分 _buildPromptSection( title: '系统提示词 (System Prompt)', content: widget.template.systemPrompt.isNotEmpty ? widget.template.systemPrompt : '未设置系统提示词', icon: Icons.settings, isEmpty: widget.template.systemPrompt.isEmpty, ), const SizedBox(height: 24), // 用户提示词部分 _buildPromptSection( title: '用户提示词 (User Prompt)', content: widget.template.userPrompt.isNotEmpty ? widget.template.userPrompt : '未设置用户提示词', icon: Icons.person, isEmpty: widget.template.userPrompt.isEmpty, ), const SizedBox(height: 24), // 占位符提示 _buildPlaceholderInfo(), ], ), ); } Widget _buildPromptSection({ required String title, required String content, required IconData icon, bool isEmpty = false, }) { return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, size: 20), const SizedBox(width: 8), Text( title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), const Spacer(), if (!isEmpty) IconButton( onPressed: () => _copyToClipboard(content), icon: const Icon(Icons.copy, size: 18), tooltip: '复制到剪贴板', ), ], ), const SizedBox(height: 16), Container( width: double.infinity, constraints: const BoxConstraints(minHeight: 120), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isEmpty ? Colors.grey.withOpacity(0.05) : Colors.grey.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: isEmpty ? Colors.grey.withOpacity(0.1) : Colors.grey.withOpacity(0.2), ), ), child: SelectableText( content, style: TextStyle( fontFamily: 'monospace', fontSize: 14, height: 1.4, color: isEmpty ? WebTheme.getSecondaryTextColor(context) : WebTheme.getTextColor(context), fontStyle: isEmpty ? FontStyle.italic : FontStyle.normal, ), ), ), ], ), ), ); } /// 构建占位符信息 Widget _buildPlaceholderInfo() { return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.code, size: 20), const SizedBox(width: 8), Text( '占位符说明', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), ], ), const SizedBox(height: 16), Text( '提示词中可以使用占位符来动态插入内容,常用占位符包括:', style: TextStyle( fontSize: 14, color: WebTheme.getTextColor(context), ), ), const SizedBox(height: 12), _buildPlaceholderExample('{content}', '要处理的主要内容'), _buildPlaceholderExample('{context}', '上下文信息'), _buildPlaceholderExample('{requirement}', '具体要求'), _buildPlaceholderExample('{style}', '风格要求'), _buildPlaceholderExample('{length}', '长度要求'), ], ), ), ); } Widget _buildPlaceholderExample(String placeholder, String description) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: WebTheme.getPrimaryColor(context).withOpacity(0.1), borderRadius: BorderRadius.circular(4), border: Border.all( color: WebTheme.getPrimaryColor(context).withOpacity(0.3), ), ), child: Text( placeholder, style: TextStyle( fontFamily: 'monospace', fontSize: 12, color: WebTheme.getPrimaryColor(context), fontWeight: FontWeight.w500, ), ), ), const SizedBox(width: 12), Expanded( child: Text( description, style: TextStyle( fontSize: 13, color: WebTheme.getSecondaryTextColor(context), ), ), ), ], ), ); } Widget _buildStatisticsTab() { final stats = widget.statistics ?? {}; return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildStatsCard('使用统计', [ _buildStatRow('总使用次数', stats['usageCount']?.toString() ?? '0'), _buildStatRow('本月使用', stats['monthlyUsage']?.toString() ?? '0'), _buildStatRow('本周使用', stats['weeklyUsage']?.toString() ?? '0'), _buildStatRow('今日使用', stats['dailyUsage']?.toString() ?? '0'), ], Icons.play_arrow), const SizedBox(height: 24), _buildStatsCard('用户反馈', [ _buildStatRow('收藏次数', stats['favoriteCount']?.toString() ?? '0'), _buildStatRow('平均评分', stats['averageRating']?.toString() ?? '0.0'), _buildStatRow('评分人数', stats['ratingCount']?.toString() ?? '0'), _buildStatRow('反馈次数', stats['feedbackCount']?.toString() ?? '0'), ], Icons.favorite), const SizedBox(height: 24), _buildStatsCard('性能数据', [ _buildStatRow('平均响应时间', '${stats['averageResponseTime'] ?? 0}ms'), _buildStatRow('成功率', '${stats['successRate'] ?? 100}%'), _buildStatRow('错误次数', stats['errorCount']?.toString() ?? '0'), _buildStatRow('最后使用时间', _formatDateTime(stats['lastUsedAt'] as DateTime?)), ], Icons.speed), ], ), ); } Widget _buildStatsCard(String title, List children, IconData icon) { return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, size: 20), const SizedBox(width: 8), Text( title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: WebTheme.getTextColor(context), ), ), ], ), const SizedBox(height: 16), ...children, ], ), ), ); } Widget _buildStatRow(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), ), ], ), ); } Widget _buildInfoRow(String label, String value, {int maxLines = 1}) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 120, child: Text( '$label:', style: const TextStyle(fontWeight: FontWeight.w500), ), ), Expanded( child: Text( value, style: TextStyle( color: WebTheme.getTextColor(context).withOpacity(0.8), ), maxLines: maxLines, overflow: maxLines > 1 ? TextOverflow.ellipsis : null, ), ), ], ), ); } Widget _buildStatusChip({ required String label, required String value, required Color color, }) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: color.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 12, color: color, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 2), Text( value, style: TextStyle( fontSize: 14, color: color, fontWeight: FontWeight.bold, ), ), ], ), ); } Widget _buildTag(String tag) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: WebTheme.getPrimaryColor(context).withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: WebTheme.getPrimaryColor(context).withOpacity(0.3), ), ), child: Text( tag, style: TextStyle( fontSize: 13, color: WebTheme.getPrimaryColor(context), fontWeight: FontWeight.w500, ), ), ); } String _getFeatureTypeLabel(String? featureType) { switch (featureType) { case 'AI_CHAT': return 'AI聊天'; case 'TEXT_EXPANSION': return '文本扩写'; case 'TEXT_REFACTOR': return '文本润色'; case 'TEXT_SUMMARY': return '文本总结'; case 'SCENE_TO_SUMMARY': return '场景转摘要'; case 'SUMMARY_TO_SCENE': return '摘要转场景'; case 'NOVEL_GENERATION': return '小说生成'; case 'PROFESSIONAL_FICTION_CONTINUATION': return '专业续写'; case 'SCENE_BEAT_GENERATION': return '场景节拍生成'; default: return featureType ?? '未知类型'; } } String _getLanguageLabel(String? language) { switch (language) { case 'zh': return '中文'; case 'en': return 'English'; case 'ja': return '日本語'; case 'ko': return '한국어'; default: return language ?? '中文'; } } Color _getRatingColor(double? rating) { if (rating == null) return WebTheme.getSecondaryTextColor(context); if (rating >= 4.5) return WebTheme.success; if (rating >= 3.5) return WebTheme.warning; if (rating >= 2.0) return WebTheme.error; return WebTheme.getSecondaryTextColor(context); } String _formatDateTime(DateTime? dateTime) { if (dateTime == null) return '未设置'; final now = DateTime.now(); final difference = now.difference(dateTime); if (difference.inDays > 0) { return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')}'; } else if (difference.inHours > 0) { return '${difference.inHours}小时前'; } else if (difference.inMinutes > 0) { return '${difference.inMinutes}分钟前'; } else { return '刚刚'; } } void _copyToClipboard(String text) { Clipboard.setData(ClipboardData(text: text)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('已复制到剪贴板'), duration: Duration(seconds: 2), ), ); } }