model = new \app\admin\model\Service; $this->view->assign("statusList", $this->model->getStatusList()); // 获取统一服务字典表(fa_service_type,不绑定单一类目,主服务与增项服务同源) $typesPool = Db::name('service_type') ->where('status', 'normal') ->order('weigh desc, id asc') ->select(); $this->view->assign("typesPool", $typesPool); } public function index() { $this->request->filter(['strip_tags', 'trim']); if (false === $this->request->isAjax()) { $cats = Db::name('service_cat') ->where('status', 'normal') ->order('weigh desc, id asc') ->select(); $this->view->assign('catList', $cats); return $this->view->fetch(); } if ($this->request->request('keyField')) { return $this->selectpage(); } [$where, $sort, $order, $offset, $limit] = $this->buildparams(); $list = $this->model ->with(['cat']) ->where($where) ->order($sort, $order) ->paginate($limit); $rows = $list->items(); if (!empty($rows)) { $serviceIds = []; foreach ($rows as $r) { $serviceIds[] = (int)$r['id']; } // 一次性批量查询当前页商品的细分规格,避免 N+1 查询 $allSpecs = Db::name('service_spec') ->field('id,service_id,name,base_price,description') ->where('service_id', 'in', $serviceIds) ->where('status', 'normal') ->order('weigh desc, id asc') ->select(); $specMap = []; foreach ($allSpecs as $sp) { $sid = (int)$sp['service_id']; if (!isset($specMap[$sid])) { $specMap[$sid] = []; } $spPrice = (float)$sp['base_price']; $specMap[$sid][] = $spPrice > 0 ? ($sp['name'] . '(¥' . round($spPrice) . ')') : $sp['name']; } foreach ($rows as &$r) { $sid = (int)$r['id']; $r['specs_list'] = isset($specMap[$sid]) ? $specMap[$sid] : []; // 直接在服务端解析为数组返回,防止前端 bootstrap-table escape:true 将 JSON 双引号转义为 " 导致解析失败 $stArr = []; if (!empty($r['service_types'])) { $stArr = is_array($r['service_types']) ? $r['service_types'] : (json_decode($r['service_types'], true) ?: []); } $adArr = []; if (!empty($r['addons'])) { $adArr = is_array($r['addons']) ? $r['addons'] : (json_decode($r['addons'], true) ?: []); } $r['service_types_list'] = $stArr; $r['addons_list'] = $adArr; } unset($r); } $result = ['total' => $list->total(), 'rows' => $rows]; return json($result); } /** * 添加商品并配置关联服务定价 */ public function add() { if ($this->request->isPost()) { $params = $this->request->post("row/a"); if ($params) { $params = $this->preExcludeFields($params); if ($this->dataLimit && $this->dataLimitFieldAutoFill) { $params[$this->dataLimitField] = $this->auth->id; } $params = $this->formatServiceConfig($params); $result = false; Db::startTrans(); try { $result = $this->model->allowField(true)->save($params); $serviceId = $this->model->id; // 处理商品规格列表 $this->syncSpecs($serviceId, $this->request->post('specs/a', [])); Db::commit(); } catch (ValidateException|PDOException|Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($result !== false) { $this->success(); } else { $this->error(__('No rows were inserted')); } } $this->error(__('Parameter %s can not be empty', '')); } return $this->view->fetch(); } /** * 编辑商品与关联服务定价 */ public function edit($ids = null) { $row = $this->model->get($ids); if (!$row) { $this->error(__('No Results were found')); } $adminIds = $this->getDataLimitAdminIds(); if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) { $this->error(__('You have no permission')); } if ($this->request->isPost()) { $params = $this->request->post("row/a"); if ($params) { $params = $this->preExcludeFields($params); $params = $this->formatServiceConfig($params); $result = false; Db::startTrans(); try { $result = $row->allowField(true)->save($params); // 同步处理商品规格列表 $this->syncSpecs($row->id, $this->request->post('specs/a', [])); Db::commit(); } catch (ValidateException|PDOException|Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($result !== false) { $this->success(); } else { $this->error(__('No rows were updated')); } } $this->error(__('Parameter %s can not be empty', '')); } // 解析当前商品已关联的主服务与增项服务配置 $serviceTypes = []; if (!empty($row->service_types)) { $serviceTypes = is_array($row->service_types) ? $row->service_types : json_decode($row->service_types, true); } $addons = []; if (!empty($row->addons)) { $addons = is_array($row->addons) ? $row->addons : json_decode($row->addons, true); } // 获取已有商品规格 $specs = Db::name('service_spec') ->where('service_id', $row->id) ->where('status', 'normal') ->order('weigh desc, id asc') ->select(); $catName = Db::name('service_cat')->where('id', $row->cat_id)->value('name') ?: '未归类'; $this->view->assign("row", $row); $this->view->assign("catName", $catName); $this->view->assign("serviceTypes", $serviceTypes ?: []); $this->view->assign("assignedAddons", $addons ?: []); $this->view->assign("specs", $specs ?: []); return $this->view->fetch(); } /** * 格式化商品关联的主服务与增项服务 JSON,并自动提取默认主服务价格用于底层索引兼容 */ protected function formatServiceConfig($params) { $parsedTypes = []; if (isset($params['service_types_raw'])) { if (is_string($params['service_types_raw'])) { $parsedTypes = json_decode($params['service_types_raw'], true) ?: []; } else { $parsedTypes = is_array($params['service_types_raw']) ? $params['service_types_raw'] : []; } unset($params['service_types_raw']); if (empty($parsedTypes)) { throw new ValidateException('请至少开启 1 项主服务'); } $dictSupportMap = Db::name('service_type')->column('support_fixed', 'code'); $hasDefault = false; foreach ($parsedTypes as &$pt) { $code = isset($pt['code']) ? (string)$pt['code'] : ''; $supportFixed = isset($dictSupportMap[$code]) ? (int)$dictSupportMap[$code] : (isset($pt['support_fixed']) ? (int)$pt['support_fixed'] : 1); $price = ($supportFixed === 1 && isset($pt['price'])) ? round((float)$pt['price'], 2) : 0.00; $pt['price'] = $price; $pt['support_fixed'] = $supportFixed; if ($supportFixed === 1 && !empty($pt['is_default']) && !$hasDefault) { $pt['is_default'] = 1; $hasDefault = true; } else { $pt['is_default'] = 0; } } unset($pt); if (!$hasDefault && !empty($parsedTypes)) { foreach ($parsedTypes as &$ptDef) { if ((int)$ptDef['support_fixed'] === 1) { $ptDef['is_default'] = 1; $hasDefault = true; break; } } unset($ptDef); if (!$hasDefault) { $parsedTypes[0]['is_default'] = 1; } } $params['service_types'] = json_encode(array_values($parsedTypes), JSON_UNESCAPED_UNICODE); } if (isset($params['addons_raw'])) { $parsedAddons = is_string($params['addons_raw']) ? (json_decode($params['addons_raw'], true) ?: []) : (is_array($params['addons_raw']) ? $params['addons_raw'] : []); unset($params['addons_raw']); $cleanAddons = []; foreach ($parsedAddons as $idx => $ad) { $adName = isset($ad['name']) ? trim((string)$ad['name']) : ''; $adPrice = isset($ad['price']) ? round((float)$ad['price'], 2) : 0.00; if ($adName === '' || $adPrice <= 0) { continue; } $cleanAddons[] = [ 'id' => isset($ad['id']) ? (int)$ad['id'] : ($idx + 1), 'code' => isset($ad['code']) ? (string)$ad['code'] : ('addon_' . ($idx + 1)), 'name' => $adName, 'unit' => !empty($ad['unit']) ? (string)$ad['unit'] : '份', 'price' => $adPrice, ]; } $params['addons'] = json_encode($cleanAddons, JSON_UNESCAPED_UNICODE); } // 自动从关联的主服务(优先默认首选主服务)同步底层 base_price 与 price_type if (!empty($parsedTypes)) { $defItem = $parsedTypes[0]; foreach ($parsedTypes as $pt) { if (!empty($pt['is_default'])) { $defItem = $pt; break; } } $supportFixed = isset($defItem['support_fixed']) ? (int)$defItem['support_fixed'] : 1; $params['price_type'] = $supportFixed === 1 ? 'fixed' : 'quote'; $params['base_price'] = $supportFixed === 1 ? (float)$defItem['price'] : 0.00; } return $params; } /** * 同步商品规格型号表(支持特殊规格单独设价覆盖,留空或0则沿用商品默认服务价) */ protected function syncSpecs($serviceId, $specsInput) { if (empty($serviceId)) return; $now = time(); $keepIds = []; if (!empty($specsInput) && is_array($specsInput)) { $idx = 0; foreach ($specsInput as $item) { $name = trim(isset($item['name']) ? $item['name'] : ''); if ($name === '') continue; $specDesc = isset($item['description']) ? trim($item['description']) : ''; $specPrice = (isset($item['base_price']) && is_numeric($item['base_price']) && (float)$item['base_price'] > 0) ? round((float)$item['base_price'], 2) : 0.00; $specId = isset($item['id']) ? (int)$item['id'] : 0; $weigh = max(0, 100 - $idx * 5); $idx++; if ($specId > 0) { Db::name('service_spec')->where('id', $specId)->where('service_id', $serviceId)->update([ 'name' => $name, 'base_price' => $specPrice, 'description' => $specDesc, 'weigh' => $weigh, 'updatetime' => $now, ]); $keepIds[] = $specId; } else { $newId = Db::name('service_spec')->insertGetId([ 'service_id' => $serviceId, 'name' => $name, 'base_price' => $specPrice, 'description' => $specDesc, 'weigh' => $weigh, 'status' => 'normal', 'createtime' => $now, 'updatetime' => $now, ]); $keepIds[] = $newId; } } } $delQuery = Db::name('service_spec')->where('service_id', $serviceId); if (!empty($keepIds)) { $delQuery->whereNotIn('id', $keepIds); } $delQuery->delete(); } }