param([ 'compatible_model', 'model', 'type_id', 'part_id', 'brand_name', 'page/d' => 1, 'size/d' => 10 ]); $list = ProductCompatModel::withoutField([ 'language_id', 'deleted_at' ]) ->with([ 'types' => function ($query) { $query->field(['id', 'name']); }, 'parts' => function ($query) { $query->field(['id', 'name']); }, 'compatible_models' => function ($query) { $query->field(['compat_id', 'compatible_model']); } ]) ->language(request()->lang_id) ->where(function ($query) use($params) { if (!empty($params['type_id'])) { $query->typeId($params['type_id']); } if (!empty($params['part_id'])) { $query->partId($params['part_id']); } }) ->withSearch(['model', 'brand_name', 'compatible_model'], [ 'model' => $params['model'] ?? null, 'brand_name' => $params['brand_name'] ?? null, 'compatible_model' => $params['compatible_model'] ?? null, ]) ->order(['updated_at' => 'desc']) ->paginate([ 'list_rows' => $params['size'], 'page' => $params['page'], ]) ?->bindAttr('types', ['type_name' => 'name']) ?->bindAttr('parts', ['part_name' => 'name']) ?->hidden(['types', 'parts']) ?->each(function ($item) { $compatible_models = []; foreach ($item['compatible_models'] as $model) { $compatible_models[] = $model['compatible_model']; } $item['compatible_models'] = implode(',', $compatible_models); }); return success("获取成功", $list); } /** * 导入产品兼容性数据 */ public function import() { // 获取上传文件 $file = request()->file('file'); if (empty($file)) { return error('请上传文件'); } $lang_id = request()->lang_id; // 读取文件 $keys_map = [ 'A' => 'type_name', 'B' => 'part_name', 'C' => 'brand_name', 'D' => 'level_name', 'E' => 'spec_name', 'F' => 'series_name', 'G' => 'model', 'H' => 'frequency', 'I' => 'capacity', 'J' => 'compatible_models', ]; $chunk = 500; // 每批次处理的条数 $items = 0; // 已处理的条数 $xlsx_data = []; $xlsx_reader = xlsx_stream_reader($file->getRealPath(), 2, $keys_map, true); \think\facade\Db::startTrans(); try { foreach ($xlsx_reader as $row) { $items++; $row['seq_no'] = $items; // 记录行序号,防止后续顺序打乱 $xlsx_data[] = $row; if ($items % $chunk == 0) { // 每500条,为一批次进行处理 $this->handleImport($xlsx_data, $lang_id); $xlsx_data = []; } } if (!empty($xlsx_data)) { // 处理剩余的 $this->handleImport($xlsx_data, $lang_id); } } catch (\Throwable $th) { \think\facade\Db::rollback(); return error($th->getMessage()); } \think\facade\Db::commit(); return success('操作成功'); } private function handleImport(array $xlsx_data, int $lang_id): void { list( $models, $exists_compat_ids, $compat_models, $compat_datas ) = $this->matchExistsCompatData($xlsx_data, $lang_id); // 校验数据并组装sql语句 $raw_sql = $this->validateAndBuildSql($compat_datas); if (false === \think\facade\Db::execute($raw_sql)) { throw new \Exception(sprintf('第【%s】行执行导入 SQL 失败', implode(',', array_column($xlsx_data, 'seq_no')))); } // 清除旧兼容型号数据 if (!empty($exists_compat_ids)) { ProductCompatModelsModel::destroy(function ($query) use($exists_compat_ids) { $query->where('compat_id', 'in', $exists_compat_ids); }); } // 为不遗漏id重新查询 $compat_map = ProductCompatModel::language($lang_id) ->modelName($models) ->column('id', 'model'); // 插入兼容型号数据 $compatible_models = []; foreach ($compat_models as $m) { $compat_id = $compat_map[$m['model']]; foreach ($m['compatible_models'] as $compatible_model) { $compatible_models[] = [ 'compat_id' => $compat_id, 'compatible_model' => $compatible_model, ]; } } if (!empty($compatible_models)) { ProductCompatModelsModel::insertAll($compatible_models); } } private function matchExistsCompatData(array $datas, int $lang_id) { $models = array_column($datas, 'model'); $compat_map = ProductCompatModel::language($lang_id) ->modelName($models) ->column('id', 'model'); $type_names = array_unique(array_column($datas, 'type_name')); $type_map = ProductCompatTypesModel::language($lang_id) ->typeName($type_names) ->column('id', 'name'); $part_names = array_unique(array_column($datas, 'part_name')); $part_map = ProductCompatPartsModel::language($lang_id) ->partName($part_names) ->column('id', 'name'); // 匹配已存在数据的主键、类型id及配件id $exists_compat_ids = []; $errors = []; $compat_models = []; foreach ($datas as &$d) { // 确认要更新的数据的主键 $d['id'] = null; if (isset($compat_map[$d['model']])) { $d['id'] = $compat_map[$d['model']]; $exists_compat_ids[] = $d['id']; } // 所属语言 $d['language_id'] = $lang_id; // 确认产品类型id if (empty($type_map[$d['type_name']])) { $errors[] = "第【{$d['seq_no']}】行,产品类型【{$d['type_name']}】无效"; continue; } $d['type_id'] = $type_map[$d['type_name']]; // 确认配件类型id if (empty($part_map[$d['part_name']])) { $errors[] = "第【{$d['seq_no']}】行,配件类型【{$d['part_name']}】无效"; continue; } $d['part_id'] = $part_map[$d['part_name']]; // 处理兼容型号数据 $compatible_models_str = str_replace(',', ',', $d['compatible_models']); $compatible_models_arr = explode(',', $compatible_models_str); if (!empty($compatible_models_arr)) { $compat_models[] = [ 'model' => $d['model'], 'compatible_models' => $compatible_models_arr, ]; } } unset($d); if (!empty($errors)) { throw new \Exception(implode("\n", $errors)); } return [$models, $exists_compat_ids, $compat_models, $datas]; } private function validateAndBuildSql(array $compat_datas) { $sql_values = []; $validate = new ProductCompatValidate; foreach ($compat_datas as $compat) { // 校验数据 $scene = is_null($compat['id']) ? 'add' : 'edit'; if (!$validate->scene($scene)->check($compat)) { throw new \Exception(sprintf('第【%s】行%s', $compat['seq_no'], $validate->getError())); } // 组装 sql values $sql_values[] = $this->buildSqlValue($compat); } return $this->buildRawSql($sql_values); } private function buildSqlValue(array $compat) { $pdo = \think\facade\Db::connect()->getPdo(); return sprintf( '(%d, %d, %d, %d, %s, %s, %s, %s, %s, %s, %s)', $compat['id'], $compat['language_id'], $compat['type_id'], $compat['part_id'], is_string($compat['model']) ? $pdo->quote($compat['model']) : $compat['model'], is_string($compat['brand_name']) ? $pdo->quote($compat['brand_name']) : $compat['brand_name'], is_string($compat['level_name']) ? $pdo->quote($compat['level_name']) : $compat['level_name'], is_string($compat['spec_name']) ? $pdo->quote($compat['spec_name']) : $compat['spec_name'], is_string($compat['series_name']) ? $pdo->quote($compat['series_name']) : $compat['series_name'], is_string($compat['capacity']) ? $pdo->quote($compat['capacity']) : $compat['capacity'], is_string($compat['frequency']) ? $pdo->quote($compat['frequency']) : $compat['frequency'] ); } private function buildRawSql(array $values) { return sprintf( 'INSERT INTO %s ( `id`, `language_id`, `type_id`, `part_id`, `model`, `brand_name`, `level_name`, `spec_name`, `series_name`, `capacity`, `frequency` ) VALUES %s ON DUPLICATE KEY UPDATE `type_id` = VALUES(`type_id`), `part_id` = VALUES(`part_id`), `model` = VALUES(`model`), `brand_name` = VALUES(`brand_name`), `level_name` = VALUES(`level_name`), `spec_name` = VALUES(`spec_name`), `series_name` = VALUES(`series_name`), `capacity` = VALUES(`capacity`), `frequency` = VALUES(`frequency`) ', (new ProductCompatModel)->getTable(), implode(',', $values) ); } /** * 产品兼容性数据导出 */ public function export() { $params = request()->param([ 'compatible_model', 'model', 'type_id', 'part_id', 'brand_name' ]); $list = ProductCompatModel::withoutField([ 'language_id', 'deleted_at' ]) ->with([ 'types' => function ($query) { $query->field(['id', 'name']); }, 'parts' => function ($query) { $query->field(['id', 'name']); }, 'compatible_models' => function ($query) { $query->field(['compat_id', 'compatible_model']); } ]) ->language(request()->lang_id) ->where(function ($query) use($params) { if (!empty($params['type_id'])) { $query->typeId($params['type_id']); } if (!empty($params['part_id'])) { $query->partId($params['part_id']); } }) ->withSearch(['model', 'brand_name', 'compatible_model'], [ 'model' => $params['model'] ?? null, 'brand_name' => $params['brand_name'] ?? null, 'compatible_model' => $params['compatible_model'] ?? null, ]) ->order(['updated_at' => 'desc']) ->cursor(); $schema = [ 'id' => 'id', 'type_name' => '产品类型', 'brand_name' => '品牌', 'model' => '型号', 'part_name' => '配件', 'level_name' => '级别', 'spec_name' => '规格', 'series_name' => '系列', 'capacity' => '容量', 'frequency' => '频率', 'compatible_models' => '兼容型号', 'praise_count' => '点赞数', 'sort' => '排序', 'created_at' => '新增时间', 'updated_at' => '更新时间' ]; return xlsx_writer($this->xlsxRowGenerator($list), $schema, '产品兼容性' . date('YmdHis')); } private function xlsxRowGenerator(Generator $list) { foreach ($list as $item) { $compatible_models = []; foreach ($item['compatible_models'] as $model) { $compatible_models[] = $model['compatible_model']; } yield [ 'id' => $item['id'], 'type_name' => $item['types']['type_name'], 'brand_name' => $item['brand_name'], 'model' => $item['model'], 'part_name' => $item['parts']['part_name'], 'level_name' => $item['level_name'], 'spec_name' => $item['spec_name'], 'series_name' => $item['series_name'], 'capacity' => $item['capacity'], 'frequency' => $item['frequency'], 'compatible_models' => implode(',', $compatible_models), 'praise_count' => $item['praise_count'], 'sort' => $item['sort'], 'created_at' => $item['created_at'], 'updated_at' => $item['updated_at'] ]; } } /** * 产品兼容性数据添加 */ public function save() { $post = request()->post([ 'type_id', 'part_id', 'model', 'brand_name', 'level_name', 'spec_name', 'series_name', 'capacity', 'frequency', 'compatible_models', 'sort', 'disabled' ]); $data = array_merge($post, ['language_id' => request()->lang_id]); // 校验输入 $validate = new ProductCompatValidate; if (!$validate->scene('add')->check($data)) { return error($validate->getError()); } \think\facade\Db::startTrans(); try { $compat = ProductCompatModel::create($data); if ($compat->isEmpty()) { throw new \Exception('操作失败'); } $compatible_models = []; $models = explode(',', str_replace(',', ',', $post['compatible_models'])); foreach ($models as $model) { $compatible_models[] = [ 'compat_id' => $compat->id, 'compatible_model' => trim($model), ]; } if (empty($compatible_models)) { throw new \Exception('请确认兼容型号数据为有效'); } ProductCompatModelsModel::insertAll($compatible_models); } catch (\Throwable $e) { \think\facade\Db::rollback(); return error($e->getMessage()); } \think\facade\Db::commit(); return success('操作成功'); } /** * 产品兼容性数据详细 */ public function read() { $id = request()->param('id'); $data = ProductCompatModel::withoutField(['language_id', 'deleted_at']) ->with([ 'compatible_models' => function ($query) { $query->field(['compat_id', 'compatible_model']); } ]) ->bypk($id) ->find(); if (empty($data)) { return error('获取失败'); } if (!empty($data->compatible_models)) { $compatible_models = []; foreach ($data->compatible_models as $model) { $compatible_models[] = $model['compatible_model']; } $data->compatible_models = implode(',', $compatible_models); } return success("获取成功", $data); } /** * 产品兼容性数据更新 */ public function update() { $id = request()->param('id'); $put = request()->put([ 'type_id', 'part_id', 'model', 'brand_name', 'level_name', 'spec_name', 'series_name', 'capacity', 'frequency', 'compatible_models', 'sort', 'disabled' ]); $data = array_merge($put, ['id' => $id, 'language_id' => request()->lang_id]); // 校验输入 $validate = new ProductCompatValidate; if (!$validate->scene('edit')->check($data)) { return error($validate->getError()); } \think\facade\Db::startTrans(); try { $compat = ProductCompatModel::bypk($id)->find(); if (empty($compat)) { throw new \Exception('请确认操作对象是否正确'); } if (!$compat->save($data)) { throw new \Exception('操作失败'); } $compatible_models = []; $models = explode(',', str_replace(',', ',', $put['compatible_models'])); foreach ($models as $model) { $compatible_models[] = [ 'compat_id' => $compat->id, 'compatible_model' => trim($model), ]; } if (empty($compatible_models)) { throw new \Exception('请确认兼容型号数据为有效'); } // 清除旧数据 $deleted = ProductCompatModelsModel::destroy(function($query) use ($compat) { $query->where('compat_id', '=', $compat->id); }); if (!$deleted) { throw new \Exception('删除兼容型号数据失败'); } ProductCompatModelsModel::insertAll($compatible_models); } catch (\Throwable $e) { \think\facade\Db::rollback(); return error($e->getMessage()); } \think\facade\Db::commit(); return success('操作成功'); } /** * 产品兼容性数据删除 */ public function delete() { $id = request()->param('id'); $data = ProductCompatModel::bypk($id)->find(); if (empty($data)) { return error("请确认操作对象是否正确"); } if (!$data->delete()) { return error("操作失败"); } return success("操作成功"); } }