76f266645d
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
4.7 KiB
Dart
143 lines
4.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../api/oa_client.dart';
|
|
import '../theme.dart';
|
|
import '../widgets/wecom.dart';
|
|
|
|
class PickPeoplePage extends StatefulWidget {
|
|
const PickPeoplePage({
|
|
super.key,
|
|
required this.api,
|
|
required this.title,
|
|
this.multiple = true,
|
|
this.exclude = const {},
|
|
});
|
|
final OaClient api;
|
|
final String title;
|
|
final bool multiple;
|
|
final Set<String> exclude;
|
|
|
|
@override
|
|
State<PickPeoplePage> createState() => _PickPeoplePageState();
|
|
}
|
|
|
|
class _PickPeoplePageState extends State<PickPeoplePage> {
|
|
List<Map<String, dynamic>> _items = [];
|
|
final _selected = <String, String>{};
|
|
String _q = '';
|
|
bool _loading = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_load();
|
|
}
|
|
|
|
Future<void> _load() async {
|
|
try {
|
|
final data = await widget.api.get('/staff');
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_items = asMaps(data);
|
|
_loading = false;
|
|
});
|
|
} catch (_) {
|
|
if (mounted) setState(() => _loading = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final shown = _items.where((e) {
|
|
final id = '${e['id']}';
|
|
if (widget.exclude.contains(id)) return false;
|
|
if (_q.isEmpty) return true;
|
|
return '${e['name']}${e['department']}${e['title']}'.contains(_q);
|
|
}).toList();
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
appBar: AppBar(
|
|
title: Text(widget.title),
|
|
leading: IconButton(icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context)),
|
|
actions: [
|
|
if (widget.multiple)
|
|
TextButton(
|
|
onPressed: _selected.isEmpty
|
|
? null
|
|
: () => Navigator.pop(context, _selected.entries.map((e) => {'id': e.key, 'name': e.value}).toList()),
|
|
child: Text('确定(${_selected.length})'),
|
|
),
|
|
],
|
|
),
|
|
body: Column(
|
|
children: [
|
|
WxSearchBar(hint: '搜索同事', onChanged: (v) => setState(() => _q = v.trim())),
|
|
if (_loading) const LinearProgressIndicator(minHeight: 2, color: kBind),
|
|
Expanded(
|
|
child: ListView.builder(
|
|
itemCount: shown.length,
|
|
itemBuilder: (_, i) {
|
|
final r = shown[i];
|
|
final id = '${r['id']}';
|
|
final name = '${r['name'] ?? ''}';
|
|
final on = _selected.containsKey(id);
|
|
return InkWell(
|
|
onTap: () {
|
|
if (!widget.multiple) {
|
|
Navigator.pop(context, [
|
|
{'id': id, 'name': name},
|
|
]);
|
|
return;
|
|
}
|
|
setState(() {
|
|
if (on) {
|
|
_selected.remove(id);
|
|
} else {
|
|
_selected[id] = name;
|
|
}
|
|
});
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(12, 10, 12, 0),
|
|
child: Row(
|
|
children: [
|
|
if (widget.multiple)
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 10),
|
|
child: Icon(on ? Icons.check_circle : Icons.circle_outlined, color: on ? kBind : kMute, size: 22),
|
|
),
|
|
SquareAvatar(
|
|
label: name,
|
|
fileId: '${r['avatarFileId'] ?? ''}',
|
|
api: widget.api,
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Container(
|
|
padding: const EdgeInsets.only(bottom: 10),
|
|
decoration: const BoxDecoration(border: Border(bottom: BorderSide(color: kLine, width: 0.5))),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(name, style: const TextStyle(fontSize: 16)),
|
|
Text(
|
|
[r['department'], r['title']].where((e) => e != null && '$e'.isNotEmpty).join(' · '),
|
|
style: const TextStyle(fontSize: 12, color: kMute),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|