feat: 添加E2E测试并优化Docker部署配置

- 新增Playwright E2E测试配置和测试脚本
- 优化Dockerfile和docker-compose.yml配置
- 新增novalon-nginx和novalon-website的docker-compose配置
- 优化contact页面和contact-section组件的代码结构
- 更新多个页面的SEO和元数据配置
- 添加备案图标资源
- 修复ESLint错误:转义引号、添加ESLint禁用注释、移除未使用变量

测试覆盖: 新增website-acceptance.spec.ts E2E测试
This commit is contained in:
张翔
2026-03-27 12:39:30 +08:00
parent 7a38eae6e0
commit df8043c0df
18 changed files with 468 additions and 84 deletions
+5 -13
View File
@@ -7,7 +7,7 @@ import { COMPANY_INFO, STATS } from '@/lib/constants';
import { Card, CardContent } from '@/components/ui/card';
import { PageHeader } from '@/components/ui/page-header';
import { FlipClock } from '@/components/ui/flip-clock';
import { Lightbulb, Users, Target, Award, MapPin, Mail, Phone } from 'lucide-react';
import { Lightbulb, Users, Target, Award, MapPin, Mail } from 'lucide-react';
export function AboutClient() {
const contentRef = useRef(null);
@@ -115,7 +115,7 @@ export function AboutClient() {
<div>
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-3"></h3>
<p className="text-[#5C5C5C] mb-3 leading-relaxed">
"项目交付"
&ldquo;&rdquo;
</p>
<p className="text-[#5C5C5C] mb-3 leading-relaxed">
@@ -124,7 +124,7 @@ export function AboutClient() {
</p>
<p className="text-[#5C5C5C] mt-3 leading-relaxed">
"项目是否按时交付"
&ldquo;&rdquo;
</p>
</div>
</div>
@@ -145,7 +145,7 @@ export function AboutClient() {
</li>
<li className="flex items-start gap-3">
<span className="text-green-600 font-bold"></span>
<span className="text-[#5C5C5C]">"一锤子买卖"</span>
<span className="text-[#5C5C5C]">&ldquo;&rdquo;</span>
</li>
</ul>
<p className="text-[#5C5C5C] leading-relaxed font-medium">
@@ -257,15 +257,7 @@ export function AboutClient() {
<p className="text-sm font-medium text-[#1C1C1C]">{COMPANY_INFO.email}</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="p-2 bg-white rounded-lg">
<Phone className="w-5 h-5 text-[#C41E3A]" />
</div>
<div>
<p className="text-sm text-[#5C5C5C]"></p>
<p className="text-sm font-medium text-[#1C1C1C]">{COMPANY_INFO.phone}</p>
</div>
</div>
</div>
</motion.div>
</motion.div>
+33 -40
View File
@@ -8,7 +8,7 @@ import { Textarea } from '@/components/ui/textarea';
import { Toast } from '@/components/ui/toast';
import { sanitizeInput } from '@/lib/sanitize';
import { generateCSRFToken, setCSRFTokenToStorage } from '@/lib/csrf';
import { Mail, Phone, MapPin, Send, Loader2, Clock, HeadphonesIcon, CheckCircle2 } from 'lucide-react';
import { Mail, MapPin, Send, Loader2, Clock, HeadphonesIcon, CheckCircle2 } from 'lucide-react';
import { COMPANY_INFO } from '@/lib/constants';
import { submitContactForm, ContactFormState } from './actions';
@@ -55,32 +55,35 @@ export default function ContactPage() {
const isSubmitting = isPending;
useEffect(() => {
setIsVisible(true);
const token = generateCSRFToken();
setCsrfToken(token);
setCSRFTokenToStorage(token);
requestAnimationFrame(() => {
setIsVisible(true);
const token = generateCSRFToken();
setCsrfToken(token);
setCSRFTokenToStorage(token);
});
}, []);
useEffect(() => {
if (state) {
if (state.success) {
setToastMessage(state.message || '表单提交成功!我们会尽快与您联系。');
setToastType('success');
setShowToast(true);
const newToken = generateCSRFToken();
setCsrfToken(newToken);
setCSRFTokenToStorage(newToken);
} else if (state.error) {
setToastMessage(state.error);
setToastType('error');
setShowToast(true);
if (state.errors) {
setErrors(state.errors);
requestAnimationFrame(() => {
if (state.success) {
setToastMessage(state.message || '表单提交成功!我们会尽快与您联系。');
setToastType('success');
setShowToast(true);
const newToken = generateCSRFToken();
setCsrfToken(newToken);
setCSRFTokenToStorage(newToken);
} else if (state.error) {
setToastMessage(state.error);
setToastType('error');
setShowToast(true);
if (state.errors) {
setErrors(state.errors);
}
}
}
});
}
}, [state]);
@@ -161,7 +164,7 @@ export default function ContactPage() {
`}
>
<div className="flex items-center gap-3 mb-4">
<div className="w-8 h-px bg-gradient-to-r from-[#1C1C1C] to-[#C41E3A]" />
<div className="w-8 h-px bg-linear-to-r from-[#1C1C1C] to-[#C41E3A]" />
<span className="text-sm text-[#5C5C5C] tracking-wide" data-testid="page-badge"></span>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
@@ -184,7 +187,7 @@ export default function ContactPage() {
<h2 className="text-lg font-semibold text-[#1C1C1C] mb-6"></h2>
<div className="space-y-4" data-testid="contact-info">
<div className="flex items-start gap-4 group" data-testid="email-info">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center flex-shrink-0 transition-transform duration-200 group-hover:scale-105">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center shrink-0 transition-transform duration-200 group-hover:scale-105">
<Mail className="w-5 h-5 text-white" />
</div>
<div>
@@ -194,19 +197,9 @@ export default function ContactPage() {
</a>
</div>
</div>
<div className="flex items-start gap-4 group" data-testid="phone-info">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center flex-shrink-0 transition-transform duration-200 group-hover:scale-105">
<Phone className="w-5 h-5 text-white" />
</div>
<div>
<p className="text-sm text-[#5C5C5C] mb-1"></p>
<a href={`tel:${COMPANY_INFO.phone}`} className="text-[#1C1C1C] hover:text-[#C41E3A] transition-colors duration-200" data-testid="phone-link">
{COMPANY_INFO.phone}
</a>
</div>
</div>
<div className="flex items-start gap-4 group" data-testid="address-info">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center flex-shrink-0 transition-transform duration-200 group-hover:scale-105">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center shrink-0 transition-transform duration-200 group-hover:scale-105">
<MapPin className="w-5 h-5 text-white" />
</div>
<div>
@@ -237,15 +230,15 @@ export default function ContactPage() {
</div>
<div className="space-y-3">
<div className="flex items-start gap-2">
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 flex-shrink-0" />
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 shrink-0" />
<p className="text-sm text-[#5C5C5C]"> 2 </p>
</div>
<div className="flex items-start gap-2">
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 flex-shrink-0" />
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 shrink-0" />
<p className="text-sm text-[#5C5C5C]"></p>
</div>
<div className="flex items-start gap-2">
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 flex-shrink-0" />
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 shrink-0" />
<p className="text-sm text-[#5C5C5C]"></p>
</div>
</div>
@@ -343,7 +336,7 @@ export default function ContactPage() {
type="submit"
data-testid="submit-button"
size="lg"
className="w-full group mt-auto min-h-[52px] md:min-h-0"
className="w-full group mt-auto min-h-13 md:min-h-0"
disabled={isSubmitting}
>
{isSubmitting ? (
+1 -1
View File
@@ -56,7 +56,7 @@ export const metadata: Metadata = {
default: "四川睿新致远科技有限公司 - 企业数字化转型服务商",
template: "%s | 四川睿新致远科技有限公司",
},
description: "四川睿新致远科技有限公司成立于2026年,专注于企业数字化转型服务,提供软件开发、云计算、数据分析、信息安全等一站式解决方案。联系电话:028-88888888",
description: "四川睿新致远科技有限公司成立于2026年,专注于企业数字化转型服务,提供软件开发、云计算、数据分析、信息安全等一站式解决方案。",
keywords: ["数字化转型", "企业软件", "ERP系统", "CRM系统", "云计算", "数据分析", "软件开发", "成都科技公司", "金融科技", "诺瓦隆"],
authors: [{ name: "四川睿新致远科技有限公司" }],
creator: "四川睿新致远科技有限公司",
+1 -2
View File
@@ -25,7 +25,7 @@ export default function PrivacyPolicyPage() {
<section className="mb-12">
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-4"></h2>
<p className="text-[#5C5C5C] leading-relaxed mb-4">
"我们""公司"
&ldquo;&rdquo;&ldquo;&rdquo;
</p>
<p className="text-[#5C5C5C] leading-relaxed">
访使使
@@ -145,7 +145,6 @@ export default function PrivacyPolicyPage() {
<ul className="list-none text-[#5C5C5C] space-y-2">
<li></li>
<li>contact@novalon.cn</li>
<li>028-88888888</li>
<li>驿12</li>
</ul>
</section>
+2 -3
View File
@@ -25,7 +25,7 @@ export default function TermsOfServicePage() {
<section className="mb-12">
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-4"></h2>
<p className="text-[#5C5C5C] leading-relaxed mb-4">
使"我们""公司"使使
使&ldquo;&rdquo;&ldquo;&rdquo;使使
</p>
<p className="text-[#5C5C5C] leading-relaxed">
使
@@ -113,7 +113,7 @@ export default function TermsOfServicePage() {
<section className="mb-12">
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-4"></h2>
<p className="text-[#5C5C5C] leading-relaxed mb-4">
"现状""可用"
&ldquo;&rdquo;&ldquo;&rdquo;
</p>
<p className="text-[#5C5C5C] leading-relaxed mb-4">
@@ -164,7 +164,6 @@ export default function TermsOfServicePage() {
<ul className="list-none text-[#5C5C5C] space-y-2">
<li></li>
<li>contact@novalon.cn</li>
<li>028-88888888</li>
<li>驿12</li>
</ul>
</section>
+9 -9
View File
@@ -196,7 +196,7 @@ export function ContactSection() {
`}
>
<div className="flex items-center gap-3 mb-4">
<div className="w-8 h-px bg-gradient-to-r from-[#1C1C1C] to-[#C41E3A]" />
<div className="w-8 h-px bg-linear-to-r from-[#1C1C1C] to-[#C41E3A]" />
<span className="text-sm text-[#5C5C5C] tracking-wide"></span>
</div>
<h2 id="contact-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
@@ -219,7 +219,7 @@ export function ContactSection() {
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-6"></h3>
<div className="space-y-4">
<div className="flex items-start gap-4 group">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center flex-shrink-0 transition-transform duration-200 group-hover:scale-105">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center shrink-0 transition-transform duration-200 group-hover:scale-105">
<Mail className="w-5 h-5 text-white" />
</div>
<div>
@@ -231,7 +231,7 @@ export function ContactSection() {
</div>
<div className="flex items-start gap-4 group">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center flex-shrink-0 transition-transform duration-200 group-hover:scale-105">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center shrink-0 transition-transform duration-200 group-hover:scale-105">
<MapPin className="w-5 h-5 text-white" />
</div>
<div>
@@ -262,15 +262,15 @@ export function ContactSection() {
</div>
<div className="space-y-3">
<div className="flex items-start gap-2">
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 flex-shrink-0" />
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 shrink-0" />
<p className="text-sm text-[#5C5C5C]"> 2 </p>
</div>
<div className="flex items-start gap-2">
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 flex-shrink-0" />
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 shrink-0" />
<p className="text-sm text-[#5C5C5C]"></p>
</div>
<div className="flex items-start gap-2">
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 flex-shrink-0" />
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 shrink-0" />
<p className="text-sm text-[#5C5C5C]"></p>
</div>
</div>
@@ -351,7 +351,7 @@ export function ContactSection() {
<span className="text-[#C41E3A]">*</span>
</label>
<div className="flex items-center gap-3">
<div className="bg-[#E2E8F0] px-4 py-2 rounded-md font-mono text-lg text-[#1A1A2E] min-w-[120px] text-center" data-testid="captcha-question">
<div className="bg-[#E2E8F0] px-4 py-2 rounded-md font-mono text-lg text-[#1A1A2E] min-w-30 text-center" data-testid="captcha-question">
{captcha.question}
</div>
<div className="flex-1">
@@ -373,7 +373,7 @@ export function ContactSection() {
onClick={handleCaptchaRefresh}
disabled={isSubmitting}
data-testid="refresh-captcha"
className="flex-shrink-0"
className="shrink-0"
>
<RefreshCw className="w-4 h-4" />
</Button>
@@ -382,7 +382,7 @@ export function ContactSection() {
<Button
type="submit"
size="lg"
className="w-full group mt-auto min-h-[52px] md:min-h-0"
className="w-full group mt-auto min-h-13 md:min-h-0"
disabled={isSubmitting}
data-testid="submit-button"
>
-6
View File
@@ -15,12 +15,6 @@ export function OrganizationSchema() {
"addressRegion": "四川省",
"streetAddress": "成都市高新区"
},
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+86-028-88888888",
"contactType": "customer service",
"availableLanguage": ["Chinese", "English"]
},
"sameAs": [
"https://www.novalon.cn"
]
+1 -2
View File
@@ -31,10 +31,9 @@ export const COMPANY_INFO = {
founded: '2026',
location: '四川省成都市',
email: 'contact@novalon.cn',
phone: '028-88888888',
address: '中国四川省成都市龙泉驿区幸福路12号',
icp: '蜀ICP备2026013658号',
police: '川公网安备 XXXXXXXXXXX号',
police: '川公网安备51010602003285号',
} as const;
// Navigation Items - 混合导航(首页滚动,详情页跳转)
-4
View File
@@ -134,10 +134,6 @@ export function generateConfirmationEmail(data: ContactFormData): string {
<span class="contact-icon">📧</span>
<span>${COMPANY_INFO.email}</span>
</div>
<div class="contact-item">
<span class="contact-icon">📱</span>
<span>${COMPANY_INFO.phone}</span>
</div>
<div class="contact-item">
<span class="contact-icon">📍</span>
<span>${COMPANY_INFO.address}</span>