Files
CloudHome-Ops/lib/widgets/region_picker.dart

209 lines
5.6 KiB
Dart
Raw Permalink Normal View History

import 'package:flutter/material.dart';
import '../data/region_data.dart';
/// 省→市→区三级联动结果。
class RegionResult {
const RegionResult({
required this.provinceCode,
required this.provinceName,
required this.cityCode,
required this.cityName,
required this.districtCode,
required this.districtName,
});
final String provinceCode;
final String provinceName;
final String cityCode;
final String cityName;
final String districtCode;
final String districtName;
/// 拼接展示文本(省名+市名+区名)。
String get label =>
'$provinceName$cityName$districtName';
}
/// 省→市→区三级联动底部弹起选择器。
///
/// 数据源统一来自 [RegionData](已加载的 `province.json`)。
/// 返回 [RegionResult](含 code 与 name);用户取消时弹出 null。
class RegionPicker extends StatefulWidget {
const RegionPicker({
super.key,
this.initialProvinceCode,
this.initialCityCode,
this.initialDistrictCode,
});
final String? initialProvinceCode;
final String? initialCityCode;
final String? initialDistrictCode;
/// 打开选择器底部弹窗,返回所选 [RegionResult] 或 null。
static Future<RegionResult?> show({
required BuildContext context,
String? initialProvinceCode,
String? initialCityCode,
String? initialDistrictCode,
}) {
return showModalBottomSheet<RegionResult>(
useSafeArea: true,
context: context,
isScrollControlled: true,
builder: (ctx) => RegionPicker(
initialProvinceCode: initialProvinceCode,
initialCityCode: initialCityCode,
initialDistrictCode: initialDistrictCode,
),
);
}
@override
State<RegionPicker> createState() => _RegionPickerState();
}
class _RegionPickerState extends State<RegionPicker> {
final RegionData _region = RegionData.instance;
/// 当前选中层级:0=省 1=市 2=区。
int _level = 0;
String? _provinceCode;
String? _cityCode;
String? _districtCode;
@override
void initState() {
super.initState();
_provinceCode = widget.initialProvinceCode;
_cityCode = widget.initialCityCode;
_districtCode = widget.initialDistrictCode;
// 若已带初始省,直接进入市层级以便快速定位。
if (_provinceCode != null && _provinceCode!.isNotEmpty) {
_level = 1;
if (_cityCode != null && _cityCode!.isNotEmpty) {
_level = 2;
}
}
}
List<RegionNode> get _currentList {
switch (_level) {
case 0:
return _region.provinces;
case 1:
return _region.citiesOf(_provinceCode ?? '');
case 2:
return _region.districtsOf(
_provinceCode ?? '',
_cityCode ?? '',
);
default:
return const [];
}
}
String _levelTitle() {
switch (_level) {
case 0:
return '选择省份';
case 1:
return '选择城市';
case 2:
return '选择区县';
default:
return '选择地区';
}
}
void _onSelect(RegionNode node) {
if (_level == 0) {
setState(() {
_provinceCode = node.code;
_cityCode = null;
_districtCode = null;
_level = 1;
});
} else if (_level == 1) {
setState(() {
_cityCode = node.code;
_districtCode = null;
_level = 2;
});
} else {
final provinceName = _region.nameOf(_provinceCode ?? '');
final cityName = _region.nameOf(_cityCode ?? '');
Navigator.pop(
context,
RegionResult(
provinceCode: _provinceCode ?? '',
provinceName: provinceName,
cityCode: _cityCode ?? '',
cityName: cityName,
districtCode: node.code,
districtName: node.name,
),
);
}
}
@override
Widget build(BuildContext context) {
final nodes = _currentList;
return Container(
height: MediaQuery.of(context).size.height * 0.6,
padding: const EdgeInsets.only(top: 8),
child: Column(
children: [
Row(
children: [
const SizedBox(width: 16),
Expanded(
child: Text(
_levelTitle(),
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
),
if (_level > 0)
TextButton(
onPressed: () => setState(() => _level--),
child: const Text('上一级'),
),
const SizedBox(width: 8),
],
),
const Divider(height: 1),
Expanded(
child: ListView.separated(
itemCount: nodes.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final node = nodes[index];
final isSelected = _isSelected(node);
return ListTile(
title: Text(node.name),
trailing: isSelected
? Icon(Icons.check,
color: Theme.of(context).colorScheme.primary)
: const Icon(Icons.chevron_right),
onTap: () => _onSelect(node),
);
},
),
),
],
),
);
}
bool _isSelected(RegionNode node) {
if (_level == 0) return node.code == _provinceCode;
if (_level == 1) return node.code == _cityCode;
return node.code == _districtCode;
}
}