Files
MaliangAINovalWriter/AINoval/lib/screens/admin/widgets/batch_operation_dialog.dart
2025-09-10 00:07:52 +08:00

424 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import '../../../models/prompt_models.dart';
import '../../../utils/web_theme.dart';
import '../../../widgets/common/dialog_container.dart';
import '../../../widgets/common/dialog_header.dart';
/// 批量操作确认对话框
class BatchOperationDialog extends StatefulWidget {
final String operation;
final String title;
final String description;
final List<EnhancedUserPromptTemplate> templates;
final Function(String? comment) onConfirm;
final Color? actionColor;
final bool requiresComment;
final String? commentHint;
const BatchOperationDialog({
Key? key,
required this.operation,
required this.title,
required this.description,
required this.templates,
required this.onConfirm,
this.actionColor,
this.requiresComment = false,
this.commentHint,
}) : super(key: key);
@override
State<BatchOperationDialog> createState() => _BatchOperationDialogState();
}
class _BatchOperationDialogState extends State<BatchOperationDialog> {
final _commentController = TextEditingController();
bool _isLoading = false;
@override
void dispose() {
_commentController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return DialogContainer(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
DialogHeader(
title: widget.title,
onClose: () => Navigator.of(context).pop(),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildWarningSection(),
const SizedBox(height: 24),
_buildTemplatesList(),
if (widget.requiresComment || widget.commentHint != null) ...[
const SizedBox(height: 24),
_buildCommentSection(),
],
],
),
),
),
_buildActions(),
],
),
);
}
Widget _buildWarningSection() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: (widget.actionColor ?? Colors.orange).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: (widget.actionColor ?? Colors.orange).withOpacity(0.3),
),
),
child: Row(
children: [
Icon(
Icons.warning,
color: widget.actionColor ?? Colors.orange,
size: 24,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'批量操作确认',
style: TextStyle(
fontWeight: FontWeight.bold,
color: widget.actionColor ?? Colors.orange,
fontSize: 16,
),
),
const SizedBox(height: 4),
Text(
widget.description,
style: TextStyle(
color: WebTheme.getTextColor(context).withOpacity(0.8),
fontSize: 14,
),
),
],
),
),
],
),
);
}
Widget _buildTemplatesList() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.list, size: 20),
const SizedBox(width: 8),
Text(
'影响的模板 (${widget.templates.length} 个)',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: WebTheme.getTextColor(context),
),
),
],
),
const SizedBox(height: 16),
Container(
constraints: const BoxConstraints(maxHeight: 200),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.withOpacity(0.3)),
borderRadius: BorderRadius.circular(8),
),
child: ListView.separated(
shrinkWrap: true,
itemCount: widget.templates.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) {
final template = widget.templates[index];
return ListTile(
dense: true,
leading: CircleAvatar(
radius: 12,
backgroundColor: WebTheme.getPrimaryColor(context).withOpacity(0.1),
child: Text(
'${index + 1}',
style: TextStyle(
fontSize: 10,
color: WebTheme.getPrimaryColor(context),
fontWeight: FontWeight.bold,
),
),
),
title: Text(
template.name,
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
_getFeatureTypeLabel(template.featureType.toApiString()),
style: const TextStyle(fontSize: 12),
),
trailing: _buildTemplateStatusBadge(template),
);
},
),
),
],
);
}
Widget _buildTemplateStatusBadge(EnhancedUserPromptTemplate template) {
String status;
Color color;
if (template.isVerified) {
status = '认证';
color = Colors.green;
} else if (template.isPublic) {
status = '公开';
color = Colors.blue;
} else {
status = '私有';
color = Colors.grey;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Text(
status,
style: TextStyle(
fontSize: 10,
color: color,
fontWeight: FontWeight.w500,
),
),
);
}
Widget _buildCommentSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.comment, size: 20),
const SizedBox(width: 8),
Text(
widget.requiresComment ? '操作备注 *' : '操作备注(可选)',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: WebTheme.getTextColor(context),
),
),
],
),
const SizedBox(height: 16),
TextField(
controller: _commentController,
decoration: InputDecoration(
hintText: widget.commentHint ?? '请输入操作备注...',
border: const OutlineInputBorder(),
alignLabelWithHint: true,
),
maxLines: 3,
),
if (widget.requiresComment)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'* 此操作需要填写备注信息',
style: TextStyle(
fontSize: 12,
color: Colors.red.withOpacity(0.7),
),
),
),
],
);
}
Widget _buildActions() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: WebTheme.getCardColor(context),
border: Border(
top: BorderSide(
color: WebTheme.getBorderColor(context),
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: _isLoading ? null : () => Navigator.of(context).pop(),
child: const Text('取消'),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: _isLoading ? null : _handleConfirm,
style: ElevatedButton.styleFrom(
backgroundColor: widget.actionColor ?? WebTheme.getPrimaryColor(context),
foregroundColor: WebTheme.white,
),
child: _isLoading
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
)
: Text('确认${widget.operation}'),
),
],
),
);
}
Future<void> _handleConfirm() async {
// 检查是否需要备注且未填写
if (widget.requiresComment && _commentController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写操作备注')),
);
return;
}
setState(() {
_isLoading = true;
});
try {
final comment = _commentController.text.trim();
await widget.onConfirm(comment.isNotEmpty ? comment : null);
if (mounted) {
Navigator.of(context).pop();
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('操作失败: $e')),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
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 ?? '未知';
}
}
}
/// 批量操作类型枚举
enum BatchOperationType {
review,
verify,
publish,
delete,
export,
}
///
class BatchOperationConfig {
final BatchOperationType type;
final String title;
final String description;
final Color actionColor;
final bool requiresComment;
final String? commentHint;
const BatchOperationConfig({
required this.type,
required this.title,
required this.description,
required this.actionColor,
this.requiresComment = false,
this.commentHint,
});
static const Map<BatchOperationType, BatchOperationConfig> configs = {
BatchOperationType.review: BatchOperationConfig(
type: BatchOperationType.review,
title: '批量审核',
description: '您即将批量审核选中的模板。审核通过的模板将被发布为公共模板。',
actionColor: Colors.green,
requiresComment: false,
commentHint: '可以添加审核意见(可选)',
),
BatchOperationType.verify: BatchOperationConfig(
type: BatchOperationType.verify,
title: '批量认证',
description: '您即将为选中的模板添加官方认证标识。认证后的模板将显示认证徽章。',
actionColor: Colors.blue,
),
BatchOperationType.publish: BatchOperationConfig(
type: BatchOperationType.publish,
title: '批量发布',
description: '您即将批量发布选中的模板。发布后的模板将对所有用户可见。',
actionColor: Colors.indigo,
),
BatchOperationType.delete: BatchOperationConfig(
type: BatchOperationType.delete,
title: '批量删除',
description: '您即将永久删除选中的模板。此操作不可撤销,请谨慎操作!',
actionColor: Colors.red,
requiresComment: true,
commentHint: '请说明删除原因',
),
BatchOperationType.export: BatchOperationConfig(
type: BatchOperationType.export,
title: '批量导出',
description: '您即将导出选中的模板数据。导出的数据可用于备份或迁移。',
actionColor: Colors.orange,
),
};
}