Files
AIOA/mobile/lib/features/admin/presentation/admin_page.dart
T
2026-07-18 21:26:44 +08:00

268 lines
8.1 KiB
Dart

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['bindings']! as List<Map<String, Object?>>).map(
(item) => Card(
child: ListTile(
leading: Icon(
item['status'] == 'ACTIVE' ? Icons.link : Icons.link_off,
),
title: Text(
'${item['business_type']}${item['process_definition_key']}',
),
subtitle: Text(
'表单 ${item['form_key']} v${item['form_version']} · 优先级 ${item['priority']}',
),
),
),
),
const Divider(),
const Text('表单版本'),
...(data['formVersions']! as List<Map<String, Object?>>).map(
(item) => ListTile(
title: Text('${item['form_key']} v${item['version']}'),
subtitle: Text('${item['status']}'),
),
),
const Divider(),
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']}'),
),
);
},
);
}