feat: 新增地址选择插件及订阅消息等多项功能优化

- 新增地址选择插件,支持地图位置选择并返回地址和经纬度
- 新增微信订阅消息发送功能,支持审核结果、提现通知等场景
- 新增订单支付状态查询、提现收款人姓名保存等接口
- 新增服务区域管理,支持按省市区划分并同步到信息发布
- 新增资金流水和营收报表导出功能
- 新增可用优惠券查询、订单退款拆分处理等功能
- 优化后台角标显示、首页数据统计、UI配置下发等
- 修复订单支付并发问题、地区管理逻辑等
这个提交包含在:
2026-10-22 17:14:56 +08:00
父节点 088a08d43a
当前提交 cb12bf5fc8
共有 180 个文件被更改,包括 9695 次插入 和 1680 次删除

查看文件

@@ -11,7 +11,8 @@ use think\Db;
* GET /api/v1/home/categories H2 分类导航
* GET /api/v1/home/search/hot H3 热门搜索
* GET /api/v1/home/feed H4 信息流(支持 lat/lng + sort=nearby 距离排序)
* GET /api/v1/home/areas H5 区域列表(用户端下拉筛选,读后台「区域管理」)
* GET /api/v1/home/areas H5 服务区域列表(平铺省/市/区县,区县为服务区域)
* GET /api/v1/home/locate H6 按定位坐标匹配服务区域
*/
class Home extends V1
{
@@ -90,28 +91,81 @@ class Home extends V1
}
/**
* H5 区域列表(用户端筛选用)
* GET /api/v1/home/areas → [string]
* 规则:仅 enabled=1,按 sort asc;与后台「区域管理」(fc_area) 同源,
* 前端首页/信息列表/发布页的「区域」选项均由此下发,不再前端写死。
* H5 服务区域列表(平铺:每条记录自带省 / 市 / 区县)
* GET /api/v1/home/areas → [{id,province,city,district,lat,lng}]
* 规则:仅 enabled=1;按省、市、sort、id 排序。
* 区县为最终服务区域;省 / 市由前端按 distinct 值生成级联列。
*/
public function areas()
{
$rows = Db::name('fc_area')
->where('enabled', 1)
->order('sort asc')
->order('province asc, city asc, sort asc, id asc')
->select();
$list = [];
foreach ($rows as $row) {
$list[] = (string)$row['name'];
$list[] = [
'id' => (int)$row['id'],
'province' => (string)$row['province'],
'city' => (string)$row['city'],
'district' => (string)$row['district'],
'lat' => $row['lat'] !== null ? (float)$row['lat'] : null,
'lng' => $row['lng'] !== null ? (float)$row['lng'] : null,
];
}
$this->ok($list);
}
/**
* H6 按定位坐标匹配服务区域(区县)
* GET /api/v1/home/locate?lat=&lng= → {areaId,province,city,district,distance}
* 规则:在启用的服务区域中心点中取最近的一个(Haversine),超出 300km 视为不在服务范围,返回 null。
*/
public function locate()
{
$lat = $this->input('lat', null);
$lng = $this->input('lng', null);
if (!is_numeric($lat) || !is_numeric($lng)) {
$this->ok(null);
}
$lat = (float)$lat;
$lng = (float)$lng;
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180 || ($lat == 0.0 && $lng == 0.0)) {
$this->ok(null);
}
$rows = Db::name('fc_area')
->where('enabled', 1)
->whereNotNull('lat')
->whereNotNull('lng')
->select();
$best = null;
foreach ($rows as $row) {
$distance = $this->haversine($lat, $lng, (float)$row['lat'], (float)$row['lng']);
if ($best === null || $distance < $best['distance']) {
$best = ['row' => $row, 'distance' => $distance];
}
}
if (!$best || $best['distance'] > 300) {
$this->ok(null);
}
$area = $best['row'];
$this->ok([
'areaId' => (int)$area['id'],
'province' => (string)$area['province'],
'city' => (string)$area['city'],
'district' => (string)$area['district'],
'distance' => $best['distance'],
]);
}
/**
* H4 信息流
* GET /api/v1/home/feed {categoryId?,keyword?,area?,sort?,lat?,lng?,page,size}
* GET /api/v1/home/feed {categoryId?,keyword?,areaId?,area?,sort?,lat?,lng?,page,size}
* → {list,page,size,total,hasMore}
* 规则:仅 audit_status=1 AND status=1 AND deletetime IS NULL
* - sort=nearby 时按距离升序(无坐标的信息排最后;请求未带 lat/lng 则回落发布时间倒序);
@@ -124,6 +178,11 @@ class Home extends V1
$categoryId = (int)$this->input('categoryId', 0);
$keyword = trim((string)$this->input('keyword', ''));
$area = trim((string)$this->input('area', ''));
// 服务区域 ID(兼容上一版的 districtId 参数)
$areaId = (int)$this->input('areaId', 0);
if (!$areaId) {
$areaId = (int)$this->input('districtId', 0);
}
$sort = (string)$this->input('sort', '');
// 容错处理:过滤前端小程序可能序列化出的 undefined 与 null 字符串
if ($area === 'undefined' || $area === 'null') {
@@ -146,9 +205,22 @@ class Home extends V1
if ($keyword !== '') {
$query->where('title', 'like', '%' . $keyword . '%');
}
if ($area !== '') {
$query->where('area', $area);
}
$applyArea = function ($q) use ($area, $areaId) {
// 服务区域 = 区县:areaId 优先,兼容旧的 area=区县名称
if ($areaId > 0) {
$row = Db::name('fc_area')->where('id', $areaId)->find();
if ($row) {
$q->where(function ($w) use ($row) {
$w->where('district_id', (int)$row['id'])->whereOr('area', (string)$row['district']);
});
}
return;
}
if ($area !== '') {
$q->where('area', $area);
}
};
$applyArea($query);
$counter = Db::name('fc_post')
->where('audit_status', 1)
@@ -160,9 +232,7 @@ class Home extends V1
if ($keyword !== '') {
$counter->where('title', 'like', '%' . $keyword . '%');
}
if ($area !== '') {
$counter->where('area', $area);
}
$applyArea($counter);
$total = $counter->count();
// 「附近」:距离在 PHP 内用 Haversine 计算后排序(不用 SQL 距离函数,避免 MySQL 兼容问题);
@@ -306,6 +376,7 @@ class Home extends V1
'price' => (float)$row['price'],
'priceUnit' => (string)$row['price_unit'],
'area' => (string)$row['area'],
'areaId' => (int)$row['district_id'],
'address' => (string)$row['address'],
'contact' => $this->maskPhone($row['phone']),
'views' => (int)$row['views'],