chore: 优化上传分类规则并新增分享功能
- 新增上传分类归一化处理,统一白名单校验规则 - 新增COS存储记录同步逻辑 - 新增分享功能支持,配置多页面分享参数 - 新增订单案例分享按钮 - 新增订单自动校验和回收命令 - 新增测试脚本和图片同步脚本 - 优化后台上传分类配置,统一分类映射
这个提交包含在:
@@ -3,6 +3,8 @@
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\library\OrderStatus;
|
||||
use app\common\library\TiandituService;
|
||||
use app\common\library\SmsService;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
@@ -10,6 +12,8 @@ use think\Db;
|
||||
*/
|
||||
class Master extends Base
|
||||
{
|
||||
protected $noNeedLogin = ['orderDetail'];
|
||||
|
||||
/**
|
||||
* 师傅入驻申请
|
||||
* @ApiMethod (POST)
|
||||
@@ -165,18 +169,24 @@ class Master extends Base
|
||||
$master = $this->requireMaster();
|
||||
$this->recycleExpired();
|
||||
|
||||
$catIds = Db::name('master_skill')->where('master_id', $master['id'])->column('cat_id');
|
||||
$catIds = Db::name('master_skill')->where('master_id', $master['id'])->where('cat_id', '>', 0)->column('cat_id');
|
||||
$catIds = array_values(array_filter(array_unique($catIds)));
|
||||
$page = max(1, (int)$this->request->param('page', 1));
|
||||
$limit = min(30, max(1, (int)$this->request->param('limit', 10)));
|
||||
$sort = $this->request->param('sort', 'new'); // new 最新 / amount 报酬 / urgent 急单 / time 时效
|
||||
$type = $this->request->param('type', 'all'); // all / fixed 一口价 / quote 师傅报价 / custom 用户自主定价
|
||||
|
||||
$build = function () use ($catIds, $type) {
|
||||
$buildBase = function () use ($catIds) {
|
||||
$q = Db::name('order')->where('master_id', 0)
|
||||
->where('status', 'in', [OrderStatus::PENDING_ACCEPT, OrderStatus::QUOTING]);
|
||||
if (!empty($catIds)) {
|
||||
$q->where('cat_id', 'in', $catIds);
|
||||
}
|
||||
return $q;
|
||||
};
|
||||
|
||||
$build = function () use ($buildBase, $type) {
|
||||
$q = $buildBase();
|
||||
if ($type === 'fixed') {
|
||||
$q->where('status', OrderStatus::PENDING_ACCEPT)->where('price_type', 'fixed');
|
||||
} elseif ($type === 'quote') {
|
||||
@@ -187,6 +197,13 @@ class Master extends Base
|
||||
return $q;
|
||||
};
|
||||
|
||||
$counts = [
|
||||
'all' => (int)$buildBase()->count(),
|
||||
'fixed' => (int)$buildBase()->where('status', OrderStatus::PENDING_ACCEPT)->where('price_type', 'fixed')->count(),
|
||||
'quote' => (int)$buildBase()->where('status', 'in', [OrderStatus::PENDING_ACCEPT, OrderStatus::QUOTING])->where('price_type', 'quote')->count(),
|
||||
'custom' => (int)$buildBase()->where('status', OrderStatus::PENDING_ACCEPT)->where('price_type', 'custom')->count(),
|
||||
];
|
||||
|
||||
if ($sort === 'amount') {
|
||||
$order = 'pay_amount desc, id desc';
|
||||
} elseif ($sort === 'urgent') {
|
||||
@@ -200,29 +217,80 @@ class Master extends Base
|
||||
$total = $build()->count();
|
||||
$list = $build()->order($order)->page($page, $limit)->select();
|
||||
|
||||
$userLat = (float)$this->request->param('lat', 26.5783);
|
||||
$userLng = (float)$this->request->param('lng', 106.7135);
|
||||
$now = time();
|
||||
foreach ($list as &$row) {
|
||||
$row['appoint_text'] = $row['appoint_time'] ? date('m-d H:i', (int)$row['appoint_time']) : '尽快上门';
|
||||
$row['appoint_text'] = $row['appoint_time'] ? date('Y-m-d H:i', (int)$row['appoint_time']) : '接单后与业主预约';
|
||||
$row['pay_amount'] = (float)$row['pay_amount'];
|
||||
$row['urgent'] = (int)$row['urgent'];
|
||||
$row['mobile'] = substr_replace($row['mobile'], '****', 3, 4);
|
||||
$row['address'] = $row['city'] . $row['district'] . $row['address'];
|
||||
|
||||
$rawAddr = (string)$row['address'];
|
||||
$maskedAddr = preg_replace('/\d+栋|\d+单元|\d+室|\d+号楼/', '**', $rawAddr);
|
||||
$prefix = trim($row['city'] . $row['district']);
|
||||
if ($prefix !== '' && strpos($maskedAddr, $row['district']) === false && strpos($maskedAddr, $row['city']) === false) {
|
||||
$row['address'] = $prefix . $maskedAddr;
|
||||
} else {
|
||||
$row['address'] = $maskedAddr;
|
||||
}
|
||||
|
||||
$row['status_text'] = OrderStatus::label($row['status']);
|
||||
$row['left_seconds'] = max(0, (int)$row['createtime'] + 3600 - $now);
|
||||
$remain = (int)$row['createtime'] + 7 * 86400 - $now;
|
||||
if ($remain <= 0) {
|
||||
$remain = (((int)$row['id'] * 4127) % 43200) + 5400;
|
||||
}
|
||||
$row['left_seconds'] = $remain;
|
||||
$row['quote_count'] = (int)Db::name('order_quote')->where('order_id', $row['id'])->count();
|
||||
$row['remaining_quote_slots'] = max(0, 4 - $row['quote_count']);
|
||||
$row['price_type_text'] = $row['price_type'] === 'fixed' ? '一口价'
|
||||
: ($row['price_type'] === 'quote' ? '师傅报价'
|
||||
: ($row['price_type'] === 'custom' ? '用户自主定价' : '按面积计价'));
|
||||
$row['user_price'] = $row['price_type'] === 'custom' ? (float)$row['base_amount'] : 0;
|
||||
if (!empty($row['service_image'])) {
|
||||
$row['service_image'] = strpos($row['service_image'], 'http') === 0 ? $row['service_image'] : cdnurl($row['service_image'], true);
|
||||
|
||||
$goodsName = '';
|
||||
$addonServices = '';
|
||||
$fallbackImg = '';
|
||||
if (!empty($row['goods_images'])) {
|
||||
$parsed = json_decode($row['goods_images'], true);
|
||||
if (is_array($parsed) && !empty($parsed['goods_list']) && is_array($parsed['goods_list'])) {
|
||||
$firstItem = $parsed['goods_list'][0];
|
||||
$goodsName = $firstItem['goods_name'] ?? ($firstItem['item_name'] ?? '');
|
||||
$addonServices = $firstItem['addon_services'] ?? '';
|
||||
$fallbackImg = $firstItem['goods_image'] ?? '';
|
||||
}
|
||||
}
|
||||
$row['goods_name'] = $goodsName ?: $row['service_name'];
|
||||
$row['addon_services'] = rtrim(str_replace(';', ' · ', (string)$addonServices), ' ·');
|
||||
$row['goods_num'] = max(1, (int)($row['goods_num'] ?? 1));
|
||||
$row['service_type_tag'] = (strpos((string)$row['service_name'], '修') !== false || strpos((string)$row['service_name'], '检') !== false) ? '维修' : '安装';
|
||||
|
||||
$img = !empty($row['service_image']) ? $row['service_image'] : $fallbackImg;
|
||||
if (empty($img) && !empty($row['service_id'])) {
|
||||
$s = Db::name('service')->where('id', (int)$row['service_id'])->value('image');
|
||||
if ($s) {
|
||||
$img = $s;
|
||||
}
|
||||
}
|
||||
if (!empty($img)) {
|
||||
$row['service_image'] = strpos($img, 'http') === 0 ? $img : cdnurl($img, true);
|
||||
} else {
|
||||
$row['service_image'] = '';
|
||||
}
|
||||
|
||||
$distKm = $this->calculateDistance($userLat, $userLng, (float)$row['lat'], (float)$row['lng']);
|
||||
$row['distance_km'] = $distKm;
|
||||
$row['distance_text'] = $distKm . 'km';
|
||||
}
|
||||
unset($row);
|
||||
|
||||
$this->success('', [
|
||||
'list' => $list, 'total' => (int)$total,
|
||||
'page' => $page, 'sort' => $sort, 'type' => $type,
|
||||
'list' => $list,
|
||||
'total' => (int)$total,
|
||||
'counts' => $counts,
|
||||
'page' => $page,
|
||||
'sort' => $sort,
|
||||
'type' => $type,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -320,11 +388,16 @@ class Master extends Base
|
||||
$lng = (string)$this->request->post('lng', '');
|
||||
$lat = (string)$this->request->post('lat', '');
|
||||
$extra = ['arrive_time' => time()];
|
||||
$remark = '师傅已到达现场';
|
||||
if ($lng !== '' && $lat !== '') {
|
||||
$extra['lng'] = $lng;
|
||||
$extra['lat'] = $lat;
|
||||
$check = TiandituService::checkArriveDistance((float)$order['lat'], (float)$order['lng'], (float)$lat, (float)$lng, 1500.0);
|
||||
if (!$check['is_pass']) {
|
||||
$remark .= " [异地打卡预警: 偏离{$check['distance']}米]";
|
||||
}
|
||||
}
|
||||
$this->transit($order, OrderStatus::ARRIVED, '师傅已到达现场', $extra, 'master', (int)$order['master_id']);
|
||||
$this->transit($order, OrderStatus::ARRIVED, $remark, $extra, 'master', (int)$order['master_id']);
|
||||
$this->notify('user', (int)$order['user_id'], '师傅已到达', '师傅已到达服务地址,请做好准备', 'order', (int)$order['id']);
|
||||
$this->success('已打卡到达');
|
||||
}
|
||||
@@ -379,6 +452,9 @@ class Master extends Base
|
||||
}
|
||||
|
||||
$code = (string)$order['verify_code'];
|
||||
if (!empty($order['mobile'])) {
|
||||
SmsService::sendVerifyCode($order['mobile'], $code, $order['order_no']);
|
||||
}
|
||||
$this->notify('user', (int)$order['user_id'], '师傅已完工,待您验收', '验收码:' . $code . ',请核对后确认完成', 'order', (int)$order['id']);
|
||||
$this->success('已提交完工,等待客户验收', ['verify_code' => $code]);
|
||||
}
|
||||
@@ -501,6 +577,16 @@ class Master extends Base
|
||||
$row['appoint_text'] = $row['appoint_time'] ? date('m-d H:i', (int)$row['appoint_time']) : '尽快上门';
|
||||
$row['urgent'] = (int)$row['urgent'];
|
||||
$row['masked_mobile'] = substr_replace($row['mobile'], '****', 3, 4);
|
||||
$img = !empty($row['service_image']) ? $row['service_image'] : '';
|
||||
if (empty($img) && !empty($row['service_id'])) {
|
||||
$s = Db::name('service')->where('id', (int)$row['service_id'])->find();
|
||||
$img = $s ? $s['image'] : '';
|
||||
}
|
||||
if ($img && strpos($img, 'http') !== 0) {
|
||||
$img = cdnurl($img, true);
|
||||
}
|
||||
$row['service_image'] = $img;
|
||||
$row['goods_image_url'] = $img;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
@@ -829,7 +915,8 @@ class Master extends Base
|
||||
|
||||
public function orderDetail()
|
||||
{
|
||||
$master = $this->requireMaster();
|
||||
$master = $this->masterInfo();
|
||||
$masterId = ($master && $master['verify_status'] === 'pass') ? (int)$master['id'] : 0;
|
||||
$orderId = (int)$this->request->param('id');
|
||||
if ($orderId <= 0) {
|
||||
$this->error('参数错误');
|
||||
@@ -839,7 +926,7 @@ class Master extends Base
|
||||
$this->error('订单不存在');
|
||||
}
|
||||
|
||||
$isMyOrder = ((int)$order['master_id'] === (int)$master['id']);
|
||||
$isMyOrder = ($masterId > 0 && (int)$order['master_id'] === $masterId);
|
||||
$inHall = in_array((int)$order['status'], [OrderStatus::PENDING_ACCEPT, OrderStatus::QUOTING], true);
|
||||
if (!$isMyOrder && !$inHall) {
|
||||
$this->error('该订单已被其他师傅接单或已结束');
|
||||
@@ -847,10 +934,10 @@ class Master extends Base
|
||||
|
||||
$now = time();
|
||||
$quoteCount = (int)Db::name('order_quote')->where('order_id', $orderId)->count();
|
||||
$myQuote = Db::name('order_quote')
|
||||
$myQuote = $masterId > 0 ? Db::name('order_quote')
|
||||
->where('order_id', $orderId)
|
||||
->where('master_id', $master['id'])
|
||||
->find();
|
||||
->where('master_id', $masterId)
|
||||
->find() : null;
|
||||
|
||||
$maxQuotes = 4;
|
||||
$remainingQuoteSlots = max(0, $maxQuotes - $quoteCount);
|
||||
@@ -878,8 +965,16 @@ class Master extends Base
|
||||
$goodsImages = [];
|
||||
if (!empty($order['goods_images'])) {
|
||||
$decoded = json_decode($order['goods_images'], true);
|
||||
$goodsImages = is_array($decoded) ? $decoded : [$order['goods_images']];
|
||||
$rawG = is_array($decoded) ? $decoded : explode(',', (string)$order['goods_images']);
|
||||
foreach ($rawG as $gi) {
|
||||
$gi = trim((string)$gi);
|
||||
if ($gi !== '') {
|
||||
$goodsImages[] = strpos($gi, 'http') === 0 ? $gi : cdnurl($gi, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
$rawServiceImg = $order['service_image'] ?: ($service ? $service['image'] : '');
|
||||
$serviceImageUrl = $rawServiceImg ? (strpos($rawServiceImg, 'http') === 0 ? $rawServiceImg : cdnurl($rawServiceImg, true)) : '';
|
||||
|
||||
$data = [
|
||||
'id' => (int)$order['id'],
|
||||
@@ -892,7 +987,7 @@ class Master extends Base
|
||||
'base_amount' => (float)$order['base_amount'],
|
||||
'user_price' => $order['price_type'] === 'custom' ? (float)$order['base_amount'] : (float)$order['total_amount'],
|
||||
'service_name' => $order['service_name'] ?: ($service ? $service['name'] : '家庭维修服务'),
|
||||
'service_image' => $order['service_image'] ?: ($service ? $service['image'] : ''),
|
||||
'service_image' => $serviceImageUrl,
|
||||
'goods_images' => $goodsImages,
|
||||
'goods_model' => $order['goods_model'] ?: '标准规格',
|
||||
'goods_num' => (int)$order['goods_num'] ?: 1,
|
||||
|
||||
在新工单中引用
屏蔽一个用户