146 lines
4.4 KiB
Dart
146 lines
4.4 KiB
Dart
|
|
import 'dart:convert';
|
|||
|
|
|
|||
|
|
import 'package:flutter/services.dart' show rootBundle;
|
|||
|
|
|
|||
|
|
/// 省市区节点(省 → 市 → 区三级树)。
|
|||
|
|
///
|
|||
|
|
/// 对应 `assets/data/MCityPiker/province.json` 结构:
|
|||
|
|
/// `{code, name, sub:[{code, name, sub:[{code, name}]}]}`。
|
|||
|
|
class RegionNode {
|
|||
|
|
const RegionNode({
|
|||
|
|
required this.code,
|
|||
|
|
required this.name,
|
|||
|
|
this.sub = const [],
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 行政区划代码(省 6 位、市 6 位、区 6 位)。
|
|||
|
|
final String code;
|
|||
|
|
|
|||
|
|
/// 行政区划名称(如「北京市」「朝阳区」)。
|
|||
|
|
final String name;
|
|||
|
|
|
|||
|
|
/// 下一级子节点(省含市,市含区,区无 sub)。
|
|||
|
|
final List<RegionNode> sub;
|
|||
|
|
|
|||
|
|
factory RegionNode.fromJson(Map<String, dynamic> json) {
|
|||
|
|
final subRaw = json['sub'];
|
|||
|
|
final List<RegionNode> children = subRaw is List
|
|||
|
|
? subRaw
|
|||
|
|
.whereType<Map<String, dynamic>>()
|
|||
|
|
.map(RegionNode.fromJson)
|
|||
|
|
.toList()
|
|||
|
|
: const <RegionNode>[];
|
|||
|
|
return RegionNode(
|
|||
|
|
code: json['code']?.toString() ?? '',
|
|||
|
|
name: json['name']?.toString() ?? '',
|
|||
|
|
sub: children,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 省市区数据源。
|
|||
|
|
///
|
|||
|
|
/// 统一由 [RegionData.load] 懒加载 `province.json` 并缓存为
|
|||
|
|
/// `List<RegionNode>` 树;组件禁止直接 `rootBundle` 读取,避免重复解析
|
|||
|
|
/// (文件约 284KB)。
|
|||
|
|
class RegionData {
|
|||
|
|
RegionData._();
|
|||
|
|
|
|||
|
|
/// 全局单例。
|
|||
|
|
static final RegionData instance = RegionData._();
|
|||
|
|
|
|||
|
|
/// 是否已加载。
|
|||
|
|
bool get isLoaded => _loaded;
|
|||
|
|
bool _loaded = false;
|
|||
|
|
|
|||
|
|
/// 省级节点树(已缓存)。
|
|||
|
|
List<RegionNode> get provinces => List.unmodifiable(_tree);
|
|||
|
|
final List<RegionNode> _tree = [];
|
|||
|
|
|
|||
|
|
/// 省份名称缓存(code → 名称),用于反查。
|
|||
|
|
final Map<String, String> _nameByCode = {};
|
|||
|
|
|
|||
|
|
/// 懒加载省市区数据。重复调用安全(仅首次解析)。
|
|||
|
|
Future<void> load() async {
|
|||
|
|
if (_loaded) return;
|
|||
|
|
final raw = await rootBundle
|
|||
|
|
.loadString('assets/data/MCityPiker/province.json');
|
|||
|
|
final decoded = jsonDecode(raw);
|
|||
|
|
final list = decoded is List
|
|||
|
|
? decoded.whereType<Map<String, dynamic>>().map(RegionNode.fromJson)
|
|||
|
|
: const <RegionNode>[];
|
|||
|
|
_tree
|
|||
|
|
..clear()
|
|||
|
|
..addAll(list);
|
|||
|
|
_buildNameIndex(_tree);
|
|||
|
|
_loaded = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 构建 code → 名称 反查索引(省/市/区全部登记)。
|
|||
|
|
void _buildNameIndex(List<RegionNode> nodes) {
|
|||
|
|
for (final node in nodes) {
|
|||
|
|
if (node.code.isNotEmpty) {
|
|||
|
|
_nameByCode[node.code] = node.name;
|
|||
|
|
}
|
|||
|
|
if (node.sub.isNotEmpty) {
|
|||
|
|
_buildNameIndex(node.sub);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 获取某省下的市级节点;[provinceCode] 为空或不存在时返回空列表。
|
|||
|
|
List<RegionNode> citiesOf(String provinceCode) {
|
|||
|
|
for (final province in _tree) {
|
|||
|
|
if (province.code == provinceCode) return province.sub;
|
|||
|
|
}
|
|||
|
|
return const [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 获取某市下的区级节点;
|
|||
|
|
/// [provinceCode]/[cityCode] 任一为空或不存在时返回空列表。
|
|||
|
|
List<RegionNode> districtsOf(String provinceCode, String cityCode) {
|
|||
|
|
for (final province in _tree) {
|
|||
|
|
if (province.code != provinceCode) continue;
|
|||
|
|
for (final city in province.sub) {
|
|||
|
|
if (city.code == cityCode) return city.sub;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return const [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 按 code 反查名称(省/市/区通用)。
|
|||
|
|
String nameOf(String code) => _nameByCode[code] ?? '';
|
|||
|
|
|
|||
|
|
/// 由历历史纯文本地址尽力映射省市区。
|
|||
|
|
///
|
|||
|
|
/// 当前实现:遍历所有省份/城市/区名称,若 [address] 包含该名称,
|
|||
|
|
/// 则按「省 → 市 → 区」优先级取最长命中,返回
|
|||
|
|
/// `[provinceCode, cityCode, districtCode]`;匹配不到的维度为空字符串。
|
|||
|
|
/// 由于区名可能重复,此映射仅作尽力回显,无法保证 100% 准确。
|
|||
|
|
List<String> tryMapAddress(String address) {
|
|||
|
|
final text = address.trim();
|
|||
|
|
if (text.isEmpty) return const ['', '', ''];
|
|||
|
|
|
|||
|
|
String provinceCode = '';
|
|||
|
|
String cityCode = '';
|
|||
|
|
String districtCode = '';
|
|||
|
|
|
|||
|
|
for (final province in _tree) {
|
|||
|
|
if (text.contains(province.name)) {
|
|||
|
|
provinceCode = province.code;
|
|||
|
|
for (final city in province.sub) {
|
|||
|
|
if (text.contains(city.name)) {
|
|||
|
|
cityCode = city.code;
|
|||
|
|
for (final district in city.sub) {
|
|||
|
|
if (text.contains(district.name)) {
|
|||
|
|
districtCode = district.code;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return [provinceCode, cityCode, districtCode];
|
|||
|
|
}
|
|||
|
|
}
|