1110 lines
50 KiB
PHP
1110 lines
50 KiB
PHP
<?php
|
||
|
||
namespace app\support;
|
||
|
||
use support\think\Db;
|
||
|
||
class ContentService
|
||
{
|
||
private const HOME_GROUP = 'app_home_content';
|
||
private const POLICY_GROUP = 'app_policy_content';
|
||
private const META_GROUP = 'app_meta_content';
|
||
private const HELP_TABLE = 'help_articles';
|
||
private static bool $bootstrapped = false;
|
||
|
||
public function bootstrapDefaults(): void
|
||
{
|
||
if (self::$bootstrapped) {
|
||
return;
|
||
}
|
||
|
||
$this->ensureConfigDefaults();
|
||
$this->ensureHelpArticlesTable();
|
||
$this->seedHelpArticles();
|
||
$this->ensurePolicyHelpArticles();
|
||
self::$bootstrapped = true;
|
||
}
|
||
|
||
public function getHomeConfig(): array
|
||
{
|
||
$this->bootstrapDefaults();
|
||
|
||
$defaults = $this->defaultHomeConfig();
|
||
$configMap = Db::name('system_configs')
|
||
->where('config_group', self::HOME_GROUP)
|
||
->column('config_value', 'config_key');
|
||
|
||
return [
|
||
'banners' => $this->decodeJsonConfig($configMap['banners_json'] ?? '', $defaults['banners']),
|
||
'page_visuals' => $this->normalizeObjectConfig(
|
||
$this->decodeJsonObjectConfig($configMap['page_visuals_json'] ?? '', $defaults['page_visuals']),
|
||
['order_background_image_url', 'report_background_image_url'],
|
||
$defaults['page_visuals']
|
||
),
|
||
'service_entries' => $this->decodeJsonConfig($configMap['service_entries_json'] ?? '', $defaults['service_entries']),
|
||
'category_visuals' => $this->decodeJsonConfig($configMap['category_visuals_json'] ?? '', $defaults['category_visuals']),
|
||
'quick_entries' => $this->decodeJsonConfig($configMap['quick_entries_json'] ?? '', $defaults['quick_entries']),
|
||
'trust_metrics' => $this->decodeJsonConfig($configMap['trust_metrics_json'] ?? '', $defaults['trust_metrics']),
|
||
'trust_points' => $this->decodeJsonConfig($configMap['trust_points_json'] ?? '', $defaults['trust_points']),
|
||
'faqs' => $this->decodeJsonConfig($configMap['faqs_json'] ?? '', $defaults['faqs']),
|
||
];
|
||
}
|
||
|
||
public function getPolicyConfig(): array
|
||
{
|
||
$this->bootstrapDefaults();
|
||
|
||
$defaults = $this->defaultPolicyConfig();
|
||
$configMap = Db::name('system_configs')
|
||
->where('config_group', self::POLICY_GROUP)
|
||
->column('config_value', 'config_key');
|
||
|
||
return [
|
||
'legal_entries' => $this->hydratePolicyItems(
|
||
$this->decodeJsonConfig($configMap['legal_entries_json'] ?? '', $defaults['legal_entries'])
|
||
),
|
||
'appraisal_agreements' => $this->hydratePolicyItems(
|
||
$this->decodeJsonConfig($configMap['appraisal_agreements_json'] ?? '', $defaults['appraisal_agreements'])
|
||
),
|
||
];
|
||
}
|
||
|
||
public function getMetaConfig(): array
|
||
{
|
||
$this->bootstrapDefaults();
|
||
|
||
$defaults = $this->defaultMetaConfig();
|
||
$configMap = Db::name('system_configs')
|
||
->where('config_group', self::META_GROUP)
|
||
->column('config_value', 'config_key');
|
||
|
||
return [
|
||
'help_categories' => $this->decodeJsonConfig($configMap['help_categories_json'] ?? '', $defaults['help_categories']),
|
||
'report_risk_defaults' => $this->decodeJsonConfig($configMap['report_risk_defaults_json'] ?? '', $defaults['report_risk_defaults']),
|
||
'ticket_types' => $this->decodeJsonConfig($configMap['ticket_types_json'] ?? '', $defaults['ticket_types']),
|
||
'ticket_statuses' => $this->decodeJsonConfig($configMap['ticket_statuses_json'] ?? '', $defaults['ticket_statuses']),
|
||
'message_events' => $this->decodeJsonConfig($configMap['message_events_json'] ?? '', $defaults['message_events']),
|
||
'message_page_copy' => $this->decodeJsonObjectConfig($configMap['message_page_copy_json'] ?? '', $defaults['message_page_copy']),
|
||
];
|
||
}
|
||
|
||
public function saveHomeConfig(array $payload): void
|
||
{
|
||
$this->bootstrapDefaults();
|
||
|
||
$defaults = $this->defaultHomeConfig();
|
||
$existing = $this->getHomeConfig();
|
||
$categoryVisuals = array_key_exists('category_visuals', $payload)
|
||
? $payload['category_visuals']
|
||
: $existing['category_visuals'];
|
||
$normalized = [
|
||
'banners_json' => json_encode($this->normalizeArrayItems($payload['banners'] ?? [], ['title', 'subtitle', 'description', 'background_image_url'], $defaults['banners']), JSON_UNESCAPED_UNICODE),
|
||
'page_visuals_json' => json_encode($this->normalizeObjectConfig($payload['page_visuals'] ?? [], ['order_background_image_url', 'report_background_image_url'], $defaults['page_visuals']), JSON_UNESCAPED_UNICODE),
|
||
'service_entries_json' => json_encode($this->normalizeArrayItems($payload['service_entries'] ?? [], ['service_provider', 'title', 'tag', 'description', 'meta'], $defaults['service_entries']), JSON_UNESCAPED_UNICODE),
|
||
'category_visuals_json' => json_encode($this->normalizeArrayItems($categoryVisuals, ['category_name', 'category_code', 'image_url'], $defaults['category_visuals']), JSON_UNESCAPED_UNICODE),
|
||
'quick_entries_json' => json_encode($this->normalizeArrayItems($payload['quick_entries'] ?? [], ['code', 'title', 'desc'], $defaults['quick_entries']), JSON_UNESCAPED_UNICODE),
|
||
'trust_metrics_json' => json_encode($this->normalizeArrayItems($payload['trust_metrics'] ?? [], ['value', 'label'], $defaults['trust_metrics']), JSON_UNESCAPED_UNICODE),
|
||
'trust_points_json' => json_encode($this->normalizeArrayItems($payload['trust_points'] ?? [], ['title', 'desc'], $defaults['trust_points']), JSON_UNESCAPED_UNICODE),
|
||
'faqs_json' => json_encode($this->normalizeStringList($payload['faqs'] ?? [], $defaults['faqs']), JSON_UNESCAPED_UNICODE),
|
||
];
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
foreach ($normalized as $configKey => $configValue) {
|
||
$this->upsertSystemConfig(self::HOME_GROUP, $configKey, $configValue, $now);
|
||
}
|
||
}
|
||
|
||
public function savePolicyConfig(array $payload): void
|
||
{
|
||
$this->bootstrapDefaults();
|
||
|
||
$defaults = $this->defaultPolicyConfig();
|
||
$normalized = [
|
||
'legal_entries_json' => json_encode($this->normalizePolicyItems($payload['legal_entries'] ?? [], $defaults['legal_entries']), JSON_UNESCAPED_UNICODE),
|
||
'appraisal_agreements_json' => json_encode($this->normalizePolicyItems($payload['appraisal_agreements'] ?? [], $defaults['appraisal_agreements']), JSON_UNESCAPED_UNICODE),
|
||
];
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
foreach ($normalized as $configKey => $configValue) {
|
||
$this->upsertSystemConfig(self::POLICY_GROUP, $configKey, $configValue, $now);
|
||
}
|
||
}
|
||
|
||
public function saveMetaConfig(array $payload): void
|
||
{
|
||
$this->bootstrapDefaults();
|
||
|
||
$defaults = $this->defaultMetaConfig();
|
||
$normalized = [
|
||
'help_categories_json' => json_encode($this->normalizeArrayItems($payload['help_categories'] ?? [], ['code', 'title', 'desc'], $defaults['help_categories']), JSON_UNESCAPED_UNICODE),
|
||
'report_risk_defaults_json' => json_encode($this->normalizeArrayItems($payload['report_risk_defaults'] ?? [], ['report_type', 'title', 'text'], $defaults['report_risk_defaults']), JSON_UNESCAPED_UNICODE),
|
||
'ticket_types_json' => json_encode($this->normalizeArrayItems($payload['ticket_types'] ?? [], ['code', 'title', 'hint', 'quick_desc'], $defaults['ticket_types']), JSON_UNESCAPED_UNICODE),
|
||
'ticket_statuses_json' => json_encode($this->normalizeArrayItems($payload['ticket_statuses'] ?? [], ['code', 'title', 'desc'], $defaults['ticket_statuses']), JSON_UNESCAPED_UNICODE),
|
||
'message_events_json' => json_encode($this->normalizeArrayItems($payload['message_events'] ?? [], ['event_code', 'title', 'desc'], $defaults['message_events']), JSON_UNESCAPED_UNICODE),
|
||
'message_page_copy_json' => json_encode($this->normalizeObjectConfig($payload['message_page_copy'] ?? [], ['title', 'desc'], $defaults['message_page_copy']), JSON_UNESCAPED_UNICODE),
|
||
];
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
foreach ($normalized as $configKey => $configValue) {
|
||
$this->upsertSystemConfig(self::META_GROUP, $configKey, $configValue, $now);
|
||
}
|
||
}
|
||
|
||
public function getHelpArticles(bool $enabledOnly = false): array
|
||
{
|
||
$this->bootstrapDefaults();
|
||
|
||
$query = Db::name(self::HELP_TABLE)->order('sort_order', 'asc')->order('id', 'asc');
|
||
if ($enabledOnly) {
|
||
$query->where('is_enabled', 1);
|
||
}
|
||
|
||
return array_map(function (array $item) {
|
||
return [
|
||
'id' => (int)$item['id'],
|
||
'category' => (string)$item['category'],
|
||
'category_text' => $this->categoryText((string)$item['category']),
|
||
'title' => (string)$item['title'],
|
||
'summary' => (string)$item['summary'],
|
||
'keywords' => $this->decodeJsonConfig($item['keywords_json'] ?? '', []),
|
||
'updated_at' => (string)$item['updated_at'],
|
||
'is_recommended' => (bool)$item['is_recommended'],
|
||
'is_enabled' => (bool)$item['is_enabled'],
|
||
'sort_order' => (int)$item['sort_order'],
|
||
'content_blocks' => $this->decodeJsonConfig($item['content_blocks_json'] ?? '', []),
|
||
];
|
||
}, $query->select()->toArray());
|
||
}
|
||
|
||
public function getHelpCategories(): array
|
||
{
|
||
$meta = $this->getMetaConfig();
|
||
return $meta['help_categories'];
|
||
}
|
||
|
||
public function getTicketTypes(): array
|
||
{
|
||
$meta = $this->getMetaConfig();
|
||
return $meta['ticket_types'];
|
||
}
|
||
|
||
public function getTicketStatuses(): array
|
||
{
|
||
$meta = $this->getMetaConfig();
|
||
return $meta['ticket_statuses'];
|
||
}
|
||
|
||
public function getMessageEvents(): array
|
||
{
|
||
$meta = $this->getMetaConfig();
|
||
return $meta['message_events'];
|
||
}
|
||
|
||
public function getMessagePageCopy(): array
|
||
{
|
||
$meta = $this->getMetaConfig();
|
||
return $meta['message_page_copy'];
|
||
}
|
||
|
||
public function getHelpArticle(int $id): ?array
|
||
{
|
||
$items = $this->getHelpArticles(true);
|
||
foreach ($items as $item) {
|
||
if ((int)$item['id'] === $id) {
|
||
return $item;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
public function saveHelpArticle(array $payload): int
|
||
{
|
||
$this->bootstrapDefaults();
|
||
|
||
$id = (int)($payload['id'] ?? 0);
|
||
$category = trim((string)($payload['category'] ?? 'service'));
|
||
$title = trim((string)($payload['title'] ?? ''));
|
||
$summary = trim((string)($payload['summary'] ?? ''));
|
||
$keywords = $this->normalizeStringList($payload['keywords'] ?? [], []);
|
||
$contentBlocks = $this->normalizeStringList($payload['content_blocks'] ?? [], []);
|
||
$isRecommended = !empty($payload['is_recommended']) ? 1 : 0;
|
||
$isEnabled = array_key_exists('is_enabled', $payload) ? (!empty($payload['is_enabled']) ? 1 : 0) : 1;
|
||
$sortOrder = (int)($payload['sort_order'] ?? 0);
|
||
|
||
if ($title === '' || $summary === '') {
|
||
throw new \RuntimeException('文章标题和摘要不能为空');
|
||
}
|
||
if (!$contentBlocks) {
|
||
throw new \RuntimeException('请至少填写一段文章正文');
|
||
}
|
||
if (!in_array($category, ['service', 'report', 'shipping', 'support'], true)) {
|
||
throw new \RuntimeException('文章分类不合法');
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
$data = [
|
||
'category' => $category,
|
||
'title' => $title,
|
||
'summary' => $summary,
|
||
'keywords_json' => json_encode($keywords, JSON_UNESCAPED_UNICODE),
|
||
'content_blocks_json' => json_encode($contentBlocks, JSON_UNESCAPED_UNICODE),
|
||
'is_recommended' => $isRecommended,
|
||
'is_enabled' => $isEnabled,
|
||
'sort_order' => $sortOrder,
|
||
'updated_at' => $now,
|
||
];
|
||
|
||
if ($id > 0) {
|
||
$exists = Db::name(self::HELP_TABLE)->where('id', $id)->find();
|
||
if (!$exists) {
|
||
throw new \RuntimeException('帮助文章不存在');
|
||
}
|
||
Db::name(self::HELP_TABLE)->where('id', $id)->update($data);
|
||
return $id;
|
||
}
|
||
|
||
$data['created_at'] = $now;
|
||
return (int)Db::name(self::HELP_TABLE)->insertGetId($data);
|
||
}
|
||
|
||
public function deleteHelpArticle(int $id): void
|
||
{
|
||
$this->bootstrapDefaults();
|
||
if ($id <= 0) {
|
||
throw new \RuntimeException('文章 ID 不能为空');
|
||
}
|
||
|
||
$exists = Db::name(self::HELP_TABLE)->where('id', $id)->find();
|
||
if (!$exists) {
|
||
throw new \RuntimeException('帮助文章不存在');
|
||
}
|
||
|
||
$reference = $this->findPolicyReferenceByArticleId($id);
|
||
if ($reference !== null) {
|
||
throw new \RuntimeException(sprintf('该文章已被“%s”引用,请先在内容中心的协议与说明中解绑', $reference));
|
||
}
|
||
|
||
Db::name(self::HELP_TABLE)->where('id', $id)->delete();
|
||
}
|
||
|
||
public function categoryText(string $category): string
|
||
{
|
||
foreach ($this->getHelpCategories() as $item) {
|
||
if (($item['code'] ?? '') === $category) {
|
||
return (string)($item['title'] ?? $category);
|
||
}
|
||
}
|
||
|
||
return $category;
|
||
}
|
||
|
||
public function getReportRiskNotice(string $reportType): string
|
||
{
|
||
$meta = $this->getMetaConfig();
|
||
foreach ($meta['report_risk_defaults'] as $item) {
|
||
if (($item['report_type'] ?? '') === $reportType) {
|
||
return (string)($item['text'] ?? '');
|
||
}
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
public function ticketTypeText(string $type): string
|
||
{
|
||
foreach ($this->getTicketTypes() as $item) {
|
||
if (($item['code'] ?? '') === $type) {
|
||
return (string)($item['title'] ?? $type);
|
||
}
|
||
}
|
||
|
||
return $type;
|
||
}
|
||
|
||
public function ticketStatusText(string $status): string
|
||
{
|
||
foreach ($this->getTicketStatuses() as $item) {
|
||
if (($item['code'] ?? '') === $status) {
|
||
return (string)($item['title'] ?? $status);
|
||
}
|
||
}
|
||
|
||
return $status;
|
||
}
|
||
|
||
private function ensureConfigDefaults(): void
|
||
{
|
||
$homeDefaults = $this->defaultHomeConfig();
|
||
$defaults = [
|
||
self::HOME_GROUP => [
|
||
'banners_json' => json_encode($homeDefaults['banners'], JSON_UNESCAPED_UNICODE),
|
||
'page_visuals_json' => json_encode($homeDefaults['page_visuals'], JSON_UNESCAPED_UNICODE),
|
||
'service_entries_json' => json_encode($homeDefaults['service_entries'], JSON_UNESCAPED_UNICODE),
|
||
'category_visuals_json' => json_encode($homeDefaults['category_visuals'], JSON_UNESCAPED_UNICODE),
|
||
'quick_entries_json' => json_encode($homeDefaults['quick_entries'], JSON_UNESCAPED_UNICODE),
|
||
'trust_metrics_json' => json_encode($homeDefaults['trust_metrics'], JSON_UNESCAPED_UNICODE),
|
||
'trust_points_json' => json_encode($homeDefaults['trust_points'], JSON_UNESCAPED_UNICODE),
|
||
'faqs_json' => json_encode($homeDefaults['faqs'], JSON_UNESCAPED_UNICODE),
|
||
],
|
||
self::POLICY_GROUP => [
|
||
'legal_entries_json' => json_encode($this->defaultPolicyConfig()['legal_entries'], JSON_UNESCAPED_UNICODE),
|
||
'appraisal_agreements_json' => json_encode($this->defaultPolicyConfig()['appraisal_agreements'], JSON_UNESCAPED_UNICODE),
|
||
],
|
||
self::META_GROUP => [
|
||
'help_categories_json' => json_encode($this->defaultMetaConfig()['help_categories'], JSON_UNESCAPED_UNICODE),
|
||
'report_risk_defaults_json' => json_encode($this->defaultMetaConfig()['report_risk_defaults'], JSON_UNESCAPED_UNICODE),
|
||
'ticket_types_json' => json_encode($this->defaultMetaConfig()['ticket_types'], JSON_UNESCAPED_UNICODE),
|
||
'ticket_statuses_json' => json_encode($this->defaultMetaConfig()['ticket_statuses'], JSON_UNESCAPED_UNICODE),
|
||
'message_events_json' => json_encode($this->defaultMetaConfig()['message_events'], JSON_UNESCAPED_UNICODE),
|
||
'message_page_copy_json' => json_encode($this->defaultMetaConfig()['message_page_copy'], JSON_UNESCAPED_UNICODE),
|
||
],
|
||
];
|
||
|
||
$groupCodes = array_keys($defaults);
|
||
$existingRows = Db::name('system_configs')
|
||
->field(['config_group', 'config_key'])
|
||
->whereIn('config_group', $groupCodes)
|
||
->select()
|
||
->toArray();
|
||
|
||
$existingMap = [];
|
||
foreach ($existingRows as $item) {
|
||
$existingMap[($item['config_group'] ?? '') . '.' . ($item['config_key'] ?? '')] = true;
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
$insertRows = [];
|
||
foreach ($defaults as $groupCode => $items) {
|
||
foreach ($items as $configKey => $configValue) {
|
||
$mapKey = $groupCode . '.' . $configKey;
|
||
if (isset($existingMap[$mapKey])) {
|
||
continue;
|
||
}
|
||
|
||
$insertRows[] = [
|
||
'config_group' => $groupCode,
|
||
'config_key' => $configKey,
|
||
'config_value' => $configValue,
|
||
'remark' => '内容配置',
|
||
'created_at' => $now,
|
||
'updated_at' => $now,
|
||
];
|
||
}
|
||
}
|
||
|
||
if ($insertRows) {
|
||
Db::name('system_configs')->insertAll($insertRows);
|
||
}
|
||
}
|
||
|
||
private function ensureHelpArticlesTable(): void
|
||
{
|
||
$exists = Db::query(sprintf("SHOW TABLES LIKE '%s'", self::HELP_TABLE));
|
||
if ($exists) {
|
||
return;
|
||
}
|
||
|
||
Db::execute(sprintf(
|
||
"CREATE TABLE %s (
|
||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
category VARCHAR(32) NOT NULL DEFAULT 'service',
|
||
title VARCHAR(255) NOT NULL DEFAULT '',
|
||
summary VARCHAR(500) NOT NULL DEFAULT '',
|
||
keywords_json LONGTEXT NULL,
|
||
content_blocks_json LONGTEXT NULL,
|
||
is_recommended TINYINT(1) NOT NULL DEFAULT 0,
|
||
is_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (id),
|
||
KEY idx_help_articles_category (category),
|
||
KEY idx_help_articles_enabled (is_enabled),
|
||
KEY idx_help_articles_sort (sort_order)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='帮助中心文章'",
|
||
self::HELP_TABLE
|
||
));
|
||
}
|
||
|
||
private function ensurePolicyHelpArticles(): void
|
||
{
|
||
$defaults = $this->defaultPolicyConfig();
|
||
$configMap = Db::name('system_configs')
|
||
->where('config_group', self::POLICY_GROUP)
|
||
->column('config_value', 'config_key');
|
||
|
||
$policy = [
|
||
'legal_entries' => $this->decodeJsonConfig($configMap['legal_entries_json'] ?? '', $defaults['legal_entries']),
|
||
'appraisal_agreements' => $this->decodeJsonConfig($configMap['appraisal_agreements_json'] ?? '', $defaults['appraisal_agreements']),
|
||
];
|
||
|
||
$changed = false;
|
||
foreach (['legal_entries', 'appraisal_agreements'] as $section) {
|
||
$items = [];
|
||
foreach (($policy[$section] ?? []) as $item) {
|
||
if (!is_array($item)) {
|
||
continue;
|
||
}
|
||
|
||
$normalizedItem = $item;
|
||
$articleId = (int)($normalizedItem['article_id'] ?? 0);
|
||
if ($articleId <= 0) {
|
||
$articleId = $this->extractHelpArticleIdFromTargetUrl((string)($normalizedItem['target_url'] ?? ''));
|
||
}
|
||
|
||
if ($articleId > 0) {
|
||
$article = Db::name(self::HELP_TABLE)->field(['id'])->where('id', $articleId)->find();
|
||
if (!$article) {
|
||
$articleId = 0;
|
||
}
|
||
}
|
||
|
||
if ($articleId <= 0) {
|
||
$articleId = $this->resolveOrCreatePolicyArticleId((string)($normalizedItem['code'] ?? ''));
|
||
}
|
||
|
||
if ($articleId > 0) {
|
||
$nextTargetUrl = $this->buildHelpArticleTargetUrl($articleId);
|
||
if ((int)($normalizedItem['article_id'] ?? 0) !== $articleId || (string)($normalizedItem['target_url'] ?? '') !== $nextTargetUrl) {
|
||
$changed = true;
|
||
}
|
||
$normalizedItem['article_id'] = $articleId;
|
||
$normalizedItem['target_url'] = $nextTargetUrl;
|
||
}
|
||
|
||
$items[] = $normalizedItem;
|
||
}
|
||
|
||
$policy[$section] = $items;
|
||
}
|
||
|
||
if (!$changed) {
|
||
return;
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
$this->upsertSystemConfig(self::POLICY_GROUP, 'legal_entries_json', json_encode($policy['legal_entries'], JSON_UNESCAPED_UNICODE), $now);
|
||
$this->upsertSystemConfig(self::POLICY_GROUP, 'appraisal_agreements_json', json_encode($policy['appraisal_agreements'], JSON_UNESCAPED_UNICODE), $now);
|
||
}
|
||
|
||
private function seedHelpArticles(): void
|
||
{
|
||
$count = (int)Db::name(self::HELP_TABLE)->count();
|
||
if ($count > 0) {
|
||
return;
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
foreach ($this->defaultHelpArticles() as $index => $item) {
|
||
Db::name(self::HELP_TABLE)->insert([
|
||
'category' => $item['category'],
|
||
'title' => $item['title'],
|
||
'summary' => $item['summary'],
|
||
'keywords_json' => json_encode($item['keywords'], JSON_UNESCAPED_UNICODE),
|
||
'content_blocks_json' => json_encode($item['content_blocks'], JSON_UNESCAPED_UNICODE),
|
||
'is_recommended' => !empty($item['is_recommended']) ? 1 : 0,
|
||
'is_enabled' => 1,
|
||
'sort_order' => $item['sort_order'] ?? ($index + 1) * 10,
|
||
'created_at' => $item['updated_at'] ?? $now,
|
||
'updated_at' => $item['updated_at'] ?? $now,
|
||
]);
|
||
}
|
||
}
|
||
|
||
private function upsertSystemConfig(string $groupCode, string $configKey, string $configValue, string $now): void
|
||
{
|
||
$exists = Db::name('system_configs')
|
||
->where('config_group', $groupCode)
|
||
->where('config_key', $configKey)
|
||
->find();
|
||
|
||
$payload = [
|
||
'config_group' => $groupCode,
|
||
'config_key' => $configKey,
|
||
'config_value' => $configValue,
|
||
'remark' => '内容配置',
|
||
'updated_at' => $now,
|
||
];
|
||
|
||
if ($exists) {
|
||
Db::name('system_configs')->where('id', $exists['id'])->update($payload);
|
||
return;
|
||
}
|
||
|
||
$payload['created_at'] = $now;
|
||
Db::name('system_configs')->insert($payload);
|
||
}
|
||
|
||
private function decodeJsonConfig(string $value, array $default): array
|
||
{
|
||
if ($value === '') {
|
||
return $default;
|
||
}
|
||
|
||
$decoded = json_decode($value, true);
|
||
return is_array($decoded) ? $decoded : $default;
|
||
}
|
||
|
||
private function decodeJsonObjectConfig(string $value, array $default): array
|
||
{
|
||
if ($value === '') {
|
||
return $default;
|
||
}
|
||
|
||
$decoded = json_decode($value, true);
|
||
return is_array($decoded) ? $decoded : $default;
|
||
}
|
||
|
||
private function normalizeArrayItems(mixed $value, array $keys, array $default): array
|
||
{
|
||
if (!is_array($value)) {
|
||
return $default;
|
||
}
|
||
|
||
$normalized = [];
|
||
foreach ($value as $item) {
|
||
if (!is_array($item)) {
|
||
continue;
|
||
}
|
||
|
||
$row = [];
|
||
foreach ($keys as $key) {
|
||
$row[$key] = trim((string)($item[$key] ?? ''));
|
||
}
|
||
|
||
if (implode('', $row) === '') {
|
||
continue;
|
||
}
|
||
|
||
$normalized[] = $row;
|
||
}
|
||
|
||
return $normalized ?: $default;
|
||
}
|
||
|
||
private function normalizePolicyItems(mixed $value, array $default): array
|
||
{
|
||
if (!is_array($value)) {
|
||
return $this->hydratePolicyItems($default);
|
||
}
|
||
|
||
$normalized = [];
|
||
foreach ($value as $item) {
|
||
if (!is_array($item)) {
|
||
continue;
|
||
}
|
||
|
||
$code = trim((string)($item['code'] ?? ''));
|
||
$title = trim((string)($item['title'] ?? ''));
|
||
$desc = trim((string)($item['desc'] ?? ''));
|
||
$targetUrl = trim((string)($item['target_url'] ?? ''));
|
||
$articleId = (int)($item['article_id'] ?? 0);
|
||
|
||
if ($articleId <= 0) {
|
||
$articleId = $this->extractHelpArticleIdFromTargetUrl($targetUrl);
|
||
}
|
||
|
||
if ($articleId > 0) {
|
||
$article = Db::name(self::HELP_TABLE)->field(['id'])->where('id', $articleId)->find();
|
||
if (!$article) {
|
||
throw new \RuntimeException(sprintf('绑定文章 #%d 不存在,请重新选择', $articleId));
|
||
}
|
||
$targetUrl = $this->buildHelpArticleTargetUrl($articleId);
|
||
}
|
||
|
||
if ($code === '' && $title === '' && $desc === '' && $targetUrl === '' && $articleId <= 0) {
|
||
continue;
|
||
}
|
||
|
||
$normalized[] = [
|
||
'code' => $code,
|
||
'title' => $title,
|
||
'desc' => $desc,
|
||
'target_url' => $targetUrl,
|
||
'article_id' => $articleId,
|
||
];
|
||
}
|
||
|
||
return $normalized ?: $this->hydratePolicyItems($default);
|
||
}
|
||
|
||
private function hydratePolicyItems(array $items): array
|
||
{
|
||
$normalized = [];
|
||
foreach ($items as $item) {
|
||
if (!is_array($item)) {
|
||
continue;
|
||
}
|
||
|
||
$articleId = (int)($item['article_id'] ?? 0);
|
||
$targetUrl = trim((string)($item['target_url'] ?? ''));
|
||
|
||
if ($articleId <= 0) {
|
||
$articleId = $this->extractHelpArticleIdFromTargetUrl($targetUrl);
|
||
}
|
||
|
||
if ($articleId > 0) {
|
||
$targetUrl = $this->buildHelpArticleTargetUrl($articleId);
|
||
}
|
||
|
||
$normalized[] = [
|
||
'code' => trim((string)($item['code'] ?? '')),
|
||
'title' => trim((string)($item['title'] ?? '')),
|
||
'desc' => trim((string)($item['desc'] ?? '')),
|
||
'target_url' => $targetUrl,
|
||
'article_id' => $articleId,
|
||
];
|
||
}
|
||
|
||
return $normalized;
|
||
}
|
||
|
||
private function extractHelpArticleIdFromTargetUrl(string $targetUrl): int
|
||
{
|
||
if ($targetUrl === '') {
|
||
return 0;
|
||
}
|
||
|
||
if (!preg_match('/\/pages\/help\/detail\?id=(\d+)/', $targetUrl, $matches)) {
|
||
return 0;
|
||
}
|
||
|
||
return (int)($matches[1] ?? 0);
|
||
}
|
||
|
||
private function buildHelpArticleTargetUrl(int $articleId): string
|
||
{
|
||
return sprintf('/pages/help/detail?id=%d', $articleId);
|
||
}
|
||
|
||
private function findPolicyReferenceByArticleId(int $articleId): ?string
|
||
{
|
||
$policy = $this->getPolicyConfig();
|
||
foreach (['legal_entries' => '设置页说明入口', 'appraisal_agreements' => '下单确认协议'] as $section => $sectionName) {
|
||
foreach (($policy[$section] ?? []) as $item) {
|
||
$itemArticleId = (int)($item['article_id'] ?? 0);
|
||
if ($itemArticleId <= 0) {
|
||
$itemArticleId = $this->extractHelpArticleIdFromTargetUrl((string)($item['target_url'] ?? ''));
|
||
}
|
||
|
||
if ($itemArticleId !== $articleId) {
|
||
continue;
|
||
}
|
||
|
||
$title = trim((string)($item['title'] ?? ''));
|
||
return $title !== '' ? sprintf('%s / %s', $sectionName, $title) : $sectionName;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private function normalizeObjectConfig(mixed $value, array $keys, array $default): array
|
||
{
|
||
if (!is_array($value)) {
|
||
return $default;
|
||
}
|
||
|
||
$normalized = [];
|
||
foreach ($keys as $key) {
|
||
$normalized[$key] = trim((string)($value[$key] ?? ''));
|
||
}
|
||
|
||
return implode('', $normalized) === '' ? $default : $normalized;
|
||
}
|
||
|
||
private function normalizeStringList(mixed $value, array $default): array
|
||
{
|
||
if (!is_array($value)) {
|
||
return $default;
|
||
}
|
||
|
||
$normalized = array_values(array_filter(array_map(
|
||
fn ($item) => trim((string)$item),
|
||
$value
|
||
), fn ($item) => $item !== ''));
|
||
|
||
return $normalized ?: $default;
|
||
}
|
||
|
||
private function defaultHomeConfig(): array
|
||
{
|
||
return [
|
||
'banners' => [
|
||
[
|
||
'title' => '安心验',
|
||
'subtitle' => '独立第三方鉴定服务平台',
|
||
'description' => '专业鉴定高价值商品,报告可验真,流程可追踪。',
|
||
'background_image_url' => '',
|
||
],
|
||
],
|
||
'page_visuals' => [
|
||
'order_background_image_url' => '',
|
||
'report_background_image_url' => '',
|
||
],
|
||
'service_entries' => [
|
||
[
|
||
'service_provider' => 'anxinyan',
|
||
'title' => '实物鉴定',
|
||
'tag' => '标准服务',
|
||
'description' => '由安心验提供标准实物鉴定服务,适合正式结果交付场景。',
|
||
'meta' => '预计 48 小时内出结果 | 报告可验真',
|
||
],
|
||
[
|
||
'service_provider' => 'zhongjian',
|
||
'title' => '中检鉴定',
|
||
'tag' => '更高规格机构',
|
||
'description' => '由更高规格机构提供实物鉴定服务,适合更高要求场景。',
|
||
'meta' => '流程一致 | 出具机构不同 | 价格与时效有差异',
|
||
],
|
||
],
|
||
'category_visuals' => [
|
||
['category_name' => '奢侈品箱包', 'category_code' => 'luxury_bag', 'image_url' => ''],
|
||
['category_name' => '潮流鞋类', 'category_code' => 'sneaker', 'image_url' => ''],
|
||
['category_name' => '首饰配饰', 'category_code' => 'jewelry', 'image_url' => ''],
|
||
['category_name' => '高端美妆', 'category_code' => 'beauty', 'image_url' => ''],
|
||
['category_name' => '腕表', 'category_code' => 'watch', 'image_url' => ''],
|
||
['category_name' => '服饰', 'category_code' => 'clothing', 'image_url' => ''],
|
||
['category_name' => '3C 数码', 'category_code' => 'digital', 'image_url' => ''],
|
||
['category_name' => '古董文玩', 'category_code' => 'antique', 'image_url' => ''],
|
||
],
|
||
'quick_entries' => [
|
||
['code' => 'start', 'title' => '发起鉴定', 'desc' => '进入送检流程'],
|
||
['code' => 'orders', 'title' => '我的订单', 'desc' => '查看当前进度'],
|
||
['code' => 'reports', 'title' => '我的报告', 'desc' => '查看结果凭证'],
|
||
['code' => 'messages', 'title' => '消息中心', 'desc' => '查看服务提醒与结果通知'],
|
||
],
|
||
'trust_metrics' => [
|
||
['value' => '1280+', 'label' => '累计鉴定申请'],
|
||
['value' => '48h', 'label' => '标准结果时效'],
|
||
['value' => '100%', 'label' => '正式报告可验真'],
|
||
],
|
||
'trust_points' => [
|
||
['title' => '独立第三方', 'desc' => '保持中立判断,不参与买卖立场。'],
|
||
['title' => '报告可验真', 'desc' => '每份正式报告均支持编号与状态验证。'],
|
||
['title' => '流程可追踪', 'desc' => '从下单到出报告,关键节点一目了然。'],
|
||
['title' => '标准化作业', 'desc' => '按模板采集资料,单次鉴定后出具报告。'],
|
||
],
|
||
'faqs' => [
|
||
'实物鉴定和中检鉴定有什么区别?',
|
||
'一般多久可以出结果?',
|
||
'报告如何验证真伪?',
|
||
],
|
||
];
|
||
}
|
||
|
||
private function defaultPolicyConfig(): array
|
||
{
|
||
return [
|
||
'legal_entries' => [
|
||
[
|
||
'code' => 'privacy_policy',
|
||
'title' => '隐私说明',
|
||
'desc' => '了解平台如何处理您的订单与联系方式信息',
|
||
'target_url' => '/pages/help/index?q=%E9%9A%90%E7%A7%81',
|
||
'article_id' => 0,
|
||
],
|
||
[
|
||
'code' => 'service_notice',
|
||
'title' => '服务与通知说明',
|
||
'desc' => '了解消息提醒、工单回复与服务相关通知逻辑',
|
||
'target_url' => '/pages/help/index?q=%E6%9C%8D%E5%8A%A1',
|
||
'article_id' => 0,
|
||
],
|
||
],
|
||
'appraisal_agreements' => [
|
||
[
|
||
'code' => 'service_agreement',
|
||
'title' => '服务协议',
|
||
'desc' => '下单前请确认服务边界、报告用途与责任说明。',
|
||
'target_url' => '/pages/help/index?q=%E6%9C%8D%E5%8A%A1',
|
||
'article_id' => 0,
|
||
],
|
||
[
|
||
'code' => 'appraisal_notice',
|
||
'title' => '鉴定须知',
|
||
'desc' => '了解资料要求、流程节点与补资料处理规则。',
|
||
'target_url' => '/pages/help/index?q=%E9%89%B4%E5%AE%9A',
|
||
'article_id' => 0,
|
||
],
|
||
[
|
||
'code' => 'privacy_policy',
|
||
'title' => '隐私政策',
|
||
'desc' => '了解平台如何处理联系方式、地址和订单信息。',
|
||
'target_url' => '/pages/help/index?q=%E9%9A%90%E7%A7%81',
|
||
'article_id' => 0,
|
||
],
|
||
],
|
||
];
|
||
}
|
||
|
||
private function defaultMetaConfig(): array
|
||
{
|
||
return [
|
||
'help_categories' => [
|
||
['code' => 'all', 'title' => '全部', 'desc' => '查看全部帮助文章'],
|
||
['code' => 'service', 'title' => '服务流程', 'desc' => '了解下单、寄送、鉴定流程'],
|
||
['code' => 'report', 'title' => '报告验真', 'desc' => '了解报告查看、下载与验真'],
|
||
['code' => 'shipping', 'title' => '寄送物流', 'desc' => '了解寄送、运单和签收说明'],
|
||
['code' => 'support', 'title' => '售后支持', 'desc' => '了解补资料、工单和客服协助'],
|
||
],
|
||
'report_risk_defaults' => [
|
||
[
|
||
'report_type' => 'appraisal',
|
||
'title' => '正式鉴定报告',
|
||
'text' => '本报告基于送检商品及当前提交资料出具。若商品状态或所附资料发生变化,报告结论可能不再适用。',
|
||
],
|
||
[
|
||
'report_type' => 'inspection',
|
||
'title' => '后台补录检查单',
|
||
'text' => '本检查单为后台补录结果,请结合扫码查看的正式页面与现场实物信息综合判断。',
|
||
],
|
||
],
|
||
'ticket_types' => [
|
||
[
|
||
'code' => 'pre_consultation',
|
||
'title' => '鉴定前咨询',
|
||
'hint' => '适合流程、服务说明类问题',
|
||
'quick_desc' => '下单前流程、服务说明咨询',
|
||
],
|
||
[
|
||
'code' => 'order_issue',
|
||
'title' => '订单问题',
|
||
'hint' => '适合订单状态、支付、进度问题',
|
||
'quick_desc' => '进度、状态、支付相关',
|
||
],
|
||
[
|
||
'code' => 'upload_issue',
|
||
'title' => '上传问题',
|
||
'hint' => '适合拍摄上传、补图协助问题',
|
||
'quick_desc' => '拍摄、上传、补图协助',
|
||
],
|
||
[
|
||
'code' => 'report_issue',
|
||
'title' => '报告问题',
|
||
'hint' => '适合报告结论、验真、估值问题',
|
||
'quick_desc' => '结论、估值、验真咨询',
|
||
],
|
||
[
|
||
'code' => 'after_sales',
|
||
'title' => '售后问题',
|
||
'hint' => '适合服务反馈与后续处理',
|
||
'quick_desc' => '服务反馈与后续处理',
|
||
],
|
||
[
|
||
'code' => 'recheck',
|
||
'title' => '结果咨询',
|
||
'hint' => '适合咨询报告结论或补充说明',
|
||
'quick_desc' => '报告结论与说明咨询',
|
||
],
|
||
],
|
||
'ticket_statuses' => [
|
||
['code' => 'pending', 'title' => '待处理', 'desc' => '工单已提交,客服尚未正式开始处理。'],
|
||
['code' => 'processing', 'title' => '处理中', 'desc' => '客服正在跟进问题,您可继续补充说明或截图。'],
|
||
['code' => 'waiting_user', 'title' => '待您反馈', 'desc' => '客服需要您补充更多信息后才能继续处理。'],
|
||
['code' => 'resolved', 'title' => '已解决', 'desc' => '当前问题已处理完成,如仍有疑问可继续留言。'],
|
||
['code' => 'closed', 'title' => '已关闭', 'desc' => '工单已关闭,如需继续处理可重新发起工单。'],
|
||
],
|
||
'message_events' => [
|
||
['event_code' => 'order_created', 'title' => '下单成功', 'desc' => '用户成功创建鉴定订单后触发。'],
|
||
['event_code' => 'supplement_required', 'title' => '待补资料', 'desc' => '鉴定师发起补资料要求后触发。'],
|
||
['event_code' => 'report_published', 'title' => '报告已出具', 'desc' => '正式报告发布成功后触发。'],
|
||
['event_code' => 'return_shipped', 'title' => '物品已寄回', 'desc' => '平台登记回寄物流后触发。'],
|
||
['event_code' => 'return_received', 'title' => '回寄商品已签收', 'desc' => '用户签收回寄物品后触发。'],
|
||
['event_code' => 'ticket_reply', 'title' => '工单有新回复', 'desc' => '客服回复用户工单后触发。'],
|
||
['event_code' => 'ticket_waiting_user', 'title' => '工单待用户反馈', 'desc' => '后台将工单状态改为待用户反馈时触发。'],
|
||
['event_code' => 'ticket_resolved', 'title' => '工单已解决', 'desc' => '后台将工单状态改为已解决时触发。'],
|
||
['event_code' => 'ticket_closed', 'title' => '工单已关闭', 'desc' => '后台将工单状态改为已关闭时触发。'],
|
||
],
|
||
'message_page_copy' => [
|
||
'title' => '服务提醒与处理进度',
|
||
'desc' => '这里会统一展示订单流转、补资料、报告出具和工单回复等关键通知,方便您集中查看。',
|
||
],
|
||
];
|
||
}
|
||
|
||
private function defaultHelpArticles(): array
|
||
{
|
||
return [
|
||
[
|
||
'category' => 'service',
|
||
'title' => '实物鉴定和中检鉴定有什么区别?',
|
||
'summary' => '两种服务的核心流程一致,差异主要体现在出具机构、时效与价格上。',
|
||
'keywords' => ['实物鉴定', '中检鉴定', '服务区别'],
|
||
'updated_at' => '2026-04-21 09:00:00',
|
||
'is_recommended' => true,
|
||
'sort_order' => 10,
|
||
'content_blocks' => [
|
||
'实物鉴定和中检鉴定都会经过下单、填写信息、上传资料、寄送商品、鉴定和查看报告这几个核心步骤。',
|
||
'两者最大的区别在于出具机构不同。实物鉴定由安心验提供标准实物鉴定服务;中检鉴定由更高规格合作机构提供服务,适合对机构资质有更高要求的场景。',
|
||
'中检鉴定通常价格更高、时效也会略长一些。下单前建议先根据您的使用场景、预算和时效要求选择合适服务。',
|
||
],
|
||
],
|
||
[
|
||
'category' => 'service',
|
||
'title' => '一般多久可以出结果?',
|
||
'summary' => '标准版通常 48 小时左右,具体取决于服务类型、资料完整度和物流节点。',
|
||
'keywords' => ['时效', '出结果', '多久'],
|
||
'updated_at' => '2026-04-21 09:00:00',
|
||
'is_recommended' => true,
|
||
'sort_order' => 20,
|
||
'content_blocks' => [
|
||
'安心验标准版通常在 48 小时左右完成处理,中检鉴定因机构流程要求更高,时效会相对更长。',
|
||
'如果您上传的资料不完整、需要补图,或者商品物流尚未签收,整体时效会顺延。',
|
||
'建议您在订单详情和消息中心关注关键节点,一旦有补资料要求或报告出具通知,系统会第一时间提醒。',
|
||
],
|
||
],
|
||
[
|
||
'category' => 'report',
|
||
'title' => '报告如何验证真伪?',
|
||
'summary' => '正式报告出具后,可通过报告详情页或验真页输入编号进行验证。',
|
||
'keywords' => ['报告', '验真', '验证真伪'],
|
||
'updated_at' => '2026-04-21 09:00:00',
|
||
'is_recommended' => true,
|
||
'sort_order' => 30,
|
||
'content_blocks' => [
|
||
'正式报告发布后,您可以在报告中心进入报告详情,再点击“去验真”进入验真页面。',
|
||
'验真页会展示报告编号、机构、商品摘要和结论摘要。请以验真页显示的结果为准。',
|
||
'如果您对报告内容或验真结果仍有疑问,可以直接通过客服工单联系人工支持。',
|
||
],
|
||
],
|
||
[
|
||
'category' => 'shipping',
|
||
'title' => '商品寄出后还需要做什么?',
|
||
'summary' => '寄出商品后,请尽快回到“查看寄送”页填写快递公司和运单号。',
|
||
'keywords' => ['寄送', '运单', '物流'],
|
||
'updated_at' => '2026-04-21 09:00:00',
|
||
'is_recommended' => false,
|
||
'sort_order' => 40,
|
||
'content_blocks' => [
|
||
'寄出商品后,请保留寄件凭证,并尽快在订单详情或寄送页填写快递公司和运单号。',
|
||
'提交运单后,订单会显示“已提交运单”,后续签收和处理节点也会继续同步。',
|
||
'贵重商品建议选择可追踪快递,并在包裹内附上订单号或鉴定单号。',
|
||
],
|
||
],
|
||
[
|
||
'category' => 'support',
|
||
'title' => '收到补资料提醒后该怎么处理?',
|
||
'summary' => '收到补资料提醒后,请进入订单详情或补资料页,按要求重新上传指定资料。',
|
||
'keywords' => ['补资料', '补图', '上传'],
|
||
'updated_at' => '2026-04-21 09:00:00',
|
||
'is_recommended' => false,
|
||
'sort_order' => 50,
|
||
'content_blocks' => [
|
||
'如果鉴定师认为现有资料还不足以完成判断,系统会推送补资料通知到消息中心。',
|
||
'您可以直接点击消息进入补资料页,按要求上传缺失资料,再提交补资料。',
|
||
'提交完成后,订单会重新进入鉴定流程。如仍有疑问,也可以通过客服工单寻求协助。',
|
||
],
|
||
],
|
||
[
|
||
'category' => 'support',
|
||
'title' => '如何联系客服并查看处理进度?',
|
||
'summary' => '您可以从订单详情、验真页、“我的”页等入口发起工单,并在工单详情查看客服回复。',
|
||
'keywords' => ['客服', '工单', '处理进度'],
|
||
'updated_at' => '2026-04-21 09:00:00',
|
||
'is_recommended' => false,
|
||
'sort_order' => 60,
|
||
'content_blocks' => [
|
||
'目前用户端已支持发起工单、继续留言、查看客服回复和附件。',
|
||
'客服回复后,消息中心会收到提醒,点击即可进入对应工单详情。',
|
||
'如果工单状态变成“待您反馈”或“已解决”,系统也会同步推送状态通知。',
|
||
],
|
||
],
|
||
];
|
||
}
|
||
|
||
private function defaultPolicyHelpArticles(): array
|
||
{
|
||
return [
|
||
'privacy_policy' => [
|
||
'category' => 'service',
|
||
'title' => '隐私政策',
|
||
'summary' => '说明平台如何处理联系方式、地址、订单等个人信息,以及相关使用边界。',
|
||
'keywords' => ['隐私政策', '个人信息', '联系方式', '地址', '订单信息'],
|
||
'is_recommended' => false,
|
||
'sort_order' => 70,
|
||
'content_blocks' => [
|
||
'平台会在您下单、填写寄回地址、提交运单和联系客服等场景中收集必要的信息,仅用于完成鉴定服务、订单履约、结果通知和售后支持。',
|
||
'联系方式、地址、订单编号、商品资料和服务记录会用于鉴定流程推进、物流寄回、消息提醒和问题追踪,不会用于与本次服务无关的处理场景。',
|
||
'如需了解或更正相关信息,可通过设置页、地址管理、订单详情或客服工单入口进行处理,我们会按平台规则提供协助。',
|
||
],
|
||
],
|
||
'service_notice' => [
|
||
'category' => 'service',
|
||
'title' => '服务与通知说明',
|
||
'summary' => '说明消息提醒、工单回复、补资料和结果通知的触发方式与查看入口。',
|
||
'keywords' => ['服务通知', '消息提醒', '工单回复', '补资料', '结果通知'],
|
||
'is_recommended' => false,
|
||
'sort_order' => 80,
|
||
'content_blocks' => [
|
||
'订单创建、补资料、报告出具、物品寄回和工单回复等关键节点都会通过消息中心统一提醒,方便您集中查看当前处理进度。',
|
||
'若鉴定师需要补充资料,系统会推送待补资料通知,您可直接进入订单详情或补资料页继续上传。',
|
||
'若对通知内容有疑问,可通过客服工单继续追问,客服回复后也会再次触发站内提醒。',
|
||
],
|
||
],
|
||
'service_agreement' => [
|
||
'category' => 'service',
|
||
'title' => '服务协议',
|
||
'summary' => '说明鉴定服务边界、报告用途、结果交付方式与相关责任说明。',
|
||
'keywords' => ['服务协议', '鉴定服务', '报告用途', '责任说明'],
|
||
'is_recommended' => false,
|
||
'sort_order' => 90,
|
||
'content_blocks' => [
|
||
'平台提供的是独立第三方鉴定服务,服务结果基于送检商品、提交资料及实际履约节点综合判断,并以正式页面或报告展示内容为准。',
|
||
'不同服务类型在出具机构、价格、时效和交付形式上可能存在差异,下单前请结合自身需求确认所选服务方案。',
|
||
'若因资料缺失、物流未签收、商品状态变化或其他需补充核验的情况影响处理进度,平台会通过补资料或消息通知继续提示您后续操作。',
|
||
],
|
||
],
|
||
'appraisal_notice' => [
|
||
'category' => 'service',
|
||
'title' => '鉴定须知',
|
||
'summary' => '说明资料要求、流程节点、补资料处理规则与常见注意事项。',
|
||
'keywords' => ['鉴定须知', '资料要求', '流程节点', '补资料'],
|
||
'is_recommended' => false,
|
||
'sort_order' => 100,
|
||
'content_blocks' => [
|
||
'请尽量按模板上传清晰、完整的商品资料,并在寄送实物后及时填写物流信息,这会直接影响鉴定效率与处理时效。',
|
||
'若当前资料不足以支持判断,鉴定师会发起补资料要求,订单会暂停在待补资料节点,待您补齐后再继续流转。',
|
||
'正式报告仅对当前送检商品及本次服务资料负责,如商品状态、附件情况或所附证明材料发生变化,相关结论可能需要重新核验。',
|
||
],
|
||
],
|
||
];
|
||
}
|
||
|
||
private function resolveOrCreatePolicyArticleId(string $code): int
|
||
{
|
||
$code = trim($code);
|
||
if ($code === '') {
|
||
return 0;
|
||
}
|
||
|
||
$definitions = $this->defaultPolicyHelpArticles();
|
||
$definition = $definitions[$code] ?? null;
|
||
if (!$definition) {
|
||
return 0;
|
||
}
|
||
|
||
$existing = Db::name(self::HELP_TABLE)
|
||
->where('title', $definition['title'])
|
||
->find();
|
||
if ($existing) {
|
||
return (int)$existing['id'];
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
return (int)Db::name(self::HELP_TABLE)->insertGetId([
|
||
'category' => $definition['category'],
|
||
'title' => $definition['title'],
|
||
'summary' => $definition['summary'],
|
||
'keywords_json' => json_encode($definition['keywords'], JSON_UNESCAPED_UNICODE),
|
||
'content_blocks_json' => json_encode($definition['content_blocks'], JSON_UNESCAPED_UNICODE),
|
||
'is_recommended' => !empty($definition['is_recommended']) ? 1 : 0,
|
||
'is_enabled' => 1,
|
||
'sort_order' => (int)$definition['sort_order'],
|
||
'created_at' => $now,
|
||
'updated_at' => $now,
|
||
]);
|
||
}
|
||
}
|