890 lines
35 KiB
PHP
890 lines
35 KiB
PHP
<?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();
|
|
}
|
|
}
|