r117/app/controllers/ApiController.php · 约 20957 字符
1<?php2/**3 * 业务 API(H5 前端调用)4 * 所有输出统一 {ok:1, ...} / {ok:0, msg}5 */6class ApiController extends Controller {7 private function imgArr($s) {8 return array_values(array_filter(explode('|', (string)$s), fn($x) => trim($x) !== ''));9 }1011 // 首页轮播12 public function banners() {13 $list = (new BannerModel())->all();14 $data = array_map(fn($b) => [15 'id' => $b['id'], 'title' => $b['title'], 'image' => $b['image'], 'link' => $b['link']16 ], $list);17 $this->json(['ok' => 1, 'data' => $data]);18 }1920 // 分类:场景 / 品类 / 尺寸21 public function categories() {22 $cats = (new CategoryModel())->all();23 $scenes = []; $kinds = []; $sizes = [];24 foreach ($cats as $c) {25 if ($c['type'] === 'scene') $scenes[] = $c['name'];26 elseif ($c['type'] === 'kind') $kinds[] = ['id' => $c['id'], 'name' => $c['name']];27 elseif ($c['type'] === 'size') $sizes[] = $c['name'];28 }29 $this->json(['ok' => 1, 'scenes' => $scenes, 'kinds' => $kinds, 'sizes' => $sizes]);30 }3132 // 商品列表(支持 品类/场景/关键词/标签/排序 过滤)33 public function products() {34 $kind = (int)$this->input('kind', 0);35 $scene = trim((string)$this->input('scene', ''));36 $kw = trim((string)$this->input('kw', ''));37 $tag = trim((string)$this->input('tag', ''));38 $sort = trim((string)$this->input('sort', 'default'));39 $sql = "SELECT * FROM products WHERE status=1";40 $params = [];41 if ($kind) { $sql .= " AND kind_id=?"; $params[] = $kind; }42 if ($scene) { $sql .= " AND scenes LIKE ?"; $params[] = "%$scene%"; }43 if ($kw) { $sql .= " AND (name LIKE ? OR tags LIKE ?)"; $params[] = "%$kw%"; $params[] = "%$kw%"; }44 if ($tag) { $sql .= " AND $tag=1"; }45 if ($sort === 'sales') $sql .= " ORDER BY sales DESC";46 elseif ($sort === 'price_asc') $sql .= " ORDER BY price ASC";47 elseif ($sort === 'price_desc') $sql .= " ORDER BY price DESC";48 elseif ($sort === 'rating') $sql .= " ORDER BY rating DESC";49 else $sql .= " ORDER BY is_hot DESC, sales DESC";50 $st = $this->db->prepare($sql);51 $st->execute($params);52 $rows = $st->fetchAll();53 $data = array_map(function ($p) {54 return [55 'id' => $p['id'], 'name' => $p['name'], 'price' => (float)$p['price'],56 'original_price' => (float)$p['original_price'], 'sales' => (int)$p['sales'],57 'rating' => (float)$p['rating'], 'stock' => (int)$p['stock'],58 'is_new' => (int)$p['is_new'], 'is_hot' => (int)$p['is_hot'],59 'is_recommend' => (int)$p['is_recommend'], 'tags' => $p['tags'],60 'images' => $this->imgArr($p['images'])61 ];62 }, $rows);63 $this->json(['ok' => 1, 'data' => $data]);64 }6566 // 商品详情(含 SKU 与 评价)67 public function product_detail() {68 $id = (int)$this->input('id', 0);69 $p = (new ProductModel())->find($id);70 if (!$p) $this->json(['ok' => 0, 'msg' => '商品不存在'], 404);71 $skus = (new SkuModel())->all(['product_id' => $id]);72 $skus = array_map(fn($s) => [73 'id' => $s['id'], 'size' => $s['size'], 'flavor' => $s['flavor'],74 'price' => (float)$s['price'], 'stock' => (int)$s['stock']75 ], $skus);76 $revs = $this->db->prepare("SELECT r.*, m.username FROM reviews r LEFT JOIN members m ON m.id=r.member_id WHERE r.product_id=? AND r.status=1 ORDER BY r.id DESC");77 $revs->execute([$id]);78 $reviews = array_map(fn($r) => [79 'id' => $r['id'], 'rating' => (int)$r['rating'], 'content' => $r['content'],80 'username' => $r['username'] ?: '匿名', 'created_at' => $r['created_at'],81 'images' => $this->imgArr($r['images']), 'reply' => $r['reply']82 ], $revs->fetchAll());83 $this->json(['ok' => 1, 'product' => [84 'id' => $p['id'], 'name' => $p['name'], 'price' => (float)$p['price'],85 'original_price' => (float)$p['original_price'], 'sales' => (int)$p['sales'],86 'rating' => (float)$p['rating'], 'stock' => (int)$p['stock'],87 'is_new' => (int)$p['is_new'], 'is_hot' => (int)$p['is_hot'],88 'is_recommend' => (int)$p['is_recommend'], 'description' => $p['description'],89 'ingredients' => $p['ingredients'], 'allergen' => $p['allergen'],90 'shelf_life' => $p['shelf_life'], 'advice' => $p['advice'],91 'images' => $this->imgArr($p['images']), 'tags' => $p['tags'], 'scenes' => $p['scenes']92 ], 'skus' => $skus, 'reviews' => $reviews]);93 }9495 // 搜索96 public function search() {97 $kw = trim((string)$this->input('kw', ''));98 $st = $this->db->prepare("SELECT * FROM products WHERE status=1 AND (name LIKE ? OR tags LIKE ? OR scenes LIKE ?) ORDER BY sales DESC");99 $st->execute(["%$kw%", "%$kw%", "%$kw%"]);100 $data = array_map(fn($p) => [101 'id' => $p['id'], 'name' => $p['name'], 'price' => (float)$p['price'],102 'sales' => (int)$p['sales'], 'images' => $this->imgArr($p['images'])103 ], $st->fetchAll());104 $this->json(['ok' => 1, 'data' => $data]);105 }106107 // 推荐(热销 + 推荐)108 public function recommend() {109 $st = $this->db->prepare("SELECT * FROM products WHERE status=1 AND (is_recommend=1 OR is_hot=1) ORDER BY is_recommend DESC, sales DESC LIMIT 8");110 $st->execute();111 $data = array_map(fn($p) => [112 'id' => $p['id'], 'name' => $p['name'], 'price' => (float)$p['price'],113 'images' => $this->imgArr($p['images'])114 ], $st->fetchAll());115 $this->json(['ok' => 1, 'data' => $data]);116 }117118 // ---- 购物车 ----119 public function cart_add() {120 $member_id = (int)$this->input('member_id', 0);121 $product_id = (int)$this->input('product_id', 0);122 $sku_id = (int)$this->input('sku_id', 0);123 $qty = max(1, (int)$this->input('qty', 1));124 if (!$product_id || !$sku_id) $this->json(['ok' => 0, 'msg' => '参数缺失'], 400);125 (new CartModel())->create([126 'member_id' => $member_id, 'product_id' => $product_id, 'sku_id' => $sku_id, 'qty' => $qty127 ]);128 $this->json(['ok' => 1, 'msg' => '已加入购物车']);129 }130131 public function cart_list() {132 $member_id = (int)$this->input('member_id', 0);133 $st = $this->db->prepare("SELECT c.*, p.name AS pname, p.images, s.size, s.flavor, s.price FROM carts c134 JOIN products p ON p.id=c.product_id JOIN skus s ON s.id=c.sku_id135 WHERE c.member_id=? ORDER BY c.id DESC");136 $st->execute([$member_id]);137 $rows = $st->fetchAll();138 $data = array_map(fn($r) => [139 'id' => $r['id'], 'product_id' => $r['product_id'], 'sku_id' => $r['sku_id'],140 'qty' => (int)$r['qty'], 'name' => $r['pname'], 'images' => $this->imgArr($r['images']),141 'size' => $r['size'], 'flavor' => $r['flavor'], 'price' => (float)$r['price'],142 'subtotal' => round((float)$r['price'] * (int)$r['qty'], 2)143 ], $rows);144 $total = array_sum(array_column($data, 'subtotal'));145 $this->json(['ok' => 1, 'data' => $data, 'total' => round($total, 2)]);146 }147148 public function cart_update() {149 $id = (int)$this->input('id', 0);150 $qty = max(1, (int)$this->input('qty', 1));151 (new CartModel())->update($id, ['qty' => $qty]);152 $this->json(['ok' => 1]);153 }154155 public function cart_remove() {156 $id = (int)$this->input('id', 0);157 (new CartModel())->delete($id);158 $this->json(['ok' => 1]);159 }160161 // ---- 会员 ----162 public function member_register() {163 $username = trim((string)$this->input('username', ''));164 $phone = trim((string)$this->input('phone', ''));165 if (!$username) $this->json(['ok' => 0, 'msg' => '请填写昵称'], 400);166 $id = (new MemberModel())->create([167 'username' => $username, 'phone' => $phone, 'level' => 'normal',168 'points' => 0, 'balance' => 0, 'growth' => 0, 'status' => 1169 ]);170 $this->json(['ok' => 1, 'member_id' => $id]);171 }172173 public function member_info() {174 $member_id = (int)$this->input('member_id', 2);175 $m = (new MemberModel())->find($member_id);176 if (!$m) $this->json(['ok' => 0, 'msg' => '会员不存在'], 404);177 $levelName = ['normal' => '普通会员', 'silver' => '银卡会员', 'gold' => '金卡会员', 'diamond' => '钻石会员'][$m['level']] ?? '普通会员';178 $coupons = $this->db->prepare("SELECT COUNT(*) FROM coupon_user WHERE member_id=? AND status='unused'");179 $coupons->execute([$member_id]);180 $this->json(['ok' => 1, 'member' => [181 'id' => $m['id'], 'username' => $m['username'], 'phone' => $m['phone'],182 'level' => $m['level'], 'level_name' => $levelName,183 'points' => (int)$m['points'], 'balance' => (float)$m['balance'],184 'growth' => (int)$m['growth'], 'birthday' => $m['birthday']185 ], 'coupons_count' => (int)$coupons->fetchColumn()]);186 }187188 // ---- 优惠券 ----189 public function coupon_list() {190 $list = (new CouponModel())->all();191 $list = array_filter($list, fn($c) => (int)$c['status'] === 1);192 $data = array_map(function ($c) {193 $desc = $c['type'] === 'discount' ? ($c['discount'] * 10) . '折'194 : ($c['type'] === 'no_threshold' ? '无门槛¥' . $c['value']195 : ($c['threshold'] > 0 ? '满' . $c['threshold'] . '减' . $c['value'] : '¥' . $c['value'] . '券'));196 return ['id' => $c['id'], 'name' => $c['name'], 'type' => $c['type'],197 'threshold' => (float)$c['threshold'], 'value' => (float)$c['value'],198 'discount' => (float)$c['discount'], 'valid_days' => (int)$c['valid_days'],199 'received' => (int)$c['received'], 'total' => (int)$c['total']];200 }, $list);201 $this->json(['ok' => 1, 'data' => array_values($data)]);202 }203204 public function coupon_receive() {205 $member_id = (int)$this->input('member_id', 0);206 $coupon_id = (int)$this->input('coupon_id', 0);207 $c = (new CouponModel())->find($coupon_id);208 if (!$c || (int)$c['status'] !== 1) $this->json(['ok' => 0, 'msg' => '券不存在或已下架'], 400);209 if ((int)$c['received'] >= (int)$c['total']) $this->json(['ok' => 0, 'msg' => '已被领完'], 400);210 // 防重复领取(同一券每人限领 1 张)211 $chk = $this->db->prepare("SELECT COUNT(*) FROM coupon_user WHERE member_id=? AND coupon_id=?");212 $chk->execute([$member_id, $coupon_id]);213 if ($chk->fetchColumn() > 0) $this->json(['ok' => 0, 'msg' => '您已领取过该券'], 400);214 (new CouponUserModel())->create(['coupon_id' => $coupon_id, 'member_id' => $member_id, 'status' => 'unused']);215 $this->db->prepare("UPDATE coupons SET received=received+1 WHERE id=?")->execute([$coupon_id]);216 $this->json(['ok' => 1, 'msg' => '领取成功']);217 }218219 public function coupon_my() {220 $member_id = (int)$this->input('member_id', 0);221 $st = $this->db->prepare("SELECT cu.*, c.name, c.type, c.threshold, c.value, c.discount, c.valid_days222 FROM coupon_user cu JOIN coupons c ON c.id=cu.coupon_id WHERE cu.member_id=? ORDER BY cu.id DESC");223 $st->execute([$member_id]);224 $data = array_map(function ($r) {225 $desc = $r['type'] === 'discount' ? ($r['discount'] * 10) . '折'226 : ($r['type'] === 'no_threshold' ? '无门槛¥' . $r['value']227 : ($r['threshold'] > 0 ? '满' . $r['threshold'] . '减' . $r['value'] : '¥' . $r['value'] . '券'));228 return ['id' => $r['id'], 'coupon_id' => $r['coupon_id'], 'name' => $r['name'],229 'type' => $r['type'], 'threshold' => (float)$r['threshold'], 'value' => (float)$r['value'],230 'discount' => (float)$r['discount'], 'status' => $r['status'], 'desc' => $desc];231 }, $st->fetchAll());232 $this->json(['ok' => 1, 'data' => $data]);233 }234235 // ---- 订单 ----236 public function order_create() {237 $member_id = (int)$this->input('member_id', 0);238 $items = $this->input('items', []);239 if (!is_array($items) || !$items) $this->json(['ok' => 0, 'msg' => '购物车为空'], 400);240 $contact_name = trim((string)$this->input('contact_name', ''));241 $phone = trim((string)$this->input('phone', ''));242 if (!$contact_name || !$phone) $this->json(['ok' => 0, 'msg' => '请填写收货人信息'], 400);243 $delivery_type = $this->input('delivery_type', 'delivery');244 $delivery_time = trim((string)$this->input('delivery_time', ''));245 $card_msg = trim((string)$this->input('card_msg', ''));246 $remark = trim((string)$this->input('remark', ''));247 $coupon_user_id = (int)$this->input('coupon_user_id', 0);248 $points_used = max(0, (int)$this->input('points_used', 0));249250 $set = [];251 $rs = $this->db->query("SELECT k,v FROM settings");252 foreach ($rs as $row) $set[$row['k']] = $row['v'];253 $fee_cfg = (float)($set['delivery_fee'] ?? 15);254 $free_cfg = (float)($set['free_delivery_amount'] ?? 199);255256 $total = 0; $orderItems = [];257 foreach ($items as $it) {258 $pid = (int)($it['product_id'] ?? 0);259 $sid = (int)($it['sku_id'] ?? 0);260 $qty = max(1, (int)($it['qty'] ?? 1));261 $sku = (new SkuModel())->find($sid);262 $prod = (new ProductModel())->find($pid);263 if (!$sku || !$prod) continue;264 $sub = round((float)$sku['price'] * $qty, 2);265 $total += $sub;266 $orderItems[] = [267 'product_id' => $pid, 'sku_id' => $sid, 'name' => $prod['name'],268 'size' => $sku['size'], 'flavor' => $sku['flavor'],269 'price' => (float)$sku['price'], 'qty' => $qty, 'subtotal' => $sub270 ];271 }272 if (!$orderItems) $this->json(['ok' => 0, 'msg' => '下单失败:商品无效'], 400);273274 // 配送费275 $delivery_fee = 0;276 if ($delivery_type === 'delivery' && $total < $free_cfg) $delivery_fee = $fee_cfg;277278 // 优惠券279 $coupon_amount = 0;280 if ($coupon_user_id) {281 $cu = $this->db->prepare("SELECT cu.*, c.threshold, c.value, c.discount, c.type FROM coupon_user cu JOIN coupons c ON c.id=cu.coupon_id WHERE cu.id=? AND cu.member_id=? AND cu.status='unused'");282 $cu->execute([$coupon_user_id, $member_id]);283 $coupon = $cu->fetch();284 if ($coupon) {285 if ($coupon['type'] === 'discount') {286 $coupon_amount = round($total * (1 - (float)$coupon['discount']), 2);287 } elseif ($coupon['type'] === 'no_threshold') {288 $coupon_amount = (float)$coupon['value'];289 } elseif ((float)$coupon['threshold'] <= $total) {290 $coupon_amount = (float)$coupon['value'];291 }292 }293 }294 // 积分抵扣(100 积分 = 1 元,最多抵 10% 订单额)295 $points_amount = 0;296 if ($points_used > 0) {297 $m = (new MemberModel())->find($member_id);298 $points_used = min($points_used, (int)$m['points']);299 $points_amount = round($points_used / 100, 2);300 $maxPoints = floor($total * 0.1 * 100);301 if ($points_amount > $total * 0.1) { $points_used = $maxPoints; $points_amount = round($maxPoints / 100, 2); }302 }303304 $pay_amount = round(max(0, $total + $delivery_fee - $coupon_amount - $points_amount), 2);305 $order_no = date('YmdHis') . str_pad($member_id, 3, '0', STR_PAD_LEFT) . rand(10, 99);306307 $oid = (new OrderModel())->create([308 'order_no' => $order_no, 'member_id' => $member_id, 'contact_name' => $contact_name,309 'phone' => $phone, 'address' => trim((string)$this->input('address', '')),310 'delivery_type' => $delivery_type, 'delivery_time' => $delivery_time,311 'card_msg' => $card_msg, 'remark' => $remark, 'coupon_id' => $coupon_user_id,312 'coupon_amount' => $coupon_amount, 'points_used' => $points_used,313 'total_amount' => round($total + $delivery_fee, 2), 'pay_amount' => $pay_amount,314 'status' => 'paid'315 ]);316 foreach ($orderItems as $oi) {317 (new OrderItemModel())->create(array_merge($oi, ['order_id' => $oid]));318 $this->db->prepare("UPDATE skus SET stock=stock-?, sales=sales+? WHERE id=?")319 ->execute([$oi['qty'], $oi['qty'], $oi['sku_id']]);320 }321 // 清空购物车(用户维度)322 $this->db->prepare("DELETE FROM carts WHERE member_id=?")->execute([$member_id]);323 // 优惠券标记已用324 if ($coupon_user_id) $this->db->prepare("UPDATE coupon_user SET status='used', used_at=datetime('now','localtime') WHERE id=?")->execute([$coupon_user_id]);325 // 积分:消费得积分(1 元 = 1 分)+ 成长值326 if ($member_id) {327 $earn = (int)$pay_amount;328 $this->db->prepare("UPDATE members SET points=points+?, growth=growth+? WHERE id=?")329 ->execute([$earn, $earn, $member_id]);330 }331 $this->json(['ok' => 1, 'order_id' => $oid, 'order_no' => $order_no, 'pay_amount' => $pay_amount]);332 }333334 public function order_list() {335 $member_id = (int)$this->input('member_id', 0);336 $status = trim((string)$this->input('status', ''));337 $sql = "SELECT * FROM orders WHERE member_id=?";338 $params = [$member_id];339 if ($status) { $sql .= " AND status=?"; $params[] = $status; }340 $sql .= " ORDER BY id DESC";341 $st = $this->db->prepare($sql);342 $st->execute($params);343 $data = [];344 foreach ($st->fetchAll() as $o) {345 $it = $this->db->prepare("SELECT * FROM order_items WHERE order_id=?");346 $it->execute([$o['id']]);347 $data[] = array_merge($o, ['items' => $it->fetchAll()]);348 }349 $this->json(['ok' => 1, 'data' => $data]);350 }351352 public function order_detail() {353 $id = (int)$this->input('id', 0);354 $o = (new OrderModel())->find($id);355 if (!$o) $this->json(['ok' => 0, 'msg' => '订单不存在'], 404);356 $it = $this->db->prepare("SELECT * FROM order_items WHERE order_id=?");357 $it->execute([$id]);358 $this->json(['ok' => 1, 'order' => array_merge($o, ['items' => $it->fetchAll()])]);359 }360361 public function order_cancel() {362 $id = (int)$this->input('id', 0);363 $o = (new OrderModel())->find($id);364 if (!$o) $this->json(['ok' => 0, 'msg' => '订单不存在'], 404);365 if (!in_array($o['status'], ['pending', 'paid', 'making'])) {366 $this->json(['ok' => 0, 'msg' => '当前状态不可取消'], 400);367 }368 (new OrderModel())->update($id, ['status' => 'canceled']);369 $this->json(['ok' => 1, 'msg' => '已取消']);370 }371372 public function order_review() {373 $order_id = (int)$this->input('order_id', 0);374 $product_id = (int)$this->input('product_id', 0);375 $member_id = (int)$this->input('member_id', 0);376 $rating = max(1, min(5, (int)$this->input('rating', 5)));377 $content = trim((string)$this->input('content', ''));378 (new ReviewModel())->create([379 'order_id' => $order_id, 'product_id' => $product_id, 'member_id' => $member_id,380 'rating' => $rating, 'content' => $content, 'images' => '[]', 'status' => 1381 ]);382 (new OrderModel())->update($order_id, ['status' => 'done']);383 $this->json(['ok' => 1, 'msg' => '评价成功']);384 }385386 // ---- 定制蛋糕 ----387 public function custom_submit() {388 $member_id = (int)$this->input('member_id', 0);389 $image = trim((string)$this->input('image', ''));390 $text = trim((string)$this->input('text', ''));391 $shape = trim((string)$this->input('shape', ''));392 $size = trim((string)$this->input('size', '8寸'));393 $note = trim((string)$this->input('note', ''));394 (new CustomCakeModel())->create([395 'member_id' => $member_id, 'image' => $image, 'text' => $text,396 'shape' => $shape, 'size' => $size, 'note' => $note, 'status' => 'pending'397 ]);398 $this->json(['ok' => 1, 'msg' => '定制需求已提交,客服将尽快与您确认']);399 }400401 // ---- 店铺设置 ----402 public function settings() {403 $rs = $this->db->query("SELECT k,v FROM settings");404 $data = [];405 foreach ($rs as $row) $data[$row['k']] = $row['v'];406 $this->json(['ok' => 1, 'data' => $data]);407 }408}409