fix(seo): 修复页面标题公司名重复与品牌名繁简体不一致问题

- 新增 COMPANY_INFO.displayName 属性用于页面标题和SEO元数据
- 统一所有页面 metadata 使用 displayName(简体"睿新致远")
- 视觉展示元素保留 shortName(繁体"睿新致遠"配合青柳隷書字体)
- 修复关于/联系/团队页面标题中公司名重复出现的问题
- 修复新闻ID从数字改为SEO友好slug
- 更新结构化数据使用完整公司名
- 修复ESLint报错:引号转义、组件displayName、any类型替换
This commit is contained in:
张翔
2026-05-03 09:15:14 +08:00
parent f0657ce9f4
commit 1bf22a8f95
29 changed files with 183 additions and 76 deletions
+2
View File
@@ -35,6 +35,8 @@ jest.mock('lucide-react', () => ({
jest.mock('@/lib/constants', () => ({
COMPANY_INFO: {
name: '四川睿新致远科技有限公司',
shortName: '睿新致遠',
displayName: '睿新致远',
description: '以智慧连接数字趋势,以伙伴身份陪您成长',
email: 'contact@novalon.cn',
phone: '028-88888888',
+15 -8
View File
@@ -15,28 +15,32 @@ jest.mock('next/navigation', () => ({
}));
jest.mock('next/link', () => {
return ({ children, href, onClick, ...props }: any) => (
const MockLink = ({ children, href, onClick, ...props }: { children: React.ReactNode; href: string; onClick?: () => void; [key: string]: unknown }) => (
<a href={href} onClick={onClick} {...props}>
{children}
</a>
);
MockLink.displayName = 'MockLink';
return MockLink;
});
jest.mock('next/image', () => {
return ({ src, alt, width, height, className, ...props }: any) => (
const MockImage = ({ src, alt, width, height, className, ...props }: { src: string; alt: string; width: number; height: number; className?: string; [key: string]: unknown }) => (
<img src={src} alt={alt} width={width} height={height} className={className} {...props} />
);
MockImage.displayName = 'MockImage';
return MockImage;
});
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, className, ...props }: any) => (
div: ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => (
<div className={className} {...props}>
{children}
</div>
),
},
AnimatePresence: ({ children }: any) => <>{children}</>,
AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}));
jest.mock('lucide-react', () => ({
@@ -44,18 +48,21 @@ jest.mock('lucide-react', () => ({
X: () => <span data-testid="x-icon" />,
}));
jest.mock('@/components/ui/button', () => ({
Button: ({ children, className, asChild, ...props }: any) => (
jest.mock('@/components/ui/button', () => {
const MockButton = ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => (
<button className={className} {...props}>
{children}
</button>
),
}));
);
MockButton.displayName = 'MockButton';
return { Button: MockButton };
});
jest.mock('@/lib/constants', () => ({
COMPANY_INFO: {
name: '四川睿新致远科技有限公司',
shortName: '睿新致遠',
displayName: '睿新致远',
},
NAVIGATION: [
{ id: 'home', label: '首页', href: '/' },
+7 -3
View File
@@ -108,7 +108,11 @@ function HeaderContent() {
label={item.label}
items={MEGA_DROPDOWN_DATA[item.dropdownKey!] ?? []}
isOpen={openDropdown === item.id}
onToggle={() => setOpenDropdown(openDropdown === item.id ? null : item.id)}
onToggle={() => {
setOpenDropdown((prev) => prev === item.id ? null : item.id);
}}
onOpen={() => setOpenDropdown(item.id)}
onClose={() => setOpenDropdown((prev) => prev === item.id ? null : prev)}
/>
) : (
<StaticLink
@@ -218,13 +222,13 @@ function HeaderContent() {
</motion.div>
))}
<div className="mt-6 px-4 pt-6 border-t border-[#E2E8F0]">
<Button
<Button
className="w-full"
asChild
size="lg"
>
<StaticLink href="/contact" onClick={() => setIsOpen(false)}>
联系我们
立即咨询
</StaticLink>
</Button>
</div>
+41 -2
View File
@@ -11,10 +11,15 @@ interface MegaDropdownProps {
items: MegaDropdownItem[];
isOpen: boolean;
onToggle: () => void;
onOpen?: () => void;
onClose?: () => void;
}
export function MegaDropdown({ label, items, isOpen, onToggle }: MegaDropdownProps) {
const HOVER_DELAY = 150;
export function MegaDropdown({ label, items, isOpen, onToggle, onOpen, onClose }: MegaDropdownProps) {
const dropdownRef = useRef<HTMLDivElement>(null);
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
@@ -26,8 +31,42 @@ export function MegaDropdown({ label, items, isOpen, onToggle }: MegaDropdownPro
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [isOpen, onToggle]);
useEffect(() => {
return () => {
if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current);
}
};
}, []);
const handleMouseEnter = () => {
if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current);
hoverTimeoutRef.current = null;
}
if (!isOpen) {
if (onOpen) {
onOpen();
} else {
onToggle();
}
}
};
const handleMouseLeave = () => {
hoverTimeoutRef.current = setTimeout(() => {
if (isOpen) {
if (onClose) {
onClose();
} else {
onToggle();
}
}
}, HOVER_DELAY);
};
return (
<div ref={dropdownRef} className="relative">
<div ref={dropdownRef} className="relative" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<button
onClick={onToggle}
className={`