文件
fenleixinxi/application/common/library/NationalArea.php
MiAn bb6418f51b feat: 新增电话拨打功能及相关配置,完善农产品分类与地区数据
- 新增商家/信息详情页电话拨打接口,支持每日额度限制与拨打日志记录
- 新增平台免责声明内容及协议接口,完善用户服务协议相关配置
- 新增农产品分类图标与上市角标展示,优化分类管理界面样式
- 新增全国行政区划工具类与城市选择组件,重构地区数据结构
- 新增SSH自检与部署脚本,完善项目部署流程
2026-10-27 17:05:00 +08:00

189 行
6.0 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\common\library;
use think\Db;
/**
* 全国标准省/市/区县服务区域工具类
*
* 规则:
* - 默认全国所有省、市、区县全部开通,无需后台手动维护;
* - 数据源取自 application/extra/national_areas.json(由全国行政区划字典构建,涵盖全国 34 个省级行政区、全部地级市及区县);
* - 同时合并 fa_fc_area 表中历史自定义记录,确保旧 ID 与历史数据 100% 兼容。
*/
class NationalArea
{
/** @var array|null 内存级缓存 */
protected static $allCache = null;
/**
* 获取全国全部省/市/区县平铺列表
*
* @return array<int, array{id:int,province:string,city:string,district:string,lat:float|null,lng:float|null}>
*/
public static function all()
{
if (self::$allCache !== null) {
return self::$allCache;
}
$jsonFile = APP_PATH . 'extra' . DS . 'national_areas.json';
$list = [];
if (is_file($jsonFile)) {
$decoded = json_decode((string)file_get_contents($jsonFile), true);
if (is_array($decoded)) {
$list = $decoded;
}
}
// 合并数据库中已有的自定义启用区域(避免重复)
try {
$dbRows = Db::name('fc_area')
->where('enabled', 1)
->order('sort asc, id asc')
->select();
$seenKey = [];
$seenId = [];
foreach ($list as $idx => $item) {
$k = $item['province'] . '|' . $item['city'] . '|' . $item['district'];
$seenKey[$k] = $idx;
$seenId[(int)$item['id']] = $idx;
}
foreach ($dbRows as $row) {
$p = (string)$row['province'];
$c = (string)$row['city'];
$d = (string)$row['district'];
$k = $p . '|' . $c . '|' . $d;
$id = (int)$row['id'];
if (isset($seenKey[$k])) {
$targetIdx = $seenKey[$k];
if ($row['lat'] !== null && $row['lng'] !== null) {
$list[$targetIdx]['lat'] = (float)$row['lat'];
$list[$targetIdx]['lng'] = (float)$row['lng'];
}
continue;
}
if (!isset($seenId[$id])) {
$list[] = [
'id' => $id,
'province' => $p,
'city' => $c,
'district' => $d,
'lat' => $row['lat'] !== null ? (float)$row['lat'] : null,
'lng' => $row['lng'] !== null ? (float)$row['lng'] : null,
];
}
}
} catch (\Throwable $e) {
// 忽略数据库读取异常,以全国静态字典兜底
}
self::$allCache = $list;
return self::$allCache;
}
/**
* 按区域 ID 查找单条省/市/区县记录
*
* @param int $areaId 区县ID
* @return array|null
*/
public static function findById($areaId)
{
$areaId = (int)$areaId;
if ($areaId <= 0) {
return null;
}
foreach (self::all() as $row) {
if ((int)$row['id'] === $areaId) {
return $row;
}
}
return null;
}
/**
* 按区县名称(及可选省/市)查找匹配记录
*
* @param string $district 区县名称
* @param string $city 可选城市名称
* @param string $province 可选省份名称
* @return array|null
*/
public static function findByName($district, $city = '', $province = '')
{
$district = trim((string)$district);
$city = trim((string)$city);
$province = trim((string)$province);
if ($district === '') {
return null;
}
foreach (self::all() as $row) {
if ($row['district'] === $district) {
if ($city !== '' && $row['city'] !== $city) {
continue;
}
if ($province !== '' && $row['province'] !== $province) {
continue;
}
return $row;
}
}
return null;
}
/**
* 按经纬度坐标(gcj02)匹配最近的全国区县
*
* @param float $lat 纬度
* @param float $lng 经度
* @return array|null {areaId, province, city, district, distance}
*/
public static function locate($lat, $lng)
{
$best = null;
foreach (self::all() as $row) {
if ($row['lat'] === null || $row['lng'] === null) {
continue;
}
$dist = self::haversine($lat, $lng, (float)$row['lat'], (float)$row['lng']);
if ($best === null || $dist < $best['distance']) {
$best = ['row' => $row, 'distance' => $dist];
}
}
if (!$best || $best['distance'] > 1000) {
return null;
}
$area = $best['row'];
return [
'areaId' => (int)$area['id'],
'province' => (string)$area['province'],
'city' => (string)$area['city'],
'district' => (string)$area['district'],
'distance' => $best['distance'],
];
}
/**
* 计算两点间 Haversine 球面距离(km,保留 1 位小数)
*
* @param float $lat1
* @param float $lng1
* @param float $lat2
* @param float $lng2
* @return float
*/
public static function haversine($lat1, $lng1, $lat2, $lng2)
{
$earth = 6371.0;
$rad = M_PI / 180;
$dLat = ($lat2 - $lat1) * $rad;
$dLng = ($lng2 - $lng1) * $rad;
$a = sin($dLat / 2) * sin($dLat / 2)
+ cos($lat1 * $rad) * cos($lat2 * $rad) * sin($dLng / 2) * sin($dLng / 2);
$c = 2 * asin(min(1.0, sqrt($a)));
return round($earth * $c, 1);
}
}