r117/app/controllers/AdminController.php · 约 13305 字符
1<?php2/**3 * 后台管理 API(商户端)4 * 写操作统一经 guardWrite() 拦截:ADMIN_READ_ONLY=true 时拒绝写入(后台只读)。5 */6class AdminController extends Controller {7 private function imgStr($arr) { return is_array($arr) ? implode('|', $arr) : (string)$arr; }89 public function login() {10 $user = trim((string)$this->input('user', ''));11 $pass = trim((string)$this->input('pass', ''));12 if ($user === ADMIN_USER && $pass === ADMIN_PASS) {13 $this->json(['ok' => 1, 'token' => 'demo-token', 'msg' => '登录成功']);14 }15 $this->json(['ok' => 0, 'msg' => '账号或密码错误'], 401);16 }1718 // 后台全局配置(含只读开关状态)19 public function config() {20 $this->json([21 'ok' => 1,22 'read_only' => defined('ADMIN_READ_ONLY') ? ADMIN_READ_ONLY : false,23 'admin_user' => ADMIN_USER,24 'site_name' => SITE_NAME25 ]);26 }2728 // 数据看板29 public function dashboard() {30 $d = [];31 $d['sales_today'] = (float)$this->db->query("SELECT COALESCE(SUM(pay_amount),0) FROM orders WHERE status<>'canceled' AND DATE(created_at)=DATE('now','localtime')")->fetchColumn();32 $d['orders_today'] = (int)$this->db->query("SELECT COUNT(*) FROM orders WHERE DATE(created_at)=DATE('now','localtime')")->fetchColumn();33 $d['order_total'] = (int)$this->db->query("SELECT COUNT(*) FROM orders")->fetchColumn();34 $d['member_total'] = (int)$this->db->query("SELECT COUNT(*) FROM members")->fetchColumn();35 $d['product_total'] = (int)$this->db->query("SELECT COUNT(*) FROM products")->fetchColumn();36 $d['review_total'] = (int)$this->db->query("SELECT COUNT(*) FROM reviews")->fetchColumn();37 $d['pending_orders'] = (int)$this->db->query("SELECT COUNT(*) FROM orders WHERE status IN ('paid','making')")->fetchColumn();38 // 近 7 日销售额39 $trend = $this->db->query("SELECT DATE(created_at) AS d, COALESCE(SUM(pay_amount),0) AS s FROM orders WHERE created_at >= datetime('now','-6 days','localtime') AND status<>'canceled' GROUP BY DATE(created_at) ORDER BY d")->fetchAll();40 $d['trend'] = $trend;41 // 热销 TOP542 $top = $this->db->query("SELECT p.name, SUM(oi.qty) AS qty FROM order_items oi JOIN products p ON p.id=oi.product_id GROUP BY oi.product_id ORDER BY qty DESC LIMIT 5")->fetchAll();43 $d['top_products'] = $top;44 // 会员等级分布45 $lv = $this->db->query("SELECT level, COUNT(*) AS c FROM members GROUP BY level")->fetchAll();46 $d['level_dist'] = $lv;47 $this->json(['ok' => 1, 'data' => $d]);48 }4950 // ---- 商品 ----51 public function products_list() {52 $rows = $this->db->query("SELECT p.*, c.name AS kind_name FROM products p LEFT JOIN categories c ON c.id=p.kind_id ORDER BY p.id DESC")->fetchAll();53 $data = array_map(function ($p) {54 return [55 'id' => $p['id'], 'name' => $p['name'], 'kind_name' => $p['kind_name'],56 'price' => (float)$p['price'], 'original_price' => (float)$p['original_price'],57 'sales' => (int)$p['sales'], 'stock' => (int)$p['stock'], 'status' => (int)$p['status'],58 'is_new' => (int)$p['is_new'], 'is_hot' => (int)$p['is_hot'], 'is_recommend' => (int)$p['is_recommend'],59 'scenes' => $p['scenes']60 ];61 }, $rows);62 $this->json(['ok' => 1, 'data' => $data]);63 }6465 public function product_save() {66 $this->guardWrite();67 $id = (int)$this->input('id', 0);68 $data = [69 'name' => trim((string)$this->input('name', '未命名蛋糕')),70 'kind_id' => (int)$this->input('kind_id', 0),71 'scenes' => trim((string)$this->input('scenes', '')),72 'price' => (float)$this->input('price', 0),73 'original_price' => (float)$this->input('original_price', 0),74 'stock' => (int)$this->input('stock', 99),75 'is_new' => (int)$this->input('is_new', 0),76 'is_hot' => (int)$this->input('is_hot', 0),77 'is_recommend' => (int)$this->input('is_recommend', 0),78 'description' => trim((string)$this->input('description', '')),79 'ingredients' => trim((string)$this->input('ingredients', '')),80 'allergen' => trim((string)$this->input('allergen', '')),81 'shelf_life' => trim((string)$this->input('shelf_life', '')),82 'advice' => trim((string)$this->input('advice', '')),83 'images' => $this->imgStr($this->input('images', '🎂')),84 'tags' => trim((string)$this->input('tags', '')),85 ];86 if ($id) {87 (new ProductModel())->update($id, $data);88 $this->json(['ok' => 1, 'id' => $id, 'msg' => '已更新']);89 } else {90 $newId = (new ProductModel())->create($data);91 // 新商品默认生成 4 尺寸 × 原味 SKU92 $ins = $this->db->prepare("INSERT INTO skus (product_id,size,flavor,price,stock) VALUES (?,?,?,?,?)");93 $base = $data['price'] ?: 168;94 foreach (['6寸' => 0.7, '8寸' => 1.0, '10寸' => 1.35, '12寸' => 1.7] as $sz => $mul) {95 $ins->execute([$newId, $sz, '原味', round($base * $mul), 99]);96 }97 $this->json(['ok' => 1, 'id' => $newId, 'msg' => '已新增']);98 }99 }100101 public function product_delete() {102 $this->guardWrite();103 $id = (int)$this->input('id', 0);104 (new ProductModel())->delete($id);105 $this->db->prepare("DELETE FROM skus WHERE product_id=?")->execute([$id]);106 $this->json(['ok' => 1]);107 }108109 public function product_toggle() {110 $this->guardWrite();111 $id = (int)$this->input('id', 0);112 $st = $this->db->prepare("UPDATE products SET status = 1 - status WHERE id=?");113 $st->execute([$id]);114 $this->json(['ok' => 1]);115 }116117 // ---- 订单 ----118 public function orders_list() {119 $status = trim((string)$this->input('status', ''));120 $sql = "SELECT * FROM orders";121 $params = [];122 if ($status) { $sql .= " WHERE status=?"; $params[] = $status; }123 $sql .= " ORDER BY id DESC LIMIT 200";124 $st = $this->db->prepare($sql);125 $st->execute($params);126 $this->json(['ok' => 1, 'data' => $st->fetchAll()]);127 }128129 public function order_detail() {130 $id = (int)$this->input('id', 0);131 $o = (new OrderModel())->find($id);132 if (!$o) $this->json(['ok' => 0, 'msg' => '订单不存在'], 404);133 $it = $this->db->prepare("SELECT * FROM order_items WHERE order_id=?");134 $it->execute([$id]);135 $this->json(['ok' => 1, 'order' => array_merge($o, ['items' => $it->fetchAll()])]);136 }137138 public function order_update_status() {139 $this->guardWrite();140 $id = (int)$this->input('id', 0);141 $status = trim((string)$this->input('status', ''));142 (new OrderModel())->update($id, ['status' => $status]);143 $this->json(['ok' => 1, 'msg' => '状态已更新']);144 }145146 // ---- 会员 ----147 public function members_list() {148 $rows = (new MemberModel())->all();149 $data = array_map(fn($m) => [150 'id' => $m['id'], 'username' => $m['username'], 'phone' => $m['phone'],151 'level' => $m['level'], 'points' => (int)$m['points'], 'balance' => (float)$m['balance'],152 'growth' => (int)$m['growth'], 'birthday' => $m['birthday'], 'status' => (int)$m['status']153 ], $rows);154 $this->json(['ok' => 1, 'data' => $data]);155 }156157 public function member_save() {158 $this->guardWrite();159 $id = (int)$this->input('id', 0);160 $data = [161 'username' => trim((string)$this->input('username', '')),162 'phone' => trim((string)$this->input('phone', '')),163 'level' => trim((string)$this->input('level', 'normal')),164 'points' => (int)$this->input('points', 0),165 'balance' => (float)$this->input('balance', 0),166 'growth' => (int)$this->input('growth', 0),167 'birthday' => trim((string)$this->input('birthday', '')),168 ];169 if ($id) { (new MemberModel())->update($id, $data); }170 else { $id = (new MemberModel())->create($data); }171 $this->json(['ok' => 1, 'id' => $id]);172 }173174 public function member_delete() {175 $this->guardWrite();176 (new MemberModel())->delete((int)$this->input('id', 0));177 $this->json(['ok' => 1]);178 }179180 // ---- 优惠券 ----181 public function coupons_list() {182 $rows = (new CouponModel())->all();183 $data = array_map(fn($c) => [184 'id' => $c['id'], 'name' => $c['name'], 'type' => $c['type'],185 'threshold' => (float)$c['threshold'], 'value' => (float)$c['value'],186 'discount' => (float)$c['discount'], 'valid_days' => (int)$c['valid_days'],187 'total' => (int)$c['total'], 'received' => (int)$c['received'], 'used' => (int)$c['used'],188 'status' => (int)$c['status']189 ], $rows);190 $this->json(['ok' => 1, 'data' => $data]);191 }192193 public function coupon_save() {194 $this->guardWrite();195 $id = (int)$this->input('id', 0);196 $data = [197 'name' => trim((string)$this->input('name', '新券')),198 'type' => trim((string)$this->input('type', 'full_reduce')),199 'threshold' => (float)$this->input('threshold', 0),200 'value' => (float)$this->input('value', 0),201 'discount' => (float)$this->input('discount', 1),202 'valid_days' => (int)$this->input('valid_days', 30),203 'total' => (int)$this->input('total', 999),204 'status' => (int)$this->input('status', 1),205 ];206 if ($id) { (new CouponModel())->update($id, $data); }207 else { $id = (new CouponModel())->create($data); }208 $this->json(['ok' => 1, 'id' => $id]);209 }210211 public function coupon_delete() {212 $this->guardWrite();213 (new CouponModel())->delete((int)$this->input('id', 0));214 $this->json(['ok' => 1]);215 }216217 // ---- 评价 ----218 public function reviews_list() {219 $st = $this->db->query("SELECT r.*, p.name AS pname, m.username FROM reviews r LEFT JOIN products p ON p.id=r.product_id LEFT JOIN members m ON m.id=r.member_id ORDER BY r.id DESC");220 $this->json(['ok' => 1, 'data' => $st->fetchAll()]);221 }222223 public function review_reply() {224 $this->guardWrite();225 $id = (int)$this->input('id', 0);226 (new ReviewModel())->update($id, ['reply' => trim((string)$this->input('reply', ''))]);227 $this->json(['ok' => 1]);228 }229230 public function review_delete() {231 $this->guardWrite();232 (new ReviewModel())->delete((int)$this->input('id', 0));233 $this->json(['ok' => 1]);234 }235236 // ---- 轮播 ----237 public function banners_list() {238 $this->json(['ok' => 1, 'data' => (new BannerModel())->all()]);239 }240241 public function banner_save() {242 $this->guardWrite();243 $id = (int)$this->input('id', 0);244 $data = [245 'title' => trim((string)$this->input('title', '')),246 'image' => trim((string)$this->input('image', '🎂')),247 'link' => trim((string)$this->input('link', '#home')),248 'sort' => (int)$this->input('sort', 0),249 'status' => (int)$this->input('status', 1),250 ];251 if ($id) { (new BannerModel())->update($id, $data); }252 else { $id = (new BannerModel())->create($data); }253 $this->json(['ok' => 1, 'id' => $id]);254 }255256 public function banner_delete() {257 $this->guardWrite();258 (new BannerModel())->delete((int)$this->input('id', 0));259 $this->json(['ok' => 1]);260 }261262 // ---- 定制需求 ----263 public function custom_list() {264 $st = $this->db->query("SELECT c.*, m.username FROM custom_cakes c LEFT JOIN members m ON m.id=c.member_id ORDER BY c.id DESC");265 $this->json(['ok' => 1, 'data' => $st->fetchAll()]);266 }267268 public function custom_update() {269 $this->guardWrite();270 $id = (int)$this->input('id', 0);271 (new CustomCakeModel())->update($id, ['status' => trim((string)$this->input('status', 'approved'))]);272 $this->json(['ok' => 1]);273 }274275 // ---- 设置 ----276 public function settings_get() {277 $rs = $this->db->query("SELECT k,v FROM settings");278 $data = [];279 foreach ($rs as $row) $data[$row['k']] = $row['v'];280 $data['read_only'] = defined('ADMIN_READ_ONLY') ? ADMIN_READ_ONLY : false;281 $this->json(['ok' => 1, 'data' => $data]);282 }283284 public function settings_save() {285 $this->guardWrite();286 $keys = ['shop_name','phone','address','delivery_fee','free_delivery_amount','delivery_range_km','delivery_advance_hours','open_time','notice'];287 foreach ($keys as $k) {288 $v = $this->input($k, null);289 if ($v !== null) {290 $this->db->prepare("INSERT INTO settings (k,v) VALUES (?,?) ON CONFLICT(k) DO UPDATE SET v=excluded.v")291 ->execute([$k, trim((string)$v)]);292 }293 }294 $this->json(['ok' => 1, 'msg' => '设置已保存']);295 }296}297