feat: complete MVP administration tools
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
import 'package:aioa_mobile/features/admin/application/admin_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class AdminPage extends ConsumerWidget {
|
||||
const AdminPage({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final data = ref.watch(adminDashboardProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('OA 管理中心')),
|
||||
body: data.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, _) => Center(
|
||||
child: FilledButton(
|
||||
onPressed: () => ref.invalidate(adminDashboardProvider),
|
||||
child: Text('加载失败,点击重试\n$error'),
|
||||
),
|
||||
),
|
||||
data: (value) => DefaultTabController(
|
||||
length: 4,
|
||||
child: Column(
|
||||
children: [
|
||||
const TabBar(
|
||||
isScrollable: true,
|
||||
tabs: [
|
||||
Tab(text: '指标'),
|
||||
Tab(text: '组织'),
|
||||
Tab(text: '流程'),
|
||||
Tab(text: '审计'),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
_Metrics(value['metrics']! as Map<String, Object?>),
|
||||
_Organization(value),
|
||||
_Workflow(value),
|
||||
_Audit(value['audits']! as List<Map<String, Object?>>),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Metrics extends StatelessWidget {
|
||||
const _Metrics(this.data);
|
||||
final Map<String, Object?> data;
|
||||
@override
|
||||
Widget build(BuildContext context) => GridView.count(
|
||||
padding: const EdgeInsets.all(12),
|
||||
crossAxisCount: 2,
|
||||
childAspectRatio: 1.5,
|
||||
children: data.entries
|
||||
.where((entry) => entry.key != 'leaveByStatus')
|
||||
.map(
|
||||
(entry) => Card(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'${entry.value}',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
Text(entry.key, textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
class _Organization extends ConsumerWidget {
|
||||
const _Organization(this.data);
|
||||
final Map<String, Object?> data;
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final users = data['users']! as List<Map<String, Object?>>;
|
||||
final roles = data['roles']! as List<Map<String, Object?>>;
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(12),
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
onPressed: () => _add(context, ref),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('新增部门'),
|
||||
),
|
||||
...users.map(
|
||||
(user) => Card(
|
||||
child: ListTile(
|
||||
title: Text('${user['displayName']}'),
|
||||
subtitle: Text(
|
||||
'${user['username']} · ${(user['roles'] as List).join('、')}',
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.manage_accounts),
|
||||
onPressed: () => _roles(context, ref, user, roles),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _add(BuildContext context, WidgetRef ref) async {
|
||||
final code = TextEditingController();
|
||||
final name = TextEditingController();
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: const Text('新增部门'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: code,
|
||||
decoration: const InputDecoration(labelText: '编码'),
|
||||
),
|
||||
TextField(
|
||||
controller: name,
|
||||
decoration: const InputDecoration(labelText: '名称'),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
child: const Text('创建'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) {
|
||||
await ref
|
||||
.read(adminDashboardProvider.notifier)
|
||||
.createDepartment(code.text, name.text);
|
||||
}
|
||||
code.dispose();
|
||||
name.dispose();
|
||||
}
|
||||
|
||||
Future<void> _roles(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
Map<String, Object?> user,
|
||||
List<Map<String, Object?>> available,
|
||||
) async {
|
||||
final selected = (user['roles'] as List).cast<String>().toSet();
|
||||
final result = await showDialog<Set<String>>(
|
||||
context: context,
|
||||
builder: (dialogContext) => StatefulBuilder(
|
||||
builder: (context, setState) => AlertDialog(
|
||||
title: Text('${user['displayName']} 的角色'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: available.map((role) {
|
||||
final code = '${role['code']}';
|
||||
return CheckboxListTile(
|
||||
value: selected.contains(code),
|
||||
title: Text('${role['name']}'),
|
||||
onChanged: (checked) => setState(
|
||||
() => checked == true
|
||||
? selected.add(code)
|
||||
: selected.remove(code),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
actions: [
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, selected),
|
||||
child: const Text('保存'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
if (result != null) {
|
||||
await ref
|
||||
.read(adminDashboardProvider.notifier)
|
||||
.replaceRoles('${user['id']}', result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Workflow extends StatelessWidget {
|
||||
const _Workflow(this.data);
|
||||
final Map<String, Object?> data;
|
||||
@override
|
||||
Widget build(BuildContext context) => ListView(
|
||||
padding: const EdgeInsets.all(12),
|
||||
children: [
|
||||
const Text('流程定义'),
|
||||
...(data['definitions']! as List<Map<String, Object?>>).map(
|
||||
(item) => ListTile(
|
||||
title: Text('${item['name'] ?? item['key']} v${item['version']}'),
|
||||
subtitle: Text('${item['id']}'),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
const Text('最近实例'),
|
||||
...(data['instances']! as List<Map<String, Object?>>).map(
|
||||
(item) => ListTile(
|
||||
title: Text('${item['businessKey'] ?? item['id']}'),
|
||||
subtitle: Text(item['active'] == true ? '运行中' : '已结束'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class _Audit extends StatelessWidget {
|
||||
const _Audit(this.items);
|
||||
final List<Map<String, Object?>> items;
|
||||
@override
|
||||
Widget build(BuildContext context) => ListView.builder(
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text('${item['action']}'),
|
||||
subtitle: Text('${item['resourceType']} · ${item['traceId']}'),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user