数据库: - db/schema_phase1.sql 16 张业务表(类目/服务/地址/师傅/订单/流水/取证/报价/评价/券/钱包/提现/结算/消息) - db/seed_phase1.sql 15 类目 + 32 服务项目 + 新人券(幂等) 后端 API (application/api/controller): - Base/Auth/Home/Address/Order/Master/Review - app/common/library/OrderStatus 11 状态机 + 流转矩阵 - application/api/common.php PHP 8.5 兼容垫片(E_DEPRECATED 不再升级为异常) 测试: - tests/e2e_flow.js 用户下单→支付→师傅抢单→履约→验收→结算→评价 全链路 65 项断言 - 含异常用例:越权/重复操作/非法状态流转/2h 未联系自动回流
211 行
7.6 KiB
PHP
211 行
7.6 KiB
PHP
<?php
|
|
|
|
namespace app\api\controller;
|
|
|
|
use think\Db;
|
|
|
|
/**
|
|
* 首页 / 服务类目 / 服务项目 / 费用试算
|
|
*/
|
|
class Home extends Base
|
|
{
|
|
protected $noNeedLogin = ['index', 'cats', 'services', 'detail'];
|
|
|
|
/**
|
|
* 首页聚合:类目 + 热门服务 + 我的角标
|
|
*/
|
|
public function index()
|
|
{
|
|
$cats = Db::name('service_cat')
|
|
->field('id,name,icon')
|
|
->where('status', 'normal')
|
|
->order('weigh desc,id asc')
|
|
->select();
|
|
|
|
$hot = Db::name('service')
|
|
->field('id,cat_id,name,price_type,base_price,max_price,unit,image,sales')
|
|
->where('status', 'normal')
|
|
->order('sales desc,id asc')
|
|
->limit(6)
|
|
->select();
|
|
|
|
$data = [
|
|
'cats' => $cats,
|
|
'hot' => $this->formatServices($hot),
|
|
'banners' => [
|
|
['id' => 1, 'title' => '家装季 · 双11狂欢大促', 'desc' => '下单立减 · 满额再返券', 'date' => '10.23 - 11.15'],
|
|
],
|
|
'badge' => ['coupon' => 0, 'unread' => 0],
|
|
];
|
|
|
|
if ($this->auth->isLogin()) {
|
|
$uid = $this->uid();
|
|
$data['badge']['coupon'] = (int)Db::name('user_coupon')->where('user_id', $uid)->where('status', 'unused')->count();
|
|
$data['badge']['unread'] = (int)Db::name('message')->where('receive_type', 'user')->where('receive_id', $uid)->where('is_read', 0)->count();
|
|
}
|
|
|
|
$this->success('', $data);
|
|
}
|
|
|
|
/**
|
|
* 全部类目
|
|
*/
|
|
public function cats()
|
|
{
|
|
$cats = Db::name('service_cat')
|
|
->field('id,name,icon')
|
|
->where('status', 'normal')
|
|
->order('weigh desc,id asc')
|
|
->select();
|
|
$this->success('', ['list' => $cats]);
|
|
}
|
|
|
|
/**
|
|
* 类目下的服务列表
|
|
*/
|
|
public function services()
|
|
{
|
|
$catId = (int)$this->request->param('cat_id');
|
|
$keyword = trim((string)$this->request->param('keyword'));
|
|
if ($catId <= 0 && $keyword === '') {
|
|
$this->error('请选择服务类目');
|
|
}
|
|
$query = Db::name('service')->where('status', 'normal');
|
|
if ($catId > 0) {
|
|
$query->where('cat_id', $catId);
|
|
}
|
|
if ($keyword !== '') {
|
|
$query->where('name', 'like', '%' . $keyword . '%');
|
|
}
|
|
$list = $query->order('weigh desc,sales desc,id asc')->select();
|
|
$this->success('', ['list' => $this->formatServices($list)]);
|
|
}
|
|
|
|
/**
|
|
* 服务详情
|
|
*/
|
|
public function detail()
|
|
{
|
|
$id = (int)$this->request->param('id');
|
|
$service = Db::name('service')->where('id', $id)->where('status', 'normal')->find();
|
|
if (!$service) {
|
|
$this->error('服务不存在');
|
|
}
|
|
$cat = Db::name('service_cat')->where('id', $service['cat_id'])->find();
|
|
$service['cat_name'] = $cat ? $cat['name'] : '';
|
|
$this->success('', ['detail' => $this->formatService($service)]);
|
|
}
|
|
|
|
/**
|
|
* 费用试算(基础价 + 楼层费 + 加急费 - 优惠)
|
|
*/
|
|
public function calc()
|
|
{
|
|
$serviceId = (int)$this->request->param('service_id');
|
|
$service = Db::name('service')->where('id', $serviceId)->where('status', 'normal')->find();
|
|
if (!$service) {
|
|
$this->error('服务不存在');
|
|
}
|
|
$result = $this->calcAmount($service, [
|
|
'floor' => (int)$this->request->param('floor', 1),
|
|
'has_elevator' => (int)$this->request->param('has_elevator', 1),
|
|
'urgent' => (int)$this->request->param('urgent', 0),
|
|
], (int)$this->request->param('coupon_id', 0), $this->uid());
|
|
|
|
$this->success('', $result);
|
|
}
|
|
|
|
/**
|
|
* 金额计算(下单与试算共用,保证前后端口径一致)
|
|
*/
|
|
public static function calcAmount($service, $params, $couponId = 0, $uid = 0)
|
|
{
|
|
$base = (float)$service['base_price'];
|
|
$floor = max(0, (int)$params['floor']);
|
|
$freeFloor = (int)$service['free_floor'];
|
|
$floorFee = (float)$service['floor_fee'];
|
|
|
|
$floorAmount = 0;
|
|
if ((int)$params['has_elevator'] === 0 && $floor > $freeFloor) {
|
|
$floorAmount = ($floor - $freeFloor) * $floorFee;
|
|
}
|
|
|
|
$urgentAmount = (int)$params['urgent'] === 1 ? round($base * 0.2, 2) : 0;
|
|
$total = round($base + $floorAmount + $urgentAmount, 2);
|
|
|
|
$discount = 0;
|
|
$couponInfo = null;
|
|
if ($couponId > 0 && $uid > 0) {
|
|
$uc = Db::name('user_coupon')->where('id', $couponId)->where('user_id', $uid)->where('status', 'unused')->find();
|
|
if ($uc) {
|
|
$coupon = Db::name('coupon')->where('id', $uc['coupon_id'])->where('status', 'normal')->find();
|
|
$notExpired = $coupon && (int)$coupon['end_time'] === 0 ? true : ($coupon && $coupon['end_time'] > time());
|
|
if ($notExpired && $total >= (float)$coupon['min_amount']) {
|
|
$discount = $coupon['type'] === 'cash'
|
|
? (float)$coupon['amount']
|
|
: round($total * (1 - (float)$coupon['discount']), 2);
|
|
$discount = min($discount, $total);
|
|
$couponInfo = ['id' => (int)$uc['id'], 'name' => $coupon['name']];
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'price_type' => $service['price_type'],
|
|
'base_amount' => round($base, 2),
|
|
'floor_amount' => round($floorAmount, 2),
|
|
'urgent_amount' => round($urgentAmount, 2),
|
|
'total_amount' => $total,
|
|
'discount_amount' => round($discount, 2),
|
|
'pay_amount' => max(0, round($total - $discount, 2)),
|
|
'coupon' => $couponInfo,
|
|
'free_floor' => $freeFloor,
|
|
'floor_fee' => round($floorFee, 2),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 列表格式化
|
|
*/
|
|
public function formatServices($list)
|
|
{
|
|
$ret = [];
|
|
foreach ($list as $row) {
|
|
$ret[] = $this->formatService($row);
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* 单项格式化(价格文案统一由此输出)
|
|
*/
|
|
public function formatService($row)
|
|
{
|
|
$type = $row['price_type'];
|
|
$priceText = '¥' . (float)$row['base_price'] . '起';
|
|
if ($type === 'range') {
|
|
$priceText = '¥' . (float)$row['base_price'] . '-' . (float)$row['max_price'] . '/' . $row['unit'];
|
|
} elseif ($type === 'quote') {
|
|
$priceText = '¥' . (float)$row['base_price'] . '起 · 师傅报价';
|
|
}
|
|
return [
|
|
'id' => (int)$row['id'],
|
|
'cat_id' => (int)$row['cat_id'],
|
|
'name' => $row['name'],
|
|
'cat_name' => isset($row['cat_name']) ? $row['cat_name'] : '',
|
|
'image' => $row['image'],
|
|
'price_type' => $type,
|
|
'price_type_text' => $type === 'fixed' ? '一口价' : ($type === 'quote' ? '师傅报价' : '按面积计价'),
|
|
'base_price' => (float)$row['base_price'],
|
|
'max_price' => (float)$row['max_price'],
|
|
'unit' => $row['unit'],
|
|
'price_text' => $priceText,
|
|
'sales' => (int)$row['sales'],
|
|
'floor_fee' => isset($row['floor_fee']) ? (float)$row['floor_fee'] : 0,
|
|
'free_floor' => isset($row['free_floor']) ? (int)$row['free_floor'] : 0,
|
|
'need_model' => isset($row['need_model']) ? (int)$row['need_model'] : 1,
|
|
'description' => isset($row['description']) ? $row['description'] : '',
|
|
];
|
|
}
|
|
}
|