feat: 社交账号设置(微博/GitHub/X/Facebook/知乎/邮箱)+ JSON-LD sameAs(Person/Organization/WebSite)+ og:image/og:locale/article 时间 + twitter 完整卡片
This commit is contained in:
@@ -64,6 +64,13 @@ class BlogSettings extends Page
|
|||||||
// 维护
|
// 维护
|
||||||
'site_closed' => Setting::get('site_closed', '0'),
|
'site_closed' => Setting::get('site_closed', '0'),
|
||||||
'site_closed_note' => Setting::get('site_closed_note', ''),
|
'site_closed_note' => Setting::get('site_closed_note', ''),
|
||||||
|
// 社交账号
|
||||||
|
'social_weibo' => Setting::get('social_weibo', ''),
|
||||||
|
'social_github' => Setting::get('social_github', ''),
|
||||||
|
'social_twitter' => Setting::get('social_twitter', ''),
|
||||||
|
'social_facebook' => Setting::get('social_facebook', ''),
|
||||||
|
'social_zhihu' => Setting::get('social_zhihu', ''),
|
||||||
|
'social_email' => Setting::get('social_email', ''),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +126,16 @@ class BlogSettings extends Page
|
|||||||
TextInput::make('watermark_size')->label('水印触发尺寸')->placeholder('300x300'),
|
TextInput::make('watermark_size')->label('水印触发尺寸')->placeholder('300x300'),
|
||||||
TextInput::make('watermarktrans')->label('水印透明度(%)')->numeric(),
|
TextInput::make('watermarktrans')->label('水印透明度(%)')->numeric(),
|
||||||
]),
|
]),
|
||||||
|
Tab::make('社交账号')
|
||||||
|
->columns(2)
|
||||||
|
->schema([
|
||||||
|
TextInput::make('social_weibo')->label('微博')->url()->placeholder('https://weibo.com/xxx'),
|
||||||
|
TextInput::make('social_github')->label('GitHub')->url()->placeholder('https://github.com/xxx'),
|
||||||
|
TextInput::make('social_twitter')->label('Twitter / X')->url()->placeholder('https://x.com/xxx'),
|
||||||
|
TextInput::make('social_facebook')->label('Facebook')->url()->placeholder('https://facebook.com/xxx'),
|
||||||
|
TextInput::make('social_zhihu')->label('知乎')->url()->placeholder('https://zhihu.com/people/xxx'),
|
||||||
|
TextInput::make('social_email')->label('邮箱')->email()->placeholder('contact@example.com'),
|
||||||
|
]),
|
||||||
Tab::make('维护')
|
Tab::make('维护')
|
||||||
->schema([
|
->schema([
|
||||||
Toggle::make('site_closed')->label('关闭站点')->default(false),
|
Toggle::make('site_closed')->label('关闭站点')->default(false),
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class SeoMiddleware
|
|||||||
private function structuredData(Request $request): array
|
private function structuredData(Request $request): array
|
||||||
{
|
{
|
||||||
$siteName = blog_setting('site_name', config('blog.name'));
|
$siteName = blog_setting('site_name', config('blog.name'));
|
||||||
|
$sameAs = $this->socialLinks();
|
||||||
|
|
||||||
$route = $request->route();
|
$route = $request->route();
|
||||||
|
|
||||||
@@ -70,18 +71,24 @@ class SeoMiddleware
|
|||||||
});
|
});
|
||||||
|
|
||||||
if ($post && $post->status === 'published') {
|
if ($post && $post->status === 'published') {
|
||||||
return [
|
$data = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'BlogPosting',
|
'@type' => 'BlogPosting',
|
||||||
'headline' => $post->title,
|
'headline' => $post->title,
|
||||||
'url' => route('posts.show', $post->slug ?: $post->id),
|
'url' => route('posts.show', $post->slug ?: $post->id),
|
||||||
'datePublished' => $post->published_at?->toIso8601String(),
|
'datePublished' => $post->published_at?->toIso8601String(),
|
||||||
'dateModified' => $post->updated_at?->toIso8601String(),
|
'dateModified' => $post->updated_at?->toIso8601String(),
|
||||||
'author' => ['@type' => 'Person', 'name' => $post->author?->name ?? $siteName],
|
'author' => ['@type' => 'Person', 'name' => $post->author?->name ?? $siteName] + ($sameAs ? ['sameAs' => $sameAs] : []),
|
||||||
'publisher' => ['@type' => 'Organization', 'name' => $siteName],
|
'publisher' => ['@type' => 'Organization', 'name' => $siteName, 'url' => url('/')] + ($sameAs ? ['sameAs' => $sameAs] : []),
|
||||||
'description' => Str::limit(strip_tags($post->excerpt_or_fallback), 200),
|
'description' => Str::limit(strip_tags($post->excerpt_or_fallback), 200),
|
||||||
'mainEntityOfPage' => route('posts.show', $post->slug ?: $post->id),
|
'mainEntityOfPage' => route('posts.show', $post->slug ?: $post->id),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($cover = $post->getFirstMedia('cover')) {
|
||||||
|
$data['image'] = [$cover->getUrl('card') ?: $cover->getUrl()];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,26 +97,52 @@ class SeoMiddleware
|
|||||||
'@type' => 'WebSite',
|
'@type' => 'WebSite',
|
||||||
'name' => $siteName,
|
'name' => $siteName,
|
||||||
'url' => url('/'),
|
'url' => url('/'),
|
||||||
];
|
] + ($sameAs ? ['sameAs' => $sameAs] : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后台配置的社交账号 -> schema sameAs(Facebook / GitHub / Twitter 等平台识别)。
|
||||||
|
*/
|
||||||
|
private function socialLinks(): array
|
||||||
|
{
|
||||||
|
$links = [];
|
||||||
|
|
||||||
|
foreach (['social_weibo', 'social_github', 'social_twitter', 'social_facebook', 'social_zhihu'] as $key) {
|
||||||
|
if ($url = blog_setting($key, '')) {
|
||||||
|
$links[] = $url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($links));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ogTags(Request $request, array $data): string
|
private function ogTags(Request $request, array $data): string
|
||||||
{
|
{
|
||||||
$siteName = blog_setting('site_name', config('blog.name'));
|
$siteName = blog_setting('site_name', config('blog.name'));
|
||||||
$isArticle = ($data['@type'] ?? '') === 'BlogPosting';
|
$isArticle = ($data['@type'] ?? '') === 'BlogPosting';
|
||||||
|
$image = $data['image'][0] ?? null;
|
||||||
|
|
||||||
$tags = [
|
$tags = [
|
||||||
'<meta property="og:site_name" content="'.e($siteName).'">',
|
'<meta property="og:site_name" content="'.e($siteName).'">',
|
||||||
|
'<meta property="og:locale" content="zh_CN">',
|
||||||
'<meta property="og:type" content="'.($isArticle ? 'article' : 'website').'">',
|
'<meta property="og:type" content="'.($isArticle ? 'article' : 'website').'">',
|
||||||
'<meta property="og:title" content="'.e($data['headline'] ?? $siteName).'">',
|
'<meta property="og:title" content="'.e($data['headline'] ?? $siteName).'">',
|
||||||
'<meta property="og:url" content="'.e(url()->current()).'">',
|
'<meta property="og:url" content="'.e(url()->current()).'">',
|
||||||
'<meta property="og:description" content="'.e($data['description'] ?? blog_setting('site_description', '')).'">',
|
'<meta property="og:description" content="'.e($data['description'] ?? blog_setting('site_description', '')).'">',
|
||||||
'<meta name="twitter:card" content="summary_large_image">',
|
'<meta name="twitter:card" content="'.($image ? 'summary_large_image' : 'summary').'">',
|
||||||
'<meta name="twitter:title" content="'.e($data['headline'] ?? $siteName).'">',
|
'<meta name="twitter:title" content="'.e($data['headline'] ?? $siteName).'">',
|
||||||
|
'<meta name="twitter:description" content="'.e($data['description'] ?? blog_setting('site_description', '')).'">',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($image) {
|
||||||
|
$tags[] = '<meta property="og:image" content="'.e($image).'">';
|
||||||
|
$tags[] = '<meta property="og:image:alt" content="'.e($data['headline'] ?? $siteName).'">';
|
||||||
|
$tags[] = '<meta name="twitter:image" content="'.e($image).'">';
|
||||||
|
}
|
||||||
|
|
||||||
if ($isArticle) {
|
if ($isArticle) {
|
||||||
$tags[] = '<meta property="article:published_time" content="'.e($data['datePublished'] ?? '').'">';
|
$tags[] = '<meta property="article:published_time" content="'.e($data['datePublished'] ?? '').'">';
|
||||||
|
$tags[] = '<meta property="article:modified_time" content="'.e($data['dateModified'] ?? '').'">';
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode("\n", $tags);
|
return implode("\n", $tags);
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake-jpg
|
||||||
Reference in New Issue
Block a user