This commit is contained in:
wushumin
2026-05-11 15:28:27 +08:00
commit 9aac78b8da
289 changed files with 67193 additions and 0 deletions

View File

@@ -0,0 +1,889 @@
<?php
namespace app\controller\admin;
use app\support\CatalogTemplateSampleImageService;
use app\support\ContentService;
use app\support\FileStorageService;
use support\Request;
use support\think\Db;
class CatalogController
{
public function overview(Request $request)
{
return api_success([
'cards' => [
[
'title' => '启用品类',
'value' => (int)Db::name('catalog_categories')->where('is_enabled', 1)->count(),
'desc' => '当前前台可用的鉴定品类数量',
],
[
'title' => '启用品牌',
'value' => (int)Db::name('catalog_brands')->where('is_enabled', 1)->count(),
'desc' => '已配置并启用的品牌数量',
],
],
]);
}
public function categories(Request $request)
{
$rows = Db::name('catalog_categories')
->field([
'id',
'name',
'code',
'sort_order',
'is_enabled',
'need_shipping',
'supported_service_types',
])
->order('sort_order', 'asc')
->select()
->toArray();
$categoryVisuals = $this->categoryVisualMap($request);
$templateSummaryMap = [];
$appraisalTemplateSummaryMap = [];
if ($rows) {
$categoryIds = array_map(fn (array $item) => (int)$item['id'], $rows);
$templateRows = Db::name('upload_templates')
->field(['id', 'scope_id'])
->where('scope_type', 'category')
->whereIn('scope_id', $categoryIds)
->where('is_enabled', 1)
->select()
->toArray();
$templateIds = array_map(fn (array $item) => (int)$item['id'], $templateRows);
$itemCountMap = [];
if ($templateIds) {
$itemRows = Db::name('upload_template_items')
->fieldRaw('template_id, COUNT(*) AS item_count')
->whereIn('template_id', $templateIds)
->where('is_enabled', 1)
->group('template_id')
->select()
->toArray();
foreach ($itemRows as $item) {
$itemCountMap[(int)$item['template_id']] = (int)$item['item_count'];
}
}
foreach ($templateRows as $item) {
$categoryId = (int)($item['scope_id'] ?? 0);
if ($categoryId <= 0) {
continue;
}
if (!isset($templateSummaryMap[$categoryId])) {
$templateSummaryMap[$categoryId] = [
'template_count' => 0,
'item_count' => 0,
];
}
$templateSummaryMap[$categoryId]['template_count'] += 1;
$templateSummaryMap[$categoryId]['item_count'] += $itemCountMap[(int)$item['id']] ?? 0;
}
$appraisalTemplateRows = Db::name('appraisal_templates')
->field(['id', 'scope_id', 'is_default'])
->where('scope_type', 'category')
->whereIn('scope_id', $categoryIds)
->where('is_enabled', 1)
->order('is_default', 'desc')
->order('id', 'desc')
->select()
->toArray();
$appraisalTemplateIds = array_map(fn (array $item) => (int)$item['id'], $appraisalTemplateRows);
$pointCountMap = [];
if ($appraisalTemplateIds) {
$pointRows = Db::name('appraisal_template_key_points')
->fieldRaw('template_id, COUNT(*) AS point_count')
->whereIn('template_id', $appraisalTemplateIds)
->group('template_id')
->select()
->toArray();
foreach ($pointRows as $item) {
$pointCountMap[(int)$item['template_id']] = (int)$item['point_count'];
}
}
foreach ($appraisalTemplateRows as $item) {
$categoryId = (int)($item['scope_id'] ?? 0);
if ($categoryId <= 0) {
continue;
}
if (isset($appraisalTemplateSummaryMap[$categoryId])) {
continue;
}
if (!isset($appraisalTemplateSummaryMap[$categoryId])) {
$appraisalTemplateSummaryMap[$categoryId] = [
'template_count' => 0,
'point_count' => 0,
];
}
$appraisalTemplateSummaryMap[$categoryId]['template_count'] = 1;
$appraisalTemplateSummaryMap[$categoryId]['point_count'] += $pointCountMap[(int)$item['id']] ?? 0;
}
}
$list = array_map(function (array $item) use ($templateSummaryMap, $appraisalTemplateSummaryMap, $categoryVisuals) {
$summary = $templateSummaryMap[(int)$item['id']] ?? ['template_count' => 0, 'item_count' => 0];
$appraisalSummary = $appraisalTemplateSummaryMap[(int)$item['id']] ?? ['template_count' => 0, 'point_count' => 0];
$codeKey = $this->categoryMatchKey((string)$item['code']);
$nameKey = $this->categoryMatchKey((string)$item['name']);
return [
'id' => (int)$item['id'],
'name' => $item['name'],
'code' => $item['code'],
'image_url' => $categoryVisuals['code:' . $codeKey] ?? $categoryVisuals['name:' . $nameKey] ?? '',
'sort_order' => (int)$item['sort_order'],
'is_enabled' => (bool)$item['is_enabled'],
'need_shipping' => (bool)$item['need_shipping'],
'supported_service_types' => $this->decodeJsonArray($item['supported_service_types'] ?? null),
'upload_template_count' => (int)$summary['template_count'],
'upload_template_item_count' => (int)$summary['item_count'],
'upload_template_summary' => (int)$summary['template_count'] > 0
? sprintf('%d 套模板 / %d 项采集项', (int)$summary['template_count'], (int)$summary['item_count'])
: '未配置模板',
'appraisal_template_count' => 1,
'appraisal_template_point_count' => (int)$appraisalSummary['point_count'],
'appraisal_template_summary' => sprintf('%d 个自定义鉴定项', (int)$appraisalSummary['point_count']),
];
}, $rows);
return api_success(['list' => $list]);
}
public function uploadTemplates(Request $request)
{
$categoryId = (int)$request->input('category_id', 0);
if ($categoryId <= 0) {
return api_error('品类 ID 不能为空', 422);
}
$category = Db::name('catalog_categories')->where('id', $categoryId)->find();
if (!$category) {
return api_error('品类不存在', 404);
}
$serviceProviders = $this->decodeJsonArray($category['supported_service_types'] ?? null);
if (!$serviceProviders) {
$serviceProviders = ['anxinyan'];
}
$existingRows = Db::name('upload_templates')
->where('scope_type', 'category')
->where('scope_id', $categoryId)
->whereIn('service_provider', $serviceProviders)
->order('id', 'desc')
->select()
->toArray();
$existingMap = [];
foreach ($existingRows as $row) {
$provider = (string)($row['service_provider'] ?? '');
if ($provider === '' || isset($existingMap[$provider])) {
continue;
}
$existingMap[$provider] = $row;
}
$list = array_map(function (string $serviceProvider) use ($categoryId, $category, $existingMap, $request) {
$existing = $existingMap[$serviceProvider] ?? null;
$items = [];
if ($existing) {
$itemRows = Db::name('upload_template_items')
->where('template_id', (int)$existing['id'])
->order('sort_order', 'asc')
->order('id', 'asc')
->select()
->toArray();
$items = array_map(fn (array $item) => [
'id' => (int)$item['id'],
'item_code' => (string)$item['item_code'],
'item_name' => (string)$item['item_name'],
'is_required' => (bool)$item['is_required'],
'guide_text' => (string)$item['guide_text'],
'sample_image_url' => $this->templateSampleImageService()->normalizeUrl((string)$item['sample_image_url'], $request),
'max_upload_count' => (int)$item['max_upload_count'],
'sort_order' => (int)$item['sort_order'],
'is_enabled' => (bool)$item['is_enabled'],
], $itemRows);
}
return [
'id' => $existing ? (int)$existing['id'] : null,
'category_id' => $categoryId,
'category_name' => (string)$category['name'],
'service_provider' => $serviceProvider,
'service_provider_text' => $this->serviceProviderText($serviceProvider),
'name' => $existing['name'] ?? sprintf('%s-%s上传模板', (string)$category['name'], $this->serviceProviderText($serviceProvider)),
'code' => $existing['code'] ?? sprintf('upload_category_%d_%s', $categoryId, $serviceProvider),
'is_enabled' => $existing ? (bool)$existing['is_enabled'] : true,
'is_default' => $existing ? (bool)$existing['is_default'] : ($serviceProvider === 'anxinyan'),
'items' => $items,
];
}, $serviceProviders);
return api_success([
'category' => [
'id' => $categoryId,
'name' => (string)$category['name'],
'code' => (string)$category['code'],
],
'list' => $list,
]);
}
public function saveUploadTemplates(Request $request)
{
$categoryId = (int)$request->input('category_id', 0);
$templates = $request->input('templates', []);
if ($categoryId <= 0) {
return api_error('品类 ID 不能为空', 422);
}
if (!is_array($templates)) {
return api_error('模板数据格式不正确', 422);
}
$category = Db::name('catalog_categories')->where('id', $categoryId)->find();
if (!$category) {
return api_error('品类不存在', 404);
}
$serviceProviders = $this->decodeJsonArray($category['supported_service_types'] ?? null);
if (!$serviceProviders) {
$serviceProviders = ['anxinyan'];
}
$allowedProviders = array_fill_keys($serviceProviders, true);
$now = date('Y-m-d H:i:s');
$defaultTemplateId = 0;
$orphanSampleImageUrls = [];
Db::startTrans();
try {
foreach ($templates as $template) {
if (!is_array($template)) {
continue;
}
$serviceProvider = trim((string)($template['service_provider'] ?? ''));
if ($serviceProvider === '' || !isset($allowedProviders[$serviceProvider])) {
continue;
}
$templateId = (int)($template['id'] ?? 0);
$exists = null;
if ($templateId > 0) {
$exists = Db::name('upload_templates')->where('id', $templateId)->find();
}
if (!$exists) {
$exists = Db::name('upload_templates')
->where('scope_type', 'category')
->where('scope_id', $categoryId)
->where('service_provider', $serviceProvider)
->order('id', 'desc')
->find();
}
$payload = [
'name' => trim((string)($template['name'] ?? '')) ?: sprintf('%s-%s上传模板', (string)$category['name'], $this->serviceProviderText($serviceProvider)),
'code' => trim((string)($template['code'] ?? '')) ?: sprintf('upload_category_%d_%s', $categoryId, $serviceProvider),
'scope_type' => 'category',
'scope_id' => $categoryId,
'service_provider' => $serviceProvider,
'is_default' => !empty($template['is_default']) ? 1 : 0,
'is_enabled' => array_key_exists('is_enabled', $template) ? (!empty($template['is_enabled']) ? 1 : 0) : 1,
'updated_at' => $now,
];
if ($exists) {
Db::name('upload_templates')->where('id', (int)$exists['id'])->update($payload);
$savedTemplateId = (int)$exists['id'];
} else {
$payload['created_at'] = $now;
$savedTemplateId = (int)Db::name('upload_templates')->insertGetId($payload);
}
if ($serviceProvider === 'anxinyan') {
$defaultTemplateId = $savedTemplateId;
}
$existingItemRows = Db::name('upload_template_items')
->where('template_id', $savedTemplateId)
->select()
->toArray();
$existingSampleUrls = array_values(array_filter(array_map(
fn (array $item) => $this->templateSampleImageService()->storagePath((string)($item['sample_image_url'] ?? '')),
$existingItemRows
)));
Db::name('upload_template_items')->where('template_id', $savedTemplateId)->delete();
$items = is_array($template['items'] ?? null) ? $template['items'] : [];
$insertRows = [];
$nextSampleUrls = [];
foreach ($items as $index => $item) {
if (!is_array($item)) {
continue;
}
$itemCode = trim((string)($item['item_code'] ?? ''));
$itemName = trim((string)($item['item_name'] ?? ''));
if ($itemCode === '' || $itemName === '') {
continue;
}
$insertRows[] = [
'template_id' => $savedTemplateId,
'item_code' => $itemCode,
'item_name' => $itemName,
'is_required' => !empty($item['is_required']) ? 1 : 0,
'guide_text' => trim((string)($item['guide_text'] ?? '')),
'sample_image_url' => $this->templateSampleImageService()->storagePath((string)($item['sample_image_url'] ?? '')),
'max_upload_count' => max(1, (int)($item['max_upload_count'] ?? 1)),
'sort_order' => (int)($item['sort_order'] ?? (($index + 1) * 10)),
'is_enabled' => array_key_exists('is_enabled', $item) ? (!empty($item['is_enabled']) ? 1 : 0) : 1,
'created_at' => $now,
'updated_at' => $now,
];
$sampleUrl = $this->templateSampleImageService()->storagePath((string)($item['sample_image_url'] ?? ''));
if ($sampleUrl !== '') {
$nextSampleUrls[] = $sampleUrl;
}
}
if ($insertRows) {
Db::name('upload_template_items')->insertAll($insertRows);
}
$removedSampleUrls = array_values(array_diff($existingSampleUrls, $nextSampleUrls));
if ($removedSampleUrls) {
$orphanSampleImageUrls = array_values(array_unique(array_merge($orphanSampleImageUrls, $removedSampleUrls)));
}
}
Db::name('catalog_categories')->where('id', $categoryId)->update([
'default_upload_template_id' => $defaultTemplateId > 0 ? $defaultTemplateId : null,
'updated_at' => $now,
]);
Db::commit();
} catch (\Throwable $e) {
Db::rollback();
return api_error('上传模板保存失败', 500, [
'detail' => $e->getMessage(),
]);
}
foreach ($orphanSampleImageUrls as $fileUrl) {
$this->templateSampleImageService()->delete($fileUrl);
}
return api_success([
'category_id' => $categoryId,
], '上传模板已保存');
}
public function appraisalTemplates(Request $request)
{
$categoryId = (int)$request->input('category_id', 0);
if ($categoryId <= 0) {
return api_error('品类 ID 不能为空', 422);
}
$category = Db::name('catalog_categories')->where('id', $categoryId)->find();
if (!$category) {
return api_error('品类不存在', 404);
}
$template = Db::name('appraisal_templates')
->where('scope_type', 'category')
->where('scope_id', $categoryId)
->where('is_enabled', 1)
->order('is_default', 'desc')
->order('id', 'desc')
->find();
$points = [];
if ($template) {
$pointRows = Db::name('appraisal_template_key_points')
->where('template_id', (int)$template['id'])
->order('sort_order', 'asc')
->order('id', 'asc')
->select()
->toArray();
$points = array_map(fn (array $item) => [
'id' => (int)$item['id'],
'point_code' => (string)$item['point_code'],
'point_name' => (string)$item['point_name'],
'point_type' => (string)$item['point_type'],
'options' => $this->decodeJsonArray($item['options_json'] ?? null),
'sort_order' => (int)$item['sort_order'],
'is_required' => (bool)$item['is_required'],
], $pointRows);
}
$payload = [
'id' => $template ? (int)$template['id'] : null,
'category_id' => $categoryId,
'category_name' => (string)$category['name'],
'service_provider' => 'category',
'service_provider_text' => '通用品类模板',
'name' => $template['name'] ?? sprintf('%s鉴定模板', (string)$category['name']),
'code' => $template['code'] ?? sprintf('appraisal_category_%d', $categoryId),
'is_enabled' => true,
'is_default' => true,
'result_options' => [],
'condition_options' => [],
'valuation_hint' => '',
'key_points' => $points,
];
return api_success([
'category' => [
'id' => $categoryId,
'name' => (string)$category['name'],
'code' => (string)$category['code'],
],
'template' => $payload,
'list' => [$payload],
]);
}
public function saveAppraisalTemplates(Request $request)
{
$categoryId = (int)$request->input('category_id', 0);
$template = $request->input('template', null);
$templates = $request->input('templates', []);
if ($categoryId <= 0) {
return api_error('品类 ID 不能为空', 422);
}
if (!is_array($template)) {
$template = is_array($templates) ? ($templates[0] ?? []) : [];
}
if (!is_array($template)) {
return api_error('模板数据格式不正确', 422);
}
$category = Db::name('catalog_categories')->where('id', $categoryId)->find();
if (!$category) {
return api_error('品类不存在', 404);
}
$now = date('Y-m-d H:i:s');
Db::startTrans();
try {
$exists = Db::name('appraisal_templates')
->where('scope_type', 'category')
->where('scope_id', $categoryId)
->order('is_default', 'desc')
->order('id', 'desc')
->find();
$payload = [
'name' => sprintf('%s鉴定模板', (string)$category['name']),
'code' => sprintf('appraisal_category_%d', $categoryId),
'scope_type' => 'category',
'scope_id' => $categoryId,
'service_provider' => 'category',
'is_default' => 1,
'is_enabled' => 1,
'result_options_json' => json_encode([], JSON_UNESCAPED_UNICODE),
'condition_rule_json' => json_encode([], JSON_UNESCAPED_UNICODE),
'valuation_rule_json' => json_encode([], JSON_UNESCAPED_UNICODE),
'updated_at' => $now,
];
if ($exists) {
Db::name('appraisal_templates')->where('id', (int)$exists['id'])->update($payload);
$savedTemplateId = (int)$exists['id'];
} else {
$payload['created_at'] = $now;
$savedTemplateId = (int)Db::name('appraisal_templates')->insertGetId($payload);
}
$otherTemplateIds = Db::name('appraisal_templates')
->where('scope_type', 'category')
->where('scope_id', $categoryId)
->where('id', '<>', $savedTemplateId)
->column('id');
if ($otherTemplateIds) {
Db::name('appraisal_templates')->whereIn('id', $otherTemplateIds)->update([
'is_enabled' => 0,
'is_default' => 0,
'updated_at' => $now,
]);
}
Db::name('appraisal_template_key_points')->where('template_id', $savedTemplateId)->delete();
$points = is_array($template['key_points'] ?? null) ? $template['key_points'] : [];
$insertRows = [];
foreach ($points as $index => $point) {
if (!is_array($point)) {
continue;
}
$pointName = trim((string)($point['point_name'] ?? ''));
if ($pointName === '') {
continue;
}
$pointCode = $this->normalizeCode((string)($point['point_code'] ?? '')) ?: sprintf('point_%d', $index + 1);
$pointType = trim((string)($point['point_type'] ?? 'text'));
if (!in_array($pointType, ['text', 'textarea', 'select', 'boolean'], true)) {
$pointType = 'text';
}
$insertRows[] = [
'template_id' => $savedTemplateId,
'point_code' => $pointCode,
'point_name' => $pointName,
'point_type' => $pointType,
'options_json' => json_encode($this->normalizeArray($point['options'] ?? []), JSON_UNESCAPED_UNICODE),
'sort_order' => (int)($point['sort_order'] ?? (($index + 1) * 10)),
'is_required' => !empty($point['is_required']) ? 1 : 0,
'created_at' => $now,
'updated_at' => $now,
];
}
if ($insertRows) {
Db::name('appraisal_template_key_points')->insertAll($insertRows);
}
Db::name('catalog_categories')->where('id', $categoryId)->update([
'default_appraisal_template_id' => $savedTemplateId,
'updated_at' => $now,
]);
Db::commit();
} catch (\Throwable $e) {
Db::rollback();
return api_error('鉴定模板保存失败', 500, [
'detail' => $e->getMessage(),
]);
}
return api_success([
'category_id' => $categoryId,
], '鉴定模板已保存');
}
public function uploadTemplateSampleImage(Request $request)
{
try {
$asset = $this->templateSampleImageService()->upload($request);
return api_success($asset, '示例图上传成功');
} catch (\Throwable $e) {
return api_error($e->getMessage(), 422);
}
}
public function deleteUploadTemplateSampleImage(Request $request)
{
$fileUrl = trim((string)$request->input('file_url', ''));
if ($fileUrl === '') {
return api_error('文件地址不能为空', 422);
}
$this->templateSampleImageService()->delete($fileUrl);
return api_success([
'file_url' => $fileUrl,
], '示例图已删除');
}
public function saveCategory(Request $request)
{
$id = (int)$request->input('id', 0);
$name = trim((string)$request->input('name', ''));
$code = trim((string)$request->input('code', ''));
$imageUrl = trim((string)$request->input('image_url', ''));
if ($name === '' || $code === '') {
return api_error('品类名称和编码不能为空', 422);
}
$previous = $id > 0
? Db::name('catalog_categories')->field(['name', 'code'])->where('id', $id)->find()
: null;
$payload = [
'name' => $name,
'code' => $code,
'sort_order' => (int)$request->input('sort_order', 0),
'is_enabled' => $request->input('is_enabled', true) ? 1 : 0,
'need_shipping' => $request->input('need_shipping', true) ? 1 : 0,
'supported_service_types' => json_encode($this->normalizeArray($request->input('supported_service_types', [])), JSON_UNESCAPED_UNICODE),
'updated_at' => date('Y-m-d H:i:s'),
];
if ($id > 0) {
Db::name('catalog_categories')->where('id', $id)->update($payload);
$this->saveCategoryVisual($name, $code, $imageUrl, is_array($previous) ? $previous : null);
return api_success(['id' => $id], '更新成功');
}
$payload['created_at'] = date('Y-m-d H:i:s');
$newId = Db::name('catalog_categories')->insertGetId($payload);
$this->saveCategoryVisual($name, $code, $imageUrl);
return api_success(['id' => (int)$newId], '创建成功');
}
public function brands(Request $request)
{
$rows = Db::name('catalog_brands')
->alias('b')
->leftJoin('catalog_brand_categories cbc', 'cbc.brand_id = b.id')
->leftJoin('catalog_categories c', 'c.id = cbc.category_id')
->field([
'b.id',
'b.name',
'b.en_name',
'b.code',
'b.sort_order',
'b.is_enabled',
'b.supported_service_types',
'GROUP_CONCAT(DISTINCT cbc.category_id) AS category_ids',
'GROUP_CONCAT(DISTINCT c.name) AS category_names',
])
->group('b.id')
->order('b.sort_order', 'asc')
->select()
->toArray();
$list = array_map(function (array $item) {
return [
'id' => (int)$item['id'],
'name' => $item['name'],
'en_name' => $item['en_name'],
'code' => $item['code'],
'sort_order' => (int)$item['sort_order'],
'is_enabled' => (bool)$item['is_enabled'],
'category_ids' => $this->decodeIntList($item['category_ids'] ?? ''),
'category_names' => $item['category_names'] ?: '',
'supported_service_types' => $this->decodeJsonArray($item['supported_service_types'] ?? null),
];
}, $rows);
return api_success(['list' => $list]);
}
public function saveBrand(Request $request)
{
$id = (int)$request->input('id', 0);
$name = trim((string)$request->input('name', ''));
$enName = trim((string)$request->input('en_name', ''));
$code = trim((string)$request->input('code', ''));
$categoryIds = $this->normalizeIntArray($request->input('category_ids', []));
if ($name === '' || $code === '') {
return api_error('品牌名称和编码不能为空', 422);
}
$payload = [
'name' => $name,
'en_name' => $enName,
'code' => $code,
'sort_order' => (int)$request->input('sort_order', 0),
'is_enabled' => $request->input('is_enabled', true) ? 1 : 0,
'supported_service_types' => json_encode($this->normalizeArray($request->input('supported_service_types', [])), JSON_UNESCAPED_UNICODE),
'updated_at' => date('Y-m-d H:i:s'),
];
Db::startTrans();
try {
if ($id > 0) {
Db::name('catalog_brands')->where('id', $id)->update($payload);
Db::name('catalog_brand_categories')->where('brand_id', $id)->delete();
foreach ($categoryIds as $categoryId) {
Db::name('catalog_brand_categories')->insert([
'brand_id' => $id,
'category_id' => $categoryId,
'created_at' => date('Y-m-d H:i:s'),
]);
}
Db::commit();
return api_success(['id' => $id], '更新成功');
}
$payload['logo'] = '';
$payload['created_at'] = date('Y-m-d H:i:s');
$newId = Db::name('catalog_brands')->insertGetId($payload);
foreach ($categoryIds as $categoryId) {
Db::name('catalog_brand_categories')->insert([
'brand_id' => $newId,
'category_id' => $categoryId,
'created_at' => date('Y-m-d H:i:s'),
]);
}
Db::commit();
return api_success(['id' => (int)$newId], '创建成功');
} catch (\Throwable $e) {
Db::rollback();
return api_error('品牌保存失败', 500, [
'detail' => $e->getMessage(),
]);
}
}
private function decodeJsonArray(mixed $value): array
{
if (is_array($value)) {
return array_values($value);
}
if (is_string($value) && $value !== '') {
$decoded = json_decode($value, true);
return is_array($decoded) ? array_values($decoded) : [];
}
return [];
}
private function decodeJsonObject(mixed $value): array
{
if (is_array($value)) {
return $value;
}
if (is_string($value) && $value !== '') {
$decoded = json_decode($value, true);
return is_array($decoded) ? $decoded : [];
}
return [];
}
private function normalizeArray(mixed $value): array
{
if (is_array($value)) {
return array_values(array_filter(array_map(static fn ($item) => trim((string)$item), $value), static fn ($item) => $item !== ''));
}
if (is_string($value) && $value !== '') {
return array_values(array_filter(array_map('trim', explode(',', $value)), static fn ($item) => $item !== ''));
}
return [];
}
private function normalizeCode(string $value): string
{
$normalized = strtolower(trim($value));
$normalized = (string)preg_replace('/[^a-z0-9_]+/', '_', $normalized);
$normalized = trim($normalized, '_');
return $normalized;
}
private function normalizeIntArray(mixed $value): array
{
if (!is_array($value)) {
return [];
}
return array_values(array_filter(array_map(static fn ($item) => (int)$item, $value), static fn ($item) => $item > 0));
}
private function decodeIntList(string $value): array
{
if ($value === '') {
return [];
}
return array_values(array_filter(array_map(static fn ($item) => (int)$item, explode(',', $value)), static fn ($item) => $item > 0));
}
private function serviceProviderText(string $serviceProvider): string
{
return $serviceProvider === 'zhongjian' ? '中检鉴定' : '实物鉴定';
}
private function categoryVisualMap(Request $request): array
{
$items = (new ContentService())->getHomeConfig()['category_visuals'] ?? [];
if (!is_array($items)) {
return [];
}
$map = [];
$storage = new FileStorageService();
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
$imageUrl = trim((string)($item['image_url'] ?? ''));
if ($imageUrl === '') {
continue;
}
$imageUrl = $storage->normalizeUrl($imageUrl, $request);
$categoryCode = $this->categoryMatchKey((string)($item['category_code'] ?? ''));
if ($categoryCode !== '') {
$map['code:' . $categoryCode] = $imageUrl;
}
$categoryName = $this->categoryMatchKey((string)($item['category_name'] ?? ''));
if ($categoryName !== '') {
$map['name:' . $categoryName] = $imageUrl;
}
}
return $map;
}
private function saveCategoryVisual(string $categoryName, string $categoryCode, string $imageUrl, ?array $previous = null): void
{
$contentService = new ContentService();
$homeConfig = $contentService->getHomeConfig();
$items = is_array($homeConfig['category_visuals'] ?? null) ? $homeConfig['category_visuals'] : [];
$removeKeys = [
'code:' . $this->categoryMatchKey($categoryCode),
'name:' . $this->categoryMatchKey($categoryName),
];
if ($previous) {
$removeKeys[] = 'code:' . $this->categoryMatchKey((string)($previous['code'] ?? ''));
$removeKeys[] = 'name:' . $this->categoryMatchKey((string)($previous['name'] ?? ''));
}
$removeKeys = array_values(array_filter(array_unique($removeKeys), static fn ($key) => !str_ends_with($key, ':')));
$nextItems = [];
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
$itemKeys = [
'code:' . $this->categoryMatchKey((string)($item['category_code'] ?? '')),
'name:' . $this->categoryMatchKey((string)($item['category_name'] ?? '')),
];
if (array_intersect($removeKeys, $itemKeys)) {
continue;
}
$nextItems[] = $item;
}
$nextItems[] = [
'category_name' => $categoryName,
'category_code' => $categoryCode,
'image_url' => $imageUrl,
];
$homeConfig['category_visuals'] = $nextItems;
$contentService->saveHomeConfig($homeConfig);
}
private function categoryMatchKey(string $value): string
{
$value = trim($value);
$normalized = preg_replace('/[\s\p{Cf}]+/u', '', $value);
return strtolower($normalized ?? $value);
}
private function templateSampleImageService(): CatalogTemplateSampleImageService
{
return new CatalogTemplateSampleImageService();
}
}