ops-console v1.0.2 (versionCode 8) 源码入库
This commit is contained in:
208
lib/widgets/region_picker.dart
Normal file
208
lib/widgets/region_picker.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user