mirror of
https://github.com/qinglong-app/qinglong_app.git
synced 2025-10-09 16:48:19 +08:00
add detail page
This commit is contained in:
@@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'base_viewmodel.dart';
|
import 'base_viewmodel.dart';
|
||||||
|
|
||||||
class BaseStateWidget<T extends BaseViewModel> extends ConsumerStatefulWidget {
|
class BaseStateWidget<T extends BaseViewModel> extends ConsumerStatefulWidget {
|
||||||
final Widget Function(BuildContext context, T value, Widget? child) builder;
|
final Widget Function(WidgetRef context, T value, Widget? child) builder;
|
||||||
final ProviderBase<T> model;
|
final ProviderBase<T> model;
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final Function(T)? onReady;
|
final Function(T)? onReady;
|
||||||
@@ -37,7 +37,7 @@ class _BaseStateWidgetState<T extends BaseViewModel> extends ConsumerState<BaseS
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var viewModel = ref.watch<T>(widget.model);
|
var viewModel = ref.watch<T>(widget.model);
|
||||||
if (viewModel.currentState == PageState.CONTENT) {
|
if (viewModel.currentState == PageState.CONTENT) {
|
||||||
return widget.builder(context, viewModel, widget.child);
|
return widget.builder(ref, viewModel, widget.child);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.currentState == PageState.LOADING) {
|
if (viewModel.currentState == PageState.LOADING) {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import 'dart:ffi';
|
||||||
|
|
||||||
import 'package:qinglong_app/base/http/http.dart';
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
import 'package:qinglong_app/module/login/login_bean.dart';
|
import 'package:qinglong_app/module/login/login_bean.dart';
|
||||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||||
|
import 'package:qinglong_app/module/task/task_detail/task_detail_bean.dart';
|
||||||
|
|
||||||
import 'url.dart';
|
import 'url.dart';
|
||||||
|
|
||||||
@@ -18,4 +21,21 @@ class Api {
|
|||||||
static Future<HttpResponse<List<TaskBean>>> crons() async {
|
static Future<HttpResponse<List<TaskBean>>> crons() async {
|
||||||
return await Http.get<List<TaskBean>>(Url.TASKS, {"searchValue": ""});
|
return await Http.get<List<TaskBean>>(Url.TASKS, {"searchValue": ""});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<HttpResponse<NullResponse>> startTasks(List<String> crons) async {
|
||||||
|
return await Http.put<NullResponse>(Url.RUN_TASKS, crons);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<HttpResponse<NullResponse>> stopTasks(List<String> crons) async {
|
||||||
|
return await Http.put<NullResponse>(Url.RUN_TASKS, crons);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<HttpResponse<String>> inTimeLog(String cron) async {
|
||||||
|
return await Http.get<String>(Url.INTIME_LOG(cron), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Future<HttpResponse<TaskDetailBean>> taskDetail(String cron) async {
|
||||||
|
return await Http.get<TaskDetailBean>(Url.TASK_DETAIL+cron, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ffi';
|
import 'dart:core';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_log/dio_log.dart';
|
import 'package:dio_log/dio_log.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:qinglong_app/base/http/token_interceptor.dart';
|
import 'package:qinglong_app/base/http/token_interceptor.dart';
|
||||||
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
import 'package:qinglong_app/main.dart';
|
|
||||||
|
|
||||||
import '../../json.jc.dart';
|
import '../../json.jc.dart';
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ class Http {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<HttpResponse<T>> get<T>(String uri, Map<String, String> json, {bool compute = true}) async {
|
static Future<HttpResponse<T>> get<T>(String uri, Map<String, String>? json, {bool compute = true}) async {
|
||||||
_init();
|
_init();
|
||||||
var response = await _dio!.get(uri, queryParameters: json);
|
var response = await _dio!.get(uri, queryParameters: json);
|
||||||
|
|
||||||
@@ -74,7 +73,7 @@ class Http {
|
|||||||
try {
|
try {
|
||||||
if (response.data["code"] == 200) {
|
if (response.data["code"] == 200) {
|
||||||
if (response.data["data"] != null) {
|
if (response.data["data"] != null) {
|
||||||
if (T is Void) {
|
if (T == NullResponse) {
|
||||||
return HttpResponse<T>(
|
return HttpResponse<T>(
|
||||||
success: true,
|
success: true,
|
||||||
code: 200,
|
code: 200,
|
||||||
@@ -83,7 +82,7 @@ class Http {
|
|||||||
|
|
||||||
dynamic data = response.data["data"];
|
dynamic data = response.data["data"];
|
||||||
T t;
|
T t;
|
||||||
if (T is String) {
|
if (T == String) {
|
||||||
if (data is String) {
|
if (data is String) {
|
||||||
t = data as T;
|
t = data as T;
|
||||||
} else {
|
} else {
|
||||||
@@ -109,7 +108,7 @@ class Http {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return HttpResponse<T>(
|
return HttpResponse<T>(
|
||||||
success: false,
|
success: true,
|
||||||
code: 200,
|
code: 200,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -173,3 +172,5 @@ class CronBean with BaseBean<CronBean> {
|
|||||||
void decode<T>() async {
|
void decode<T>() async {
|
||||||
compute(DeserializeAction.invokeJson, DeserializeAction<T>({}));
|
compute(DeserializeAction.invokeJson, DeserializeAction<T>({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NullResponse{}
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
class Url {
|
class Url {
|
||||||
static const LOGIN = "/api/user/login";
|
static const LOGIN = "/api/user/login";
|
||||||
static const TASKS = "/api/crons";
|
static const TASKS = "/api/crons";
|
||||||
|
static const RUN_TASKS = "/api/crons/run";
|
||||||
|
static const STOP_TASKS = "/api/crons/stop";
|
||||||
|
static const TASK_DETAIL = "/api/crons/";
|
||||||
|
|
||||||
|
static INTIME_LOG(String cronId) {
|
||||||
|
return "/api/crons/$cronId/log";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class Routes {
|
|||||||
static Route<dynamic>? generateRoute(RouteSettings settings) {
|
static Route<dynamic>? generateRoute(RouteSettings settings) {
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case route_HomePage:
|
case route_HomePage:
|
||||||
return CupertinoPageRoute(builder: (context) => const HomePage());
|
return MaterialPageRoute(builder: (context) => const HomePage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -22,14 +22,16 @@ class ThemeViewModel extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ThemeData darkTheme = ThemeData.dark().copyWith(
|
ThemeData darkTheme = ThemeData.dark().copyWith(
|
||||||
primaryColor: Color(0xffffffff),
|
primaryColor: const Color(0xffffffff),
|
||||||
);
|
);
|
||||||
ThemeData lightTheme = ThemeData.light().copyWith(
|
ThemeData lightTheme = ThemeData.light().copyWith(
|
||||||
primaryColor: Color(0xFF0F77FE),
|
primaryColor: const Color(0xFF0F77FE),
|
||||||
|
scaffoldBackgroundColor: Colors.white,
|
||||||
);
|
);
|
||||||
|
|
||||||
abstract class ThemeColors {
|
abstract class ThemeColors {
|
||||||
Color taskTitleColor();
|
Color taskTitleColor();
|
||||||
|
Color searchBarBg();
|
||||||
}
|
}
|
||||||
|
|
||||||
class LightartThemeColors extends ThemeColors {
|
class LightartThemeColors extends ThemeColors {
|
||||||
@@ -37,6 +39,12 @@ class LightartThemeColors extends ThemeColors {
|
|||||||
Color taskTitleColor() {
|
Color taskTitleColor() {
|
||||||
return Color(0xff333333);
|
return Color(0xff333333);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color searchBarBg() {
|
||||||
|
return const Color(0xffF7F7F7);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DartThemeColors extends ThemeColors {
|
class DartThemeColors extends ThemeColors {
|
||||||
@@ -44,4 +52,11 @@ class DartThemeColors extends ThemeColors {
|
|||||||
Color taskTitleColor() {
|
Color taskTitleColor() {
|
||||||
return Colors.white;
|
return Colors.white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color searchBarBg() {
|
||||||
|
return const Color(0xff2E312E);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
74
lib/module/task/intime_log/intime_log_page.dart
Normal file
74
lib/module/task/intime_log/intime_log_page.dart
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
|
|
||||||
|
class InTimeLogPage extends StatefulWidget {
|
||||||
|
final String cronId;
|
||||||
|
final bool needTimer;
|
||||||
|
|
||||||
|
const InTimeLogPage(this.cronId, this.needTimer, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_InTimeLogPageState createState() => _InTimeLogPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InTimeLogPageState extends State<InTimeLogPage> {
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
String? content;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
if (widget.needTimer) {
|
||||||
|
_timer = Timer.periodic(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
(timer) {
|
||||||
|
getLogData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||||
|
getLogData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogData() async {
|
||||||
|
HttpResponse<String> response = await Api.inTimeLog(widget.cronId);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
content = response.bean;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
child: content == null
|
||||||
|
? const Center(
|
||||||
|
child: CupertinoActivityIndicator(),
|
||||||
|
)
|
||||||
|
: CupertinoScrollbar(
|
||||||
|
child: SelectableText(
|
||||||
|
content!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ class TaskBean {
|
|||||||
int? isDisabled;
|
int? isDisabled;
|
||||||
String? logPath;
|
String? logPath;
|
||||||
int? isPinned;
|
int? isPinned;
|
||||||
num? lastExecutionTime;
|
int? lastExecutionTime;
|
||||||
int? lastRunningTime;
|
int? lastRunningTime;
|
||||||
String? pid;
|
String? pid;
|
||||||
|
|
||||||
|
|||||||
63
lib/module/task/task_detail/task_detail_bean.dart
Normal file
63
lib/module/task/task_detail/task_detail_bean.dart
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
||||||
|
|
||||||
|
@JsonConversion()
|
||||||
|
class TaskDetailBean {
|
||||||
|
String? name;
|
||||||
|
String? command;
|
||||||
|
String? schedule;
|
||||||
|
bool? saved;
|
||||||
|
String? sId;
|
||||||
|
int? created;
|
||||||
|
int? status;
|
||||||
|
String? timestamp;
|
||||||
|
int? isSystem;
|
||||||
|
int? isDisabled;
|
||||||
|
String? logPath;
|
||||||
|
int? isPinned;
|
||||||
|
|
||||||
|
TaskDetailBean(
|
||||||
|
{this.name,
|
||||||
|
this.command,
|
||||||
|
this.schedule,
|
||||||
|
this.saved,
|
||||||
|
this.sId,
|
||||||
|
this.created,
|
||||||
|
this.status,
|
||||||
|
this.timestamp,
|
||||||
|
this.isSystem,
|
||||||
|
this.isDisabled,
|
||||||
|
this.logPath,
|
||||||
|
this.isPinned});
|
||||||
|
|
||||||
|
TaskDetailBean.fromJson(Map<String, dynamic> json) {
|
||||||
|
name = json['name'];
|
||||||
|
command = json['command'];
|
||||||
|
schedule = json['schedule'];
|
||||||
|
saved = json['saved'];
|
||||||
|
sId = json['_id'];
|
||||||
|
created = json['created'];
|
||||||
|
status = json['status'];
|
||||||
|
timestamp = json['timestamp'];
|
||||||
|
isSystem = json['isSystem'];
|
||||||
|
isDisabled = json['isDisabled'];
|
||||||
|
logPath = json['log_path'];
|
||||||
|
isPinned = json['isPinned'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['name'] = this.name;
|
||||||
|
data['command'] = this.command;
|
||||||
|
data['schedule'] = this.schedule;
|
||||||
|
data['saved'] = this.saved;
|
||||||
|
data['_id'] = this.sId;
|
||||||
|
data['created'] = this.created;
|
||||||
|
data['status'] = this.status;
|
||||||
|
data['timestamp'] = this.timestamp;
|
||||||
|
data['isSystem'] = this.isSystem;
|
||||||
|
data['isDisabled'] = this.isDisabled;
|
||||||
|
data['log_path'] = this.logPath;
|
||||||
|
data['isPinned'] = this.isPinned;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
lib/module/task/task_detail/task_detail_page.dart
Normal file
36
lib/module/task/task_detail/task_detail_page.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qinglong_app/base/base_state_widget.dart';
|
||||||
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
|
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||||
|
import 'package:qinglong_app/module/task/task_detail/task_detail_viewmodel.dart';
|
||||||
|
|
||||||
|
class TaskDetailPage extends ConsumerStatefulWidget {
|
||||||
|
final TaskBean taskBean;
|
||||||
|
|
||||||
|
const TaskDetailPage(this.taskBean, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TaskDetailPageState createState() => _TaskDetailPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TaskDetailPageState extends ConsumerState<TaskDetailPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: QlAppBar(
|
||||||
|
canBack: true,
|
||||||
|
title: widget.taskBean.name ?? "",
|
||||||
|
),
|
||||||
|
body: BaseStateWidget<TaskDetailViewModel>(
|
||||||
|
model: taskDetailProvider,
|
||||||
|
builder: (WidgetRef context, TaskDetailViewModel value, Widget? child) {
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
|
onReady: (model){
|
||||||
|
model.loadDetail(widget.taskBean.sId!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
lib/module/task/task_detail/task_detail_viewmodel.dart
Normal file
28
lib/module/task/task_detail/task_detail_viewmodel.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qinglong_app/base/base_viewmodel.dart';
|
||||||
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
|
import 'package:qinglong_app/module/task/task_detail/task_detail_bean.dart';
|
||||||
|
|
||||||
|
var taskDetailProvider = AutoDisposeChangeNotifierProvider<TaskDetailViewModel>((ref) {
|
||||||
|
return TaskDetailViewModel();
|
||||||
|
});
|
||||||
|
|
||||||
|
class TaskDetailViewModel extends BaseViewModel {
|
||||||
|
TaskDetailBean? bean;
|
||||||
|
|
||||||
|
TaskDetailViewModel();
|
||||||
|
|
||||||
|
Future<void> loadDetail(String id) async {
|
||||||
|
loading(notify: true);
|
||||||
|
|
||||||
|
HttpResponse<TaskDetailBean> response = await Api.taskDetail(id);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
bean = response.bean;
|
||||||
|
success();
|
||||||
|
} else {
|
||||||
|
failed(response.message, notify: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:qinglong_app/base/base_state_widget.dart';
|
import 'package:qinglong_app/base/base_state_widget.dart';
|
||||||
import 'package:qinglong_app/base/theme.dart';
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
|
import 'package:qinglong_app/module/task/intime_log/intime_log_page.dart';
|
||||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||||
import 'package:qinglong_app/module/task/task_viewmodel.dart';
|
import 'package:qinglong_app/module/task/task_viewmodel.dart';
|
||||||
|
import 'package:qinglong_app/utils/utils.dart';
|
||||||
|
|
||||||
class TaskPage extends StatefulWidget {
|
class TaskPage extends StatefulWidget {
|
||||||
const TaskPage({Key? key}) : super(key: key);
|
const TaskPage({Key? key}) : super(key: key);
|
||||||
@@ -15,27 +17,38 @@ class TaskPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _TaskPageState extends State<TaskPage> {
|
class _TaskPageState extends State<TaskPage> {
|
||||||
|
String? _searchKey = null;
|
||||||
|
TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseStateWidget<TaskViewModel>(
|
return BaseStateWidget<TaskViewModel>(
|
||||||
builder: (context, model, child) {
|
builder: (ref, model, child) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
return model.loadData(false);
|
return model.loadData(false);
|
||||||
},
|
},
|
||||||
child: ListView.separated(
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.symmetric(
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||||
vertical: 15,
|
|
||||||
),
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return TaskItemCell(model.list[index]);
|
if (index == 0) {
|
||||||
},
|
return searchCell(ref);
|
||||||
itemCount: model.list.length,
|
}
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
|
||||||
return const SizedBox(
|
TaskBean item = model.list[index - 1];
|
||||||
height: 10,
|
|
||||||
);
|
if ((item.name == null || item.name!.contains(_searchKey ?? "")) || (item.command == null || item.command!.contains(_searchKey ?? ""))) {
|
||||||
|
return TaskItemCell(item);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
itemCount: model.list.length + 1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -45,6 +58,43 @@ class _TaskPageState extends State<TaskPage> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget searchCell(WidgetRef context) {
|
||||||
|
return Container(
|
||||||
|
color: context.watch(themeProvider).themeColor.searchBarBg(),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
child: CupertinoSearchTextField(
|
||||||
|
onSubmitted: (value) {
|
||||||
|
setState(() {
|
||||||
|
_searchKey = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuffixTap: () {
|
||||||
|
_searchController.text = "";
|
||||||
|
setState(() {
|
||||||
|
_searchKey = "";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
controller: _searchController,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
30,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
prefixInsets: const EdgeInsets.only(
|
||||||
|
top: 10,
|
||||||
|
bottom: 6,
|
||||||
|
left: 15,
|
||||||
|
),
|
||||||
|
placeholder: "搜索",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TaskItemCell extends ConsumerWidget {
|
class TaskItemCell extends ConsumerWidget {
|
||||||
@@ -63,35 +113,47 @@ class TaskItemCell extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
icon: CupertinoIcons.memories,
|
icon: bean.status == 0 ? CupertinoIcons.stop_circle : CupertinoIcons.memories,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
backgroundColor: Color(0xFF0F77FE),
|
backgroundColor: const Color(0xFF0F77FE),
|
||||||
onPressed: (BuildContext context) {},
|
onPressed: (BuildContext context) {
|
||||||
|
if (bean.status == 0) {
|
||||||
|
stopCron(context, ref);
|
||||||
|
} else {
|
||||||
|
startCron(context, ref);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
icon: CupertinoIcons.clock_fill,
|
icon: CupertinoIcons.clock_fill,
|
||||||
onPressed: (BuildContext context) {},
|
onPressed: (BuildContext context) {
|
||||||
|
logCron(context, ref);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
endActionPane: ActionPane(
|
endActionPane: ActionPane(
|
||||||
motion: ScrollMotion(),
|
motion: const ScrollMotion(),
|
||||||
extentRatio: 0.15,
|
extentRatio: 0.15,
|
||||||
children: [
|
children: [
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
backgroundColor: Colors.cyan,
|
backgroundColor: Colors.cyan,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
onPressed: (_) {},
|
onPressed: (_) {
|
||||||
|
more(context, ref);
|
||||||
|
},
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
icon: CupertinoIcons.ellipsis,
|
icon: CupertinoIcons.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(
|
color: bean.isPinned == 1 ? ref.watch(themeProvider).themeColor.searchBarBg() : Colors.transparent,
|
||||||
|
margin: const EdgeInsets.only(bottom: 7, top: 7),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 15,
|
horizontal: 15,
|
||||||
vertical: 5,
|
vertical: 5,
|
||||||
),
|
),
|
||||||
@@ -126,7 +188,7 @@ class TaskItemCell extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
"${bean.lastRunningTime ?? "-"}",
|
(bean.lastExecutionTime == null || bean.lastExecutionTime == 0) ? "-" : Utils.formatMessageTime(bean.lastExecutionTime!),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -153,4 +215,112 @@ class TaskItemCell extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startCron(BuildContext context, WidgetRef ref) {
|
||||||
|
showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CupertinoAlertDialog(
|
||||||
|
title: const Text("确认运行"),
|
||||||
|
content: Text("确认运行定时任务 ${bean.name ?? ""} 吗"),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text("取消"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text("确定"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ref.read(taskProvider).runCrons(bean.sId!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCron(BuildContext context, WidgetRef ref) {
|
||||||
|
showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CupertinoAlertDialog(
|
||||||
|
title: const Text("确认停止"),
|
||||||
|
content: Text("确认停止定时任务 ${bean.name ?? ""} 吗"),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text("取消"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text("确定"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ref.read(taskProvider).stopCrons(bean.sId!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logCron(BuildContext context, WidgetRef ref) {
|
||||||
|
showCupertinoDialog(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CupertinoAlertDialog(
|
||||||
|
title: Text(
|
||||||
|
"${bean.name}运行日志",
|
||||||
|
maxLines: 1,
|
||||||
|
style: const TextStyle(overflow: TextOverflow.ellipsis),
|
||||||
|
),
|
||||||
|
content: InTimeLogPage(bean.sId!, bean.status == 0),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: const Text("知道了"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
context: context);
|
||||||
|
}
|
||||||
|
|
||||||
|
more(BuildContext context, WidgetRef ref) {
|
||||||
|
showCupertinoModalPopup<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => CupertinoActionSheet(
|
||||||
|
title: const Text('更多操作'),
|
||||||
|
actions: <CupertinoActionSheetAction>[
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
child: const Text('编辑'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
child: Text(bean.isDisabled! == 1 ? "启用" : "禁用"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
child: const Text('删除'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
child: Text(bean.isPinned! == 0 ? "置顶" : "取消置顶"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
list.clear();
|
list.clear();
|
||||||
list.addAll(result.bean!);
|
list.addAll(result.bean!);
|
||||||
|
|
||||||
|
sortList();
|
||||||
|
success();
|
||||||
|
} else {
|
||||||
|
list.clear();
|
||||||
|
failed(result.message, notify: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sortList() {
|
||||||
list.sort((a, b) {
|
list.sort((a, b) {
|
||||||
return a.isDisabled! - b.isDisabled!;
|
return a.isDisabled! - b.isDisabled!;
|
||||||
});
|
});
|
||||||
@@ -27,11 +36,21 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
list.sort((a, b) {
|
list.sort((a, b) {
|
||||||
return a.status! - b.status!;
|
return a.status! - b.status!;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
success();
|
Future<void> runCrons(String cron) async {
|
||||||
} else {
|
HttpResponse<NullResponse> result = await Api.startTasks([cron]);
|
||||||
list.clear();
|
if (result.success) {
|
||||||
failed(result.message, notify: true);
|
list.firstWhere((element) => element.sId == cron).status = 0;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stopCrons(String cron) async {
|
||||||
|
HttpResponse<NullResponse> result = await Api.stopTasks([cron]);
|
||||||
|
if (result.success) {
|
||||||
|
list.firstWhere((element) => element.sId == cron).status = 1;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,28 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
static void hideKeyBoard(BuildContext context) {
|
static void hideKeyBoard(BuildContext context) {
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String formatMessageTime(int time) {
|
||||||
|
DateTime current = DateTime.now();
|
||||||
|
DateTime chatTime = DateTime.fromMillisecondsSinceEpoch(time * 1000);
|
||||||
|
if (current.year == chatTime.year) {
|
||||||
|
if (current.day == chatTime.day) {
|
||||||
|
if (chatTime.hour <= 12) {
|
||||||
|
return DateFormat("上午 H:mm").format(chatTime);
|
||||||
|
} else {
|
||||||
|
return DateFormat("下午 H:mm").format(chatTime);
|
||||||
|
}
|
||||||
|
} else if (chatTime.day == current.day - 1) {
|
||||||
|
return DateFormat("昨天 H:mm").format(chatTime);
|
||||||
|
} else {
|
||||||
|
return DateFormat("M/d H:mm").format(chatTime);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return DateFormat("yyyy/M/d HH:mm").format(chatTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,6 +289,13 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.17.0"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ dependencies:
|
|||||||
dio_log: ^2.0.2
|
dio_log: ^2.0.2
|
||||||
json_conversion_annotation: ^0.0.4
|
json_conversion_annotation: ^0.0.4
|
||||||
logger: ^1.1.0
|
logger: ^1.1.0
|
||||||
|
intl: ^0.17.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.0.0
|
build_runner: ^2.0.0
|
||||||
|
|||||||
@@ -16,7 +16,5 @@ import 'package:qinglong_app/module/login/login_viewmodel.dart';
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
testWidgets("", (_){
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user