马良AI写作初始化仓库
This commit is contained in:
201
AINoval/lib/widgets/common/custom_text_editor.dart
Normal file
201
AINoval/lib/widgets/common/custom_text_editor.dart
Normal file
@@ -0,0 +1,201 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:ainoval/utils/web_theme.dart';
|
||||
|
||||
/// 文本编辑器操作按钮类型
|
||||
enum EditorAction {
|
||||
expand, // 展开
|
||||
copy, // 复制
|
||||
}
|
||||
|
||||
/// 自定义文本编辑器组件
|
||||
/// 支持多行文本编辑、占位符和操作按钮
|
||||
class CustomTextEditor extends StatefulWidget {
|
||||
/// 构造函数
|
||||
const CustomTextEditor({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.placeholder = '请输入内容...',
|
||||
this.minLines = 3,
|
||||
this.maxLines = 10,
|
||||
this.showActions = true,
|
||||
this.actions = const [EditorAction.expand, EditorAction.copy],
|
||||
this.onExpand,
|
||||
this.onCopy,
|
||||
this.enabled = true,
|
||||
this.readOnly = false,
|
||||
});
|
||||
|
||||
/// 文本控制器
|
||||
final TextEditingController? controller;
|
||||
|
||||
/// 占位符文字
|
||||
final String placeholder;
|
||||
|
||||
/// 最小行数
|
||||
final int minLines;
|
||||
|
||||
/// 最大行数
|
||||
final int maxLines;
|
||||
|
||||
/// 是否显示操作按钮
|
||||
final bool showActions;
|
||||
|
||||
/// 操作按钮列表
|
||||
final List<EditorAction> actions;
|
||||
|
||||
/// 展开回调
|
||||
final VoidCallback? onExpand;
|
||||
|
||||
/// 复制回调
|
||||
final VoidCallback? onCopy;
|
||||
|
||||
/// 是否启用
|
||||
final bool enabled;
|
||||
|
||||
/// 是否只读
|
||||
final bool readOnly;
|
||||
|
||||
@override
|
||||
State<CustomTextEditor> createState() => _CustomTextEditorState();
|
||||
}
|
||||
|
||||
class _CustomTextEditorState extends State<CustomTextEditor> {
|
||||
late TextEditingController _controller;
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = widget.controller ?? TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.controller == null) {
|
||||
_controller.dispose();
|
||||
}
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = WebTheme.isDarkMode(context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// 文本输入区域
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: widget.minLines * 24.0,
|
||||
maxHeight: widget.maxLines * 24.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.enabled
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
enabled: widget.enabled,
|
||||
readOnly: widget.readOnly,
|
||||
maxLines: null,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1.5,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: _controller.text.isEmpty ? widget.placeholder : null,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 操作按钮区域
|
||||
if (widget.showActions && widget.actions.isNotEmpty)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: widget.actions.map((action) => _buildActionButton(context, action, isDark)).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建操作按钮
|
||||
Widget _buildActionButton(BuildContext context, EditorAction action, bool isDark) {
|
||||
IconData icon;
|
||||
String label;
|
||||
VoidCallback? onPressed;
|
||||
bool enabled = widget.enabled && !widget.readOnly;
|
||||
|
||||
switch (action) {
|
||||
case EditorAction.expand:
|
||||
icon = Icons.open_in_full;
|
||||
label = '展开';
|
||||
onPressed = enabled ? widget.onExpand : null;
|
||||
break;
|
||||
case EditorAction.copy:
|
||||
icon = Icons.content_copy;
|
||||
label = '复制';
|
||||
onPressed = enabled ? widget.onCopy : null;
|
||||
// 复制功能在有内容时才启用
|
||||
enabled = enabled && _controller.text.isNotEmpty;
|
||||
break;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 4),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: onPressed,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 12,
|
||||
color: enabled
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: enabled
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user