Files
CloudHome-Ops/lib/data/region_data.dart

146 lines
4.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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];
}
}