625 lines
18 KiB
Dart
625 lines
18 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
|||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
|
|
|
|||
|
|
import '../../../models/schedule.dart';
|
|||
|
|
import '../../../models/sys_log.dart';
|
|||
|
|
import '../../../services/api_client.dart';
|
|||
|
|
import '../../../services/sys_service.dart';
|
|||
|
|
|
|||
|
|
/// 定时任务管理页。
|
|||
|
|
class SchedulePage extends ConsumerStatefulWidget {
|
|||
|
|
const SchedulePage({super.key});
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
ConsumerState<SchedulePage> createState() => _SchedulePageState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _SchedulePageState extends ConsumerState<SchedulePage> {
|
|||
|
|
final SystemService _service = SystemService();
|
|||
|
|
|
|||
|
|
final List<Schedule> _items = [];
|
|||
|
|
bool _loading = false;
|
|||
|
|
int _page = 1;
|
|||
|
|
bool _hasMore = true;
|
|||
|
|
String? _error;
|
|||
|
|
|
|||
|
|
final ScrollController _scrollController = ScrollController();
|
|||
|
|
final int _limit = 20;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_scrollController.addListener(_onScroll);
|
|||
|
|
_loadData();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_scrollController.removeListener(_onScroll);
|
|||
|
|
_scrollController.dispose();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _onScroll() {
|
|||
|
|
if (_scrollController.position.pixels >=
|
|||
|
|
_scrollController.position.maxScrollExtent - 200) {
|
|||
|
|
_loadMore();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _loadData({bool reset = false}) async {
|
|||
|
|
if (_loading) return;
|
|||
|
|
if (reset) {
|
|||
|
|
_page = 1;
|
|||
|
|
_hasMore = true;
|
|||
|
|
_items.clear();
|
|||
|
|
}
|
|||
|
|
setState(() {
|
|||
|
|
_loading = true;
|
|||
|
|
_error = null;
|
|||
|
|
});
|
|||
|
|
try {
|
|||
|
|
final list = await _service.getScheduleList(page: _page, limit: _limit);
|
|||
|
|
_items.addAll(list);
|
|||
|
|
_hasMore = list.length >= _limit;
|
|||
|
|
} on ApiException catch (e) {
|
|||
|
|
_error = e.message;
|
|||
|
|
} catch (e) {
|
|||
|
|
_error = e.toString();
|
|||
|
|
} finally {
|
|||
|
|
if (mounted) setState(() => _loading = false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _loadMore() async {
|
|||
|
|
if (_loading || !_hasMore) return;
|
|||
|
|
_page++;
|
|||
|
|
await _loadData();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _refresh() async {
|
|||
|
|
await _loadData(reset: true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _showForm({Schedule? schedule}) async {
|
|||
|
|
final result = await showModalBottomSheet<Map<String, dynamic>>(
|
|||
|
|
context: context,
|
|||
|
|
isScrollControlled: true,
|
|||
|
|
builder: (context) => _ScheduleForm(schedule: schedule),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (result == null) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await _service.saveSchedule(result);
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('保存成功')),
|
|||
|
|
);
|
|||
|
|
_refresh();
|
|||
|
|
}
|
|||
|
|
} on ApiException catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text(e.message)),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text('保存失败:$e')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _deleteSchedule(Schedule schedule) async {
|
|||
|
|
final confirmed = await showDialog<bool>(
|
|||
|
|
context: context,
|
|||
|
|
builder: (context) => AlertDialog(
|
|||
|
|
title: const Text('确认删除'),
|
|||
|
|
content: Text('确定要删除任务「${schedule.beanName}.${schedule.methodName}」吗?'),
|
|||
|
|
actions: [
|
|||
|
|
TextButton(
|
|||
|
|
onPressed: () => Navigator.pop(context, false),
|
|||
|
|
child: const Text('取消'),
|
|||
|
|
),
|
|||
|
|
FilledButton(
|
|||
|
|
onPressed: () => Navigator.pop(context, true),
|
|||
|
|
child: const Text('删除'),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (confirmed != true) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await _service.deleteSchedule([int.tryParse(schedule.jobId) ?? 0]);
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('删除成功')),
|
|||
|
|
);
|
|||
|
|
_refresh();
|
|||
|
|
}
|
|||
|
|
} on ApiException catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text(e.message)),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text('删除失败:$e')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _pauseSchedule(Schedule schedule) async {
|
|||
|
|
try {
|
|||
|
|
await _service.pauseSchedule(schedule.jobId);
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('任务已暂停')),
|
|||
|
|
);
|
|||
|
|
_refresh();
|
|||
|
|
}
|
|||
|
|
} on ApiException catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text(e.message)),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text('暂停失败:$e')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _resumeSchedule(Schedule schedule) async {
|
|||
|
|
try {
|
|||
|
|
await _service.resumeSchedule(schedule.jobId);
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('任务已恢复')),
|
|||
|
|
);
|
|||
|
|
_refresh();
|
|||
|
|
}
|
|||
|
|
} on ApiException catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text(e.message)),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text('恢复失败:$e')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _runSchedule(Schedule schedule) async {
|
|||
|
|
try {
|
|||
|
|
await _service.runSchedule(schedule.jobId);
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
const SnackBar(content: Text('任务已触发执行')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} on ApiException catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text(e.message)),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
if (mounted) {
|
|||
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|||
|
|
SnackBar(content: Text('执行失败:$e')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
return Scaffold(
|
|||
|
|
appBar: AppBar(automaticallyImplyLeading: false, title: const Text('定时任务')),
|
|||
|
|
body: _buildBody(),
|
|||
|
|
floatingActionButton: FloatingActionButton(
|
|||
|
|
onPressed: () => _showForm(),
|
|||
|
|
child: const Icon(Icons.add),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildBody() {
|
|||
|
|
if (_error != null && _items.isEmpty) {
|
|||
|
|
return Center(
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisSize: MainAxisSize.min,
|
|||
|
|
children: [
|
|||
|
|
Text(_error!),
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
FilledButton.tonal(
|
|||
|
|
onPressed: _refresh,
|
|||
|
|
child: const Text('重试'),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_items.isEmpty && !_loading) {
|
|||
|
|
return const Center(child: Text('暂无数据'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return RefreshIndicator(
|
|||
|
|
onRefresh: _refresh,
|
|||
|
|
child: ListView.builder(
|
|||
|
|
controller: _scrollController,
|
|||
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|||
|
|
itemCount: _items.length + 1,
|
|||
|
|
itemBuilder: (context, index) {
|
|||
|
|
if (index == _items.length) {
|
|||
|
|
if (_loading) {
|
|||
|
|
return const Padding(
|
|||
|
|
padding: EdgeInsets.all(16),
|
|||
|
|
child: Center(child: CircularProgressIndicator()),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
if (!_hasMore) {
|
|||
|
|
return const Padding(
|
|||
|
|
padding: EdgeInsets.all(16),
|
|||
|
|
child: Center(child: Text('没有更多数据')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
return const SizedBox.shrink();
|
|||
|
|
}
|
|||
|
|
final schedule = _items[index];
|
|||
|
|
return Card(
|
|||
|
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
|||
|
|
child: ListTile(
|
|||
|
|
title: Text('${schedule.beanName}.${schedule.methodName}'),
|
|||
|
|
subtitle: Text(
|
|||
|
|
'Cron:${schedule.cronExpression} | ${schedule.status == 0 ? '正常' : '暂停'}'
|
|||
|
|
'${schedule.remark.isNotEmpty ? "\n${schedule.remark}" : ""}',
|
|||
|
|
),
|
|||
|
|
trailing: PopupMenuButton<String>(
|
|||
|
|
onSelected: (value) {
|
|||
|
|
switch (value) {
|
|||
|
|
case 'edit':
|
|||
|
|
_showForm(schedule: schedule);
|
|||
|
|
break;
|
|||
|
|
case 'delete':
|
|||
|
|
_deleteSchedule(schedule);
|
|||
|
|
break;
|
|||
|
|
case 'pause':
|
|||
|
|
_pauseSchedule(schedule);
|
|||
|
|
break;
|
|||
|
|
case 'resume':
|
|||
|
|
_resumeSchedule(schedule);
|
|||
|
|
break;
|
|||
|
|
case 'run':
|
|||
|
|
_runSchedule(schedule);
|
|||
|
|
break;
|
|||
|
|
case 'log':
|
|||
|
|
_showScheduleLog(schedule);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
itemBuilder: (context) => [
|
|||
|
|
const PopupMenuItem(value: 'edit', child: Text('编辑')),
|
|||
|
|
const PopupMenuItem(value: 'delete', child: Text('删除')),
|
|||
|
|
const PopupMenuItem(value: 'pause', child: Text('暂停')),
|
|||
|
|
const PopupMenuItem(value: 'resume', child: Text('恢复')),
|
|||
|
|
const PopupMenuItem(value: 'run', child: Text('立即运行')),
|
|||
|
|
const PopupMenuItem(value: 'log', child: Text('日志')),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _showScheduleLog(Schedule schedule) {
|
|||
|
|
Navigator.push(
|
|||
|
|
context,
|
|||
|
|
MaterialPageRoute(
|
|||
|
|
builder: (context) => _ScheduleLogPage(jobId: schedule.jobId),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 定时任务日志页。
|
|||
|
|
class _ScheduleLogPage extends StatefulWidget {
|
|||
|
|
const _ScheduleLogPage({required this.jobId});
|
|||
|
|
|
|||
|
|
final String jobId;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
State<_ScheduleLogPage> createState() => _ScheduleLogPageState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _ScheduleLogPageState extends State<_ScheduleLogPage> {
|
|||
|
|
final SystemService _service = SystemService();
|
|||
|
|
|
|||
|
|
final List<SysLog> _items = [];
|
|||
|
|
bool _loading = false;
|
|||
|
|
int _page = 1;
|
|||
|
|
bool _hasMore = true;
|
|||
|
|
String? _error;
|
|||
|
|
|
|||
|
|
final ScrollController _scrollController = ScrollController();
|
|||
|
|
final int _limit = 20;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_scrollController.addListener(_onScroll);
|
|||
|
|
_loadData();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_scrollController.removeListener(_onScroll);
|
|||
|
|
_scrollController.dispose();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _onScroll() {
|
|||
|
|
if (_scrollController.position.pixels >=
|
|||
|
|
_scrollController.position.maxScrollExtent - 200) {
|
|||
|
|
_loadMore();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _loadData({bool reset = false}) async {
|
|||
|
|
if (_loading) return;
|
|||
|
|
if (reset) {
|
|||
|
|
_page = 1;
|
|||
|
|
_hasMore = true;
|
|||
|
|
_items.clear();
|
|||
|
|
}
|
|||
|
|
setState(() {
|
|||
|
|
_loading = true;
|
|||
|
|
_error = null;
|
|||
|
|
});
|
|||
|
|
try {
|
|||
|
|
final list =
|
|||
|
|
await _service.getScheduleLogList(page: _page, limit: _limit);
|
|||
|
|
_items.addAll(list);
|
|||
|
|
_hasMore = list.length >= _limit;
|
|||
|
|
} on ApiException catch (e) {
|
|||
|
|
_error = e.message;
|
|||
|
|
} catch (e) {
|
|||
|
|
_error = e.toString();
|
|||
|
|
} finally {
|
|||
|
|
if (mounted) setState(() => _loading = false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _loadMore() async {
|
|||
|
|
if (_loading || !_hasMore) return;
|
|||
|
|
_page++;
|
|||
|
|
await _loadData();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _refresh() async {
|
|||
|
|
await _loadData(reset: true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String _formatDate(DateTime? dt) {
|
|||
|
|
if (dt == null) return '';
|
|||
|
|
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-'
|
|||
|
|
'${dt.day.toString().padLeft(2, '0')} '
|
|||
|
|
'${dt.hour.toString().padLeft(2, '0')}:'
|
|||
|
|
'${dt.minute.toString().padLeft(2, '0')}:'
|
|||
|
|
'${dt.second.toString().padLeft(2, '0')}';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
return Scaffold(
|
|||
|
|
appBar: AppBar(automaticallyImplyLeading: false, title: const Text('任务日志')),
|
|||
|
|
body: _buildBody(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Widget _buildBody() {
|
|||
|
|
if (_error != null && _items.isEmpty) {
|
|||
|
|
return Center(
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisSize: MainAxisSize.min,
|
|||
|
|
children: [
|
|||
|
|
Text(_error!),
|
|||
|
|
const SizedBox(height: 8),
|
|||
|
|
FilledButton.tonal(
|
|||
|
|
onPressed: _refresh,
|
|||
|
|
child: const Text('重试'),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_items.isEmpty && !_loading) {
|
|||
|
|
return const Center(child: Text('暂无日志'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return RefreshIndicator(
|
|||
|
|
onRefresh: _refresh,
|
|||
|
|
child: ListView.builder(
|
|||
|
|
controller: _scrollController,
|
|||
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|||
|
|
itemCount: _items.length + 1,
|
|||
|
|
itemBuilder: (context, index) {
|
|||
|
|
if (index == _items.length) {
|
|||
|
|
if (_loading) {
|
|||
|
|
return const Padding(
|
|||
|
|
padding: EdgeInsets.all(16),
|
|||
|
|
child: Center(child: CircularProgressIndicator()),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
if (!_hasMore) {
|
|||
|
|
return const Padding(
|
|||
|
|
padding: EdgeInsets.all(16),
|
|||
|
|
child: Center(child: Text('没有更多数据')),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
return const SizedBox.shrink();
|
|||
|
|
}
|
|||
|
|
final log = _items[index];
|
|||
|
|
return Card(
|
|||
|
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
|||
|
|
child: Padding(
|
|||
|
|
padding: const EdgeInsets.all(12),
|
|||
|
|
child: Column(
|
|||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|||
|
|
children: [
|
|||
|
|
Text(
|
|||
|
|
log.operation.isEmpty ? '(无操作描述)' : log.operation,
|
|||
|
|
style: Theme.of(context).textTheme.titleSmall,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 4),
|
|||
|
|
Text('用户:${log.username}'),
|
|||
|
|
const SizedBox(height: 2),
|
|||
|
|
Text(
|
|||
|
|
'方法:${log.method}',
|
|||
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|||
|
|
maxLines: 2,
|
|||
|
|
overflow: TextOverflow.ellipsis,
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 2),
|
|||
|
|
Text('耗时:${log.time}ms'),
|
|||
|
|
const SizedBox(height: 2),
|
|||
|
|
Text(
|
|||
|
|
'时间:${_formatDate(log.createDate)}',
|
|||
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 定时任务表单。
|
|||
|
|
class _ScheduleForm extends StatefulWidget {
|
|||
|
|
const _ScheduleForm({this.schedule});
|
|||
|
|
|
|||
|
|
final Schedule? schedule;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
State<_ScheduleForm> createState() => _ScheduleFormState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _ScheduleFormState extends State<_ScheduleForm> {
|
|||
|
|
final _formKey = GlobalKey<FormState>();
|
|||
|
|
late final TextEditingController _beanName;
|
|||
|
|
late final TextEditingController _methodName;
|
|||
|
|
late final TextEditingController _params;
|
|||
|
|
late final TextEditingController _cronExpression;
|
|||
|
|
late final TextEditingController _remark;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
final s = widget.schedule;
|
|||
|
|
_beanName = TextEditingController(text: s?.beanName ?? '');
|
|||
|
|
_methodName = TextEditingController(text: s?.methodName ?? '');
|
|||
|
|
_params = TextEditingController(text: s?.params ?? '');
|
|||
|
|
_cronExpression = TextEditingController(text: s?.cronExpression ?? '');
|
|||
|
|
_remark = TextEditingController(text: s?.remark ?? '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_beanName.dispose();
|
|||
|
|
_methodName.dispose();
|
|||
|
|
_params.dispose();
|
|||
|
|
_cronExpression.dispose();
|
|||
|
|
_remark.dispose();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
final isEdit = widget.schedule != null;
|
|||
|
|
return SingleChildScrollView(
|
|||
|
|
padding: EdgeInsets.only(
|
|||
|
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
|||
|
|
),
|
|||
|
|
child: Padding(
|
|||
|
|
padding: const EdgeInsets.all(16),
|
|||
|
|
child: Form(
|
|||
|
|
key: _formKey,
|
|||
|
|
child: Column(
|
|||
|
|
mainAxisSize: MainAxisSize.min,
|
|||
|
|
children: [
|
|||
|
|
Text(isEdit ? '编辑任务' : '新增任务',
|
|||
|
|
style: Theme.of(context).textTheme.titleMedium),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
TextFormField(
|
|||
|
|
controller: _beanName,
|
|||
|
|
decoration: const InputDecoration(labelText: 'Bean名称'),
|
|||
|
|
validator: (v) =>
|
|||
|
|
(v == null || v.trim().isEmpty) ? '请输入Bean名称' : null,
|
|||
|
|
),
|
|||
|
|
TextFormField(
|
|||
|
|
controller: _methodName,
|
|||
|
|
decoration: const InputDecoration(labelText: '方法名称'),
|
|||
|
|
validator: (v) =>
|
|||
|
|
(v == null || v.trim().isEmpty) ? '请输入方法名称' : null,
|
|||
|
|
),
|
|||
|
|
TextFormField(
|
|||
|
|
controller: _params,
|
|||
|
|
decoration: const InputDecoration(labelText: '参数'),
|
|||
|
|
),
|
|||
|
|
TextFormField(
|
|||
|
|
controller: _cronExpression,
|
|||
|
|
decoration: const InputDecoration(labelText: 'Cron表达式'),
|
|||
|
|
validator: (v) =>
|
|||
|
|
(v == null || v.trim().isEmpty) ? '请输入Cron表达式' : null,
|
|||
|
|
),
|
|||
|
|
TextFormField(
|
|||
|
|
controller: _remark,
|
|||
|
|
decoration: const InputDecoration(labelText: '备注'),
|
|||
|
|
),
|
|||
|
|
const SizedBox(height: 16),
|
|||
|
|
FilledButton(
|
|||
|
|
onPressed: () {
|
|||
|
|
if (_formKey.currentState?.validate() != true) return;
|
|||
|
|
final data = <String, dynamic>{
|
|||
|
|
if (isEdit) 'jobId': widget.schedule!.jobId,
|
|||
|
|
'beanName': _beanName.text.trim(),
|
|||
|
|
'methodName': _methodName.text.trim(),
|
|||
|
|
'params': _params.text.trim(),
|
|||
|
|
'cronExpression': _cronExpression.text.trim(),
|
|||
|
|
'remark': _remark.text.trim(),
|
|||
|
|
};
|
|||
|
|
Navigator.pop(context, data);
|
|||
|
|
},
|
|||
|
|
child: const Text('保存'),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|