validator; } // 枚举 private const MAPS = [ 'BODY' => [ 'SEDAN' => '三厢轿车', 'SUV'=>'SUV', 'HB'=>'两厢/掀背', 'MPV'=>'MPV', 'PICKUP'=>'皮卡' ], 'DRIVE' => [ 'FWD'=>'前驱', 'RWD'=>'后驱', 'AWD'=>'四驱(AWD)', '4WD'=>'四驱(4WD)' ], 'MOTOR_LAYOUT' => [ 'FWD'=>'前置单电机', 'RWD'=>'后置单电机', 'AWD'=>'双电机四驱', 'DM-I'=>'DM-i', 'DM-P'=>'DM-p' ], 'FUEL' => [ 'GAS'=>'汽油', 'HEV'=>'油电混动', 'PHEV'=>'插电混动', 'EV'=>'纯电', 'EREV'=>'增程' ], 'TRANS' => [ 'MT'=>'手动', 'AT'=>'自动', 'CVT'=>'无级', 'DCT'=>'双离合', '1SPEED'=>'单速' ], 'RANGE_STD' => [ 'CLTC'=>'CLTC', 'WLTP'=>'WLTP', 'EPA'=>'EPA', 'NEDC'=>'NEDC' ], 'EMISSION' => [ 'CHN6'=>'国六', 'EURO6'=>'欧6', 'EPA'=>'美国EPA' ], ]; // *_code取中文 public function label(string $dict, ?string $code): ?string { if ($code === null || $code === '') return null; $dict = strtoupper($dict); $code = strtoupper($code); return self::MAPS[$dict][$code] ?? $code; } // 静态映射把 *_code → 中文 private function backfill(array $d): array { $d['body_type'] = $this->label('BODY', $d['body_type_code']); $d['drive_type'] = $this->label('DRIVE', $d['drive_type_code']); $d['motor_layout'] = $this->label('MOTOR_LAYOUT', $d['motor_layout_code']); $d['fuel_type'] = $this->label('FUEL', $d['fuel_type_code']); $d['transmission'] = $this->label('TRANS', $d['transmission_code']); $d['range_std'] = $this->label('RANGE_STD', $d['range_std_code']); $d['emission_std'] = $this->label('EMISSION', $d['emission_std_code']); return $d; } protected function buildQueryConfig(Request $request, string $scene = 'default'): array { $p = $request->all(); $config = []; $config['model'] = $this->modelClass; switch ($scene) { case 'default': $config['where']['is_active'] = 1; $config['order']['id'] = 'desc'; break; case 'index': $config['where']['is_active'] = 1; $config['order']['id'] = 'desc'; break; case 'show': $config['where']['id'] = $p['id']; $config['where']['is_active'] = 1; break; case 'store': break; case 'update': $config['where']['id'] = $p['id']; break; case 'destroy': $config['where']['id'] = $p['id']; break; } return $config; } public function beforeServiceStore($data, Request $request) { $data = $this->backfill($data); if (empty($data['model_code'])) { $data['model_code'] = ModelNaming::buildModelCode($data); } if (empty($data['model_name'])) { $data['model_name'] = ModelNaming::buildModelName($data); } $data['sku_code'] = SkuCodeBuilder::build($data); $data['corp_id'] = 0; $data['created_by'] = $request->user->uid; $data['updated_by'] = $request->user->uid; // 指纹 $data['fingerprint'] = Fingerprint::makeFromArray($data); return $data; } protected function afterServiceStore($res) { return $res; } public function beforeServiceUpdate($data, Request $request) { $data = $this->backfill($data); if (empty($data['model_code'])) { $data['model_code'] = ModelNaming::buildModelCode($data); } if (empty($data['model_name'])) { $data['model_name'] = ModelNaming::buildModelName($data); } // 填充系统字段 $data['updated_by'] = $request->user->uid; // 指纹 $data['fingerprint'] = Fingerprint::makeFromArray($data); return $data; } protected function afterServiceUpdate($res) { return $res; } /** SKU 字段名 → dict 名(用于 i18n 取词 & 必填校验) */ protected static array $SKU_FIELD_TO_DICT = [ 'brand_code' => 'BRAND', 'series_code' => 'SERIES', 'model_code' => 'MODEL', 'trim_code' => 'TRIM', 'trim_level_code' => 'TRIM_LEVEL', 'edition_code' => 'EDITION', 'fuel_type_code' => 'FUEL', 'drive_type_code' => 'DRIVE', 'transmission_code' => 'TRANS', 'motor_layout_code' => 'MOTOR_LAYOUT', 'body_type_code' => 'BODY', 'range_std_code' => 'RANGE_STD', 'emission_std_code' => 'EMISSION', 'color_exterior_code'=> 'COLOR', 'color_interior_code'=> 'COLOR', ]; /** 创建 SKU(含指纹与字典校验) */ public static function create(array $p, int $userId=0, int $corpId=0): VehicleSku { // SkuValidator::validateForCreate($p); // 必填字典:由映射表控制(一般 BRAND/SERIES/FUEL) $requiredDicts = MappingService::requiredDicts(); foreach (self::$SKU_FIELD_TO_DICT as $field => $dict) { if (!MappingService::enabled($dict)) continue; $isRequired = in_array($dict, $requiredDicts, true); if ($isRequired) { DictService::ensureExists($dict, $p[$field] ?? null, $field); } } // 填充系统字段 $now = time(); $p['sku_code'] = $p['sku_code'] ?? ('SKU'.date('Ymd').'-'.substr(bin2hex(random_bytes(3)),0,6)); $p['corp_id'] = $p['corp_id'] ?? $corpId; $p['created_by'] = $p['created_by'] ?? $userId; $p['updated_by'] = $p['updated_by'] ?? $userId; $p['created_at'] = $p['created_at'] ?? $now; $p['updated_at'] = $p['updated_at'] ?? $now; // 指纹 $p['fingerprint'] = Fingerprint::make($p /*, $scopeCorp=true/false*/); return DB::connection()->transaction(function () use ($p) { $sku = new VehicleSku($p); $sku->save(); return $sku; }); } /** 更新 SKU(部分字段;指纹随事实变动而刷新) */ public static function update(int $id, array $p, int $userId=0): VehicleSku { SkuValidator::validateForUpdate($p); $sku = VehicleSku::findOrFail($id); // 若传入了必填 dict 的 code,需要确认字典存在 $requiredDicts = MappingService::requiredDicts(); foreach (self::$SKU_FIELD_TO_DICT as $field => $dict) { if (!array_key_exists($field, $p)) continue; if (!MappingService::enabled($dict)) continue; $isRequired = in_array($dict, $requiredDicts, true); if ($isRequired) { DictService::ensureExists($dict, $p[$field], $field); } else { // 可选的:若传了 code 则也校验存在 if (($p[$field] ?? null) !== null && $p[$field] !== '') { $exists = \app\model\DictTerm::where('dict',$dict)->where('code',$p[$field])->exists(); if (!$exists) throw new \InvalidArgumentException("字典不存在:{$dict}.{$p[$field]}"); } } } // 刷新系统字段 $p['updated_by'] = $userId; $p['updated_at'] = time(); // 赋值保存 $sku->fill($p); // 只要影响到指纹的事实字段变动,就重算指纹 $fingerFields = [ 'brand_code','series_code','model_year','fuel_type_code', 'drive_type_code','transmission_code','body_type_code', 'engine_disp_l','engine_turbo','battery_kwh','range_km', 'range_std_code','emission_std_code', 'corp_id' ]; foreach ($fingerFields as $f) { if (array_key_exists($f, $p)) { $sku->fingerprint = Fingerprint::make($sku->toArray()); break; } } $sku->save(); return $sku; } /** 详情(带 i18n label 与标准标题) */ public static function detail(int $id, string $locale='zh-CN'): array { $sku = VehicleSku::findOrFail($id); $data = $sku->toArray(); $labelOf = function(string $field) use ($data, $locale) { $dict = self::$SKU_FIELD_TO_DICT[$field] ?? null; if (!$dict) return null; $code = $data[$field] ?? null; // 若是品牌/车系,允许以中文兜底 $fallback = null; if ($field === 'brand_code') $fallback = $data['brand_name'] ?? null; if ($field === 'series_code') $fallback = $data['series_name'] ?? null; // 版型/等级/特别版允许中文兜底 if ($field === 'trim_code') $fallback = $data['trim'] ?? null; if ($field === 'trim_level_code') $fallback = $data['trim_level'] ?? null; if ($field === 'edition_code') $fallback = $data['edition_name'] ?? null; // 颜色兜底中文 if ($field === 'color_exterior_code') $fallback = $data['color_exterior_text'] ?? null; if ($field === 'color_interior_code') $fallback = $data['color_interior_text'] ?? null; return \app\service\DictService::label($dict, $code, $locale, $fallback); }; $i18n = [ 'brand_label' => $labelOf('brand_code'), 'series_label' => $labelOf('series_code'), 'model_label' => \app\service\DictService::label('MODEL', $data['model_code'] ?? null, $locale, $data['model_name'] ?? null), 'trim_label' => $labelOf('trim_code'), 'trim_level_label' => $labelOf('trim_level_code'), 'edition_label' => $labelOf('edition_code'), 'fuel_label' => $labelOf('fuel_type_code'), 'drive_label' => $labelOf('drive_type_code'), 'trans_label' => $labelOf('transmission_code'), 'motor_layout_label' => $labelOf('motor_layout_code'), 'body_label' => $labelOf('body_type_code'), 'range_std_label' => $labelOf('range_std_code'), 'emission_label' => $labelOf('emission_std_code'), 'color_exterior_label' => $labelOf('color_exterior_code'), 'color_interior_label' => $labelOf('color_interior_code'), 'listing_title' => TitleBuilder::build($data, $locale), ]; return ['sku'=>$data, 'i18n'=>$i18n]; } /** 列表查询(支持基础筛选 & i18n 列) */ public static function list(array $q, string $locale='zh-CN', int $page=1, int $pageSize=20): array { $builder = VehicleSku::query(); // 常用筛选 if (!empty($q['brand_code'])) $builder->where('brand_code', $q['brand_code']); if (!empty($q['series_code'])) $builder->where('series_code', $q['series_code']); if (!empty($q['fuel_type_code'])) $builder->where('fuel_type_code', $q['fuel_type_code']); if (!empty($q['model_year'])) $builder->where('model_year', (int)$q['model_year']); if (isset($q['is_active'])) $builder->where('is_active', (int)$q['is_active']); $builder->orderByDesc('created_at'); $total = (clone $builder)->count(); $rows = $builder->forPage($page, $pageSize)->get()->toArray(); // 附加 i18n 字段(轻量) foreach ($rows as &$row) { $row['brand_label'] = DictService::label('BRAND', $row['brand_code'] ?? null, $locale, $row['brand_name'] ?? null); $row['series_label'] = DictService::label('SERIES', $row['series_code'] ?? null, $locale, $row['series_name'] ?? null); $row['fuel_label'] = DictService::label('FUEL', $row['fuel_type_code'] ?? null, $locale, null); $row['listing_title']= TitleBuilder::build($row, $locale); } return ['total'=>$total, 'list'=>$rows, 'page'=>$page, 'page_size'=>$pageSize]; } }