代码改动: - SEO-02 产品页标题品牌重复:Product::seoTitle() 去掉品牌后缀,由视图统一追加 $siteName - SEO-05/Geo-03 结构化数据:布局增 WebSite+SearchAction;产品页 Product+BreadcrumbList;文章页 Article+BreadcrumbList(含作者/发布日期) - SEO-06 hreflang 自引用(zh-CN/x-default) - SEC-02 新增 public/.well-known/security.txt - GEO-05 新增 public/llms.txt 记录文档(供未来开发者接手): - CHANGELOG.md:版本与变更日志 - docs/SECURITY_SEO_REMEDIATION.md:逐项状态总表 + 改动位置 + 上线 curl 自检清单 (SEC-01/REL-01~03/PHY-01~04 等需 Tailwind 构建或云端操作项标记为待办,未自动改以免破坏站点)
318 lines
20 KiB
PHP
318 lines
20 KiB
PHP
<!DOCTYPE html>
|
||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||
@php
|
||
$siteName = App\Models\Setting::get('site_name', '深圳市云创芯电子有限公司');
|
||
$seoTitleDefault = App\Models\Setting::get('seo_title_default', $siteName . ' - 多口 TypeC 接口IC');
|
||
$seoDescDefault = App\Models\Setting::get('seo_description_default', '深圳市云创芯电子有限公司提供多口 TypeC 接口IC。');
|
||
$logoUrl = '/images/logo-dark.png'; // 固定品牌 logo,直接指向深色版,不依赖数据库设置(避免服务器库未同步导致回退旧 logo)
|
||
$faviconUrl = App\Models\Setting::get('favicon_url', '/images/ccc-icon.ico');
|
||
$icpNo = App\Models\Setting::get('icp_no', '');
|
||
$securityFilingNo = App\Models\Setting::get('security_filing_no', '粤公网安备44030002015390号');
|
||
|
||
// 主题配色:后台「站点设置」可切换;默认 azure(沉稳蓝)
|
||
$themes = [
|
||
'azure' => ['primary' => '#4a8af4', 'hover' => '#76a9f7', 'accent' => '#6fa8f7', 'info' => '#4a8af4', 'rgb' => '74,138,244'],
|
||
'slate' => ['primary' => '#6e8cb0', 'hover' => '#8aa6c6', 'accent' => '#7d98ba', 'info' => '#6e8cb0', 'rgb' => '110,140,176'],
|
||
'indigo' => ['primary' => '#7b7ef0', 'hover' => '#9b9df6', 'accent' => '#8f86f2', 'info' => '#7b7ef0', 'rgb' => '123,126,240'],
|
||
'teal' => ['primary' => '#2bb6a4', 'hover' => '#4fd0bd', 'accent' => '#34c0ad', 'info' => '#2bb6a4', 'rgb' => '43,182,164'],
|
||
'amber' => ['primary' => '#e8a13c', 'hover' => '#f2b765', 'accent' => '#ecae52', 'info' => '#e8a13c', 'rgb' => '232,161,60'],
|
||
];
|
||
$themeKey = App\Models\Setting::get('theme', 'azure');
|
||
$theme = $themes[$themeKey] ?? $themes['azure'];
|
||
@endphp
|
||
<title>@yield('title', $seoTitleDefault)</title>
|
||
<meta name="description" content="@yield('description', $seoDescDefault)">
|
||
<link rel="canonical" href="{{ url()->current() }}">
|
||
<link rel="alternate" hreflang="zh-CN" href="{{ url()->current() }}">
|
||
<link rel="alternate" hreflang="x-default" href="{{ url('/') }}">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:site_name" content="{{ $siteName }}">
|
||
<meta property="og:title" content="@yield('title', $seoTitleDefault)">
|
||
<meta property="og:description" content="@yield('description', $seoDescDefault)">
|
||
<meta property="og:url" content="{{ url()->current() }}">
|
||
<meta property="og:image" content="{{ asset($logoUrl) }}">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<meta name="twitter:title" content="@yield('title', $seoTitleDefault)">
|
||
<meta name="twitter:description" content="@yield('description', $seoDescDefault)">
|
||
<meta name="twitter:image" content="{{ asset($logoUrl) }}">
|
||
<link rel="icon" href="{{ asset($faviconUrl) }}" sizes="any">
|
||
<link rel="stylesheet" href="{{ asset('css/app.css') }}?v={{ filemtime(public_path('css/app.css')) }}">
|
||
<!-- 主题配色覆盖:由后台「站点设置 → 主题」选择,注入后覆盖 app.css 默认 :root 变量(主色 + 光晕同步联动) -->
|
||
<style id="theme-override">
|
||
:root {
|
||
--color-primary: {{ $theme['primary'] }};
|
||
--color-primary-hover: {{ $theme['hover'] }};
|
||
--color-accent: {{ $theme['accent'] }};
|
||
--color-info: {{ $theme['info'] }};
|
||
--glow-color: {{ $theme['rgb'] }};
|
||
}
|
||
</style>
|
||
<!-- Tailwind Play CDN(零构建);颜色全部映射到 :root CSS 变量,源码不硬编码颜色 -->
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
primary: 'var(--color-primary)',
|
||
'primary-hover': 'var(--color-primary-hover)',
|
||
'primary-soft': 'var(--color-primary-soft)',
|
||
surface: 'var(--color-surface)',
|
||
'surface-2': 'var(--color-surface-2)',
|
||
line: 'var(--color-border)',
|
||
fg: 'var(--color-text)',
|
||
muted: 'var(--color-text-muted)',
|
||
danger: 'var(--color-danger)',
|
||
'danger-hover': 'var(--color-danger-hover)',
|
||
'danger-soft': 'var(--color-danger-soft)',
|
||
success: 'var(--color-success)',
|
||
'success-soft': 'var(--color-success-soft)',
|
||
warning: 'var(--color-warning)',
|
||
'warning-soft': 'var(--color-warning-soft)',
|
||
info: 'var(--color-info)',
|
||
'info-soft': 'var(--color-info-soft)',
|
||
bg: 'var(--color-bg)',
|
||
accent: 'var(--color-accent)',
|
||
},
|
||
fontFamily: {
|
||
sans: ['Inter', 'Noto Sans SC', 'system-ui', '-apple-system', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'PingFang SC', 'Microsoft YaHei', 'sans-serif'],
|
||
},
|
||
borderRadius: {
|
||
sm: 'var(--radius-sm)',
|
||
DEFAULT: 'var(--radius)',
|
||
lg: 'var(--radius-lg)',
|
||
},
|
||
boxShadow: {
|
||
soft: '0 1px 2px rgba(15, 23, 42, 0.06)',
|
||
card: '0 4px 14px rgba(15, 23, 42, 0.08)',
|
||
},
|
||
maxWidth: {
|
||
'8xl': '80rem',
|
||
},
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
<script src="{{ asset('js/vendor/vue.global.prod.js') }}" defer></script>
|
||
<script src="{{ asset('js/app.js') }}?v={{ filemtime(public_path('js/app.js')) }}" defer></script>
|
||
<script type="application/ld+json">
|
||
{
|
||
"@@context": "https://schema.org",
|
||
"@@type": "Organization",
|
||
"name": {!! json_encode($siteName, JSON_UNESCAPED_UNICODE) !!},
|
||
"url": {!! json_encode(url('/'), JSON_UNESCAPED_UNICODE) !!},
|
||
"logo": {!! json_encode(asset($logoUrl), JSON_UNESCAPED_UNICODE) !!},
|
||
"description": {!! json_encode($seoDescDefault, JSON_UNESCAPED_UNICODE) !!}
|
||
}
|
||
</script>
|
||
<script type="application/ld+json">
|
||
{
|
||
"@@context": "https://schema.org",
|
||
"@@type": "WebSite",
|
||
"name": {!! json_encode($siteName, JSON_UNESCAPED_UNICODE) !!},
|
||
"url": {!! json_encode(url('/'), JSON_UNESCAPED_UNICODE) !!},
|
||
"potentialAction": {
|
||
"@@type": "SearchAction",
|
||
"target": {
|
||
"@@type": "EntryPoint",
|
||
"urlTemplate": {!! json_encode(url('/search') . '?q={search_term_string}', JSON_UNESCAPED_UNICODE) !!}
|
||
},
|
||
"query-input": "required name=search_term_string"
|
||
}
|
||
}
|
||
</script>
|
||
</head>
|
||
<body class="font-sans text-fg bg-surface antialiased">
|
||
|
||
@php
|
||
// 前台顶部导航菜单(后台「导航菜单」可全权管理:显示/隐藏 + 排序)
|
||
$navMenus = \App\Models\NavigationMenu::visible()->ordered()->get();
|
||
@endphp
|
||
|
||
<!-- 顶部导航:TI 企业蓝风格,sticky + 留白 + 清晰层级 -->
|
||
<header class="sticky top-0 z-50 bg-surface/90 backdrop-blur border-b border-line">
|
||
<div class="max-w-8xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div class="flex items-center justify-between h-14 lg:h-[68px]">
|
||
<a class="flex items-center gap-3 shrink-0" href="{{ url('/') }}" aria-label="{{ $siteName }}">
|
||
<img src="{{ asset($logoUrl) }}" alt="{{ $siteName }}" class="h-12 w-auto lg:h-14 drop-shadow-sm">
|
||
</a>
|
||
|
||
<nav class="hidden lg:flex items-center gap-7">
|
||
@foreach ($navMenus as $m)
|
||
<a href="{{ Str::startsWith($m->url, ['http://', 'https://']) ? $m->url : url($m->url) }}" class="text-lg lg:text-xl font-medium text-muted hover:text-primary transition-colors">{{ $m->label }}</a>
|
||
@endforeach
|
||
</nav>
|
||
|
||
<div class="hidden lg:flex items-center gap-5">
|
||
<!-- 全站搜索 -->
|
||
<form action="{{ url('/search') }}" method="GET" class="relative">
|
||
<x-icon name="search" :size="20" class="absolute left-3 top-1/2 -translate-y-1/2 text-muted pointer-events-none" />
|
||
<input type="search" name="q" value="{{ request('q', '') }}" placeholder="搜索产品、文章、术语…" class="w-56 rounded-lg border border-line bg-surface-2 pl-10 pr-3 py-2.5 text-lg text-fg placeholder:text-muted focus:border-primary focus:outline-none focus:shadow-[0_0_0_3px_var(--color-primary-soft)]">
|
||
</form>
|
||
|
||
@auth
|
||
@if (Auth::user()->role !== 'user')
|
||
<a href="{{ url('/admin') }}" class="text-lg lg:text-xl font-medium text-muted hover:text-primary transition-colors">后台</a>
|
||
@endif
|
||
<a href="{{ route('articles.submit') }}" class="text-lg lg:text-xl font-medium text-muted hover:text-primary transition-colors">投稿</a>
|
||
<x-level-badge :user="Auth::user()" />
|
||
<span class="text-lg lg:text-xl text-muted">{{ Auth::user()->name }}</span>
|
||
<a href="{{ route('auth.settings') }}" class="text-lg lg:text-xl font-medium text-muted hover:text-primary transition-colors">设置</a>
|
||
<form method="POST" action="{{ url('/logout') }}" class="inline">
|
||
@csrf
|
||
<button type="submit" class="text-lg lg:text-xl font-medium text-muted hover:text-danger transition-colors">退出</button>
|
||
</form>
|
||
@endauth
|
||
</div>
|
||
|
||
<!-- 移动端菜单按钮 -->
|
||
<button id="nav-toggle" type="button" class="lg:hidden inline-flex items-center justify-center p-2 rounded-lg text-muted hover:bg-surface-2" aria-label="菜单">
|
||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 移动端展开菜单 -->
|
||
<div id="nav-menu" class="lg:hidden hidden border-t border-line bg-surface">
|
||
<div class="max-w-8xl mx-auto px-4 py-3 space-y-1">
|
||
<form action="{{ url('/search') }}" method="GET" class="relative mb-2">
|
||
<x-icon name="search" :size="20" class="absolute left-3 top-1/2 -translate-y-1/2 text-muted pointer-events-none" />
|
||
<input type="search" name="q" value="{{ request('q', '') }}" placeholder="搜索产品、文章、术语…" class="w-full rounded-lg border border-line bg-surface-2 pl-10 pr-3 py-2.5 text-lg text-fg placeholder:text-muted focus:border-primary focus:outline-none focus:shadow-[0_0_0_3px_var(--color-primary-soft)]">
|
||
</form>
|
||
@foreach ($navMenus as $m)
|
||
<a href="{{ Str::startsWith($m->url, ['http://', 'https://']) ? $m->url : url($m->url) }}" class="block px-3 py-2.5 rounded-lg text-lg font-medium text-muted hover:bg-surface-2 hover:text-primary">{{ $m->label }}</a>
|
||
@endforeach
|
||
@auth
|
||
@if (Auth::user()->role !== 'user')
|
||
<a href="{{ url('/admin') }}" class="block px-3 py-2.5 rounded-lg text-lg font-medium text-muted hover:bg-surface-2 hover:text-primary">后台</a>
|
||
@endif
|
||
<a href="{{ route('articles.submit') }}" class="block px-3 py-2.5 rounded-lg text-lg font-medium text-muted hover:bg-surface-2 hover:text-primary">投稿</a>
|
||
<div class="px-3 py-2.5"><x-level-badge :user="Auth::user()" /></div>
|
||
<a href="{{ route('auth.settings') }}" class="block px-3 py-2.5 rounded-lg text-lg font-medium text-muted hover:bg-surface-2 hover:text-primary">设置</a>
|
||
<form method="POST" action="{{ url('/logout') }}" class="block">
|
||
@csrf
|
||
<button type="submit" class="w-full text-left px-3 py-2.5 rounded-lg text-lg font-medium text-muted hover:bg-surface-2 hover:text-danger">退出({{ Auth::user()->name }})</button>
|
||
</form>
|
||
@endauth
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="min-h-[60vh]">
|
||
<div class="max-w-8xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
@if (session('success'))
|
||
<div class="flex items-center gap-2 my-6 rounded-lg border border-success/30 bg-success-soft px-4 py-3 text-sm text-success">
|
||
<x-icon name="check" :size="18" />
|
||
<span>{{ session('success') }}</span>
|
||
</div>
|
||
@endif
|
||
@if ($errors?->any())
|
||
<div class="flex items-center gap-2 my-6 rounded-lg border border-danger/30 bg-danger-soft px-4 py-3 text-sm text-danger">
|
||
<x-icon name="alert" :size="18" />
|
||
<span>{{ $errors->first() ?: '表单填写有误,请检查后重试。' }}</span>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
@yield('content')
|
||
</main>
|
||
|
||
<footer class="mt-16 border-t border-line bg-surface">
|
||
<div class="max-w-8xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||
<div class="grid gap-8 md:grid-cols-4">
|
||
<div class="md:col-span-2">
|
||
<div class="flex items-center gap-3 mb-3">
|
||
<img src="{{ asset($logoUrl) }}" alt="{{ $siteName }}" class="h-12 w-auto drop-shadow-sm">
|
||
</div>
|
||
<p class="text-sm text-muted max-w-md">TypeC 产品顾问|多口 TypeC 接口IC 方案提供商,专注协议转换与同步整流控制芯片,面向充电与外设厂商。</p>
|
||
</div>
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-fg mb-3">产品与资源</h4>
|
||
<ul class="space-y-2 text-sm text-muted">
|
||
<li><a href="{{ url('/products') }}" class="hover:text-primary transition-colors">产品中心</a></li>
|
||
<li><a href="{{ url('/solutions') }}" class="hover:text-primary transition-colors">解决方案</a></li>
|
||
<li><a href="{{ url('/about') }}" class="hover:text-primary transition-colors">关于我们</a></li>
|
||
<li><a href="{{ url('/articles') }}" class="hover:text-primary transition-colors">技术文章</a></li>
|
||
<li><a href="{{ url('/news') }}" class="hover:text-primary transition-colors">新闻动态</a></li>
|
||
<li><a href="{{ url('/glossary') }}" class="hover:text-primary transition-colors">术语库</a></li>
|
||
<li><a href="{{ url('/forum') }}" class="hover:text-primary transition-colors">技术论坛</a></li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-fg mb-3">联系我们</h4>
|
||
<ul class="space-y-2.5 text-sm text-muted">
|
||
<li class="flex items-start gap-2">
|
||
<x-icon name="box" :size="16" class="mt-0.5 shrink-0 text-muted" />
|
||
<span>深圳市云创芯电子有限公司</span>
|
||
</li>
|
||
<li class="flex items-center gap-2">
|
||
<x-icon name="mail" :size="16" class="shrink-0 text-muted" />
|
||
<a href="mailto:service@51chip.net" class="hover:text-primary transition-colors">service@51chip.net</a>
|
||
</li>
|
||
<li class="flex items-center gap-2">
|
||
<x-icon name="phone" :size="16" class="shrink-0 text-muted" />
|
||
<a href="tel:13510116819" class="hover:text-primary transition-colors">13510116819</a>
|
||
</li>
|
||
<li><a href="{{ url('/contact') }}" class="hover:text-primary transition-colors">更多联络方式</a></li>
|
||
@auth
|
||
@if (Auth::user()->role !== 'user')
|
||
<li><a href="{{ url('/admin') }}" class="hover:text-primary transition-colors">管理后台</a></li>
|
||
@endif
|
||
@endauth
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
@php $footerContent = App\Models\Setting::get('footer_content'); @endphp
|
||
@if ($footerContent)
|
||
<div class="mt-8 footer-rich">
|
||
{!! \Purifier::clean($footerContent, 'article') !!}
|
||
</div>
|
||
@endif
|
||
<div class="mt-8 pt-6 border-t border-line text-sm text-muted">
|
||
<p class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
||
<span>© {{ date('Y') }} {{ $siteName }}. 保留所有权利。</span>
|
||
<a href="{{ url('/sitemap') }}" class="hover:text-primary transition-colors">网站地图</a>
|
||
@if ($icpNo)
|
||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow noopener noreferrer" class="icp-link">{{ $icpNo }}</a>
|
||
@endif
|
||
@php
|
||
// 公安网备号:优先取后台「站点设置」值,缺省内置粤公网安备44030002015390号
|
||
$secRaw = trim($securityFilingNo);
|
||
if ($secRaw === '') {
|
||
$secRaw = '粤公网安备44030002015390号';
|
||
}
|
||
// 提取数字编号,用于公安网备核验深链
|
||
preg_match('/\d{6,}/', $secRaw, $secMatch);
|
||
$secCode = $secMatch[0] ?? '44030002015390';
|
||
// 规范显示文案:缺「粤公网安备」前缀时自动补上并加「号」
|
||
if (!preg_match('/粤公网安备/', $secRaw)) {
|
||
$secRaw = '粤公网安备' . preg_replace('/\D/', '', $secRaw) . '号';
|
||
}
|
||
@endphp
|
||
<a href="https://beian.mps.gov.cn/#/query/webSearch?code={{ $secCode }}" target="_blank" rel="nofollow noopener noreferrer" class="icp-link inline-flex items-center gap-1">
|
||
<img src="{{ asset('images/gongan_badge.png') }}" width="16" height="17" alt="公安网备" loading="lazy" class="inline-block align-middle" onerror="this.onerror=null;this.outerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" viewBox="0 0 24 24"><path d="M12 2l8 3v6c0 5-4 9-8 11-4-2-8-6-8-11V5l8-3z" fill="#1a3a8f" stroke="#d4af37" stroke-width="1"/><polygon points="12,7 13.5,10.5 17,10.5 14,12.8 15,16.5 12,14.5 9,16.5 10,12.8 7,10.5 10.5,10.5" fill="#ffffff"/></svg>'">
|
||
{{ $secRaw }}
|
||
</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
(function () {
|
||
var btn = document.getElementById('nav-toggle');
|
||
var menu = document.getElementById('nav-menu');
|
||
if (btn && menu) {
|
||
btn.addEventListener('click', function () {
|
||
menu.classList.toggle('hidden');
|
||
});
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|