fix(analytics,ui): remove duplicate contact conversion and add brand identity to homepage hero
- Remove redundant `trackConversion('contact_form_submission')` in contact-content-v3.tsx
and rely on `trackContactForm(..., true)` to avoid double-counting conversions in GA4
- Update contact-content-v3.test.tsx assertions to match single analytics call
- Add Logo + company name + NOVALON to homepage HeroSection in home-content-v14.tsx
- Update homepage visual regression baselines across desktop/tablet/mobile
- Record progress in README.md
@@ -58,6 +58,7 @@
|
|||||||
- [x] 全站 dogfood 问题修复(2026-07-25):修复列表详情页导航、CMS 登录状态、服务详情页软 404 等 High 优先级问题;其中软 404 根因定位为 `(marketing)/loading.tsx` 形成的 Loading/Suspense 边界吞掉了动态路由 `notFound()` 抛出的 NEXT_NOT_FOUND 错误,导致未知 slug 返回 HTTP 200,已移除该 loading.tsx 并修复根 `not-found.tsx`;type-check / lint(0 errors,172 既有 warnings)/ test:unit(72 suites / 954 tests)/ 功能 E2E 140 条全部通过
|
- [x] 全站 dogfood 问题修复(2026-07-25):修复列表详情页导航、CMS 登录状态、服务详情页软 404 等 High 优先级问题;其中软 404 根因定位为 `(marketing)/loading.tsx` 形成的 Loading/Suspense 边界吞掉了动态路由 `notFound()` 抛出的 NEXT_NOT_FOUND 错误,导致未知 slug 返回 HTTP 200,已移除该 loading.tsx 并修复根 `not-found.tsx`;type-check / lint(0 errors,172 既有 warnings)/ test:unit(72 suites / 954 tests)/ 功能 E2E 140 条全部通过
|
||||||
- [x] dogfood 剩余问题验证与视觉基线更新(2026-07-26):浏览器实测确认主导航「产品/解决方案」下拉菜单 click/hover 均可正常展开,问题已随 `(marketing)/loading.tsx` 移除解决;同步修正 `e2e/nav-dropdown.spec.ts` 中断言文本(`睿新ERP管理系统` → `ERP 管理系统`、`行业方案` → `行业解决方案`);更新 desktop/tablet/mobile/webkit/firefox 全量视觉回归基线(105 张快照);type-check / lint(0 errors,172 既有 warnings)/ test:unit(72 suites / 954 tests)/ 下拉菜单 E2E 全部通过
|
- [x] dogfood 剩余问题验证与视觉基线更新(2026-07-26):浏览器实测确认主导航「产品/解决方案」下拉菜单 click/hover 均可正常展开,问题已随 `(marketing)/loading.tsx` 移除解决;同步修正 `e2e/nav-dropdown.spec.ts` 中断言文本(`睿新ERP管理系统` → `ERP 管理系统`、`行业方案` → `行业解决方案`);更新 desktop/tablet/mobile/webkit/firefox 全量视觉回归基线(105 张快照);type-check / lint(0 errors,172 既有 warnings)/ test:unit(72 suites / 954 tests)/ 下拉菜单 E2E 全部通过
|
||||||
- [x] 全量回归与 E2E 配置修复(2026-07-27):修复 `e2e/playwright.config.ts` 中 `storageState` 路径错误(由 `./e2e/storageState.json` 改为基于配置文件目录的绝对路径),该错误导致 firefox/webkit 全量 E2E 运行时无法读取 Cookie 偏好状态而全部失败;将 `e2e/p2-functional-e2e.spec.ts` 中「Footer 导航链接可点击」拆分为隐私政策、服务条款两个独立测试,并改用 `page.evaluate` 直接触发点击,避免 Firefox 中连续全页导航与 Playwright locator 评估的竞态超时;重新执行全量回归:type-check / lint(0 errors,172 既有 warnings)/ test:unit(72 suites / 954 tests)/ E2E 全量(631 passed / 8 skipped / 0 failed,含视觉回归、CMS 工作流)全部通过
|
- [x] 全量回归与 E2E 配置修复(2026-07-27):修复 `e2e/playwright.config.ts` 中 `storageState` 路径错误(由 `./e2e/storageState.json` 改为基于配置文件目录的绝对路径),该错误导致 firefox/webkit 全量 E2E 运行时无法读取 Cookie 偏好状态而全部失败;将 `e2e/p2-functional-e2e.spec.ts` 中「Footer 导航链接可点击」拆分为隐私政策、服务条款两个独立测试,并改用 `page.evaluate` 直接触发点击,避免 Firefox 中连续全页导航与 Playwright locator 评估的竞态超时;重新执行全量回归:type-check / lint(0 errors,172 既有 warnings)/ test:unit(72 suites / 954 tests)/ E2E 全量(631 passed / 8 skipped / 0 failed,含视觉回归、CMS 工作流)全部通过
|
||||||
|
- [x] 联系表单 conversion 去重与首页 Hero 品牌标识补齐(2026-07-27):移除 `contact-content-v3.tsx` 中冗余的 `trackConversion('contact_form_submission')` 调用,改为 `trackContactForm(..., true)`,避免一次成功提交在 GA4 中被计为两次转化;同步更新 `contact-content-v3.test.tsx` 中断言;在 `home-content-v14.tsx` 首页 Hero 主标题上方增加 Logo + 公司中文名 + NOVALON 英文品牌标识,补齐截图中缺失的品牌信息;更新首页相关 desktop/tablet/mobile 视觉回归基线;type-check / lint(0 errors,172 既有 warnings)/ 相关 unit 测试全部通过
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 593 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 593 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 593 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 786 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 719 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 719 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 585 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 816 KiB After Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 816 KiB After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 816 KiB After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 712 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 657 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 657 KiB |
@@ -370,10 +370,19 @@ describe('ContactContentV3 - 联系表单集成测试', () => {
|
|||||||
// 成功消息同时出现在 form 成功状态和 toast 中,用 getAllByText
|
// 成功消息同时出现在 form 成功状态和 toast 中,用 getAllByText
|
||||||
expect(screen.getAllByText('感谢您的留言,我们会尽快与您联系。').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('感谢您的留言,我们会尽快与您联系。').length).toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
// Should have called analytics
|
// Should have called analytics exactly once via trackContactForm
|
||||||
const { trackContactForm, trackConversion } = await import('@/lib/analytics');
|
const { trackContactForm, trackConversion } = await import('@/lib/analytics');
|
||||||
expect(trackContactForm).toHaveBeenCalled();
|
expect(trackContactForm).toHaveBeenCalledTimes(1);
|
||||||
expect(trackConversion).toHaveBeenCalledWith('contact_form_submission');
|
expect(trackContactForm).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: '张三',
|
||||||
|
email: 'zhangsan@example.com',
|
||||||
|
company: '咨询产品方案',
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// trackConversion 不应再被显式调用,避免重复上报 conversion
|
||||||
|
expect(trackConversion).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show error toast when API returns error', async () => {
|
it('should show error toast when API returns error', async () => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
HelpCircle,
|
HelpCircle,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { trackContactForm, trackConversion } from '@/lib/analytics';
|
import { trackContactForm } from '@/lib/analytics';
|
||||||
import { BreadcrumbSchema } from '@/components/seo/structured-data';
|
import { BreadcrumbSchema } from '@/components/seo/structured-data';
|
||||||
import { LegacyTooltip as Tooltip } from '@/components/ui/tooltip';
|
import { LegacyTooltip as Tooltip } from '@/components/ui/tooltip';
|
||||||
|
|
||||||
@@ -166,12 +166,16 @@ function ContactFormContent({ data }: { data: ContactData }) {
|
|||||||
const resultData = await response.json();
|
const resultData = await response.json();
|
||||||
|
|
||||||
if (response.ok && (resultData.success === 'true' || resultData.success === true)) {
|
if (response.ok && (resultData.success === 'true' || resultData.success === true)) {
|
||||||
trackContactForm({
|
// trackContactForm 内部已发送 form_submit 事件并触发 conversion,
|
||||||
name: formData.name,
|
// 不再额外调用 trackConversion,避免同一个提交被重复计为两次转化。
|
||||||
email: formData.email,
|
trackContactForm(
|
||||||
company: formData.subject,
|
{
|
||||||
});
|
name: formData.name,
|
||||||
trackConversion('contact_form_submission');
|
email: formData.email,
|
||||||
|
company: formData.subject,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
setToastMessage(data.successMessage || '提交成功!我们会尽快与您联系。');
|
setToastMessage(data.successMessage || '提交成功!我们会尽快与您联系。');
|
||||||
setToastType('success');
|
setToastType('success');
|
||||||
setShowToast(true);
|
setShowToast(true);
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { ArrowRight, Lightbulb, Database, Layers } from 'lucide-react';
|
import { ArrowRight, Lightbulb, Database, Layers } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { EASE_OUT } from '@/components/ui/page-decoration';
|
import { EASE_OUT } from '@/components/ui/page-decoration';
|
||||||
|
import { COMPANY_INFO } from '@/lib/constants';
|
||||||
|
|
||||||
// ===== 服务数据降级兜底 =====
|
// ===== 服务数据降级兜底 =====
|
||||||
const FALLBACK_SERVICES = [
|
const FALLBACK_SERVICES = [
|
||||||
@@ -104,6 +106,31 @@ function HeroSection({ heroData, stats }: {
|
|||||||
|
|
||||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-24 sm:py-32 md:py-40 lg:py-48">
|
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-24 sm:py-32 md:py-40 lg:py-48">
|
||||||
<div className="max-w-4xl">
|
<div className="max-w-4xl">
|
||||||
|
{/* 品牌标识:Logo + 公司名 */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0, ease: EASE_OUT }}
|
||||||
|
className="flex items-center gap-3 mb-8"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="/logo.svg"
|
||||||
|
alt={`${COMPANY_INFO.name} Logo`}
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
|
className="h-10 w-auto sm:h-12"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm sm:text-base font-semibold text-ink leading-tight">
|
||||||
|
{COMPANY_INFO.name}
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] sm:text-xs tracking-[0.2em] text-text-muted uppercase">
|
||||||
|
NOVALON
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
{/* 主标题:大胆、直接 */}
|
{/* 主标题:大胆、直接 */}
|
||||||
<motion.h1
|
<motion.h1
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
|
|||||||