QinGang_interested/lib/pages/user/firm_list_page.dart

466 lines
13 KiB
Dart
Raw Normal View History

2025-12-12 09:11:30 +08:00
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/mine/onboarding_full_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
// lpinyin 用于中文转拼音
import 'package:lpinyin/lpinyin.dart';
import 'package:qhd_prevention/tools/tools.dart';
class FirmListPage extends StatefulWidget {
const FirmListPage({super.key});
@override
State<FirmListPage> createState() => _FirmListPageState();
}
class _FirmListPageState extends State<FirmListPage> {
List _firmList = []; // 原始数据(全部)
List _displayList = []; // 经过搜索过滤后用于显示的数据
final Map<String, List<Map>> _sections = {};
final List<String> _sectionOrder = [];
final ScrollController _scrollController = ScrollController();
final TextEditingController _searchController = TextEditingController();
static const double headerHeight = 40.0;
static const double itemHeight = 64.0;
@override
void initState() {
super.initState();
_getFirmList();
}
Future<void> _getFirmList() async {
try {
LoadingDialogHelper.show();
final result = await BasicInfoApi.getFirmList({'enterpriseType': 3});
LoadingDialogHelper.hide();
if (result['success'] == true && result['data'] is List) {
setState(() {
_firmList = List.from(result['data']);
_displayList = List.from(_firmList);
});
_groupAndSort();
} else {
setState(() {
_firmList = [];
_displayList = [];
_sections.clear();
_sectionOrder.clear();
});
}
} catch (e) {
// 处理异常
setState(() {
_firmList = [];
_displayList = [];
_sections.clear();
_sectionOrder.clear();
});
}
}
// 取企业显示名(优先字段)
String _firmName(Map m) {
final keys = [
'corpName',
'companyName',
'name',
'firmName',
'title',
'epcProjectName',
];
for (final k in keys) {
if (m.containsKey(k) &&
m[k] != null &&
m[k].toString().trim().isNotEmpty) {
return m[k].toString().trim();
}
}
// 若没有合适字段,尝试取第一个非空 value
for (final entry in m.entries) {
final v = entry.value;
if (v is String && v.trim().isNotEmpty) return v.trim();
if (v is num) return v.toString();
}
return '未命名企业';
}
// 将 displayList 按拼音首字母分组并排序
void _groupAndSort() {
_sections.clear();
for (final raw in _displayList) {
if (raw is! Map) continue;
final name = _firmName(raw);
String shortPinyin = '';
try {
shortPinyin = PinyinHelper.getShortPinyin(name);
} catch (_) {
shortPinyin = '';
}
final firstLetter =
(shortPinyin.isNotEmpty
? shortPinyin[0].toUpperCase()
: (name.isNotEmpty ? name[0].toUpperCase() : '#'));
final letter =
RegExp(r'^[A-Z]$').hasMatch(firstLetter) ? firstLetter : '#';
_sections.putIfAbsent(letter, () => []).add(raw);
}
// 对每个分组内部按拼音全拼排序(不带声调)
for (final k in _sections.keys) {
_sections[k]!.sort((a, b) {
final na = _firmName(a);
final nb = _firmName(b);
String pa;
String pb;
try {
pa =
PinyinHelper.getPinyin(
na,
separator: '',
format: PinyinFormat.WITHOUT_TONE,
).toLowerCase();
} catch (_) {
pa = na.toLowerCase();
}
try {
pb =
PinyinHelper.getPinyin(
nb,
separator: '',
format: PinyinFormat.WITHOUT_TONE,
).toLowerCase();
} catch (_) {
pb = nb.toLowerCase();
}
return pa.compareTo(pb);
});
}
// 构建分组顺序A..Z然后 '#'
final letters = List<String>.generate(
26,
(i) => String.fromCharCode(65 + i),
);
final order = <String>[];
for (final l in letters) {
if (_sections.containsKey(l)) order.add(l);
}
if (_sections.containsKey('#')) order.add('#');
setState(() {
_sectionOrder
..clear()
..addAll(order);
});
}
// 过滤函数:支持中文直接匹配,也支持拼音匹配
void _applyFilter(String q) {
final query = q.trim();
if (query.isEmpty) {
setState(() {
_displayList = List.from(_firmList);
});
_groupAndSort();
return;
}
final qLower = query.toLowerCase();
final filtered = <dynamic>[];
for (final raw in _firmList) {
if (raw is! Map) continue;
final name = _firmName(raw);
final nameLower = name.toLowerCase();
bool matched = false;
// 1) 中文/英文直接包含
if (nameLower.contains(qLower)) matched = true;
// 2) 拼音匹配
if (!matched) {
try {
final pinyin =
PinyinHelper.getPinyin(
name,
separator: '',
format: PinyinFormat.WITHOUT_TONE,
).toLowerCase();
if (pinyin.contains(qLower)) matched = true;
} catch (_) {
// ignore
}
}
if (matched) filtered.add(raw);
}
setState(() {
_displayList = filtered;
});
_groupAndSort();
}
// 计算某个分组的列表起始偏移(基于 headerHeight/itemHeight 的估算)
double _offsetForSection(String letter) {
double offset = 0.0;
for (final l in _sectionOrder) {
if (l == letter) break;
offset += headerHeight;
final cnt = _sections[l]?.length ?? 0;
offset += cnt * itemHeight;
}
return offset;
}
void _jumpToLetter(String letter) {
if (_sectionOrder.isEmpty) return;
if (_sections.containsKey(letter)) {
final off = _offsetForSection(letter);
_scrollController.animateTo(
off,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
);
return;
}
final all =
List<String>.generate(26, (i) => String.fromCharCode(65 + i)) + ['#'];
final idx = all.indexOf(letter);
if (idx == -1) return;
for (int i = idx + 1; i < all.length; i++) {
final l = all[i];
if (_sections.containsKey(l)) {
final off = _offsetForSection(l);
_scrollController.animateTo(
off,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
);
return;
}
}
final last = _sectionOrder.last;
final off = _offsetForSection(last);
_scrollController.animateTo(
off,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
);
}
List<String> get _azIndex {
final list = List<String>.generate(26, (i) => String.fromCharCode(65 + i));
list.add('#');
return list;
}
@override
Widget build(BuildContext context) {
// 右侧字母索引(灰色圆角背景),使用固定高度以忽略键盘导致的可用高度变化
final mq = MediaQuery.of(context);
final fixedIndexHeight =
mq.size.height - kToolbarHeight - mq.padding.top - 24-100;
return Scaffold(
backgroundColor: Colors.white,
appBar: MyAppbar(title: '选择企业'),
body: SafeArea(
child: Column(
children: [
// 搜索框(固定在顶部)
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
child: SearchBarWidget(
onSearch: (keyboard) {},
controller: _searchController,
isShowSearchButton: false,
resetButtonText: '重置',
showResetButton: true,
onTextChanged: (text) {
_applyFilter(text);
},
onReset: () {
_searchController.clear();
_applyFilter('');
},
),
),
// 列表区域
Expanded(
child: Stack(
children: [
// 列表区域RefreshIndicator 必须包裹可滚动控件ListView
Padding(
padding: const EdgeInsets.only(right: 48.0), // 给右侧字母索引留空间
child: _buildListView(),
),
// 24 是上/下 margin 的大致预留12 + 12根据你视觉需要可调
Positioned(
right: 8,
top: 12,
// 不使用 bottom避免随键盘收缩改为固定高度独立于 viewInsets
height: fixedIndexHeight,
child: _buildAlphabetIndex(fixedIndexHeight),
),
],
),
),
],
),
),
);
}
Widget _buildListView() {
if (_sectionOrder.isEmpty) {
return ListView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
children: [
const SizedBox(height: 30),
Center(
child: Text(
_displayList.isEmpty ? ' 暂无企业' : ' 正在加载…',
style: TextStyle(fontSize: 15, color: Colors.grey[600]),
),
),
],
);
}
final children = <Widget>[];
for (final letter in _sectionOrder) {
children.add(_buildSectionHeader(letter));
final items = _sections[letter] ?? [];
for (final item in items) {
children.add(_buildItem(item));
}
}
return ListView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(vertical: 0),
children: children,
);
}
Widget _buildSectionHeader(String letter) {
return Container(
height: headerHeight,
padding: const EdgeInsets.symmetric(horizontal: 15),
color: Colors.grey.shade100,
alignment: Alignment.centerLeft,
child: Text(
letter,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
);
}
Widget _buildItem(Map firm) {
final name = _firmName(firm);
return InkWell(
onTap: () {
pushPage(OnboardingFullPage(scanData: firm), context);
},
child: Container(
height: itemHeight,
padding: const EdgeInsets.symmetric(horizontal: 15),
color: Colors.white,
child: Row(
children: [
Expanded(
child: Text(
name,
style: const TextStyle(fontSize: 15),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
}
Widget _buildAlphabetIndex(double height) {
final letters = _azIndex;
return SizedBox(
width: 20,
height: height,
child: Container(
// 背景圆角盒子占满高度
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 4),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(10),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Column(
// 等间隔排列字母,使它们在固定高度内均匀分布,不会被键盘压缩
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:
letters.map((l) {
final enabled = _sections.containsKey(l);
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _jumpToLetter(l),
child: Container(
// 尽量让每个字母区域可点击,并根据启用状态改变样式
padding: const EdgeInsets.symmetric(
vertical: 2,
horizontal: 4,
),
alignment: Alignment.center,
child: Text(
l,
style: TextStyle(
fontSize: 11,
color: enabled ? Colors.blue : Colors.grey.shade400,
fontWeight: FontWeight.w500,
),
),
),
);
}).toList(),
),
),
);
}
@override
void dispose() {
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
}