refactor/google-analytics #16
+4
-1
@@ -294,4 +294,7 @@ findings.md
|
||||
AGENTS.md
|
||||
|
||||
# dogfood
|
||||
dogfood-output/
|
||||
dogfood-output/
|
||||
|
||||
# docs
|
||||
docs/
|
||||
@@ -71,11 +71,8 @@ test.describe('网站全面测试验收', () => {
|
||||
await page.goto('/about');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const contactSection = page.locator('text=/联系我们/').locator('..').locator('..');
|
||||
if (await contactSection.isVisible()) {
|
||||
const phoneText = contactSection.locator('text=/联系电话|028-88888888/');
|
||||
expect(await phoneText.count()).toBe(0);
|
||||
}
|
||||
const phoneElements = page.locator('text=/电话|028-|1[3-9]\\d{9}/');
|
||||
expect(await phoneElements.count()).toBe(0);
|
||||
});
|
||||
|
||||
test('响应式设计正常工作', async ({ page }) => {
|
||||
@@ -120,12 +117,14 @@ test.describe('网站全面测试验收', () => {
|
||||
await page.goto('/contact');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
await submitButton.click();
|
||||
|
||||
const nameInput = page.locator('input[name="name"]');
|
||||
const errorMessage = nameInput.locator('..').locator('text=/至少需要2个字符/');
|
||||
await expect(errorMessage).toBeVisible();
|
||||
await nameInput.fill('a');
|
||||
await nameInput.blur();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const errorMessage = page.locator('text=至少需要2个字符').first();
|
||||
await expect(errorMessage).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('页面加载性能良好', async ({ page }) => {
|
||||
@@ -142,8 +141,15 @@ test.describe('网站全面测试验收', () => {
|
||||
});
|
||||
|
||||
test('无障碍访问正常', async ({ page }) => {
|
||||
const accessibilityIssues = await page.accessibility.snapshot();
|
||||
expect(accessibilityIssues).toBeDefined();
|
||||
const mainHeading = page.locator('h1');
|
||||
await expect(mainHeading).toBeVisible();
|
||||
|
||||
const nav = page.locator('nav').first();
|
||||
await expect(nav).toBeVisible();
|
||||
|
||||
const buttons = page.locator('button');
|
||||
const buttonCount = await buttons.count();
|
||||
expect(buttonCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('联系我们页面没有返回按钮覆盖logo', async ({ page }) => {
|
||||
|
||||
Generated
+533
-129
@@ -18,6 +18,7 @@
|
||||
"critters": "^0.0.23",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.34.3",
|
||||
"jsdom": "^29.0.2",
|
||||
"lucide-react": "^0.563.0",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.3",
|
||||
@@ -320,25 +321,51 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
|
||||
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
|
||||
"dev": true,
|
||||
"version": "5.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
|
||||
"integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/css-calc": "^2.1.3",
|
||||
"@csstools/css-color-parser": "^3.0.9",
|
||||
"@csstools/css-parser-algorithms": "^3.0.4",
|
||||
"@csstools/css-tokenizer": "^3.0.3",
|
||||
"lru-cache": "^10.4.3"
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@csstools/css-calc": "^3.2.0",
|
||||
"@csstools/css-color-parser": "^4.1.0",
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
|
||||
"integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||
"bidi-js": "^1.0.3",
|
||||
"css-tree": "^3.2.1",
|
||||
"is-potential-custom-element-name": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/generational-cache": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
|
||||
"integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/nwsapi": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
|
||||
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@axe-core/playwright": {
|
||||
"version": "4.11.2",
|
||||
@@ -2271,6 +2298,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@bramus/specificity": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
||||
"integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-tree": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"specificity": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@commitlint/cli": {
|
||||
"version": "20.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.5.0.tgz",
|
||||
@@ -2581,10 +2620,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
|
||||
"dev": true,
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
|
||||
"integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2597,14 +2635,13 @@
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-calc": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
|
||||
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
|
||||
"dev": true,
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz",
|
||||
"integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2617,18 +2654,17 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-color-parser": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
|
||||
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
|
||||
"dev": true,
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz",
|
||||
"integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2641,22 +2677,21 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/color-helpers": "^5.1.0",
|
||||
"@csstools/css-calc": "^2.1.4"
|
||||
"@csstools/color-helpers": "^6.0.2",
|
||||
"@csstools/css-calc": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-parser-algorithms": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
|
||||
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
|
||||
"dev": true,
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
|
||||
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2669,17 +2704,40 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz",
|
||||
"integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"peerDependencies": {
|
||||
"css-tree": "^3.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"css-tree": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-tokenizer": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
|
||||
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
|
||||
"dev": true,
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
|
||||
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2692,7 +2750,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dimforge/rapier3d-compat": {
|
||||
@@ -3263,6 +3321,23 @@
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bytes": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz",
|
||||
"integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@noble/hashes": "^1.8.0 || ^2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@noble/hashes": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
|
||||
@@ -9051,6 +9126,15 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.4",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
||||
@@ -9960,6 +10044,19 @@
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.27.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||
@@ -9993,6 +10090,142 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/@asamuzakjp/css-color": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
|
||||
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/css-calc": "^2.1.3",
|
||||
"@csstools/css-color-parser": "^3.0.9",
|
||||
"@csstools/css-parser-algorithms": "^3.0.4",
|
||||
"@csstools/css-tokenizer": "^3.0.3",
|
||||
"lru-cache": "^10.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/@csstools/color-helpers": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/@csstools/css-calc": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
|
||||
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/@csstools/css-color-parser": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
|
||||
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/color-helpers": "^5.1.0",
|
||||
"@csstools/css-calc": "^2.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/@csstools/css-parser-algorithms": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
|
||||
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/@csstools/css-tokenizer": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
|
||||
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
@@ -10298,17 +10531,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
|
||||
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
|
||||
"dev": true,
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
|
||||
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"whatwg-url": "^14.0.0"
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
@@ -10407,7 +10639,6 @@
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dedent": {
|
||||
@@ -12497,16 +12728,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
|
||||
"dev": true,
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
|
||||
"integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-encoding": "^3.1.1"
|
||||
"@exodus/bytes": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
@@ -13302,7 +13532,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
@@ -13993,6 +14222,153 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/data-urls": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
|
||||
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"whatwg-url": "^14.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-encoding": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/jsdom": {
|
||||
"version": "26.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
"decimal.js": "^10.5.0",
|
||||
"html-encoding-sniffer": "^4.0.0",
|
||||
"http-proxy-agent": "^7.0.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"nwsapi": "^2.2.16",
|
||||
"parse5": "^7.2.1",
|
||||
"rrweb-cssom": "^0.8.0",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^5.1.1",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"webidl-conversions": "^7.0.0",
|
||||
"whatwg-encoding": "^3.1.1",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"whatwg-url": "^14.1.1",
|
||||
"ws": "^8.18.0",
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/tldts": {
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
|
||||
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^6.1.86"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/tldts-core": {
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
|
||||
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/tough-cookie": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
|
||||
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^6.1.32"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/tr46": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
||||
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/whatwg-mimetype": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/whatwg-url": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "^5.1.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-node": {
|
||||
"version": "30.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz",
|
||||
@@ -14633,35 +15009,35 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "26.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"version": "29.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz",
|
||||
"integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
"decimal.js": "^10.5.0",
|
||||
"html-encoding-sniffer": "^4.0.0",
|
||||
"http-proxy-agent": "^7.0.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"@asamuzakjp/css-color": "^5.1.5",
|
||||
"@asamuzakjp/dom-selector": "^7.0.6",
|
||||
"@bramus/specificity": "^2.4.2",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.1",
|
||||
"@exodus/bytes": "^1.15.0",
|
||||
"css-tree": "^3.2.1",
|
||||
"data-urls": "^7.0.0",
|
||||
"decimal.js": "^10.6.0",
|
||||
"html-encoding-sniffer": "^6.0.0",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"nwsapi": "^2.2.16",
|
||||
"parse5": "^7.2.1",
|
||||
"rrweb-cssom": "^0.8.0",
|
||||
"lru-cache": "^11.2.7",
|
||||
"parse5": "^8.0.0",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^5.1.1",
|
||||
"tough-cookie": "^6.0.1",
|
||||
"undici": "^7.24.5",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"webidl-conversions": "^7.0.0",
|
||||
"whatwg-encoding": "^3.1.1",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"whatwg-url": "^14.1.1",
|
||||
"ws": "^8.18.0",
|
||||
"webidl-conversions": "^8.0.1",
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.1",
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^3.0.0"
|
||||
@@ -14672,6 +15048,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/entities": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
|
||||
"integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/lru-cache": {
|
||||
"version": "11.3.5",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
|
||||
"integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/parse5": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
||||
"integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
@@ -15757,6 +16166,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.27.1",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
|
||||
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -17114,7 +17529,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -17467,7 +17881,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -17814,7 +18227,6 @@
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"xmlchars": "^2.2.0"
|
||||
@@ -18687,7 +19099,6 @@
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
@@ -18886,13 +19297,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
|
||||
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
|
||||
"dev": true,
|
||||
"version": "7.0.28",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz",
|
||||
"integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^6.1.86"
|
||||
"tldts-core": "^7.0.28"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
@@ -18902,7 +19312,6 @@
|
||||
"version": "7.0.28",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz",
|
||||
"integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tldts-icann": {
|
||||
@@ -18915,13 +19324,6 @@
|
||||
"tldts-core": "^7.0.28"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts/node_modules/tldts-core": {
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
|
||||
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
|
||||
@@ -18989,29 +19391,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
|
||||
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
|
||||
"dev": true,
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
|
||||
"integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^6.1.32"
|
||||
"tldts": "^7.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
||||
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||
"dev": true,
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
||||
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
@@ -19331,6 +19731,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
|
||||
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
@@ -19582,7 +19991,6 @@
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-name-validator": "^5.0.0"
|
||||
@@ -19616,13 +20024,12 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"dev": true,
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
|
||||
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
@@ -19647,27 +20054,26 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||
"dev": true,
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
|
||||
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||
"dev": true,
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
|
||||
"integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "^5.1.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
"@exodus/bytes": "^1.11.0",
|
||||
"tr46": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/when-exit": {
|
||||
@@ -19995,7 +20401,6 @@
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -20005,7 +20410,6 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"critters": "^0.0.23",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.34.3",
|
||||
"jsdom": "^29.0.2",
|
||||
"lucide-react": "^0.563.0",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.3",
|
||||
|
||||
@@ -2,58 +2,31 @@
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useRef, useState, useEffect, useMemo } from 'react';
|
||||
import { useRef, useMemo } from 'react';
|
||||
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 } from 'lucide-react';
|
||||
import { Lightbulb, Users, Target, MapPin, Mail } from 'lucide-react';
|
||||
|
||||
export function AboutClient() {
|
||||
const contentRef = useRef(null);
|
||||
const isContentInView = useInView(contentRef, { once: true, margin: '-100px' });
|
||||
const [operationTime, setOperationTime] = useState({ days: 0, months: 0, years: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const foundingDate = new Date('2026-01-15');
|
||||
const calculateTime = () => {
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - foundingDate.getTime();
|
||||
|
||||
const totalDays = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
const years = Math.floor(totalDays / 365);
|
||||
const months = Math.floor((totalDays % 365) / 30);
|
||||
const days = totalDays % 30;
|
||||
|
||||
setOperationTime({ days, months, years });
|
||||
};
|
||||
|
||||
calculateTime();
|
||||
const timer = setInterval(calculateTime, 60000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const values = useMemo(() => [
|
||||
{
|
||||
icon: Lightbulb,
|
||||
title: '创新驱动',
|
||||
description: '持续探索前沿技术,以创新思维解决业务挑战,为客户创造差异化价值',
|
||||
title: '务实',
|
||||
description: '不追逐风口,只做真正为客户创造价值的事。每一个方案都源于对业务场景的深入洞察。',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: '客户至上',
|
||||
description: '深入理解客户需求,提供个性化解决方案,建立长期合作伙伴关系',
|
||||
title: '陪伴',
|
||||
description: '交付只是开始,长期陪跑才是我们的承诺。我们关注的不只是项目是否上线,更是您的业务是否真正改善。',
|
||||
},
|
||||
{
|
||||
icon: Target,
|
||||
title: '追求卓越',
|
||||
description: '以最高标准要求自己,持续优化产品和服务质量,超越客户期望',
|
||||
},
|
||||
{
|
||||
icon: Award,
|
||||
title: '诚信为本',
|
||||
description: '坚持透明沟通,信守承诺,以诚信赢得客户信任和尊重',
|
||||
title: '专业',
|
||||
description: '用扎实的工程能力和行业经验赢得信任。既懂技术又懂业务,提供真正可落地的解决方案。',
|
||||
},
|
||||
], []);
|
||||
|
||||
@@ -75,8 +48,8 @@ export function AboutClient() {
|
||||
},
|
||||
{
|
||||
date: '2026年2月',
|
||||
title: '产品发布',
|
||||
description: '自主研发的ERP、CRM等产品陆续上线,为客户提供一站式数字化服务',
|
||||
title: '产品研发',
|
||||
description: '启动ERP、CRM等自研产品的研发工作,致力于为企业提供一站式数字化服务',
|
||||
},
|
||||
], []);
|
||||
|
||||
@@ -154,12 +127,6 @@ export function AboutClient() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FlipClock
|
||||
years={operationTime.years}
|
||||
months={operationTime.months}
|
||||
days={operationTime.days}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
@@ -183,7 +150,7 @@ export function AboutClient() {
|
||||
className="mb-16"
|
||||
>
|
||||
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-6 text-center">核心价值观</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{values.map((value, idx) => (
|
||||
<motion.div
|
||||
key={value.title}
|
||||
|
||||
@@ -87,7 +87,7 @@ jest.mock('@/components/ui/flip-clock', () => ({
|
||||
jest.mock('@/lib/constants', () => ({
|
||||
COMPANY_INFO: {
|
||||
name: '四川睿新致远科技有限公司',
|
||||
shortName: '睿新致遠',
|
||||
shortName: '睿新致远',
|
||||
description: '以智慧连接数字趋势,以伙伴身份陪您成长',
|
||||
address: '四川省成都市龙泉驿区',
|
||||
email: 'contact@ruixin.com',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { AboutClient } from './client';
|
||||
|
||||
export const metadata = {
|
||||
title: `关于我们 - ${COMPANY_INFO.name}`,
|
||||
title: '关于我们',
|
||||
description: `了解${COMPANY_INFO.name}的品牌故事。我们不只是技术供应商,更是您数字化转型的成长伙伴。以智慧连接数字趋势,以伙伴身份陪您成长。`,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '客户案例 - 睿新致远',
|
||||
description: '与谁同行,决定能走多远',
|
||||
title: '客户案例',
|
||||
description: '了解诺瓦隆在各行业的成功案例,包括制造业、政务服务、酒店管理、智慧农业等领域的数字化转型实践。',
|
||||
};
|
||||
|
||||
export default function CasesLayout({
|
||||
|
||||
@@ -230,23 +230,25 @@ export default function CasesPage() {
|
||||
让我们与您同行,共创美好未来
|
||||
</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<StaticLink href="/contact">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/contact">
|
||||
联系我们
|
||||
</Button>
|
||||
</StaticLink>
|
||||
<StaticLink href="/contact">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
|
||||
>
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/contact">
|
||||
立即咨询
|
||||
<ArrowLeft className="ml-2 w-4 h-4 rotate-180" />
|
||||
</Button>
|
||||
</StaticLink>
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const metadata = {
|
||||
title: '联系我们 - 四川睿新致远科技有限公司',
|
||||
description: '无论您有任何问题或合作意向,我们都很乐意与您交流',
|
||||
title: '联系我们',
|
||||
description: '联系四川睿新致远科技有限公司(诺瓦隆)。无论您有任何关于企业数字化转型、软件开发、数据分析等方面的咨询需求,我们都期待与您交流。',
|
||||
};
|
||||
|
||||
export default function ContactLayout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
@@ -37,14 +37,6 @@ const ProductsSection = dynamic(
|
||||
}
|
||||
);
|
||||
|
||||
const CasesSection = dynamic(
|
||||
() => import('@/components/sections/cases-section').then(mod => ({ default: mod.CasesSection })),
|
||||
{
|
||||
loading: () => <SectionSkeleton />,
|
||||
ssr: false
|
||||
}
|
||||
);
|
||||
|
||||
const AboutSection = dynamic(
|
||||
() => import('@/components/sections/about-section').then(mod => ({ default: mod.AboutSection })),
|
||||
{
|
||||
@@ -69,6 +61,8 @@ const NewsSection = dynamic(
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
function HomeContent({ heroStats }: { heroStats: ReactNode }) {
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
@@ -108,10 +102,15 @@ function HomeContent({ heroStats }: { heroStats: ReactNode }) {
|
||||
return (
|
||||
<main id="main-content" className="min-h-screen bg-white dark:bg-(--color-bg-primary)">
|
||||
<HeroSection heroStats={heroStats} />
|
||||
{/* 墨韵分割线 */}
|
||||
<div className="ink-divider" />
|
||||
<ServicesSection />
|
||||
<HomeSolutionsSection />
|
||||
{/* 墨韵分割线 */}
|
||||
<div className="ink-divider" />
|
||||
<ProductsSection />
|
||||
<CasesSection />
|
||||
{/* 墨韵分割线 */}
|
||||
<div className="ink-divider" />
|
||||
<AboutSection />
|
||||
<TeamSection />
|
||||
<NewsSection />
|
||||
|
||||
@@ -4,18 +4,46 @@ import { usePathname } from 'next/navigation';
|
||||
import { ErrorBoundary } from '@/components/ui/error-boundary';
|
||||
import { Header } from '@/components/layout/header';
|
||||
import { Footer } from '@/components/layout/footer';
|
||||
import { ProductHeader } from '@/components/layout/product-header';
|
||||
import { ProductFooter } from '@/components/layout/product-footer';
|
||||
import { ServiceHeader } from '@/components/layout/service-header';
|
||||
import { ServiceFooter } from '@/components/layout/service-footer';
|
||||
import { Breadcrumb } from '@/components/layout/breadcrumb';
|
||||
|
||||
const breadcrumbMap: Record<string, { label: string; href: string }> = {
|
||||
'/about': { label: '关于我们', href: '/about' },
|
||||
'/cases': { label: '成功案例', href: '/cases' },
|
||||
'/services': { label: '核心业务', href: '/services' },
|
||||
'/products': { label: '产品服务', href: '/products' },
|
||||
'/solutions': { label: '解决方案', href: '/solutions' },
|
||||
'/solutions': { label: '行业方案', href: '/solutions' },
|
||||
'/news': { label: '新闻动态', href: '/news' },
|
||||
'/contact': { label: '联系我们', href: '/contact' },
|
||||
'/team': { label: '核心团队', href: '/team' },
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否为产品详情页(如 /products/erp、/products/crm 等)
|
||||
* 产品列表页 /products 本身仍使用主站布局
|
||||
*/
|
||||
function isProductDetailPage(pathname: string): boolean {
|
||||
return /^\/products\/[^/]+$/.test(pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为服务详情页(如 /services/software、/services/data 等)
|
||||
* 服务列表页 /services 本身仍使用主站布局
|
||||
*/
|
||||
function isServiceDetailPage(pathname: string): boolean {
|
||||
return /^\/services\/[^/]+$/.test(pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为解决方案详情页(如 /solutions/manufacturing 等)
|
||||
* 解决方案列表页 /solutions 本身仍使用主站布局
|
||||
*/
|
||||
function isSolutionDetailPage(pathname: string): boolean {
|
||||
return /^\/solutions\/[^/]+$/.test(pathname);
|
||||
}
|
||||
|
||||
export default function MarketingLayout({
|
||||
children,
|
||||
}: {
|
||||
@@ -23,7 +51,41 @@ export default function MarketingLayout({
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const breadcrumbItem = breadcrumbMap[pathname];
|
||||
const isProductDetail = isProductDetailPage(pathname);
|
||||
const isServiceDetail = isServiceDetailPage(pathname);
|
||||
const isSolutionDetail = isSolutionDetailPage(pathname);
|
||||
|
||||
// 产品详情页使用独立布局(ProductHeader + ProductFooter)
|
||||
if (isProductDetail || isSolutionDetail) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-white">
|
||||
<ProductHeader />
|
||||
<ErrorBoundary>
|
||||
<main id="main-content" className="flex-1 pt-16">
|
||||
{children}
|
||||
</main>
|
||||
</ErrorBoundary>
|
||||
<ProductFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 服务详情页使用独立布局(ServiceHeader + ServiceFooter)
|
||||
if (isServiceDetail) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-white">
|
||||
<ServiceHeader />
|
||||
<ErrorBoundary>
|
||||
<main id="main-content" className="flex-1 pt-16">
|
||||
{children}
|
||||
</main>
|
||||
</ErrorBoundary>
|
||||
<ServiceFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 其他页面使用主站布局(Header + Footer)
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<Header />
|
||||
|
||||
@@ -114,17 +114,17 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) {
|
||||
)}
|
||||
|
||||
<div className="mt-16 flex justify-center gap-4">
|
||||
<StaticLink href="/news">
|
||||
<Button variant="outline" size="lg">
|
||||
<Button variant="outline" size="lg" asChild>
|
||||
<StaticLink href="/news">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
返回新闻列表
|
||||
</Button>
|
||||
</StaticLink>
|
||||
<StaticLink href="/contact">
|
||||
<Button size="lg" className="bg-[#C41E3A] hover:bg-[#A01830] text-white">
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<Button size="lg" className="bg-[#C41E3A] hover:bg-[#A01830] text-white" asChild>
|
||||
<StaticLink href="/contact">
|
||||
联系我们
|
||||
</Button>
|
||||
</StaticLink>
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '新闻动态',
|
||||
description: '了解四川睿新致远科技有限公司(诺瓦隆)的最新动态、产品发布和行业资讯。',
|
||||
};
|
||||
|
||||
export default function NewsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { Search, Calendar, Filter, ChevronLeft, ChevronRight, ArrowRight } from
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const categories = ['全部', '公司新闻', '产品发布'];
|
||||
const categories = ['全部', '公司新闻', '产品发布', '研发动态'];
|
||||
const ITEMS_PER_PAGE = 9;
|
||||
|
||||
export default function NewsListPage() {
|
||||
@@ -28,7 +28,7 @@ export default function NewsListPage() {
|
||||
newsItem.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
newsItem.excerpt.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
return matchesCategory && matchesSearch;
|
||||
});
|
||||
}).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||
}, [selectedCategory, searchQuery]);
|
||||
|
||||
const totalPages = Math.ceil(filteredNews.length / ITEMS_PER_PAGE);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { PRODUCTS } from '@/lib/constants';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { BackButton } from '@/components/ui/back-button';
|
||||
import { CheckCircle2, Zap, Target, Layers, CreditCard, ArrowRight } from 'lucide-react';
|
||||
import { ProductDetailClient } from './product-detail-client';
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return PRODUCTS.map((product) => ({
|
||||
@@ -14,7 +11,7 @@ export async function generateStaticParams() {
|
||||
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const product = PRODUCTS.find((p) => p.id === id);
|
||||
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
title: '产品未找到',
|
||||
@@ -35,195 +32,5 @@ export default async function ProductDetailPage({ params }: { params: Promise<{
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<div className="relative overflow-hidden bg-gradient-to-b from-[#FAFAFA] to-white">
|
||||
<div className="container-wide relative z-10 pt-32 pb-20">
|
||||
<BackButton />
|
||||
<div className="max-w-4xl">
|
||||
<div className="inline-block px-4 py-2 bg-[#C41E3A]/10 rounded-full text-[#C41E3A] text-sm mb-6">
|
||||
{product.category}
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
{product.title}
|
||||
</h1>
|
||||
<p className="text-xl text-[#5C5C5C] leading-relaxed">
|
||||
{product.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-wide relative z-10 py-16">
|
||||
<div className="max-w-4xl">
|
||||
<section className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-6">产品概述</h2>
|
||||
<p className="text-lg text-[#5C5C5C] leading-relaxed">
|
||||
{product.overview}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-6 flex items-center gap-3">
|
||||
<Zap className="w-8 h-8 text-[#C41E3A]" />
|
||||
核心功能
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{product.features.map((feature, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 p-4 bg-[#F5F7FA] rounded-lg hover:bg-[#FFFBF5] transition-colors"
|
||||
>
|
||||
<div className="w-6 h-6 bg-[#C41E3A] rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<CheckCircle2 className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<span className="text-[#1C1C1C]">{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-6 flex items-center gap-3">
|
||||
<Target className="w-8 h-8 text-[#C41E3A]" />
|
||||
产品优势
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
{product.benefits.map((benefit, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 p-4 bg-gradient-to-r from-[#FFFBF5] to-transparent rounded-lg border-l-4 border-[#C41E3A]"
|
||||
>
|
||||
<div className="w-8 h-8 bg-[#C41E3A] rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<Target className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<span className="text-[#1C1C1C] font-medium">{benefit}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-6 flex items-center gap-3">
|
||||
<Layers className="w-8 h-8 text-[#C41E3A]" />
|
||||
实施流程
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
{product.process.map((step, index) => (
|
||||
<div key={index} className="flex items-start gap-4">
|
||||
<div className="w-10 h-10 bg-[#C41E3A] rounded-full flex items-center justify-center flex-shrink-0 text-white font-bold">
|
||||
{index + 1}
|
||||
</div>
|
||||
<div className="flex-1 pb-4">
|
||||
<p className="text-[#1C1C1C] font-medium">{step}</p>
|
||||
{index < product.process.length - 1 && (
|
||||
<div className="absolute left-5 top-10 w-0.5 h-8 bg-[#C41E3A]/20" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-6 flex items-center gap-3">
|
||||
<Layers className="w-8 h-8 text-[#C41E3A]" />
|
||||
技术规格
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{product.specs.map((spec, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center gap-3 p-4 bg-[#F5F7FA] rounded-lg"
|
||||
>
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full" />
|
||||
<span className="text-[#1C1C1C]">{spec}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-6 flex items-center gap-3">
|
||||
<CreditCard className="w-8 h-8 text-[#C41E3A]" />
|
||||
价格方案
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="p-6 bg-[#F5F7FA] rounded-lg border-2 border-transparent hover:border-[#C41E3A] transition-colors">
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-2">基础版</h3>
|
||||
<p className="text-3xl font-bold text-[#C41E3A] mb-4">{product.pricing.base}</p>
|
||||
<ul className="space-y-2 text-sm text-[#5C5C5C]">
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-[#C41E3A]" />
|
||||
基础功能模块
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-[#C41E3A]" />
|
||||
邮件支持
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-[#C41E3A]" />
|
||||
标准报表
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="p-6 bg-gradient-to-br from-[#C41E3A] to-[#A01830] rounded-lg text-white relative overflow-hidden">
|
||||
<div className="absolute top-4 right-4 bg-white/20 px-3 py-1 rounded-full text-xs">
|
||||
推荐
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">标准版</h3>
|
||||
<p className="text-3xl font-bold mb-4">{product.pricing.standard}</p>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
全部功能模块
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
电话支持
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
自定义报表
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="p-6 bg-[#F5F7FA] rounded-lg border-2 border-transparent hover:border-[#C41E3A] transition-colors">
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-2">企业版</h3>
|
||||
<p className="text-3xl font-bold text-[#C41E3A] mb-4">{product.pricing.enterprise}</p>
|
||||
<ul className="space-y-2 text-sm text-[#5C5C5C]">
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-[#C41E3A]" />
|
||||
全部功能模块
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-[#C41E3A]" />
|
||||
专属客服
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-[#C41E3A]" />
|
||||
定制开发
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="flex justify-center gap-4 pt-8 border-t border-[#E5E5E5]">
|
||||
<Button variant="outline" size="lg" asChild>
|
||||
<StaticLink href="/contact">
|
||||
联系我们
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<Button size="lg" className="bg-[#C41E3A] hover:bg-[#A01830] text-white" asChild>
|
||||
<StaticLink href="/contact">
|
||||
立即咨询
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <ProductDetailClient productId={id} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { PRODUCTS } from '@/lib/constants/products';
|
||||
|
||||
const ProductHeroSection = dynamic(
|
||||
() => import('@/components/products/product-hero-section').then(mod => ({ default: mod.ProductHeroSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ProductOverviewSection = dynamic(
|
||||
() => import('@/components/products/product-overview-section').then(mod => ({ default: mod.ProductOverviewSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ProductFeaturesSection = dynamic(
|
||||
() => import('@/components/products/product-features-section').then(mod => ({ default: mod.ProductFeaturesSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ProductBenefitsSection = dynamic(
|
||||
() => import('@/components/products/product-benefits-section').then(mod => ({ default: mod.ProductBenefitsSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ProductProcessSection = dynamic(
|
||||
() => import('@/components/products/product-process-section').then(mod => ({ default: mod.ProductProcessSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ProductSpecsSection = dynamic(
|
||||
() => import('@/components/products/product-specs-section').then(mod => ({ default: mod.ProductSpecsSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ProductCTASection = dynamic(
|
||||
() => import('@/components/products/product-cta-section').then(mod => ({ default: mod.ProductCTASection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
interface ProductDetailClientProps {
|
||||
productId: string;
|
||||
}
|
||||
|
||||
export function ProductDetailClient({ productId }: ProductDetailClientProps) {
|
||||
const product = PRODUCTS.find(p => p.id === productId) || null;
|
||||
|
||||
// 产品页已改为浅色主题,无需切换 Header 深色模式
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<p className="text-[#999999]">加载中...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<ProductHeroSection product={product} />
|
||||
<ProductOverviewSection product={product} />
|
||||
<ProductFeaturesSection product={product} />
|
||||
<ProductBenefitsSection product={product} />
|
||||
<ProductProcessSection product={product} />
|
||||
<ProductSpecsSection product={product} />
|
||||
<ProductCTASection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '产品服务',
|
||||
description: '诺瓦隆(四川睿新致远科技)自研产品矩阵,包括睿新ERP管理系统、睿新CRM客户关系管理系统、睿新内容管理系统、睿新商业智能平台等企业级数字化产品。',
|
||||
};
|
||||
|
||||
export default function ProductsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -7,10 +7,12 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RippleButton } from '@/lib/animations';
|
||||
import { PageHeader } from '@/components/ui/page-header';
|
||||
import { Search, ArrowLeft, Check, TrendingUp, ChevronLeft, ChevronRight, Filter } from 'lucide-react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { motion } from 'framer-motion';
|
||||
import { InkCard } from '@/lib/animations';
|
||||
|
||||
const categories = ['全部', '企业软件', '数据产品'];
|
||||
const ITEMS_PER_PAGE = 6;
|
||||
@@ -111,15 +113,13 @@ export default function ProductsPage() {
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
{paginatedProducts.map((product, index) => (
|
||||
<motion.div
|
||||
{paginatedProducts.map((product) => (
|
||||
<InkCard
|
||||
key={product.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="group cursor-pointer rounded-xl border border-[#E5E5E5] bg-white p-0 overflow-hidden hover:border-[#C41E3A] transition-colors"
|
||||
>
|
||||
<StaticLink href={`/products/${product.id}`}>
|
||||
<Card className="h-full group cursor-pointer border-[#E5E5E5] hover:border-[#C41E3A] transition-colors">
|
||||
<Card className="h-full border-0 shadow-none bg-transparent">
|
||||
<CardHeader>
|
||||
<Badge variant="secondary" className="w-fit mb-3">
|
||||
{product.category}
|
||||
@@ -161,14 +161,14 @@ export default function ProductsPage() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" className="w-full mt-auto group-hover:bg-[#C41E3A] group-hover:text-white group-hover:border-[#C41E3A] transition-colors">
|
||||
<div className="w-full mt-auto px-4 py-2 text-center text-sm font-medium border border-[#E5E5E5] rounded-md group-hover:bg-[#C41E3A] group-hover:text-white group-hover:border-[#C41E3A] transition-colors">
|
||||
了解详情
|
||||
<ArrowLeft className="ml-2 w-4 h-4 rotate-180" />
|
||||
</Button>
|
||||
<ArrowLeft className="ml-2 w-4 h-4 rotate-180 inline" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
</InkCard>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -231,16 +231,12 @@ export default function ProductsPage() {
|
||||
<p className="text-lg text-[#5C5C5C] mb-8 max-w-2xl mx-auto">
|
||||
我们的专业团队可以根据您的业务需求,提供量身定制的产品开发和系统集成服务
|
||||
</p>
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/contact">
|
||||
<StaticLink href="/contact">
|
||||
<RippleButton className="inline-flex items-center gap-2 px-6 py-2.5 bg-[#C41E3A] hover:bg-[#A01830] text-white rounded-lg text-sm font-medium transition-colors">
|
||||
联系我们
|
||||
<ArrowLeft className="ml-2 w-4 h-4 rotate-180" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<ArrowLeft className="w-4 h-4 rotate-180" />
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@@ -1,277 +1,97 @@
|
||||
'use client';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { BackButton } from '@/components/ui/back-button';
|
||||
import {
|
||||
CheckCircle2,
|
||||
TrendingUp,
|
||||
Users,
|
||||
Target,
|
||||
Clock,
|
||||
MessageCircle,
|
||||
ArrowRight
|
||||
} from 'lucide-react';
|
||||
import { SERVICES, CASES } from '@/lib/constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { type Service } from '@/lib/constants/services';
|
||||
import { RippleButton, FadeUp } from '@/lib/animations';
|
||||
|
||||
const ServiceHeroSection = dynamic(
|
||||
() => import('@/components/services/service-hero-section').then(mod => ({ default: mod.ServiceHeroSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ServiceChallengesSection = dynamic(
|
||||
() => import('@/components/services/service-challenges-section').then(mod => ({ default: mod.ServiceChallengesSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ServiceFeaturesSection = dynamic(
|
||||
() => import('@/components/services/service-features-section').then(mod => ({ default: mod.ServiceFeaturesSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ServiceProcessSection = dynamic(
|
||||
() => import('@/components/services/service-process-section').then(mod => ({ default: mod.ServiceProcessSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ServiceOutcomesSection = dynamic(
|
||||
() => import('@/components/services/service-outcomes-section').then(mod => ({ default: mod.ServiceOutcomesSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const ServiceCTASection = dynamic(
|
||||
() => import('@/components/services/service-cta-section').then(mod => ({ default: mod.ServiceCTASection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
interface ServiceDetailClientProps {
|
||||
service: typeof SERVICES[number];
|
||||
service: Service;
|
||||
}
|
||||
|
||||
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
Code: () => (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
),
|
||||
BarChart3: () => (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
),
|
||||
Lightbulb: () => (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
</svg>
|
||||
),
|
||||
Puzzle: () => (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
const challenges = {
|
||||
software: [
|
||||
{ title: '需求不明确', description: '业务部门提不出清晰需求,开发团队反复返工' },
|
||||
{ title: '技术选型困难', description: '技术栈更新快,不知道该选什么技术方案' },
|
||||
{ title: '项目延期', description: '开发进度难以把控,上线时间一拖再拖' },
|
||||
{ title: '维护成本高', description: '系统上线后问题不断,运维压力巨大' },
|
||||
],
|
||||
data: [
|
||||
{ title: '数据孤岛', description: '各系统数据分散,无法整合分析' },
|
||||
{ title: '决策盲区', description: '缺乏数据支撑,决策凭感觉' },
|
||||
{ title: '报表滞后', description: '手工制作报表,时效性差' },
|
||||
{ title: '价值难挖', description: '数据很多,但不知道怎么用' },
|
||||
],
|
||||
consulting: [
|
||||
{ title: '方向不明', description: '数字化转型不知道从哪里入手' },
|
||||
{ title: '技术债务', description: '历史系统包袱重,新技术难以引入' },
|
||||
{ title: '人才短缺', description: '缺乏专业的技术规划和架构人才' },
|
||||
{ title: '投入浪费', description: 'IT投入不少,但看不到明显效果' },
|
||||
],
|
||||
solutions: [
|
||||
{ title: '行业壁垒', description: '不了解行业最佳实践,走弯路' },
|
||||
{ title: '方案碎片化', description: '各系统各自为政,无法协同' },
|
||||
{ title: '实施风险', description: '大型项目实施失败率高' },
|
||||
{ title: '效果难量化', description: '投入产出比不清晰,难以评估' },
|
||||
],
|
||||
};
|
||||
|
||||
const outcomes = {
|
||||
software: [
|
||||
{ value: '30%', label: '开发效率提升' },
|
||||
{ value: '50%', label: '返工率降低' },
|
||||
{ value: '100%', label: '按时交付率' },
|
||||
],
|
||||
data: [
|
||||
{ value: '70%', label: '决策效率提升' },
|
||||
{ value: '实时', label: '数据更新' },
|
||||
{ value: '100+', label: '可视化报表' },
|
||||
],
|
||||
consulting: [
|
||||
{ value: '60%', label: '方向明确度' },
|
||||
{ value: '40%', label: '试错成本降低' },
|
||||
{ value: '3x', label: '转型速度提升' },
|
||||
],
|
||||
solutions: [
|
||||
{ value: '50%', label: '实施周期缩短' },
|
||||
{ value: '30%', label: '成本降低' },
|
||||
{ value: '95%', label: '客户满意度' },
|
||||
],
|
||||
};
|
||||
|
||||
export function ServiceDetailClient({ service }: ServiceDetailClientProps) {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const serviceChallenges = challenges[service.id as keyof typeof challenges] ?? [];
|
||||
const serviceOutcomes = outcomes[service.id as keyof typeof outcomes] ?? [];
|
||||
const relatedCases = CASES.slice(0, 2);
|
||||
|
||||
const Icon = iconMap[service.icon];
|
||||
|
||||
function InlineCTABanner() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<div className="relative overflow-hidden bg-gradient-to-b from-[#FAFAFA] to-white">
|
||||
<div className="container-wide relative z-10 pt-32 pb-20">
|
||||
<BackButton />
|
||||
<div className="max-w-4xl mt-8">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center text-white">
|
||||
{Icon && <Icon />}
|
||||
</div>
|
||||
<div>
|
||||
<Badge className="mb-2 bg-[#C41E3A]/10 text-[#C41E3A] hover:bg-[#C41E3A]/20">
|
||||
核心业务
|
||||
</Badge>
|
||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-semibold text-[#1C1C1C]">
|
||||
{service.title}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-lg text-[#5C5C5C] leading-relaxed">
|
||||
{service.description}
|
||||
<div className="py-12 md:py-16">
|
||||
<div className="container-wide">
|
||||
<FadeUp delay={0}>
|
||||
<div className="max-w-2xl mx-auto bg-white rounded-2xl border border-[#E5E5E5] p-6 md:p-8 text-center shadow-sm">
|
||||
<p className="text-[#1C1C1C] font-semibold text-lg mb-3">
|
||||
想了解我们的服务如何帮助您的企业?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref={contentRef} className="container-wide py-12 md:py-16">
|
||||
<div className="max-w-4xl mx-auto space-y-16">
|
||||
|
||||
<section className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-8 border border-[#C41E3A]/20">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
|
||||
<MessageCircle className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
|
||||
您可能面临的挑战
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{serviceChallenges.map((challenge, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="p-4 bg-white rounded-lg border border-[#E5E5E5] hover:border-[#C41E3A] transition-colors"
|
||||
>
|
||||
<h3 className="font-semibold text-[#1C1C1C] mb-2">{challenge.title}</h3>
|
||||
<p className="text-sm text-[#5C5C5C]">{challenge.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-gradient-to-br from-[#F5F7FA] to-white rounded-2xl p-8 border border-[#E5E5E5]">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
|
||||
<Target className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
|
||||
我们如何帮助您
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-lg text-[#5C5C5C] leading-relaxed mb-6">
|
||||
{service.overview}
|
||||
<p className="text-[#5C5C5C] mb-6">
|
||||
预约一次免费咨询,获取专属数字化转型建议
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
{service.features.map((feature, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-[#C41E3A] mt-0.5 flex-shrink-0" />
|
||||
<span className="text-[#1C1C1C]">{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white px-6 py-3 rounded-lg font-semibold inline-flex items-center justify-center"
|
||||
>
|
||||
免费咨询
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(196, 30, 58, 0.2)"
|
||||
className="border border-[#E5E5E5] text-[#5C5C5C] hover:text-[#C41E3A] hover:border-[#C41E3A]/30 px-6 py-3 rounded-lg font-semibold inline-flex items-center justify-center"
|
||||
>
|
||||
预约演示
|
||||
</RippleButton>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-8 border border-[#C41E3A]/20">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
|
||||
<Clock className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
|
||||
服务流程
|
||||
</h2>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{service.process.map((step, index) => (
|
||||
<div key={index} className="flex items-start gap-4">
|
||||
<div className="w-10 h-10 bg-[#C41E3A] rounded-full flex items-center justify-center flex-shrink-0 text-white font-semibold">
|
||||
{index + 1}
|
||||
</div>
|
||||
<div className="flex-1 pb-4">
|
||||
<p className="text-[#1C1C1C] font-medium">{step}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-gradient-to-br from-[#F5F7FA] to-white rounded-2xl p-8 border border-[#E5E5E5]">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
|
||||
<TrendingUp className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
|
||||
您将获得的改变
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-3 gap-4">
|
||||
{serviceOutcomes.map((outcome, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="p-6 bg-white rounded-lg border border-[#E5E5E5] hover:border-[#C41E3A] transition-colors text-center"
|
||||
>
|
||||
<div className="text-3xl font-bold text-[#C41E3A] mb-2">
|
||||
{outcome.value}
|
||||
</div>
|
||||
<div className="text-sm text-[#5C5C5C]">{outcome.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<p className="text-sm text-[#5C5C5C]">
|
||||
{service.benefits.map(b => b).join(';')}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-8 border border-[#C41E3A]/20">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
|
||||
<Users className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
|
||||
相关案例
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{relatedCases.map((caseItem) => (
|
||||
<StaticLink
|
||||
key={caseItem.id}
|
||||
href={`/cases/${caseItem.id}`}
|
||||
className="group p-4 bg-white rounded-lg border border-[#E5E5E5] hover:border-[#C41E3A] transition-colors"
|
||||
>
|
||||
<Badge variant="secondary" className="mb-2">
|
||||
{caseItem.industry}
|
||||
</Badge>
|
||||
<h3 className="font-semibold text-[#1C1C1C] group-hover:text-[#C41E3A] transition-colors">
|
||||
{caseItem.title}
|
||||
</h3>
|
||||
<p className="text-sm text-[#5C5C5C] mt-2 line-clamp-2">
|
||||
{caseItem.description}
|
||||
</p>
|
||||
</StaticLink>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="flex justify-center gap-4 pt-8 border-t border-[#E5E5E5]">
|
||||
<StaticLink href="/services">
|
||||
<Button variant="outline" size="lg">
|
||||
查看其他服务
|
||||
</Button>
|
||||
</StaticLink>
|
||||
<StaticLink href="/contact">
|
||||
<Button size="lg" className="bg-[#C41E3A] hover:bg-[#A01830] text-white">
|
||||
开始您的转型之旅
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</StaticLink>
|
||||
</div>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ServiceDetailClient({ service }: ServiceDetailClientProps) {
|
||||
if (!service) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<p className="text-[#999999]">加载中...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<ServiceHeroSection service={service} />
|
||||
<ServiceChallengesSection service={service} />
|
||||
<ServiceFeaturesSection service={service} />
|
||||
<InlineCTABanner />
|
||||
<ServiceProcessSection service={service} />
|
||||
<ServiceOutcomesSection service={service} />
|
||||
<InlineCTABanner />
|
||||
<ServiceCTASection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { SERVICES } from '@/lib/constants';
|
||||
import { SERVICES } from '@/lib/constants/services';
|
||||
import { ServiceDetailClient } from './client';
|
||||
|
||||
export async function generateStaticParams() {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '核心业务',
|
||||
description: '诺瓦隆(四川睿新致远科技)提供软件开发、数据分析、技术咨询、解决方案四大核心业务,助力企业实现全方位数字化转型。',
|
||||
};
|
||||
|
||||
export default function ServicesLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { SOLUTIONS } from '@/lib/constants/solutions';
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { SolutionDetailClient } from './solution-detail-client';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return SOLUTIONS.map((solution) => ({
|
||||
id: solution.id,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const { id } = await params;
|
||||
const solution = SOLUTIONS.find((s) => s.id === id);
|
||||
if (!solution) {return { title: '解决方案 - 睿新致远' };}
|
||||
return {
|
||||
title: `${solution.industry}数字化转型方案 - 睿新致远`,
|
||||
description: solution.description,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function SolutionDetailPage({ params }: PageProps) {
|
||||
const { id } = await params;
|
||||
const solution = SOLUTIONS.find((s) => s.id === id);
|
||||
if (!solution) {notFound();}
|
||||
return <SolutionDetailClient solutionId={id} />;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
'use client';
|
||||
|
||||
import { SOLUTIONS } from '@/lib/constants/solutions';
|
||||
import { PRODUCTS } from '@/lib/constants/products';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { CheckCircle } from 'lucide-react';
|
||||
import {
|
||||
InkReveal,
|
||||
FadeUp,
|
||||
RippleButton,
|
||||
FloatingElement,
|
||||
StaggerContainer,
|
||||
StaggerItem,
|
||||
InkCard,
|
||||
SealStamp,
|
||||
} from '@/lib/animations';
|
||||
import { ScrollReveal, inkRevealVariants, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
|
||||
interface SolutionDetailClientProps {
|
||||
solutionId: string;
|
||||
}
|
||||
|
||||
export function SolutionDetailClient({ solutionId }: SolutionDetailClientProps) {
|
||||
const solution = SOLUTIONS.find((s) => s.id === solutionId) || null;
|
||||
|
||||
if (!solution) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<p className="text-[#999999]">加载中...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
{/* Section 1: Hero */}
|
||||
<section className="relative py-24 md:py-32 bg-gradient-to-b from-white to-[#F8F8F8]">
|
||||
<div className="container-wide">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<SealStamp
|
||||
delay={0.1}
|
||||
className="inline-block px-4 py-2 bg-[#C41E3A]/20 rounded-full text-[#C41E3A] text-sm mb-6"
|
||||
>
|
||||
{solution.industry}
|
||||
</SealStamp>
|
||||
<InkReveal delay={0.2}>
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-[#1C1C1C] mb-4">
|
||||
{solution.title}
|
||||
</h1>
|
||||
</InkReveal>
|
||||
<InkReveal delay={0.4}>
|
||||
<p className="text-lg md:text-xl text-[#5C5C5C] mb-4">{solution.subtitle}</p>
|
||||
</InkReveal>
|
||||
<InkReveal delay={0.5}>
|
||||
<p className="text-base text-[#5C5C5C] leading-relaxed mb-10 max-w-2xl mx-auto">
|
||||
{solution.description}
|
||||
</p>
|
||||
</InkReveal>
|
||||
<InkReveal delay={0.6}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
className="border-2 border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A] hover:text-white px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center"
|
||||
rippleColor="rgba(196, 30, 58, 0.2)"
|
||||
>
|
||||
预约演示
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center"
|
||||
rippleColor="rgba(255, 255, 255, 0.3)"
|
||||
>
|
||||
获取定制方案
|
||||
</RippleButton>
|
||||
</div>
|
||||
</InkReveal>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Section 2: Challenges */}
|
||||
<section className="py-20 md:py-28 bg-white">
|
||||
<div className="container-wide">
|
||||
<ScrollReveal variants={slideInLeftVariants} className="mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">行业痛点</h2>
|
||||
<p className="text-[#5C5C5C] mt-2">您是否也面临这些挑战?</p>
|
||||
</ScrollReveal>
|
||||
<StaggerContainer className="grid md:grid-cols-2 gap-6 max-w-5xl">
|
||||
{solution.challenges.map((challenge, index) => (
|
||||
<StaggerItem key={index}>
|
||||
<InkCard
|
||||
className="p-6 bg-[#FFFBF5] rounded-2xl border border-[#C41E3A]/10"
|
||||
hoverScale={1.02}
|
||||
hoverShadow="0 20px 40px rgba(196, 30, 58, 0.08)"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="flex-shrink-0 w-8 h-8 rounded-full bg-[#C41E3A]/10 flex items-center justify-center text-[#C41E3A] font-bold text-sm">
|
||||
{index + 1}
|
||||
</span>
|
||||
<p className="text-[#1C1C1C] leading-relaxed">{challenge}</p>
|
||||
</div>
|
||||
</InkCard>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Section 3: Our Solutions */}
|
||||
<section className="py-20 md:py-28 bg-[#F8F8F8]">
|
||||
<div className="container-wide">
|
||||
<ScrollReveal variants={inkRevealVariants} className="mb-12 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">我们的解决方案</h2>
|
||||
<p className="text-[#5C5C5C] mt-2">针对行业痛点,量身定制</p>
|
||||
</ScrollReveal>
|
||||
<StaggerContainer className="max-w-3xl mx-auto space-y-6">
|
||||
{solution.solutions.map((item, index) => (
|
||||
<StaggerItem key={index}>
|
||||
<div className="flex items-start gap-4 p-6 bg-white rounded-2xl border border-[#E5E5E5]">
|
||||
<CheckCircle className="w-6 h-6 text-[#C41E3A] flex-shrink-0 mt-0.5" />
|
||||
<p className="text-[#1C1C1C] leading-relaxed text-lg">{item}</p>
|
||||
</div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Section 4: Related Products */}
|
||||
{solution.relatedProducts.length > 0 && (
|
||||
<section className="py-16 md:py-20 bg-white">
|
||||
<div className="container-wide">
|
||||
<div className="text-center mb-10">
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-[#1C1C1C]">
|
||||
推荐产品
|
||||
</h2>
|
||||
<p className="text-[#5C5C5C] mt-2">即将上市,敬请期待</p>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 gap-6 max-w-3xl mx-auto">
|
||||
{solution.relatedProducts.map((productId) => {
|
||||
const product = PRODUCTS.find((p) => p.id === productId);
|
||||
if (!product) {return null;}
|
||||
return (
|
||||
<FadeUp key={productId}>
|
||||
<StaticLink href={`/products/${productId}`}>
|
||||
<div className="group p-6 bg-[#F8F8F8] rounded-2xl border border-[#E5E5E5] hover:border-[#C41E3A]/30 transition-all">
|
||||
<span className="inline-block px-3 py-1 bg-[#C41E3A]/10 text-[#C41E3A] text-xs font-semibold rounded-full mb-3">
|
||||
{product.category}
|
||||
</span>
|
||||
<h3 className="text-lg font-bold text-[#1C1C1C] mb-2 group-hover:text-[#C41E3A] transition-colors">
|
||||
{product.title}
|
||||
</h3>
|
||||
<p className="text-sm text-[#5C5C5C] line-clamp-2">{product.description}</p>
|
||||
</div>
|
||||
</StaticLink>
|
||||
</FadeUp>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Section 6: CTA */}
|
||||
<section className="relative py-24 md:py-32 bg-gradient-to-r from-[#C41E3A] to-[#E85D75] overflow-hidden">
|
||||
<FloatingElement
|
||||
amplitude={8}
|
||||
duration={5}
|
||||
delay={0.5}
|
||||
className="absolute -top-20 -right-20 pointer-events-none"
|
||||
>
|
||||
<div className="w-[280px] h-[280px] bg-white/10 rounded-full" />
|
||||
</FloatingElement>
|
||||
<FloatingElement
|
||||
amplitude={6}
|
||||
duration={4}
|
||||
delay={1}
|
||||
className="absolute -bottom-16 -left-16 pointer-events-none"
|
||||
>
|
||||
<div className="w-[220px] h-[220px] bg-white/10 rounded-full" />
|
||||
</FloatingElement>
|
||||
<div className="container-wide">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<InkReveal delay={0}>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
|
||||
准备好开启{solution.industry}数字化转型了吗?
|
||||
</h2>
|
||||
</InkReveal>
|
||||
<FadeUp delay={0.15}>
|
||||
<p className="text-lg text-white/90 mb-10">
|
||||
我们的专家团队将为您量身定制专属解决方案
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.3}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="bg-white text-[#C41E3A] px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center w-full sm:w-auto"
|
||||
>
|
||||
免费获取方案
|
||||
</RippleButton>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '解决方案 - 睿新致远',
|
||||
description: '三种角色,一种身份——您的成长伙伴',
|
||||
title: '行业方案',
|
||||
description: '诺瓦隆为制造业、政务服务、酒店管理、智慧农业等行业提供定制化数字化转型解决方案,助力企业实现数字化升级。',
|
||||
};
|
||||
|
||||
export default function SolutionsLayout({
|
||||
|
||||
@@ -3,11 +3,29 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PageHeader } from '@/components/ui/page-header';
|
||||
import { ArrowRight, Lightbulb, Cpu, Users, CheckCircle2 } from 'lucide-react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { MethodologySection } from '@/components/sections/methodology-section';
|
||||
import { SOLUTIONS } from '@/lib/constants/solutions';
|
||||
import { FadeUp } from '@/lib/animations';
|
||||
|
||||
const ConsultingSection = dynamic(
|
||||
() => import('@/components/solutions/consulting-section').then(mod => ({ default: mod.ConsultingSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const TechSolutionSection = dynamic(
|
||||
() => import('@/components/solutions/tech-solution-section').then(mod => ({ default: mod.TechSolutionSection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const AccompanySection = dynamic(
|
||||
() => import('@/components/solutions/accompany-section').then(mod => ({ default: mod.AccompanySection })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function SolutionsPage() {
|
||||
const contentRef = useRef(null);
|
||||
@@ -22,221 +40,53 @@ export default function SolutionsPage() {
|
||||
|
||||
<div className="container-wide relative z-10 py-16" ref={contentRef}>
|
||||
<div className="max-w-6xl mx-auto space-y-24">
|
||||
|
||||
<motion.section
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
|
||||
>
|
||||
<div className="flex items-start gap-6 mb-8">
|
||||
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
|
||||
<Lightbulb className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
|
||||
模块一:数字化转型咨询 · 参谋伙伴
|
||||
</h2>
|
||||
<p className="text-lg text-[#5C5C5C]">
|
||||
帮您看清前路,迈对第一步
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 mb-8">
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
数字化转型最大的成本,是走错方向的成本。
|
||||
</p>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
我们用行业智慧帮您洞察趋势,用理性分析帮您避开陷阱。
|
||||
</p>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
不堆砌概念,只帮您想清楚:该不该做、做什么、怎么做。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
|
||||
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
|
||||
核心价值点
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">行业趋势洞察报告</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">数字化转型成熟度评估</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">个性化实施路径规划</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
|
||||
onClick={() => {
|
||||
const element = document.getElementById('contact');
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}}
|
||||
>
|
||||
预约一次免费诊断
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
<motion.section
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
|
||||
>
|
||||
<div className="flex items-start gap-6 mb-8">
|
||||
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
|
||||
<Cpu className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
|
||||
模块二:信息技术解决方案 · 技术伙伴
|
||||
</h2>
|
||||
<p className="text-lg text-[#5C5C5C]">
|
||||
让技术真正为业务服务
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 mb-8">
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
我们不追逐“最火”的技术,只选择“最对”的技术。
|
||||
</p>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
将前沿技术深度融入您的业务场景,让每一行代码都产生业务价值。
|
||||
</p>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
您不必懂技术原理,只需要看见业务在增长。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
|
||||
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
|
||||
核心价值点
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">业务场景深度调研</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">技术方案定制开发</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">敏捷交付快速迭代</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A] hover:text-white"
|
||||
onClick={() => {
|
||||
const element = document.getElementById('cases');
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}}
|
||||
>
|
||||
查看技术案例
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
<motion.section
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
|
||||
>
|
||||
<div className="flex items-start gap-6 mb-8">
|
||||
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
|
||||
<Users className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
|
||||
模块三:长期陪跑服务 · 同行伙伴
|
||||
</h2>
|
||||
<p className="text-lg text-[#5C5C5C]">
|
||||
交付只是开始,陪伴才是常态
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 mb-8">
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
项目上线那天,是我们真正成为伙伴的开始。
|
||||
</p>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
我们建立长效服务机制,定期回访、持续优化、随时响应。
|
||||
</p>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
在您需要的时候,我们始终在场。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
|
||||
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
|
||||
核心价值点
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">专属客户成功经理</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">季度业务复盘会</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">7×24小时响应通道</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
|
||||
onClick={() => {
|
||||
const element = document.getElementById('contact');
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}}
|
||||
>
|
||||
了解陪跑服务
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</motion.section>
|
||||
<ConsultingSection />
|
||||
<TechSolutionSection />
|
||||
<AccompanySection />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 行业解决方案入口 */}
|
||||
<section className="py-20 md:py-28 bg-[#F8F8F8]">
|
||||
<div className="container-wide">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C] mb-3">
|
||||
行业解决方案
|
||||
</h2>
|
||||
<p className="text-[#5C5C5C] text-lg">
|
||||
针对不同行业的数字化痛点,提供定制化解决方案
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-5xl mx-auto">
|
||||
{SOLUTIONS.map((solution, index) => (
|
||||
<FadeUp key={solution.id} delay={index * 0.1}>
|
||||
<StaticLink href={`/solutions/${solution.id}`}>
|
||||
<div className="group p-6 md:p-8 bg-white rounded-2xl border border-[#E5E5E5] hover:border-[#C41E3A]/30 transition-all hover:shadow-lg">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="inline-block px-3 py-1 bg-[#C41E3A]/10 text-[#C41E3A] text-xs font-semibold rounded-full">
|
||||
{solution.industry}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-[#1C1C1C] mb-2 group-hover:text-[#C41E3A] transition-colors">
|
||||
{solution.title}
|
||||
</h3>
|
||||
<p className="text-[#5C5C5C] text-sm mb-4 line-clamp-2">
|
||||
{solution.description}
|
||||
</p>
|
||||
<div className="flex items-center gap-1 text-[#C41E3A] text-sm font-semibold">
|
||||
查看方案
|
||||
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
</div>
|
||||
</StaticLink>
|
||||
</FadeUp>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<MethodologySection />
|
||||
|
||||
<motion.div
|
||||
@@ -253,15 +103,15 @@ export default function SolutionsPage() {
|
||||
无论您处于哪个阶段,我们都能为您提供合适的解决方案
|
||||
</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<Button
|
||||
size="lg"
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/contact">联系我们</StaticLink>
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
|
||||
asChild
|
||||
>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Shield, Building2, Users, Code, Target, ArrowRight } from 'lucide-react
|
||||
const TEAM_PILLARS = [
|
||||
{
|
||||
icon: Shield,
|
||||
title: '12年+ 行业深耕',
|
||||
title: '12+ 年行业深耕',
|
||||
description: '核心团队长期从事技术咨询、企业数字化等领域,服务覆盖金融、制造、零售、政务、农业等多个行业,积累了丰富的跨行业经验和最佳实践。',
|
||||
},
|
||||
{
|
||||
@@ -44,7 +44,7 @@ export function TeamClient() {
|
||||
<div className="min-h-screen bg-white">
|
||||
<PageHeader
|
||||
title="核心团队"
|
||||
description="核心团队从事技术咨询、企业数字化等行业 12 年+,开发团队成员来自于多个大型传统 IT 企业"
|
||||
description="核心团队从事技术咨询、企业数字化等行业 12 年+,开发团队成员来自多个大型传统 IT 企业"
|
||||
/>
|
||||
|
||||
<div ref={contentRef} className="container-wide py-12 md:py-16">
|
||||
|
||||
@@ -2,7 +2,7 @@ import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { TeamClient } from './client';
|
||||
|
||||
export const metadata = {
|
||||
title: `核心团队 - ${COMPANY_INFO.name}`,
|
||||
title: '核心团队',
|
||||
description: `了解${COMPANY_INFO.name}的核心团队。我们的团队成员拥有丰富的行业经验和技术专长,致力于为客户提供专业的数字化转型服务。`,
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -212,7 +212,7 @@
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/* 青柳隷書 - 仅用于品牌标题"睿新致遠" */
|
||||
/* 青柳隷書 - 仅用于品牌标题"睿新致远" */
|
||||
.font-brand {
|
||||
font-family: var(--font-aoyagi-reisho), 'Aoyagi Reisho', 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
|
||||
font-weight: normal;
|
||||
|
||||
+4
-4
@@ -45,9 +45,9 @@ const maShanZheng = Ma_Shan_Zheng({
|
||||
preload: true,
|
||||
});
|
||||
|
||||
// 青柳隷書 - 仅用于品牌标题"睿新致遠"(子集版本,仅包含4个字符)
|
||||
// 青柳隷書 - 仅用于品牌标题"睿新致遠"(完整版,支持繁体字)
|
||||
const aoyagiReisho = localFont({
|
||||
src: "./fonts/AoyagiReisho-subset.ttf",
|
||||
src: "./fonts/AoyagiReisho.ttf",
|
||||
variable: "--font-aoyagi-reisho",
|
||||
display: "swap",
|
||||
preload: true,
|
||||
@@ -60,7 +60,7 @@ export const metadata: Metadata = {
|
||||
template: "%s | 四川睿新致远科技有限公司",
|
||||
},
|
||||
description: "四川睿新致远科技有限公司成立于2026年,专注于企业数字化转型服务,提供软件开发、云计算、数据分析、信息安全等一站式解决方案。",
|
||||
keywords: ["数字化转型", "企业软件", "ERP系统", "CRM系统", "云计算", "数据分析", "软件开发", "成都科技公司", "金融科技", "诺瓦隆"],
|
||||
keywords: ["数字化转型", "企业软件", "ERP系统", "CRM系统", "云计算", "数据分析", "软件开发", "成都科技公司", "诺瓦隆"],
|
||||
authors: [{ name: "四川睿新致远科技有限公司" }],
|
||||
creator: "四川睿新致远科技有限公司",
|
||||
publisher: "四川睿新致远科技有限公司",
|
||||
@@ -101,7 +101,7 @@ export const metadata: Metadata = {
|
||||
canonical: "https://www.novalon.cn",
|
||||
},
|
||||
verification: {
|
||||
google: "your-google-verification-code",
|
||||
google: "pending-verification",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Home, ArrowLeft, Search } from 'lucide-react';
|
||||
import { Home, ArrowLeft, Building2, Briefcase, Package, Trophy } from 'lucide-react';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
@@ -58,7 +58,7 @@ export default function NotFound() {
|
||||
className="flex items-center p-4 bg-white rounded-lg hover:shadow-md transition-shadow group"
|
||||
>
|
||||
<div className="w-10 h-10 bg-[#C41E3A]/10 rounded-lg flex items-center justify-center mr-4 group-hover:bg-[#C41E3A]/20 transition-colors">
|
||||
<Search className="w-5 h-5 text-[#C41E3A]" />
|
||||
<Building2 className="w-5 h-5 text-[#C41E3A]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="font-semibold text-[#1C1C1C]">关于我们</div>
|
||||
@@ -71,7 +71,7 @@ export default function NotFound() {
|
||||
className="flex items-center p-4 bg-white rounded-lg hover:shadow-md transition-shadow group"
|
||||
>
|
||||
<div className="w-10 h-10 bg-[#C41E3A]/10 rounded-lg flex items-center justify-center mr-4 group-hover:bg-[#C41E3A]/20 transition-colors">
|
||||
<Search className="w-5 h-5 text-[#C41E3A]" />
|
||||
<Briefcase className="w-5 h-5 text-[#C41E3A]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="font-semibold text-[#1C1C1C]">核心业务</div>
|
||||
@@ -84,7 +84,7 @@ export default function NotFound() {
|
||||
className="flex items-center p-4 bg-white rounded-lg hover:shadow-md transition-shadow group"
|
||||
>
|
||||
<div className="w-10 h-10 bg-[#C41E3A]/10 rounded-lg flex items-center justify-center mr-4 group-hover:bg-[#C41E3A]/20 transition-colors">
|
||||
<Search className="w-5 h-5 text-[#C41E3A]" />
|
||||
<Package className="w-5 h-5 text-[#C41E3A]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="font-semibold text-[#1C1C1C]">产品服务</div>
|
||||
@@ -97,10 +97,10 @@ export default function NotFound() {
|
||||
className="flex items-center p-4 bg-white rounded-lg hover:shadow-md transition-shadow group"
|
||||
>
|
||||
<div className="w-10 h-10 bg-[#C41E3A]/10 rounded-lg flex items-center justify-center mr-4 group-hover:bg-[#C41E3A]/20 transition-colors">
|
||||
<Search className="w-5 h-5 text-[#C41E3A]" />
|
||||
<Trophy className="w-5 h-5 text-[#C41E3A]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="font-semibold text-[#1C1C1C]">成功案例</div>
|
||||
<div className="font-semibold text-[#1C1C1C]">客户案例</div>
|
||||
<div className="text-sm text-[#5C5C5C]">客户故事</div>
|
||||
</div>
|
||||
</StaticLink>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '隐私政策 - 睿新致远',
|
||||
description: '四川睿新致远科技有限公司隐私政策',
|
||||
title: '隐私政策',
|
||||
description: '四川睿新致远科技有限公司(诺瓦隆)隐私政策。我们重视您的隐私,致力于保护您的个人信息安全。',
|
||||
};
|
||||
|
||||
export default function PrivacyPolicyPage() {
|
||||
@@ -22,6 +22,7 @@ export default function PrivacyPolicyPage() {
|
||||
<div className="container-wide py-16">
|
||||
<div className="max-w-4xl">
|
||||
<div className="prose prose-lg max-w-none">
|
||||
<p className="text-sm text-[#718096] mb-6">最后更新日期:2026年4月25日</p>
|
||||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-4">引言</h2>
|
||||
<p className="text-[#5C5C5C] leading-relaxed mb-4">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '服务条款 - 睿新致远',
|
||||
description: '四川睿新致远科技有限公司服务条款',
|
||||
title: '服务条款',
|
||||
description: '四川睿新致远科技有限公司(诺瓦隆)服务条款。请仔细阅读以下条款,使用我们的服务即表示您同意这些条款。',
|
||||
};
|
||||
|
||||
export default function TermsOfServicePage() {
|
||||
@@ -170,7 +170,7 @@ export default function TermsOfServicePage() {
|
||||
|
||||
<section className="bg-[#FFFBF5] p-6 rounded-lg border-l-4 border-[#C41E3A]">
|
||||
<p className="text-[#1C1C1C] font-medium mb-2">最后更新日期</p>
|
||||
<p className="text-[#5C5C5C]">2026年2月26日</p>
|
||||
<p className="text-[#5C5C5C]">2026年4月25日</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,9 @@ import { COMPANY_INFO, NAVIGATION } from '@/lib/constants';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="bg-[#F5F5F5] border-t border-[#E5E5E5] py-12" data-testid="footer" role="contentinfo">
|
||||
<footer className="bg-[#F5F5F5] py-12" data-testid="footer" role="contentinfo">
|
||||
{/* 顶部渐变装饰线 */}
|
||||
<div className="h-[2px] bg-gradient-to-r from-transparent via-[#C41E3A]/50 to-transparent" />
|
||||
<div className="container-wide">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
||||
<div className="bg-white rounded-xl p-6 border border-[#E5E5E5] shadow-sm hover:shadow-md hover:-translate-y-1 transition-all duration-300" data-testid="card-brand">
|
||||
@@ -114,7 +116,7 @@ export function Footer() {
|
||||
<div className="border-t border-[#E5E5E5] mt-12 pt-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<p className="text-[#5C5C5C] text-sm">
|
||||
© {new Date().getFullYear()} {COMPANY_INFO.name}. All rights reserved.
|
||||
© {new Date().getFullYear()} {COMPANY_INFO.name}。保留所有权利。
|
||||
</p>
|
||||
<div className="flex gap-6">
|
||||
<StaticLink href="/privacy" className="text-[#5C5C5C] hover:text-[#C41E3A] text-sm transition-colors duration-200">
|
||||
|
||||
@@ -15,17 +15,21 @@ jest.mock('next/navigation', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('next/link', () => {
|
||||
return ({ children, href, onClick, ...props }: any) => (
|
||||
const MockLink = ({ children, href, onClick, ...props }: any) => (
|
||||
<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 }: any) => (
|
||||
<img src={src} alt={alt} width={width} height={height} className={className} {...props} />
|
||||
);
|
||||
MockImage.displayName = 'MockImage';
|
||||
return MockImage;
|
||||
});
|
||||
|
||||
jest.mock('framer-motion', () => ({
|
||||
@@ -45,7 +49,7 @@ jest.mock('lucide-react', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/button', () => ({
|
||||
Button: ({ children, className, asChild, ...props }: any) => (
|
||||
Button: ({ children, className, ...props }: any) => (
|
||||
<button className={className} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
@@ -55,7 +59,7 @@ jest.mock('@/components/ui/button', () => ({
|
||||
jest.mock('@/lib/constants', () => ({
|
||||
COMPANY_INFO: {
|
||||
name: '四川睿新致远科技有限公司',
|
||||
shortName: '睿新致遠',
|
||||
shortName: '睿新致远',
|
||||
},
|
||||
NAVIGATION: [
|
||||
{ id: 'home', label: '首页', href: '/' },
|
||||
|
||||
@@ -13,9 +13,28 @@ import { useFocusTrap } from '@/hooks/use-focus-trap';
|
||||
function HeaderContent() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [headerTheme, setHeaderTheme] = useState<'light' | 'dark'>('light');
|
||||
const pathname = usePathname();
|
||||
const focusTrapRef = useFocusTrap<HTMLDivElement>(isOpen);
|
||||
|
||||
// 监听 data-header-theme 属性变化(产品落地页控制导航栏颜色)
|
||||
useEffect(() => {
|
||||
const handleThemeChange = () => {
|
||||
const theme = document.documentElement.getAttribute('data-header-theme');
|
||||
setHeaderTheme(theme === 'dark' ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
handleThemeChange();
|
||||
|
||||
const observer = new MutationObserver(handleThemeChange);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-header-theme'],
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
@@ -72,9 +91,13 @@ function HeaderContent() {
|
||||
className={`
|
||||
fixed top-0 left-0 right-0 z-50
|
||||
transition-all duration-300 ease-out
|
||||
${isScrolled
|
||||
? 'bg-white/95 backdrop-blur-xl border-b border-[#E2E8F0] shadow-sm'
|
||||
: 'bg-transparent'
|
||||
${headerTheme === 'dark'
|
||||
? isScrolled
|
||||
? 'bg-[#0A0A0A]/90 backdrop-blur-xl border-b border-[#1A1A1A]'
|
||||
: 'bg-transparent'
|
||||
: isScrolled
|
||||
? 'bg-white/95 backdrop-blur-xl border-b border-[#E2E8F0] shadow-sm'
|
||||
: 'bg-transparent'
|
||||
}
|
||||
`}
|
||||
>
|
||||
@@ -86,8 +109,8 @@ function HeaderContent() {
|
||||
aria-label="返回首页"
|
||||
>
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt={COMPANY_INFO.name}
|
||||
src={headerTheme === 'dark' ? '/logo-white.svg' : '/logo.svg'}
|
||||
alt={COMPANY_INFO.shortName}
|
||||
width={128}
|
||||
height={32}
|
||||
className="transition-transform duration-200 group-hover:scale-105"
|
||||
@@ -105,9 +128,13 @@ function HeaderContent() {
|
||||
className={`
|
||||
relative px-3 py-1.5 text-sm font-medium
|
||||
transition-all duration-300
|
||||
${isActive(item)
|
||||
? 'text-[#1C1C1C]'
|
||||
: 'text-[#3D3D3D] hover:text-[#1C1C1C]'
|
||||
${headerTheme === 'dark'
|
||||
? isActive(item)
|
||||
? 'text-white'
|
||||
: 'text-[#B0B0B0] hover:text-white'
|
||||
: isActive(item)
|
||||
? 'text-[#1C1C1C]'
|
||||
: 'text-[#3D3D3D] hover:text-[#1C1C1C]'
|
||||
}
|
||||
`}
|
||||
aria-current={isActive(item) ? 'page' : undefined}
|
||||
@@ -130,6 +157,8 @@ function HeaderContent() {
|
||||
<div className="hidden md:flex items-center gap-3">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={headerTheme === 'dark' ? 'outline' : 'default'}
|
||||
className={headerTheme === 'dark' ? 'border-white/30 text-white hover:bg-white/10 hover:text-white' : ''}
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/contact" data-testid="consult-button">立即咨询</StaticLink>
|
||||
@@ -137,7 +166,13 @@ function HeaderContent() {
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="md:hidden p-3 -mr-3 text-[#3D3D3D] hover:text-[#1C1C1C] hover:bg-[#F5F5F5] rounded-lg transition-all duration-200 active:scale-95"
|
||||
className={`
|
||||
md:hidden p-3 -mr-3 rounded-lg transition-all duration-200 active:scale-95
|
||||
${headerTheme === 'dark'
|
||||
? 'text-white hover:bg-white/10'
|
||||
: 'text-[#3D3D3D] hover:text-[#1C1C1C] hover:bg-[#F5F5F5]'
|
||||
}
|
||||
`}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-expanded={isOpen}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
'use client';
|
||||
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Mail } from 'lucide-react';
|
||||
import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { FloatingElement, RippleButton } from '@/lib/animations';
|
||||
|
||||
/**
|
||||
* 产品站专属 Footer
|
||||
*
|
||||
* 与主站 Footer 的区别:
|
||||
* - 浅色背景(与产品页浅色主题统一)
|
||||
* - 产品 CTA 为主(而非公司信息展示)
|
||||
* - 不显示公众号/企业微信二维码
|
||||
* - 不显示 ICP/公安备案号(强化"独立产品站"感知)
|
||||
* - 简洁的两栏布局:产品链接 + 联系方式
|
||||
*/
|
||||
|
||||
const productLinks = [
|
||||
{ label: '产品功能', href: '#features' },
|
||||
{ label: '核心优势', href: '#benefits' },
|
||||
{ label: '实施流程', href: '#process' },
|
||||
{ label: '技术规格', href: '#specs' },
|
||||
{ label: '定价方案', href: '#pricing' },
|
||||
];
|
||||
|
||||
export function ProductFooter() {
|
||||
return (
|
||||
<footer className="bg-[#F8F8F8]" role="contentinfo">
|
||||
{/* 顶部装饰线 */}
|
||||
<div className="h-[2px] bg-gradient-to-r from-transparent via-[#C41E3A]/50 to-transparent" />
|
||||
|
||||
{/* CTA 区域 */}
|
||||
<div className="container-wide py-16">
|
||||
<div className="text-center max-w-2xl mx-auto">
|
||||
<h3 className="text-2xl md:text-3xl font-bold text-[#1C1C1C] mb-4">
|
||||
需要私有化部署方案?
|
||||
</h3>
|
||||
<p className="text-[#5C5C5C] mb-8 text-lg">
|
||||
我们的技术团队将根据您的需求,提供专属的部署方案与技术支持
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<StaticLink href="/contact">
|
||||
<RippleButton
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="px-8 py-3 bg-[#C41E3A] text-white rounded-lg font-medium text-base"
|
||||
>
|
||||
获取方案
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
<StaticLink href="/contact">
|
||||
<RippleButton
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="px-8 py-3 border border-[#E5E5E5] text-[#5C5C5C] hover:text-[#1C1C1C] hover:bg-white rounded-lg font-medium text-base"
|
||||
>
|
||||
技术交流
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部信息 */}
|
||||
<div className="border-t border-[#E5E5E5]">
|
||||
<div className="container-wide py-8">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
{/* 左侧:Logo + 版权 */}
|
||||
<div className="flex flex-col items-center md:items-start gap-2">
|
||||
<span className="text-[#999999] text-sm">
|
||||
© {new Date().getFullYear()} {COMPANY_INFO.shortName}. All rights reserved.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 中间:产品链接 */}
|
||||
<nav className="flex items-center gap-6" aria-label="产品导航">
|
||||
{productLinks.map((link) => (
|
||||
<StaticLink
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="text-[#999999] hover:text-[#C41E3A] text-sm transition-colors duration-200"
|
||||
>
|
||||
{link.label}
|
||||
</StaticLink>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* 右侧:联系方式 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href={`mailto:${COMPANY_INFO.email}`}
|
||||
className="flex items-center gap-2 text-[#999999] hover:text-[#C41E3A] text-sm transition-colors duration-200"
|
||||
>
|
||||
<FloatingElement amplitude={3} duration={3}>
|
||||
<Mail className="w-4 h-4" />
|
||||
</FloatingElement>
|
||||
<span className="hidden sm:inline">{COMPANY_INFO.email}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import Image from 'next/image';
|
||||
import { ArrowLeft, Phone } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RippleButton } from '@/lib/animations';
|
||||
|
||||
/**
|
||||
* 产品站专属 Header
|
||||
*
|
||||
* 与主站 Header 的区别:
|
||||
* - 无导航菜单(产品列表、关于我们等)
|
||||
* - 只保留 Logo + 返回主站按钮
|
||||
* - 始终浅色毛玻璃风格(适配产品页浅色主题)
|
||||
* - 更简洁的视觉层次,强化"独立产品站"感知
|
||||
*/
|
||||
function ProductHeaderContent() {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
className="fixed top-0 left-0 right-0 z-50 transition-all duration-300"
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.5, ease: [0.25, 0.46, 0.45, 0.94] }}
|
||||
>
|
||||
<div
|
||||
className={`transition-all duration-300 ${
|
||||
isScrolled
|
||||
? 'bg-white/90 backdrop-blur-xl border-b border-[#E5E5E5] shadow-sm'
|
||||
: 'bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<div className="container-wide">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Logo */}
|
||||
<StaticLink href="/" className="flex items-center gap-3 group">
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="Novalon"
|
||||
width={120}
|
||||
height={32}
|
||||
className="h-8 w-auto"
|
||||
priority
|
||||
/>
|
||||
</StaticLink>
|
||||
|
||||
{/* 立即咨询 CTA */}
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white px-5 py-2 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
|
||||
rippleColor="rgba(255, 255, 255, 0.3)"
|
||||
>
|
||||
<Phone className="w-4 h-4" />
|
||||
<span className="hidden md:inline">联系我们</span>
|
||||
</RippleButton>
|
||||
</div>
|
||||
|
||||
{/* 返回主站按钮 */}
|
||||
<StaticLink href="/">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-[#E5E5E5] text-[#5C5C5C] hover:text-[#1C1C1C] hover:bg-[#F5F5F5] hover:border-[#C41E3A]/30 transition-all duration-200 text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
返回主站
|
||||
</Button>
|
||||
</StaticLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.header>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductHeader() {
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
<ProductHeaderContent />
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
'use client';
|
||||
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Mail } from 'lucide-react';
|
||||
import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { FloatingElement, RippleButton } from '@/lib/animations';
|
||||
|
||||
/**
|
||||
* 服务站专属 Footer
|
||||
*
|
||||
* 与主站 Footer 的区别:
|
||||
* - 浅色背景(与服务页浅色主题统一)
|
||||
* - 服务 CTA 为主
|
||||
* - 不显示公众号/企业微信二维码
|
||||
* - 不显示 ICP/公安备案号
|
||||
* - 简洁的两栏布局:服务链接 + 联系方式
|
||||
*/
|
||||
|
||||
const serviceLinks = [
|
||||
{ label: '服务概览', href: '#features' },
|
||||
{ label: '面临挑战', href: '#challenges' },
|
||||
{ label: '服务流程', href: '#process' },
|
||||
{ label: '预期成果', href: '#outcomes' },
|
||||
];
|
||||
|
||||
export function ServiceFooter() {
|
||||
return (
|
||||
<footer className="bg-[#F8F8F8]" role="contentinfo">
|
||||
{/* 顶部装饰线 */}
|
||||
<div className="h-[2px] bg-gradient-to-r from-transparent via-[#C41E3A]/50 to-transparent" />
|
||||
|
||||
{/* CTA 区域 */}
|
||||
<div className="container-wide py-16">
|
||||
<div className="text-center max-w-2xl mx-auto">
|
||||
<h3 className="text-2xl md:text-3xl font-bold text-[#1C1C1C] mb-4">
|
||||
准备开始您的数字化转型之旅?
|
||||
</h3>
|
||||
<p className="text-[#5C5C5C] mb-8 text-lg">
|
||||
我们的专业团队将根据您的需求,提供量身定制的服务方案
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<StaticLink href="/contact">
|
||||
<RippleButton
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="px-8 py-3 bg-[#C41E3A] text-white rounded-lg font-medium text-base"
|
||||
>
|
||||
免费咨询
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
<StaticLink href="/services">
|
||||
<RippleButton
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="px-8 py-3 border border-[#E5E5E5] text-[#5C5C5C] hover:text-[#1C1C1C] hover:bg-white rounded-lg font-medium text-base"
|
||||
>
|
||||
查看全部服务
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部信息 */}
|
||||
<div className="border-t border-[#E5E5E5]">
|
||||
<div className="container-wide py-8">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
<div className="flex flex-col items-center md:items-start gap-2">
|
||||
<span className="text-[#999999] text-sm">
|
||||
© {new Date().getFullYear()} {COMPANY_INFO.shortName}. All rights reserved.
|
||||
</span>
|
||||
</div>
|
||||
<nav className="flex items-center gap-6" aria-label="服务导航">
|
||||
{serviceLinks.map((link) => (
|
||||
<StaticLink
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="text-[#999999] hover:text-[#C41E3A] text-sm transition-colors duration-200"
|
||||
>
|
||||
{link.label}
|
||||
</StaticLink>
|
||||
))}
|
||||
</nav>
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href={`mailto:${COMPANY_INFO.email}`}
|
||||
className="flex items-center gap-2 text-[#999999] hover:text-[#C41E3A] text-sm transition-colors duration-200"
|
||||
>
|
||||
<FloatingElement amplitude={3} duration={3}>
|
||||
<Mail className="w-4 h-4" />
|
||||
</FloatingElement>
|
||||
<span className="hidden sm:inline">{COMPANY_INFO.email}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import Image from 'next/image';
|
||||
import { ArrowLeft, Phone } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RippleButton } from '@/lib/animations';
|
||||
|
||||
/**
|
||||
* 服务站专属 Header
|
||||
*
|
||||
* 与主站 Header 的区别:
|
||||
* - 无导航菜单
|
||||
* - 只保留 Logo + 返回主站按钮
|
||||
* - 始终浅色毛玻璃风格
|
||||
* - 强化"独立服务站"感知
|
||||
*/
|
||||
function ServiceHeaderContent() {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
className="fixed top-0 left-0 right-0 z-50 transition-all duration-300"
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.5, ease: [0.25, 0.46, 0.45, 0.94] }}
|
||||
>
|
||||
<div
|
||||
className={`transition-all duration-300 ${
|
||||
isScrolled
|
||||
? 'bg-white/90 backdrop-blur-xl border-b border-[#E5E5E5] shadow-sm'
|
||||
: 'bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<div className="container-wide">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="flex items-center gap-3">
|
||||
<StaticLink href="/" className="flex items-center gap-3 group">
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="Novalon"
|
||||
width={120}
|
||||
height={32}
|
||||
className="h-8 w-auto"
|
||||
priority
|
||||
/>
|
||||
</StaticLink>
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white px-5 py-2 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
|
||||
rippleColor="rgba(255, 255, 255, 0.3)"
|
||||
>
|
||||
<Phone className="w-4 h-4" />
|
||||
<span className="hidden md:inline">立即咨询</span>
|
||||
</RippleButton>
|
||||
</div>
|
||||
<StaticLink href="/">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-[#E5E5E5] text-[#5C5C5C] hover:text-[#1C1C1C] hover:bg-[#F5F5F5] hover:border-[#C41E3A]/30 transition-all duration-200 text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
返回主站
|
||||
</Button>
|
||||
</StaticLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.header>
|
||||
);
|
||||
}
|
||||
|
||||
export function ServiceHeader() {
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
<ServiceHeaderContent />
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export { ProductHeroSection } from './product-hero-section';
|
||||
export { ProductOverviewSection } from './product-overview-section';
|
||||
export { ProductFeaturesSection } from './product-features-section';
|
||||
export { ProductBenefitsSection } from './product-benefits-section';
|
||||
export { ProductProcessSection } from './product-process-section';
|
||||
export { ProductSpecsSection } from './product-specs-section';
|
||||
export { ProductPricingSection } from './product-pricing-section';
|
||||
export { ProductCTASection } from './product-cta-section';
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
import { StaggerContainer, StaggerItem, InkCard, CountUp } from '@/lib/animations';
|
||||
import type { Product } from '@/lib/constants/products';
|
||||
|
||||
interface ProductBenefitsSectionProps {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
function extractNumber(text: string): { number: number; suffix: string } | null {
|
||||
const match = text.match(/(\d+)%/);
|
||||
if (match) {
|
||||
return { number: parseInt(match[1]!, 10), suffix: '%' };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function BenefitCard({ benefit }: { benefit: string }) {
|
||||
const numberInfo = extractNumber(benefit);
|
||||
|
||||
return (
|
||||
<StaggerItem>
|
||||
<InkCard
|
||||
className="p-6 md:p-8 bg-white rounded-2xl border border-[#E5E5E5] hover:border-[#C41E3A]/30 transition-colors"
|
||||
hoverScale={1.02}
|
||||
hoverShadow="0 20px 40px rgba(196, 30, 58, 0.08)"
|
||||
>
|
||||
{numberInfo && (
|
||||
<div className="mb-4">
|
||||
<span className="text-4xl md:text-5xl font-bold bg-gradient-to-r from-[#C41E3A] to-[#E85D75] bg-clip-text text-transparent">
|
||||
<CountUp
|
||||
end={numberInfo.number}
|
||||
suffix={numberInfo.suffix}
|
||||
duration={2000}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">{benefit}</p>
|
||||
</InkCard>
|
||||
</StaggerItem>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductBenefitsSection({ product }: ProductBenefitsSectionProps) {
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<section id="benefits" ref={ref} className="relative py-20 md:py-28 bg-white overflow-hidden">
|
||||
<div className="container-wide">
|
||||
<ScrollReveal variants={slideInLeftVariants} className="mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C] text-left">
|
||||
产品优势
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<StaggerContainer className="grid md:grid-cols-2 gap-6 max-w-5xl">
|
||||
{product.benefits.map((benefit, index) => (
|
||||
<BenefitCard key={index} benefit={benefit} />
|
||||
))}
|
||||
</StaggerContainer>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
'use client';
|
||||
|
||||
import { InkReveal, FadeUp, FloatingElement, RippleButton } from '@/lib/animations';
|
||||
|
||||
export function ProductCTASection() {
|
||||
return (
|
||||
<section className="relative py-24 md:py-32 bg-gradient-to-r from-[#C41E3A] to-[#E85D75] overflow-hidden">
|
||||
{/* 右上角装饰圆形 */}
|
||||
<FloatingElement amplitude={8} duration={5} delay={0.5} className="absolute -top-20 -right-20 pointer-events-none">
|
||||
<div className="w-[280px] h-[280px] bg-white/10 rounded-full" />
|
||||
</FloatingElement>
|
||||
|
||||
{/* 左下角装饰圆形 */}
|
||||
<FloatingElement amplitude={6} duration={4} delay={1} className="absolute -bottom-16 -left-16 pointer-events-none">
|
||||
<div className="w-[220px] h-[220px] bg-white/10 rounded-full" />
|
||||
</FloatingElement>
|
||||
|
||||
<div className="container-wide">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<InkReveal delay={0}>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
|
||||
产品即将上市
|
||||
</h2>
|
||||
</InkReveal>
|
||||
|
||||
<FadeUp delay={0.15}>
|
||||
<p className="text-lg text-white/90 mb-10">
|
||||
我们正在全力研发中,敬请期待。如有合作意向,欢迎联系我们。
|
||||
</p>
|
||||
</FadeUp>
|
||||
|
||||
<FadeUp delay={0.3}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="bg-white text-[#C41E3A] px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center w-full sm:w-auto"
|
||||
>
|
||||
联系我们
|
||||
</RippleButton>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
'use client';
|
||||
|
||||
import { useRef, Fragment } from 'react';
|
||||
import { InkReveal, FadeUp, InkCard, PulseElement } from '@/lib/animations';
|
||||
import { RippleButton } from '@/lib/animations';
|
||||
import { ScrollReveal, inkRevealVariants, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
import type { Product } from '@/lib/constants/products';
|
||||
|
||||
interface ProductFeaturesSectionProps {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
function FeatureItem({
|
||||
feature,
|
||||
index,
|
||||
}: {
|
||||
feature: string;
|
||||
index: number;
|
||||
}) {
|
||||
// 解析功能标题和描述(中文冒号分隔)
|
||||
const colonIndex = feature.indexOf(':');
|
||||
const title = colonIndex > -1 ? feature.substring(0, colonIndex) : feature;
|
||||
const description = colonIndex > -1 ? feature.substring(colonIndex + 1) : '';
|
||||
|
||||
// 编号格式化
|
||||
const number = String(index + 1).padStart(2, '0');
|
||||
|
||||
return (
|
||||
<div className="min-h-[50vh] flex items-center py-16">
|
||||
<div className="container-wide">
|
||||
<div className="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 items-center">
|
||||
{/* 左侧:编号和文字 */}
|
||||
<div className="order-2 md:order-1">
|
||||
{/* 编号 - InkReveal 模糊揭示 */}
|
||||
<InkReveal delay={0}>
|
||||
<span className="block text-7xl md:text-8xl font-mono text-[#C41E3A]/10 mb-4">
|
||||
{number}
|
||||
</span>
|
||||
</InkReveal>
|
||||
|
||||
{/* 功能标题 - ScrollReveal + slideInLeft */}
|
||||
<ScrollReveal variants={slideInLeftVariants} delay={0.1}>
|
||||
<h3 className="text-2xl md:text-3xl font-bold text-[#1C1C1C] mb-4">
|
||||
{title}
|
||||
</h3>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* 功能描述 - FadeUp */}
|
||||
{description && (
|
||||
<FadeUp delay={0.2}>
|
||||
<p className="text-lg text-[#5C5C5C] leading-relaxed">
|
||||
{description}
|
||||
</p>
|
||||
</FadeUp>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 右侧:InkCard 弹簧物理悬浮 + PulseElement 脉冲同心圆 */}
|
||||
<div className="order-1 md:order-2">
|
||||
<InkCard
|
||||
className="aspect-square rounded-2xl bg-white border border-[#E5E5E5] shadow-lg flex items-center justify-center"
|
||||
hoverScale={1.03}
|
||||
hoverShadow="0 25px 50px rgba(196, 30, 58, 0.15)"
|
||||
>
|
||||
<PulseElement scale={1.08} duration={2.5}>
|
||||
<div className="w-24 h-24 rounded-full bg-[#C41E3A]/10 flex items-center justify-center">
|
||||
<div className="w-12 h-12 rounded-full bg-[#C41E3A]/20" />
|
||||
</div>
|
||||
</PulseElement>
|
||||
</InkCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductFeaturesSection({ product }: ProductFeaturesSectionProps) {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<section id="features" ref={sectionRef} className="relative bg-[#F8F8F8] overflow-hidden">
|
||||
{/* 标题 - ScrollReveal + inkRevealVariants 模糊揭示 */}
|
||||
<div className="pt-32 md:pt-40 pb-16">
|
||||
<div className="container-wide">
|
||||
<ScrollReveal variants={inkRevealVariants} delay={0}>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C] text-center mb-4">
|
||||
核心功能
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
<ScrollReveal variants={inkRevealVariants} delay={0.1}>
|
||||
<p className="text-lg text-[#5C5C5C] text-center max-w-2xl mx-auto">
|
||||
全方位覆盖企业核心业务场景
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 功能列表 */}
|
||||
{product.features.map((feature, index) => (
|
||||
<Fragment key={index}>
|
||||
<FeatureItem feature={feature} index={index} />
|
||||
{(index === 1 || index === 3) && (
|
||||
<div className="py-8">
|
||||
<div className="container-wide">
|
||||
<div className="max-w-2xl mx-auto bg-white rounded-2xl border border-[#E5E5E5] p-6 md:p-8 text-center shadow-sm">
|
||||
<p className="text-[#1C1C1C] font-semibold text-lg mb-4">
|
||||
想了解更多功能细节?
|
||||
</p>
|
||||
<p className="text-[#5C5C5C] mb-6">
|
||||
预约一次 30 分钟在线演示,了解产品如何适配您的业务
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white px-6 py-3 rounded-lg font-semibold inline-flex items-center justify-center"
|
||||
>
|
||||
预约演示
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(196, 30, 58, 0.2)"
|
||||
className="border border-[#E5E5E5] text-[#5C5C5C] hover:text-[#C41E3A] hover:border-[#C41E3A]/30 px-6 py-3 rounded-lg font-semibold inline-flex items-center justify-center"
|
||||
>
|
||||
获取方案
|
||||
</RippleButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { InkReveal, SealStamp, FloatingElement } from '@/lib/animations';
|
||||
import type { Product } from '@/lib/constants/products';
|
||||
|
||||
const InkBackground = dynamic(
|
||||
() => import('@/components/ui/ink-decoration').then(mod => ({ default: mod.InkBackground })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const DataParticleFlow = dynamic(
|
||||
() => import('@/components/effects/data-particle-flow').then(mod => ({ default: mod.DataParticleFlow })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
interface ProductHeroSectionProps {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
export function ProductHeroSection({ product }: ProductHeroSectionProps) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry?.isIntersecting) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={sectionRef}
|
||||
className="relative min-h-screen flex items-center justify-center overflow-hidden bg-gradient-to-b from-white to-[#F8F8F8]"
|
||||
>
|
||||
{/* 背景特效 */}
|
||||
<InkBackground />
|
||||
<DataParticleFlow
|
||||
particleCount={80}
|
||||
color="#C41E3A"
|
||||
intensity="subtle"
|
||||
shape="square"
|
||||
effect="pulse"
|
||||
/>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="container-wide relative z-10 py-32 md:py-40">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
{/* 分类标签 - 印章按压效果 */}
|
||||
<SealStamp
|
||||
delay={0.1}
|
||||
className="inline-block px-4 py-2 bg-[#C41E3A]/20 rounded-full text-[#C41E3A] text-sm mb-6"
|
||||
>
|
||||
即将上市
|
||||
</SealStamp>
|
||||
|
||||
{/* 产品名称 - 模糊揭示入场 */}
|
||||
<InkReveal delay={0.2}>
|
||||
<h1 className="text-4xl md:text-6xl lg:text-7xl font-bold text-[#1C1C1C] mb-6">
|
||||
{product.title}
|
||||
</h1>
|
||||
</InkReveal>
|
||||
|
||||
{/* 价值主张 - 模糊揭示入场 */}
|
||||
<InkReveal delay={0.4}>
|
||||
<p className="text-lg md:text-xl text-[#5C5C5C] leading-relaxed mb-10 max-w-2xl mx-auto">
|
||||
{product.description}
|
||||
</p>
|
||||
</InkReveal>
|
||||
|
||||
{/* CTA 按钮 */}
|
||||
<InkReveal delay={0.6}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<span className="border-2 border-[#D9D9D9] text-[#999999] px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center cursor-not-allowed select-none">
|
||||
敬请期待
|
||||
</span>
|
||||
</div>
|
||||
</InkReveal>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 滚动指示器 - 浮动元素包裹 */}
|
||||
<FloatingElement
|
||||
amplitude={10}
|
||||
duration={1.5}
|
||||
delay={1}
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={isVisible ? { opacity: 1 } : {}}
|
||||
transition={{ duration: 0.6, delay: 1 }}
|
||||
>
|
||||
<div className="text-[#999999]">
|
||||
<ChevronDown className="w-8 h-8" />
|
||||
</div>
|
||||
</motion.div>
|
||||
</FloatingElement>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import { InkReveal } from '@/lib/animations';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
import type { Product } from '@/lib/constants/products';
|
||||
|
||||
interface ProductOverviewSectionProps {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
export function ProductOverviewSection({ product }: ProductOverviewSectionProps) {
|
||||
return (
|
||||
<section id="overview" className="relative py-16 md:py-20 bg-white overflow-hidden">
|
||||
<div className="container-wide">
|
||||
<div className="max-w-3xl">
|
||||
{/* 标题 - 左对齐,slideInLeft 入场 */}
|
||||
<ScrollReveal variants={slideInLeftVariants} delay={0}>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C] mb-4">
|
||||
产品概述
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* 朱砂红装饰线 - InkReveal 入场 */}
|
||||
<InkReveal delay={0.2}>
|
||||
<div className="w-16 h-1 bg-[#C41E3A] rounded-full mb-8" />
|
||||
</InkReveal>
|
||||
|
||||
{/* 概述文字 - InkReveal 包裹整段,替代 TextReveal */}
|
||||
<InkReveal delay={0.3}>
|
||||
<p className="text-lg md:text-xl text-[#5C5C5C] leading-relaxed">
|
||||
{product.overview}
|
||||
</p>
|
||||
</InkReveal>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
import { Check } from 'lucide-react';
|
||||
import { InkCard } from '@/components/ui/animated-card';
|
||||
import { ScrollReveal, inkRevealVariants } from '@/components/ui/scroll-animations';
|
||||
import { FloatingElement, PulseElement, RippleButton } from '@/lib/animations';
|
||||
import type { Product } from '@/lib/constants/products';
|
||||
|
||||
interface ProductPricingSectionProps {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
const pricingFeatures = {
|
||||
base: ['核心功能模块', '私有化部署', '标准技术支持', '系统培训'],
|
||||
standard: ['全部功能模块', '私有化部署', '优先技术支持', '系统培训', '数据迁移服务', '定制化配置'],
|
||||
enterprise: ['全部功能模块', '私有化部署', '专属技术支持', '系统培训', '数据迁移服务', '定制化开发', 'SLA服务保障', '驻场支持'],
|
||||
};
|
||||
|
||||
function PricingCard({
|
||||
name,
|
||||
description,
|
||||
features,
|
||||
isRecommended = false,
|
||||
ctaText = '获取方案',
|
||||
}: {
|
||||
name: string;
|
||||
description: string;
|
||||
features: string[];
|
||||
isRecommended?: boolean;
|
||||
ctaText?: string;
|
||||
}) {
|
||||
const cardContent = (
|
||||
<div
|
||||
className={`
|
||||
relative p-6 md:p-8 rounded-2xl
|
||||
${isRecommended
|
||||
? 'bg-white border-2 border-[#C41E3A] text-[#1C1C1C]'
|
||||
: 'bg-white border border-[#E5E5E5]'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{isRecommended && (
|
||||
<PulseElement scale={1.08} duration={2} className="absolute -top-3 left-1/2 -translate-x-1/2 z-10">
|
||||
<div className="bg-[#C41E3A] text-white px-4 py-1 rounded-full text-sm font-semibold whitespace-nowrap">
|
||||
推荐
|
||||
</div>
|
||||
</PulseElement>
|
||||
)}
|
||||
|
||||
<h3 className="text-xl font-semibold mb-2 text-[#1C1C1C]">{name}</h3>
|
||||
|
||||
<p className={`text-sm mb-6 text-[#5C5C5C]`}>
|
||||
{description}
|
||||
</p>
|
||||
|
||||
<ul className="space-y-3 mb-8">
|
||||
{features.map((feature, index) => (
|
||||
<li key={index} className="flex items-center gap-2">
|
||||
<Check className="w-5 h-5 text-[#C41E3A]" />
|
||||
<span className="text-[#5C5C5C]">
|
||||
{feature}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className={`
|
||||
block w-full py-3 rounded-lg font-semibold text-center
|
||||
${isRecommended
|
||||
? 'bg-[#C41E3A] text-white'
|
||||
: 'border border-[#E5E5E5] text-[#5C5C5C] hover:text-[#C41E3A] hover:border-[#C41E3A]/30 bg-white'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{ctaText}
|
||||
</RippleButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isRecommended) {
|
||||
return (
|
||||
<FloatingElement amplitude={5} duration={4} className="my-0 md:-my-4">
|
||||
<InkCard hoverScale={1.03} className="h-full md:scale-105 md:z-10">
|
||||
{cardContent}
|
||||
</InkCard>
|
||||
</FloatingElement>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InkCard hoverScale={1.02} className="h-full">
|
||||
{cardContent}
|
||||
</InkCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductPricingSection({ product }: ProductPricingSectionProps) {
|
||||
return (
|
||||
<section id="pricing" className="relative py-28 md:py-36 bg-[#F5F5F5] overflow-hidden">
|
||||
<div className="container-wide">
|
||||
<ScrollReveal variants={inkRevealVariants} className="mb-4">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C] text-center">
|
||||
版本对比
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal variants={inkRevealVariants} className="mb-12">
|
||||
<p className="text-center text-[#5C5C5C] text-lg">
|
||||
根据企业规模和需求,选择最适合的部署方案
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
<PricingCard
|
||||
name={product.pricing.base}
|
||||
description="适合中小企业基础需求"
|
||||
features={pricingFeatures.base}
|
||||
ctaText="了解标准版"
|
||||
/>
|
||||
<PricingCard
|
||||
name={product.pricing.standard}
|
||||
description="适合中型企业深度应用"
|
||||
features={pricingFeatures.standard}
|
||||
isRecommended
|
||||
ctaText="立即获取方案"
|
||||
/>
|
||||
<PricingCard
|
||||
name={product.pricing.enterprise}
|
||||
description="适合大型企业定制化需求"
|
||||
features={pricingFeatures.enterprise}
|
||||
ctaText="联系定制"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { ScrollReveal, inkRevealVariants } from '@/components/ui/scroll-animations';
|
||||
import { StaggerContainer, StaggerItem, SealStamp, FadeUp } from '@/lib/animations';
|
||||
import type { Product } from '@/lib/constants/products';
|
||||
|
||||
interface ProductProcessSectionProps {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
function ProcessStep({
|
||||
step,
|
||||
index,
|
||||
total,
|
||||
}: {
|
||||
step: string;
|
||||
index: number;
|
||||
total: number;
|
||||
}) {
|
||||
const colonIndex = step.indexOf(':');
|
||||
const title = colonIndex > -1 ? step.substring(0, colonIndex) : step;
|
||||
const description = colonIndex > -1 ? step.substring(colonIndex + 1) : '';
|
||||
|
||||
return (
|
||||
<StaggerItem className="flex items-start gap-6">
|
||||
<div className="flex-shrink-0">
|
||||
<SealStamp delay={index * 0.15}>
|
||||
<div className="w-12 h-12 rounded-full bg-[#C41E3A] flex items-center justify-center text-white font-bold text-lg">
|
||||
{index + 1}
|
||||
</div>
|
||||
</SealStamp>
|
||||
{index < total - 1 && (
|
||||
<div className="w-0.5 h-16 bg-gradient-to-b from-[#C41E3A]/40 to-[#C41E3A]/10 ml-6 mt-2" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FadeUp delay={index * 0.1} className="flex-1 pb-8">
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-2">{title}</h3>
|
||||
{description && (
|
||||
<p className="text-[#5C5C5C] leading-relaxed">{description}</p>
|
||||
)}
|
||||
</FadeUp>
|
||||
</StaggerItem>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductProcessSection({ product }: ProductProcessSectionProps) {
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<section id="process" ref={ref} className="relative py-24 md:py-32 bg-white overflow-hidden">
|
||||
<div className="container-wide">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<ScrollReveal variants={inkRevealVariants} className="mb-12 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">
|
||||
实施流程
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<StaggerContainer className="mt-8" staggerDelay={0.15}>
|
||||
{product.process.map((step, index) => (
|
||||
<ProcessStep
|
||||
key={index}
|
||||
step={step}
|
||||
index={index}
|
||||
total={product.process.length}
|
||||
/>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
import { StaggerContainer, StaggerItem, InkCard } from '@/lib/animations';
|
||||
import type { Product } from '@/lib/constants/products';
|
||||
|
||||
interface ProductSpecsSectionProps {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
export function ProductSpecsSection({ product }: ProductSpecsSectionProps) {
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<section id="specs" ref={ref} className="relative py-20 md:py-28 bg-[#FAFAFA] overflow-hidden">
|
||||
<div className="container-wide">
|
||||
<ScrollReveal variants={slideInLeftVariants} className="mb-12 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">
|
||||
技术规格
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<StaggerContainer className="grid md:grid-cols-2 gap-4 max-w-3xl mx-auto">
|
||||
{product.specs.map((spec, index) => (
|
||||
<StaggerItem key={index}>
|
||||
<InkCard
|
||||
className="flex items-center gap-4 p-4 bg-white rounded-lg border border-[#E5E5E5] hover:border-[#C41E3A]/30 transition-colors"
|
||||
hoverScale={1.02}
|
||||
hoverShadow="0 12px 24px rgba(196, 30, 58, 0.06)"
|
||||
>
|
||||
<div className="w-1 h-8 bg-[#C41E3A] rounded-full flex-shrink-0" />
|
||||
<span className="text-[#1C1C1C]">{spec}</span>
|
||||
</InkCard>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -4,12 +4,17 @@ import { motion } from 'framer-motion';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { COMPANY_INFO, STATS } from '@/lib/constants';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { RippleButton } from '@/lib/animations';
|
||||
import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { ArrowRight, CheckCircle2 } from 'lucide-react';
|
||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||
|
||||
const VALUES = [
|
||||
{ title: '务实', description: '不追逐风口,只做真正为客户创造价值的事。' },
|
||||
{ title: '陪伴', description: '交付只是开始,长期陪跑才是我们的承诺。' },
|
||||
{ title: '专业', description: '用扎实的工程能力和行业经验赢得信任。' },
|
||||
];
|
||||
|
||||
export function AboutSection() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: '-100px' });
|
||||
@@ -17,14 +22,17 @@ export function AboutSection() {
|
||||
|
||||
return (
|
||||
<section id="about" role="region" aria-labelledby="about-heading" className="py-24 bg-[#FAFAFA] relative" ref={ref}>
|
||||
{/* 网格背景 */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(rgba(28,28,28,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(28,28,28,0.02)_1px,transparent_1px)] bg-size-[40px_40px]" />
|
||||
|
||||
<div className="container-wide relative z-10">
|
||||
<motion.div
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.6 }}
|
||||
className="max-w-4xl mx-auto"
|
||||
>
|
||||
{/* 标题 */}
|
||||
<div className="text-center mb-12">
|
||||
<h2 id="about-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
关于 <span className="tracking-tight font-brand text-[#C41E3A]" style={{ fontWeight: 'normal', WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale', textRendering: 'optimizeLegibility' }}>{COMPANY_INFO.shortName}</span>
|
||||
@@ -34,43 +42,55 @@ export function AboutSection() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-2xl p-8 mb-12 border border-[#E5E5E5]">
|
||||
{/* 品牌理念引用 */}
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.6, delay: 0.1 }}
|
||||
className="bg-white rounded-2xl p-8 mb-12 border border-[#E5E5E5]"
|
||||
>
|
||||
<p className="text-lg text-[#5C5C5C] leading-relaxed text-center mb-6">
|
||||
“企业需要的,不是一个高高在上的‘专家’,也不是一个做完就跑的‘卖家’,而是一个能坐下来、一起想办法的同行者。”
|
||||
</p>
|
||||
<p className="text-[#1C1C1C] font-medium text-center">
|
||||
我们只做一件事:成为您数字化转型路上,信得过的成长伙伴。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.6, delay: 0.2 }}
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-6 mb-12"
|
||||
>
|
||||
{STATS.map((stat, idx) => (
|
||||
<Card key={idx} className="text-center border-[#E5E5E5]">
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-3xl sm:text-4xl font-bold text-[#C41E3A] mb-2">{stat.value}</div>
|
||||
<div className="text-sm text-[#5C5C5C]">{stat.label}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* 核心价值观 */}
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.6, delay: 0.3 }}
|
||||
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.6, delay: 0.15 }}
|
||||
className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-16"
|
||||
>
|
||||
{VALUES.map((value) => (
|
||||
<div
|
||||
key={value.title}
|
||||
className="bg-white rounded-xl p-6 border border-[#E5E5E5] text-center"
|
||||
>
|
||||
<div className="w-10 h-10 bg-[#C41E3A]/10 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-[#C41E3A]" />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-[#1C1C1C] mb-2">{value.title}</h3>
|
||||
<p className="text-sm text-[#5C5C5C] leading-relaxed">{value.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* CTA */}
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.6, delay: 0.5 }}
|
||||
className="text-center"
|
||||
>
|
||||
<Button size="lg" variant="outline" className="group" asChild>
|
||||
<StaticLink href="/about">
|
||||
<StaticLink href="/about">
|
||||
<RippleButton className="inline-flex items-center gap-2 px-6 py-2.5 border border-[#E5E5E5] rounded-lg text-sm font-medium text-[#1C1C1C] hover:border-[#C41E3A] hover:text-[#C41E3A] transition-colors">
|
||||
了解更多关于我们
|
||||
<ArrowRight className="ml-2 w-4 h-4 transition-transform group-hover:translate-x-1" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@@ -9,17 +9,34 @@ import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { TouchSwipe } from '@/components/ui/touch-swipe';
|
||||
import { CASES } from '@/lib/constants';
|
||||
import { ArrowRight, Building2 } from 'lucide-react';
|
||||
import { ArrowRight, Building2, Hotel, Factory, Landmark, Sprout, TrendingUp } from 'lucide-react';
|
||||
|
||||
// 行业图标映射,为不同行业提供差异化视觉
|
||||
const industryIconMap: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
'酒店管理': Hotel,
|
||||
'制造业': Factory,
|
||||
'政务服务': Landmark,
|
||||
'智慧农业': Sprout,
|
||||
};
|
||||
|
||||
const industryColorMap: Record<string, { bg: string; icon: string; badge: string }> = {
|
||||
'酒店管理': { bg: 'from-amber-50 to-orange-50', icon: 'text-amber-600', badge: 'bg-amber-50 text-amber-700' },
|
||||
'制造业': { bg: 'from-blue-50 to-indigo-50', icon: 'text-blue-600', badge: 'bg-blue-50 text-blue-700' },
|
||||
'政务服务': { bg: 'from-emerald-50 to-teal-50', icon: 'text-emerald-600', badge: 'bg-emerald-50 text-emerald-700' },
|
||||
'智慧农业': { bg: 'from-green-50 to-lime-50', icon: 'text-green-600', badge: 'bg-green-50 text-green-700' },
|
||||
};
|
||||
|
||||
const defaultColors = { bg: 'from-[#F5F5F5] to-[#EDEDED]', icon: 'text-[#C41E3A]/30', badge: 'bg-white/90 text-[#1C1C1C]' };
|
||||
|
||||
export function CasesSection() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: '-100px' });
|
||||
|
||||
return (
|
||||
<section id="cases" role="region" aria-labelledby="cases-heading" className="py-24 bg-white relative overflow-hidden" ref={ref}>
|
||||
<section id="cases" role="region" aria-labelledby="cases-heading" className="py-24 bg-[#F8F8F8] relative overflow-hidden" ref={ref}>
|
||||
<div className="absolute top-1/3 left-0 w-[400px] h-[400px] bg-[rgba(196,30,58,0.03)] rounded-full blur-3xl" />
|
||||
<div className="absolute top-1/3 right-0 w-[300px] h-[300px] bg-[rgba(196,30,58,0.02)] rounded-full blur-3xl" />
|
||||
|
||||
|
||||
<div className="container-wide relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@@ -36,53 +53,81 @@ export function CasesSection() {
|
||||
</motion.div>
|
||||
|
||||
<TouchSwipe
|
||||
onSwipeLeft={() => {
|
||||
}}
|
||||
onSwipeRight={() => {
|
||||
}}
|
||||
onSwipeLeft={() => {}}
|
||||
onSwipeRight={() => {}}
|
||||
className="md:hidden"
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{CASES.map((caseItem, index) => (
|
||||
<motion.div
|
||||
key={caseItem.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: 0.1 + index * 0.1 }}
|
||||
>
|
||||
<StaticLink href={`/cases/${caseItem.id}`}>
|
||||
<Card className="h-full group cursor-pointer border-[#E5E5E5] hover:border-[#C41E3A] transition-colors overflow-hidden">
|
||||
<div className="relative h-40 bg-gradient-to-br from-[#F5F5F5] to-[#E5E5E5] flex items-center justify-center">
|
||||
<Building2 className="w-16 h-16 text-[#C41E3A]/20 group-hover:scale-110 transition-transform duration-300" />
|
||||
<div className="absolute top-4 right-4">
|
||||
<Badge className="bg-white/90 text-[#1C1C1C] hover:bg-white">
|
||||
{caseItem.industry}
|
||||
</Badge>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{CASES.map((caseItem, index) => {
|
||||
const IndustryIcon = industryIconMap[caseItem.industry] || Building2;
|
||||
const colors = industryColorMap[caseItem.industry] || defaultColors;
|
||||
return (
|
||||
<motion.div
|
||||
key={caseItem.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: 0.1 + index * 0.1 }}
|
||||
>
|
||||
<StaticLink href={`/cases/${caseItem.id}`}>
|
||||
<Card className="h-full group cursor-pointer border-[#E5E5E5] hover:border-[#C41E3A]/40 hover:shadow-lg hover:-translate-y-1 transition-all duration-300 overflow-hidden">
|
||||
{/* 行业图标区域 - 使用差异化配色 */}
|
||||
<div className={`relative h-44 bg-gradient-to-br ${colors.bg} flex items-center justify-center overflow-hidden`}>
|
||||
{/* 装饰性几何元素 */}
|
||||
<div className="absolute top-3 right-3 w-16 h-16 rounded-full border border-current opacity-10" />
|
||||
<div className="absolute bottom-3 left-3 w-10 h-10 rounded-lg border border-current opacity-10 rotate-12" />
|
||||
|
||||
<IndustryIcon className={`w-16 h-16 ${colors.icon} opacity-40 group-hover:scale-110 group-hover:opacity-60 transition-all duration-500`} />
|
||||
|
||||
<div className="absolute top-4 right-4">
|
||||
<Badge className={`${colors.badge} hover:opacity-90 text-xs font-medium`}>
|
||||
{caseItem.industry}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* 成果数据标签 */}
|
||||
{caseItem.results && caseItem.results.length > 0 && (
|
||||
<div className="absolute bottom-3 left-3 right-3 flex gap-1.5">
|
||||
{caseItem.results.slice(0, 2).map((result, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 bg-white/80 backdrop-blur-sm rounded-full text-[#1C1C1C] font-medium"
|
||||
>
|
||||
<TrendingUp className="w-2.5 h-2.5 text-[#C41E3A]" />
|
||||
{result.value}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Building2 className="w-4 h-4 text-[#C41E3A]" />
|
||||
<span className="text-sm text-[#5C5C5C]">{caseItem.client}</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors">
|
||||
{caseItem.title}
|
||||
</h3>
|
||||
<p className="text-[#5C5C5C] text-sm line-clamp-2 mb-4">
|
||||
{caseItem.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full shrink-0" />
|
||||
<span className="text-xs text-[#5C5C5C] truncate">{caseItem.client}</span>
|
||||
</div>
|
||||
<h3 className="text-base font-semibold text-[#1C1C1C] mb-2 group-hover:text-[#C41E3A] transition-colors line-clamp-2">
|
||||
{caseItem.title}
|
||||
</h3>
|
||||
<p className="text-[#5C5C5C] text-sm line-clamp-2 mb-3">
|
||||
{caseItem.description}
|
||||
</p>
|
||||
<div className="flex items-center text-[#C41E3A] text-xs font-medium opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
查看详情
|
||||
<ArrowRight className="ml-1 w-3 h-3" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TouchSwipe>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
className="text-center mt-12"
|
||||
>
|
||||
<Button variant="outline" size="lg" className="group" asChild>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { motion } from 'framer-motion';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { RippleButton } from '@/components/ui/ripple-button';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { MagneticButton, BlurReveal, CounterWithEffect } from '@/lib/animations';
|
||||
import { MagneticButton, BlurReveal, CountUp, InkReveal } from '@/lib/animations';
|
||||
import { COMPANY_INFO, STATS } from '@/lib/constants';
|
||||
import { ArrowRight, Shield, Zap, Award } from 'lucide-react';
|
||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||
@@ -28,7 +28,7 @@ function scrollTo(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLButtonElement>, id: string) {
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLButtonElement | HTMLAnchorElement>, id: string) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
scrollTo(id);
|
||||
@@ -52,25 +52,25 @@ export function HeroContent({ isVisible }: HeroContentProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function HeroTitle({ isVisible }: HeroContentProps) {
|
||||
export function HeroTitle(_props: HeroContentProps) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
|
||||
return (
|
||||
<motion.h1
|
||||
id="hero-heading"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={isVisible ? { opacity: 1, y: 0 } : {}}
|
||||
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.6, delay: 0.1 }}
|
||||
className="text-5xl sm:text-6xl lg:text-7xl tracking-tight mb-6 font-brand"
|
||||
style={{
|
||||
fontWeight: 'normal',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
MozOsxFontSmoothing: 'grayscale',
|
||||
textRendering: 'optimizeLegibility'
|
||||
}}
|
||||
>
|
||||
{COMPANY_INFO.shortName}
|
||||
</motion.h1>
|
||||
<InkReveal delay={0.1}>
|
||||
<h1
|
||||
id="hero-heading"
|
||||
className="text-5xl sm:text-6xl lg:text-7xl tracking-tight mb-6 font-brand"
|
||||
style={{
|
||||
fontWeight: 'normal',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
MozOsxFontSmoothing: 'grayscale',
|
||||
textRendering: 'optimizeLegibility',
|
||||
...(shouldReduceMotion ? { opacity: 1, filter: 'none', transform: 'none' } : {}),
|
||||
}}
|
||||
>
|
||||
{COMPANY_INFO.shortName}
|
||||
</h1>
|
||||
</InkReveal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -221,11 +221,11 @@ function HeroStatItem({ stat, index, shouldAnimate, shouldReduceMotion }: {
|
||||
>
|
||||
<div className="text-4xl sm:text-5xl font-bold text-[#C41E3A] mb-3">
|
||||
{shouldAnimate ? (
|
||||
<CounterWithEffect
|
||||
end={numericValue}
|
||||
suffix={suffix}
|
||||
effect="bounce"
|
||||
<CountUp
|
||||
end={numericValue}
|
||||
suffix={suffix}
|
||||
duration={2000}
|
||||
className="text-4xl sm:text-5xl font-bold text-[#C41E3A]"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-[#CBD5E0]">0{suffix}</span>
|
||||
|
||||
@@ -85,7 +85,7 @@ jest.mock('@/lib/animations', () => ({
|
||||
jest.mock('@/lib/constants', () => ({
|
||||
COMPANY_INFO: {
|
||||
name: '四川睿新致远科技有限公司',
|
||||
shortName: '睿新致遠',
|
||||
shortName: '睿新致远',
|
||||
description: '以智慧连接数字趋势,以伙伴身份陪您成长',
|
||||
},
|
||||
STATS: [
|
||||
@@ -98,7 +98,7 @@ jest.mock('@/lib/constants', () => ({
|
||||
|
||||
jest.mock('./hero-section-atoms', () => ({
|
||||
HeroContent: () => <div>智连未来,成长伙伴</div>,
|
||||
HeroTitle: () => <h1>睿新致遠</h1>,
|
||||
HeroTitle: () => <h1>睿新致远</h1>,
|
||||
HeroDescription: () => <p>企业数字化转型服务商</p>,
|
||||
HeroButtons: () => <div><button>立即咨询</button><button>了解更多</button></div>,
|
||||
HeroFeatures: () => <div><span>安全可靠</span><span>高效便捷</span><span>专业服务</span></div>,
|
||||
@@ -129,7 +129,7 @@ describe('HeroSection', () => {
|
||||
|
||||
it('should render company name', () => {
|
||||
render(<HeroSection heroStats={<HeroStats />} />);
|
||||
expect(screen.getByText('睿新致遠')).toBeInTheDocument();
|
||||
expect(screen.getByText('睿新致远')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render features', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { STATS } from '@/lib/constants';
|
||||
|
||||
export function HeroStatsSSR() {
|
||||
return (
|
||||
<div className="pt-16 border-t border-[#E2E8F0]">
|
||||
<div id="stats-section" className="pt-16 border-t border-[#E2E8F0]">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-12">
|
||||
{STATS.map((stat) => (
|
||||
<div
|
||||
|
||||
@@ -37,7 +37,6 @@ export function HomeSolutionsSection() {
|
||||
|
||||
return (
|
||||
<section id="solutions" role="region" aria-labelledby="solutions-heading" className="py-24 bg-[#F5F7FA] relative overflow-hidden" ref={ref}>
|
||||
<div className="absolute top-1/2 right-0 w-[400px] h-[400px] bg-[rgba(196,30,58,0.02)] rounded-full blur-3xl" />
|
||||
<div className="container-wide relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
||||
@@ -5,7 +5,8 @@ import { useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RippleButton } from '@/lib/animations';
|
||||
import { InkCard } from '@/lib/animations';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ArrowRight, Check, TrendingUp } from 'lucide-react';
|
||||
import { PRODUCTS } from '@/lib/constants';
|
||||
@@ -23,8 +24,9 @@ export function ProductsSection() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
className="text-left max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<div className="w-16 h-1 bg-[#C41E3A] rounded-full mb-6" />
|
||||
<h2 id="products-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
我们的<span className="text-[#C41E3A] font-calligraphy">产品</span>
|
||||
</h2>
|
||||
@@ -35,15 +37,13 @@ export function ProductsSection() {
|
||||
|
||||
{PRODUCTS.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{PRODUCTS.map((product, idx) => (
|
||||
<motion.div
|
||||
{PRODUCTS.map((product) => (
|
||||
<InkCard
|
||||
key={product.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: 0.1 + idx * 0.1 }}
|
||||
className="group cursor-pointer rounded-xl border border-[#E5E5E5] bg-white p-0 overflow-hidden hover:border-[#C41E3A] transition-colors"
|
||||
>
|
||||
<StaticLink href={`/products/${product.id}`}>
|
||||
<Card className="h-full flex flex-col group cursor-pointer border-[#E5E5E5] hover:border-[#1C1C1C] transition-colors">
|
||||
<Card className="h-full flex flex-col border-0 shadow-none bg-transparent">
|
||||
<CardHeader>
|
||||
<Badge variant="secondary" className="w-fit mb-3">
|
||||
{product.category}
|
||||
@@ -85,14 +85,14 @@ export function ProductsSection() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" className="w-full mt-auto group-hover:bg-[#A01830] group-hover:text-white group-hover:border-[#A01830] transition-colors">
|
||||
<div className="w-full mt-auto px-4 py-2 text-center text-sm font-medium border border-[#E5E5E5] rounded-md group-hover:bg-[#A01830] group-hover:text-white group-hover:border-[#A01830] transition-colors">
|
||||
了解详情
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
<ArrowRight className="ml-2 w-4 h-4 inline" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
</InkCard>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
@@ -119,15 +119,12 @@ export function ProductsSection() {
|
||||
<p className="text-[#718096] mb-8 max-w-2xl mx-auto">
|
||||
我们的专业团队可以根据您的业务需求,提供量身定制的产品开发和系统集成服务
|
||||
</p>
|
||||
<Button
|
||||
size="lg"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/contact">
|
||||
<StaticLink href="/contact">
|
||||
<RippleButton className="inline-flex items-center gap-2 px-6 py-2.5 bg-[#C41E3A] hover:bg-[#A01830] text-white rounded-lg text-sm font-medium transition-colors">
|
||||
联系我们
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -6,7 +6,8 @@ import { useRef } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Code, BarChart3, Lightbulb, Puzzle, ArrowRight } from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RippleButton } from '@/lib/animations';
|
||||
import { InkCard } from '@/lib/animations';
|
||||
import { SERVICES } from '@/lib/constants';
|
||||
|
||||
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
@@ -30,30 +31,28 @@ export function ServicesSection() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
className="text-left max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<div className="w-16 h-1 bg-[#C41E3A] rounded-full mb-6" />
|
||||
<h2 id="services-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
|
||||
我们的 <span className="text-[#C41E3A] font-calligraphy">核心业务</span>
|
||||
</h2>
|
||||
<p className="text-lg text-[#5C5C5C] max-w-2xl mx-auto">
|
||||
<p className="text-lg text-[#5C5C5C] max-w-2xl">
|
||||
专业技术团队,为您提供全方位的数字化解决方案
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{SERVICES.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{SERVICES.map((service, index) => {
|
||||
{SERVICES.map((service) => {
|
||||
const Icon = iconMap[service.icon];
|
||||
return (
|
||||
<motion.div
|
||||
<InkCard
|
||||
key={service.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="rounded-xl border border-[#E5E5E5] bg-white p-6 hover:border-[#C41E3A] transition-colors"
|
||||
>
|
||||
<StaticLink href={`/services/${service.id}`}>
|
||||
<Card className="p-6 h-full group cursor-pointer border-[#E5E5E5] hover:border-[#C41E3A] transition-colors">
|
||||
<Card className="p-0 h-full border-0 shadow-none bg-transparent group cursor-pointer">
|
||||
<CardContent className="p-0">
|
||||
<div className="w-12 h-12 rounded-xl bg-[#F5F5F5] flex items-center justify-center mb-4 group-hover:bg-[#C41E3A] transition-all duration-300">
|
||||
{Icon && <Icon className="w-6 h-6 text-[#1C1C1C] group-hover:text-white transition-colors" />}
|
||||
@@ -67,7 +66,7 @@ export function ServicesSection() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
</InkCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -83,12 +82,12 @@ export function ServicesSection() {
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="text-center mt-12"
|
||||
>
|
||||
<Button variant="outline" size="lg" className="group" asChild>
|
||||
<StaticLink href="/services">
|
||||
<StaticLink href="/services">
|
||||
<RippleButton className="inline-flex items-center gap-2 px-6 py-2.5 border border-[#E5E5E5] rounded-lg text-sm font-medium text-[#1C1C1C] hover:border-[#C41E3A] hover:text-[#C41E3A] transition-colors">
|
||||
查看全部服务
|
||||
<ArrowRight className="ml-2 w-4 h-4 transition-transform group-hover:translate-x-1" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -4,34 +4,62 @@ import { motion } from 'framer-motion';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowRight, Shield, Building2, Users } from 'lucide-react';
|
||||
import { RippleButton } from '@/lib/animations';
|
||||
import { ArrowRight, Briefcase, GraduationCap, Target, Users } from 'lucide-react';
|
||||
|
||||
const TEAM_HIGHLIGHTS = [
|
||||
const TEAM_MEMBERS = [
|
||||
{
|
||||
icon: Shield,
|
||||
title: '12年+ 行业深耕',
|
||||
description: '核心团队长期从事技术咨询、企业数字化等领域,积累了丰富的行业经验和最佳实践。',
|
||||
name: '创始人兼CEO',
|
||||
initials: 'CEO',
|
||||
specialties: ['企业战略', '数字化转型', '组织管理'],
|
||||
bio: '15年+企业服务经验,深耕数字化转型领域,擅长从战略高度为企业规划数字化路径。',
|
||||
icon: Target,
|
||||
accentColor: 'from-[#C41E3A] to-[#E85D75]',
|
||||
},
|
||||
{
|
||||
icon: Building2,
|
||||
title: '大型 IT 企业背景',
|
||||
description: '开发团队成员来自多个大型传统 IT 企业,具备扎实的工程能力和规范化交付经验。',
|
||||
name: '联合创始人兼CTO',
|
||||
initials: 'CTO',
|
||||
specialties: ['系统架构', '云原生', '微服务'],
|
||||
bio: '12年+技术架构经验,主导过多个大型企业级系统的设计与交付,精通分布式系统。',
|
||||
icon: GraduationCap,
|
||||
accentColor: 'from-[#C41E3A] to-[#D94466]',
|
||||
},
|
||||
{
|
||||
name: '技术总监',
|
||||
initials: 'TD',
|
||||
specialties: ['全栈开发', '数据工程', 'DevOps'],
|
||||
bio: '10年+全栈开发经验,专注于高质量软件交付和工程效能提升,推动敏捷实践落地。',
|
||||
icon: Briefcase,
|
||||
accentColor: 'from-[#C41E3A] to-[#C41E3A]',
|
||||
},
|
||||
{
|
||||
name: '咨询总监',
|
||||
initials: 'CD',
|
||||
specialties: ['业务咨询', '流程优化', '项目管理'],
|
||||
bio: '10年+管理咨询经验,擅长客户需求深度分析和解决方案设计,确保项目精准落地。',
|
||||
icon: Users,
|
||||
title: '复合型技术团队',
|
||||
description: '既懂技术又懂业务,能深入理解客户场景,提供真正落地的解决方案。',
|
||||
accentColor: 'from-[#C41E3A] to-[#A01830]',
|
||||
},
|
||||
];
|
||||
|
||||
const TEAM_STATS = [
|
||||
{ value: '12+', label: '年团队经验' },
|
||||
{ value: '80%', label: '本科及以上学历' },
|
||||
{ value: '4', label: '核心服务' },
|
||||
{ value: '5+', label: '行业覆盖' },
|
||||
];
|
||||
|
||||
export function TeamSection() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: '-100px' });
|
||||
|
||||
return (
|
||||
<section id="team" role="region" aria-labelledby="team-heading" className="py-24 bg-[#FAFAFA] relative overflow-hidden" ref={ref}>
|
||||
{/* 背景装饰 */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-[600px] bg-[rgba(196,30,58,0.02)] rounded-full blur-3xl" />
|
||||
|
||||
<div className="container-wide relative z-10">
|
||||
{/* 标题区 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
@@ -42,48 +70,86 @@ export function TeamSection() {
|
||||
核心<span className="text-[#C41E3A] font-calligraphy">团队</span>
|
||||
</h2>
|
||||
<p className="text-lg text-[#5C5C5C] leading-relaxed">
|
||||
核心团队从事技术咨询、企业数字化等行业 12 年+,开发团队成员来自于多个大型传统 IT 企业,具备扎实的工程能力和丰富的行业经验。
|
||||
来自大型IT企业的核心团队,既懂技术又懂业务,能深入理解客户场景,提供真正落地的解决方案。
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto mb-12">
|
||||
{TEAM_HIGHLIGHTS.map((item, idx) => {
|
||||
const Icon = item.icon;
|
||||
{/* 团队数据概览 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-4 max-w-4xl mx-auto mb-16"
|
||||
>
|
||||
{TEAM_STATS.map((stat) => (
|
||||
<div
|
||||
key={stat.label}
|
||||
className="text-center py-4 px-3 bg-white rounded-xl border border-[#E5E5E5]"
|
||||
>
|
||||
<div className="text-2xl sm:text-3xl font-bold bg-gradient-to-r from-[#C41E3A] to-[#E85D75] bg-clip-text text-transparent mb-1">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div className="text-xs sm:text-sm text-[#5C5C5C]">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* 团队成员卡片 */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 max-w-6xl mx-auto mb-12">
|
||||
{TEAM_MEMBERS.map((member, idx) => {
|
||||
const Icon = member.icon;
|
||||
return (
|
||||
<motion.div
|
||||
key={item.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
key={member.name}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: 0.1 + idx * 0.15 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 + idx * 0.12 }}
|
||||
>
|
||||
<div className="bg-white rounded-2xl p-8 border border-[#E5E5E5] hover:border-[#C41E3A]/30 hover:shadow-lg transition-all duration-300 h-full text-center">
|
||||
<div className="w-14 h-14 bg-[#C41E3A] rounded-2xl flex items-center justify-center mb-6 mx-auto">
|
||||
<Icon className="w-7 h-7 text-white" />
|
||||
<div className="bg-white rounded-2xl p-6 border border-[#E5E5E5] hover:border-[#C41E3A]/30 hover:shadow-lg transition-all duration-300 h-full flex flex-col group">
|
||||
{/* 头像区 */}
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className={`w-14 h-14 rounded-2xl bg-gradient-to-br ${member.accentColor} flex items-center justify-center shrink-0 group-hover:scale-105 transition-transform duration-300`}>
|
||||
<Icon className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-base font-bold text-[#1C1C1C] truncate">{member.name}</h3>
|
||||
<span className="text-xs text-[#C41E3A] font-medium">{member.initials}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 简介 */}
|
||||
<p className="text-sm text-[#5C5C5C] leading-relaxed mb-4 flex-1">{member.bio}</p>
|
||||
|
||||
{/* 专业领域标签 */}
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{member.specialties.map((spec) => (
|
||||
<span
|
||||
key={spec}
|
||||
className="inline-flex items-center text-xs px-2.5 py-1 bg-[#FAFAFA] text-[#3D3D3D] rounded-full border border-[#E5E5E5] group-hover:border-[#C41E3A]/20 transition-colors"
|
||||
>
|
||||
{spec}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-[#1C1C1C] mb-3">{item.title}</h3>
|
||||
<p className="text-sm text-[#5C5C5C] leading-relaxed">{item.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 }}
|
||||
className="text-center"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/team">
|
||||
<StaticLink href="/team">
|
||||
<RippleButton className="inline-flex items-center gap-2 px-6 py-2.5 border border-[#E5E5E5] rounded-lg text-sm font-medium text-[#1C1C1C] hover:border-[#C41E3A] hover:text-[#C41E3A] transition-colors">
|
||||
了解更多
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</RippleButton>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -13,11 +13,9 @@ export function OrganizationSchema() {
|
||||
"addressCountry": "CN",
|
||||
"addressLocality": "成都",
|
||||
"addressRegion": "四川省",
|
||||
"streetAddress": "成都市高新区"
|
||||
"streetAddress": "成都市龙泉驿区幸福路12号"
|
||||
},
|
||||
"sameAs": [
|
||||
"https://www.novalon.cn"
|
||||
]
|
||||
"sameAs": []
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -33,12 +31,7 @@ export function WebsiteSchema() {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "四川睿新致远科技有限公司",
|
||||
"url": "https://www.novalon.cn",
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": "https://www.novalon.cn/search?q={search_term_string}",
|
||||
"query-input": "required name=search_term_string"
|
||||
}
|
||||
"url": "https://www.novalon.cn"
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* ServiceCasesSection - 相关案例区域
|
||||
*
|
||||
* 展示与当前服务相关的案例卡片,使用 InkCard 实现悬浮效果,
|
||||
* StaticLink 链接到案例详情页。取前 4 个案例展示。
|
||||
*/
|
||||
import { useRef } from 'react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { StaggerContainer, StaggerItem, InkCard } from '@/lib/animations';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
import { CASES } from '@/lib/constants';
|
||||
import type { Service } from '@/lib/constants/services';
|
||||
|
||||
const SERVICE_CASE_MAP: Record<string, string[]> = {
|
||||
software: ['case-2', 'case-3'],
|
||||
data: ['case-2'],
|
||||
consulting: ['case-3', 'case-1'],
|
||||
solutions: ['case-1', 'case-4'],
|
||||
};
|
||||
|
||||
export function ServiceCasesSection({ service }: { service: Service }) {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
const relatedCaseIds = SERVICE_CASE_MAP[service.id] || [];
|
||||
const displayCases = relatedCaseIds
|
||||
.map(id => CASES.find(c => c.id === id))
|
||||
.filter((c): c is NonNullable<typeof c> => Boolean(c));
|
||||
|
||||
return (
|
||||
<section id="cases" ref={sectionRef} className="relative py-20 md:py-28 bg-[#F8F8F8] overflow-hidden">
|
||||
<div className="container-wide">
|
||||
{/* 标题区 */}
|
||||
<ScrollReveal variants={slideInLeftVariants} className="mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">
|
||||
相关案例
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* 案例卡片网格 */}
|
||||
<StaggerContainer className="grid md:grid-cols-2 gap-6">
|
||||
{displayCases.map((caseItem) => (
|
||||
<StaggerItem key={caseItem.id}>
|
||||
<StaticLink href={`/cases/${caseItem.id}`}>
|
||||
<InkCard
|
||||
className="p-6 bg-white rounded-2xl border border-[#E5E5E5] hover:border-[#C41E3A]/30 transition-colors h-full"
|
||||
hoverScale={1.02}
|
||||
>
|
||||
{/* 行业标签 */}
|
||||
<Badge variant="secondary" className="mb-3">
|
||||
{caseItem.industry}
|
||||
</Badge>
|
||||
{/* 案例标题 */}
|
||||
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-2">
|
||||
{caseItem.title}
|
||||
</h3>
|
||||
{/* 案例描述 - 限制两行 */}
|
||||
<p className="text-[#5C5C5C] text-sm leading-relaxed line-clamp-2">
|
||||
{caseItem.description}
|
||||
</p>
|
||||
</InkCard>
|
||||
</StaticLink>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* ServiceChallengesSection - 客户面临的挑战区域
|
||||
*
|
||||
* 以双列卡片网格展示客户在该业务领域可能遇到的痛点。
|
||||
* 每张卡片使用 InkCard 实现弹簧物理悬浮效果,
|
||||
* StaggerContainer + StaggerItem 实现交错入场动画。
|
||||
*/
|
||||
import { useRef } from 'react';
|
||||
import { InkReveal, StaggerContainer, StaggerItem, InkCard } from '@/lib/animations';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
import type { Service } from '@/lib/constants/services';
|
||||
|
||||
interface ServiceChallengesSectionProps {
|
||||
service: Service;
|
||||
}
|
||||
|
||||
export function ServiceChallengesSection({ service }: ServiceChallengesSectionProps) {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<section id="challenges" ref={sectionRef} className="relative py-20 md:py-28 bg-[#F8F8F8] overflow-hidden">
|
||||
<div className="container-wide">
|
||||
{/* 标题区 */}
|
||||
<ScrollReveal variants={slideInLeftVariants} className="mb-4">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">
|
||||
您可能面临的挑战
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* 朱砂红装饰线 */}
|
||||
<div className="w-16 h-1 bg-[#C41E3A] mb-6" />
|
||||
|
||||
{/* 副标题描述 */}
|
||||
<InkReveal delay={0.2}>
|
||||
<p className="text-lg text-[#5C5C5C] max-w-2xl mb-12">
|
||||
了解您在当前业务场景中可能遇到的痛点,我们已准备好针对性的解决方案。
|
||||
</p>
|
||||
</InkReveal>
|
||||
|
||||
{/* 挑战卡片网格 */}
|
||||
<StaggerContainer className="grid md:grid-cols-2 gap-6">
|
||||
{service.challenges.map((challenge, index) => (
|
||||
<StaggerItem key={index}>
|
||||
<InkCard
|
||||
className="p-6 md:p-8 bg-white rounded-2xl border border-[#E5E5E5] hover:border-[#C41E3A]/30 transition-colors"
|
||||
hoverScale={1.02}
|
||||
hoverShadow="0 20px 40px rgba(196, 30, 58, 0.08)"
|
||||
>
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-3">
|
||||
{challenge.title}
|
||||
</h3>
|
||||
<p className="text-[#5C5C5C] leading-relaxed">
|
||||
{challenge.description}
|
||||
</p>
|
||||
</InkCard>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* ServiceCTASection - 行动号召区域
|
||||
*
|
||||
* 朱砂红渐变背景,配合 FloatingElement 装饰圆形,
|
||||
* InkReveal 包裹标题,FadeUp 包裹描述和按钮组。
|
||||
* 主按钮链接到联系页,次按钮链接到服务列表页。
|
||||
*/
|
||||
import { InkReveal, FadeUp, FloatingElement, RippleButton } from '@/lib/animations';
|
||||
|
||||
export function ServiceCTASection() {
|
||||
return (
|
||||
<section className="relative py-24 md:py-32 bg-gradient-to-r from-[#C41E3A] to-[#E85D75] overflow-hidden">
|
||||
{/* 右上角装饰圆形 */}
|
||||
<FloatingElement amplitude={8} duration={5} delay={0.5} className="absolute -top-20 -right-20 pointer-events-none">
|
||||
<div className="w-[280px] h-[280px] bg-white/10 rounded-full" />
|
||||
</FloatingElement>
|
||||
|
||||
{/* 左下角装饰圆形 */}
|
||||
<FloatingElement amplitude={6} duration={4} delay={1} className="absolute -bottom-16 -left-16 pointer-events-none">
|
||||
<div className="w-[220px] h-[220px] bg-white/10 rounded-full" />
|
||||
</FloatingElement>
|
||||
|
||||
<div className="container-wide">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
{/* 标题 */}
|
||||
<InkReveal delay={0}>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
|
||||
免费获取企业数字化诊断报告
|
||||
</h2>
|
||||
</InkReveal>
|
||||
|
||||
{/* 描述文字 */}
|
||||
<FadeUp delay={0.15}>
|
||||
<p className="text-lg text-white/90 mb-10">
|
||||
我们的专家团队将为您量身定制数字化转型方案,助力企业降本增效
|
||||
</p>
|
||||
</FadeUp>
|
||||
|
||||
{/* 按钮组 */}
|
||||
<FadeUp delay={0.3}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(196, 30, 58, 0.3)"
|
||||
className="bg-white text-[#C41E3A] px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center w-full sm:w-auto"
|
||||
>
|
||||
免费获取
|
||||
</RippleButton>
|
||||
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
rippleColor="rgba(255, 255, 255, 0.2)"
|
||||
className="bg-transparent border-2 border-white text-white px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center w-full sm:w-auto"
|
||||
>
|
||||
预约演示
|
||||
</RippleButton>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* ServiceFeaturesSection - 服务功能特性区域
|
||||
*
|
||||
* 居中展示服务概览和功能列表,每项功能使用 CheckCircle2 图标
|
||||
* 配合 FadeUp 入场动画,StaggerContainer 实现交错出现效果。
|
||||
*/
|
||||
import { useRef } from 'react';
|
||||
import { CheckCircle2 } from 'lucide-react';
|
||||
import { InkReveal, FadeUp, StaggerContainer, StaggerItem } from '@/lib/animations';
|
||||
import { ScrollReveal, inkRevealVariants } from '@/components/ui/scroll-animations';
|
||||
import type { Service } from '@/lib/constants/services';
|
||||
|
||||
interface ServiceFeaturesSectionProps {
|
||||
service: Service;
|
||||
}
|
||||
|
||||
export function ServiceFeaturesSection({ service }: ServiceFeaturesSectionProps) {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<section id="features" ref={sectionRef} className="relative py-20 md:py-28 bg-white overflow-hidden">
|
||||
<div className="container-wide">
|
||||
{/* 标题区 - 居中 */}
|
||||
<ScrollReveal variants={inkRevealVariants} className="mb-4 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">
|
||||
我们如何帮助您
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* 服务概览 */}
|
||||
<InkReveal delay={0.2}>
|
||||
<p className="text-lg text-[#5C5C5C] text-center max-w-2xl mx-auto mb-12">
|
||||
{service.overview}
|
||||
</p>
|
||||
</InkReveal>
|
||||
|
||||
{/* 功能列表 */}
|
||||
<StaggerContainer className="max-w-3xl mx-auto space-y-4">
|
||||
{service.features.map((feature, index) => (
|
||||
<StaggerItem key={index}>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-[#C41E3A] mt-0.5 flex-shrink-0" />
|
||||
<FadeUp delay={index * 0.05}>
|
||||
<span className="text-[#1C1C1C] leading-relaxed">{feature}</span>
|
||||
</FadeUp>
|
||||
</div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* ServiceHeroSection - 核心业务详情页首屏区域
|
||||
*
|
||||
* 展示服务标题、描述和 CTA 按钮,配合水墨背景和粒子特效。
|
||||
* 使用 InkReveal 实现模糊揭示入场动画,SealStamp 包裹分类标签,
|
||||
* FloatingElement 驱动滚动指示器浮动效果。
|
||||
*/
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { InkReveal, SealStamp, RippleButton, FloatingElement } from '@/lib/animations';
|
||||
import type { Service } from '@/lib/constants/services';
|
||||
|
||||
/* 背景特效组件 - 必须禁用 SSR,避免 Canvas/WebGL 在服务端报错 */
|
||||
const InkBackground = dynamic(
|
||||
() => import('@/components/ui/ink-decoration').then(mod => ({ default: mod.InkBackground })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const DataParticleFlow = dynamic(
|
||||
() => import('@/components/effects/data-particle-flow').then(mod => ({ default: mod.DataParticleFlow })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
interface ServiceHeroSectionProps {
|
||||
service: Service;
|
||||
}
|
||||
|
||||
export function ServiceHeroSection({ service }: ServiceHeroSectionProps) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
/* 使用 IntersectionObserver 控制滚动指示器可见性 */
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry?.isIntersecting) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={sectionRef}
|
||||
className="relative min-h-screen flex items-center justify-center overflow-hidden bg-gradient-to-b from-white to-[#F8F8F8]"
|
||||
>
|
||||
{/* 背景特效层 */}
|
||||
<InkBackground />
|
||||
<DataParticleFlow
|
||||
particleCount={60}
|
||||
color="#C41E3A"
|
||||
intensity="subtle"
|
||||
shape="square"
|
||||
effect="pulse"
|
||||
/>
|
||||
|
||||
{/* 主内容区 */}
|
||||
<div className="container-wide relative z-10 py-32 md:py-40">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
{/* 分类标签 - 印章按压效果 */}
|
||||
<SealStamp
|
||||
delay={0.1}
|
||||
className="inline-block px-4 py-2 bg-[#C41E3A]/20 rounded-full text-[#C41E3A] text-sm mb-6"
|
||||
>
|
||||
核心业务
|
||||
</SealStamp>
|
||||
|
||||
{/* 服务标题 - 模糊揭示入场 */}
|
||||
<InkReveal delay={0.2}>
|
||||
<h1 className="text-4xl md:text-6xl lg:text-7xl font-bold text-[#1C1C1C] mb-6">
|
||||
{service.title}
|
||||
</h1>
|
||||
</InkReveal>
|
||||
|
||||
{/* 服务描述 - 模糊揭示入场 */}
|
||||
<InkReveal delay={0.4}>
|
||||
<p className="text-lg md:text-xl text-[#5C5C5C] leading-relaxed mb-10 max-w-2xl mx-auto">
|
||||
{service.description}
|
||||
</p>
|
||||
</InkReveal>
|
||||
|
||||
{/* CTA 按钮组 - 涟漪效果 */}
|
||||
<InkReveal delay={0.6}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
className="border-2 border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A] hover:text-white px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center"
|
||||
rippleColor="rgba(196, 30, 58, 0.2)"
|
||||
>
|
||||
预约演示
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
href="/contact"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center"
|
||||
rippleColor="rgba(255, 255, 255, 0.3)"
|
||||
>
|
||||
免费咨询
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
href="#challenges"
|
||||
className="border-2 border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A]/5 px-8 py-4 rounded-lg text-lg font-semibold inline-flex items-center justify-center"
|
||||
rippleColor="rgba(196, 30, 58, 0.2)"
|
||||
>
|
||||
了解详情
|
||||
</RippleButton>
|
||||
</div>
|
||||
</InkReveal>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 滚动指示器 - 浮动元素包裹 */}
|
||||
<FloatingElement
|
||||
amplitude={10}
|
||||
duration={1.5}
|
||||
delay={1}
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={isVisible ? { opacity: 1 } : {}}
|
||||
transition={{ duration: 0.6, delay: 1 }}
|
||||
>
|
||||
<div className="text-[#999999]">
|
||||
<ChevronDown className="w-8 h-8" />
|
||||
</div>
|
||||
</motion.div>
|
||||
</FloatingElement>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* ServiceOutcomesSection - 服务成果区域
|
||||
*
|
||||
* 以三列卡片网格展示量化成果数据,使用 CountUp 数字动画
|
||||
* 和渐变色文字突出关键指标。底部汇总 benefits 文本。
|
||||
*/
|
||||
import { useRef } from 'react';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
import { StaggerContainer, StaggerItem, InkCard, CountUp, InkReveal } from '@/lib/animations';
|
||||
import type { Service } from '@/lib/constants/services';
|
||||
|
||||
interface ServiceOutcomesSectionProps {
|
||||
service: Service;
|
||||
}
|
||||
|
||||
/** 从文本中提取数字部分,用于 CountUp 动画 */
|
||||
function extractNumber(text: string): number | null {
|
||||
const match = text.match(/(\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[1]!, 10);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 成果卡片子组件 */
|
||||
function OutcomeCard({ outcome }: { outcome: { value: string; label: string } }) {
|
||||
const numberValue = extractNumber(outcome.value);
|
||||
|
||||
return (
|
||||
<StaggerItem>
|
||||
<InkCard
|
||||
className="p-6 md:p-8 bg-white rounded-2xl border border-[#E5E5E5] hover:border-[#C41E3A]/30 transition-colors text-center"
|
||||
hoverScale={1.02}
|
||||
>
|
||||
{/* 数字动画区域 */}
|
||||
<div className="mb-4">
|
||||
{numberValue !== null ? (
|
||||
<span className="text-4xl md:text-5xl font-bold bg-gradient-to-r from-[#C41E3A] to-[#E85D75] bg-clip-text text-transparent">
|
||||
<CountUp
|
||||
end={numberValue}
|
||||
duration={2000}
|
||||
/>
|
||||
{/* 保留非数字后缀(如 %、+ 等) */}
|
||||
{outcome.value.replace(/(\d+)/, '')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-4xl md:text-5xl font-bold bg-gradient-to-r from-[#C41E3A] to-[#E85D75] bg-clip-text text-transparent">
|
||||
{outcome.value}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* 标签文字 */}
|
||||
<p className="text-[#5C5C5C]">{outcome.label}</p>
|
||||
</InkCard>
|
||||
</StaggerItem>
|
||||
);
|
||||
}
|
||||
|
||||
export function ServiceOutcomesSection({ service }: ServiceOutcomesSectionProps) {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<section id="outcomes" ref={sectionRef} className="relative py-20 md:py-28 bg-white overflow-hidden">
|
||||
<div className="container-wide">
|
||||
{/* 标题区 */}
|
||||
<ScrollReveal variants={slideInLeftVariants} className="mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">
|
||||
您将获得的改变
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* 成果卡片网格 */}
|
||||
<StaggerContainer className="grid sm:grid-cols-3 gap-6">
|
||||
{service.outcomes.map((outcome, index) => (
|
||||
<OutcomeCard key={index} outcome={outcome} />
|
||||
))}
|
||||
</StaggerContainer>
|
||||
|
||||
{/* benefits 汇总文本 */}
|
||||
<InkReveal delay={0.3}>
|
||||
<div className="mt-10 p-6 bg-[#F8F8F8] rounded-2xl border border-[#E5E5E5]">
|
||||
<p className="text-[#5C5C5C] leading-relaxed">
|
||||
{service.benefits.join(';')}
|
||||
</p>
|
||||
</div>
|
||||
</InkReveal>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* ServiceProcessSection - 服务流程区域
|
||||
*
|
||||
* 以时间轴形式展示服务实施步骤,左侧使用 SealStamp 编号圆形
|
||||
* 和渐变连接线,右侧使用 FadeUp 包裹步骤标题和描述。
|
||||
* 用中文冒号分隔步骤标题和描述。
|
||||
*/
|
||||
import { useRef } from 'react';
|
||||
import { ScrollReveal, inkRevealVariants } from '@/components/ui/scroll-animations';
|
||||
import { StaggerContainer, StaggerItem, SealStamp, FadeUp } from '@/lib/animations';
|
||||
import type { Service } from '@/lib/constants/services';
|
||||
|
||||
interface ServiceProcessSectionProps {
|
||||
service: Service;
|
||||
}
|
||||
|
||||
/** 流程步骤子组件 - 解析中文冒号分隔的标题和描述 */
|
||||
function ProcessStep({
|
||||
step,
|
||||
index,
|
||||
total,
|
||||
}: {
|
||||
step: string;
|
||||
index: number;
|
||||
total: number;
|
||||
}) {
|
||||
/* 用中文冒号分隔步骤标题和描述 */
|
||||
const colonIndex = step.indexOf(':');
|
||||
const title = colonIndex > -1 ? step.substring(0, colonIndex) : step;
|
||||
const description = colonIndex > -1 ? step.substring(colonIndex + 1) : '';
|
||||
|
||||
return (
|
||||
<StaggerItem className="flex items-start gap-6">
|
||||
{/* 左侧:编号圆形 + 渐变连接线 */}
|
||||
<div className="flex-shrink-0">
|
||||
<SealStamp delay={index * 0.15}>
|
||||
<div className="w-12 h-12 rounded-full bg-[#C41E3A] flex items-center justify-center text-white font-bold text-lg">
|
||||
{index + 1}
|
||||
</div>
|
||||
</SealStamp>
|
||||
{/* 最后一步不显示连接线 */}
|
||||
{index < total - 1 && (
|
||||
<div className="w-0.5 h-16 bg-gradient-to-b from-[#C41E3A]/40 to-[#C41E3A]/10 ml-6 mt-2" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 右侧:步骤标题和描述 */}
|
||||
<FadeUp delay={index * 0.1} className="flex-1 pb-8">
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-2">{title}</h3>
|
||||
{description && (
|
||||
<p className="text-[#5C5C5C] leading-relaxed">{description}</p>
|
||||
)}
|
||||
</FadeUp>
|
||||
</StaggerItem>
|
||||
);
|
||||
}
|
||||
|
||||
export function ServiceProcessSection({ service }: ServiceProcessSectionProps) {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<section id="process" ref={sectionRef} className="relative py-20 md:py-28 bg-[#F8F8F8] overflow-hidden">
|
||||
<div className="container-wide">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{/* 标题区 - 居中 */}
|
||||
<ScrollReveal variants={inkRevealVariants} className="mb-12 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#1C1C1C]">
|
||||
服务流程
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* 步骤列表 */}
|
||||
<StaggerContainer className="mt-8" staggerDelay={0.15}>
|
||||
{service.process.map((step, index) => (
|
||||
<ProcessStep
|
||||
key={index}
|
||||
step={step}
|
||||
index={index}
|
||||
total={service.process.length}
|
||||
/>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* AccompanySection - 长期陪跑服务 · 同行伙伴
|
||||
*
|
||||
* 解决方案页面的第三个模块,展示长期陪跑服务。
|
||||
* 使用水墨+朱砂红东方美学设计体系,配合滚动触发动画。
|
||||
* 内容硬编码在组件内部,无外部 Props。
|
||||
* delay 基础值较第一个模块增加 0.4,避免同时触发。
|
||||
*/
|
||||
import { motion } from 'framer-motion';
|
||||
import { Users, CheckCircle2, ArrowRight } from 'lucide-react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InkReveal, FadeUp, StaggerContainer, StaggerItem } from '@/lib/animations';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
|
||||
/** 核心价值点数据 */
|
||||
const valuePoints = [
|
||||
'专属客户成功经理',
|
||||
'季度业务复盘会',
|
||||
'7×24小时响应通道',
|
||||
];
|
||||
|
||||
export function AccompanySection() {
|
||||
return (
|
||||
<motion.section
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
|
||||
>
|
||||
{/* 标题区域:图标 + 标题 + 副标题 */}
|
||||
<div className="flex items-start gap-6 mb-8">
|
||||
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
|
||||
<Users className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<InkReveal delay={0.4}>
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
|
||||
模块三:长期陪跑服务 · 同行伙伴
|
||||
</h2>
|
||||
</InkReveal>
|
||||
<FadeUp delay={0.5}>
|
||||
<p className="text-lg text-[#5C5C5C]">
|
||||
交付只是开始,陪伴才是常态
|
||||
</p>
|
||||
</FadeUp>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 描述段落 */}
|
||||
<div className="space-y-6 mb-8">
|
||||
<FadeUp delay={0.55}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
项目上线那天,是我们真正成为伙伴的开始。
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.6}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
我们建立长效服务机制,定期回访、持续优化、随时响应。
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.65}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
在您需要的时候,我们始终在场。
|
||||
</p>
|
||||
</FadeUp>
|
||||
</div>
|
||||
|
||||
{/* 核心价值点 */}
|
||||
<div className="mb-8">
|
||||
<ScrollReveal variants={slideInLeftVariants}>
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
|
||||
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
|
||||
核心价值点
|
||||
</h3>
|
||||
</ScrollReveal>
|
||||
<StaggerContainer className="grid md:grid-cols-3 gap-4">
|
||||
{valuePoints.map((point) => (
|
||||
<StaggerItem key={point}>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">{point}</span>
|
||||
</div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
|
||||
{/* CTA 按钮 */}
|
||||
<FadeUp delay={0.8}>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/contact">
|
||||
了解陪跑服务
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</motion.section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* ConsultingSection - 数字化转型咨询 · 参谋伙伴
|
||||
*
|
||||
* 解决方案页面的第一个模块,展示数字化转型咨询服务。
|
||||
* 使用水墨+朱砂红东方美学设计体系,配合滚动触发动画。
|
||||
* 内容硬编码在组件内部,无外部 Props。
|
||||
*/
|
||||
import { motion } from 'framer-motion';
|
||||
import { Lightbulb, CheckCircle2, ArrowRight } from 'lucide-react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InkReveal, FadeUp, StaggerContainer, StaggerItem } from '@/lib/animations';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
|
||||
/** 核心价值点数据 */
|
||||
const valuePoints = [
|
||||
'行业趋势洞察报告',
|
||||
'数字化转型成熟度评估',
|
||||
'个性化实施路径规划',
|
||||
];
|
||||
|
||||
export function ConsultingSection() {
|
||||
return (
|
||||
<motion.section
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
|
||||
>
|
||||
{/* 标题区域:图标 + 标题 + 副标题 */}
|
||||
<div className="flex items-start gap-6 mb-8">
|
||||
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
|
||||
<Lightbulb className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<InkReveal delay={0}>
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
|
||||
模块一:数字化转型咨询 · 参谋伙伴
|
||||
</h2>
|
||||
</InkReveal>
|
||||
<FadeUp delay={0.1}>
|
||||
<p className="text-lg text-[#5C5C5C]">
|
||||
帮您看清前路,迈对第一步
|
||||
</p>
|
||||
</FadeUp>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 描述段落 */}
|
||||
<div className="space-y-6 mb-8">
|
||||
<FadeUp delay={0.15}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
数字化转型最大的成本,是走错方向的成本。
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.2}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
我们用行业智慧帮您洞察趋势,用理性分析帮您避开陷阱。
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.25}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
不堆砌概念,只帮您想清楚:该不该做、做什么、怎么做。
|
||||
</p>
|
||||
</FadeUp>
|
||||
</div>
|
||||
|
||||
{/* 核心价值点 */}
|
||||
<div className="mb-8">
|
||||
<ScrollReveal variants={slideInLeftVariants}>
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
|
||||
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
|
||||
核心价值点
|
||||
</h3>
|
||||
</ScrollReveal>
|
||||
<StaggerContainer className="grid md:grid-cols-3 gap-4">
|
||||
{valuePoints.map((point) => (
|
||||
<StaggerItem key={point}>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">{point}</span>
|
||||
</div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
|
||||
{/* CTA 按钮 */}
|
||||
<FadeUp delay={0.4}>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/contact">
|
||||
预约一次免费诊断
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</motion.section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* TechSolutionSection - 信息技术解决方案 · 技术伙伴
|
||||
*
|
||||
* 解决方案页面的第二个模块,展示信息技术解决方案服务。
|
||||
* 使用水墨+朱砂红东方美学设计体系,配合滚动触发动画。
|
||||
* 内容硬编码在组件内部,无外部 Props。
|
||||
* delay 基础值较第一个模块增加 0.2,避免同时触发。
|
||||
*/
|
||||
import { motion } from 'framer-motion';
|
||||
import { Cpu, CheckCircle2, ArrowRight } from 'lucide-react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InkReveal, FadeUp, StaggerContainer, StaggerItem } from '@/lib/animations';
|
||||
import { ScrollReveal, slideInLeftVariants } from '@/components/ui/scroll-animations';
|
||||
|
||||
/** 核心价值点数据 */
|
||||
const valuePoints = [
|
||||
'业务场景深度调研',
|
||||
'技术方案定制开发',
|
||||
'敏捷交付快速迭代',
|
||||
];
|
||||
|
||||
export function TechSolutionSection() {
|
||||
return (
|
||||
<motion.section
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
|
||||
>
|
||||
{/* 标题区域:图标 + 标题 + 副标题 */}
|
||||
<div className="flex items-start gap-6 mb-8">
|
||||
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
|
||||
<Cpu className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<InkReveal delay={0.2}>
|
||||
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
|
||||
模块二:信息技术解决方案 · 技术伙伴
|
||||
</h2>
|
||||
</InkReveal>
|
||||
<FadeUp delay={0.3}>
|
||||
<p className="text-lg text-[#5C5C5C]">
|
||||
让技术真正为业务服务
|
||||
</p>
|
||||
</FadeUp>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 描述段落 */}
|
||||
<div className="space-y-6 mb-8">
|
||||
<FadeUp delay={0.35}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
我们不追逐“最火”的技术,只选择“最对”的技术。
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.4}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
将前沿技术深度融入您的业务场景,让每一行代码都产生业务价值。
|
||||
</p>
|
||||
</FadeUp>
|
||||
<FadeUp delay={0.45}>
|
||||
<p className="text-lg text-[#1C1C1C] leading-relaxed">
|
||||
您不必懂技术原理,只需要看见业务在增长。
|
||||
</p>
|
||||
</FadeUp>
|
||||
</div>
|
||||
|
||||
{/* 核心价值点 */}
|
||||
<div className="mb-8">
|
||||
<ScrollReveal variants={slideInLeftVariants}>
|
||||
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
|
||||
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
|
||||
核心价值点
|
||||
</h3>
|
||||
</ScrollReveal>
|
||||
<StaggerContainer className="grid md:grid-cols-3 gap-4">
|
||||
{valuePoints.map((point) => (
|
||||
<StaggerItem key={point}>
|
||||
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
|
||||
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
|
||||
<span className="text-[#1C1C1C]">{point}</span>
|
||||
</div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
|
||||
{/* CTA 按钮(outline 样式) */}
|
||||
<FadeUp delay={0.6}>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A] hover:text-white"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/cases">
|
||||
查看技术案例
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</div>
|
||||
</FadeUp>
|
||||
</motion.section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { MessageCircle, X } from 'lucide-react';
|
||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||
|
||||
export function FloatingCTA() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isDismissed, setIsDismissed] = useState(false);
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
// 滚动超过首屏(100vh)后显示
|
||||
if (window.scrollY > window.innerHeight * 0.8) {
|
||||
setIsVisible(true);
|
||||
} else {
|
||||
setIsVisible(false);
|
||||
// 回到顶部时重置关闭状态
|
||||
setIsDismissed(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
if (isDismissed) {return null;}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? { opacity: 1 } : { opacity: 0, scale: 0.8, y: 20 }}
|
||||
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1, y: 0 }}
|
||||
exit={shouldReduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.8, y: 20 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
||||
className="fixed bottom-6 right-6 z-40 flex items-end gap-3"
|
||||
>
|
||||
{/* 关闭按钮 */}
|
||||
<button
|
||||
onClick={() => setIsDismissed(true)}
|
||||
className="w-8 h-8 rounded-full bg-white border border-[#E5E5E5] flex items-center justify-center text-[#5C5C5C] hover:text-[#1C1C1C] hover:border-[#1C1C1C] transition-colors shadow-sm"
|
||||
aria-label="关闭咨询按钮"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
|
||||
{/* 主 CTA 按钮 */}
|
||||
<StaticLink href="/contact">
|
||||
<motion.button
|
||||
whileHover={shouldReduceMotion ? {} : { scale: 1.05, y: -2 }}
|
||||
whileTap={shouldReduceMotion ? {} : { scale: 0.95 }}
|
||||
className="group relative flex items-center gap-2 px-5 py-3 rounded-full bg-[#C41E3A] text-white shadow-[0_4px_20px_rgba(196,30,58,0.35)] hover:shadow-[0_6px_28px_rgba(196,30,58,0.45)] transition-shadow"
|
||||
aria-label="立即咨询"
|
||||
>
|
||||
{/* 脉冲光晕 */}
|
||||
<span className="absolute inset-0 rounded-full bg-[#C41E3A] animate-ping opacity-20" />
|
||||
<MessageCircle className="w-5 h-5 relative z-10" />
|
||||
<span className="text-sm font-medium relative z-10 hidden sm:inline">立即咨询</span>
|
||||
</motion.button>
|
||||
</StaticLink>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
@@ -50,19 +50,20 @@ export interface RippleButtonProps
|
||||
rippleColor?: string;
|
||||
rippleDuration?: number;
|
||||
children?: React.ReactNode;
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
const RippleButton = React.forwardRef<HTMLButtonElement, RippleButtonProps>(
|
||||
({ className, variant, size, rippleColor, rippleDuration = 800, onClick, children, disabled, ...props }, ref) => {
|
||||
const RippleButton = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, RippleButtonProps>(
|
||||
({ className, variant, size, rippleColor, rippleDuration = 800, onClick, children, disabled, href, ...props }, ref) => {
|
||||
const [ripples, setRipples] = React.useState<Ripple[]>([]);
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
|
||||
if (disabled) {return;}
|
||||
|
||||
|
||||
const button = e.currentTarget;
|
||||
const rect = button.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
@@ -86,15 +87,19 @@ const RippleButton = React.forwardRef<HTMLButtonElement, RippleButtonProps>(
|
||||
return 'rgba(255, 255, 255, 0.4)';
|
||||
};
|
||||
|
||||
const Component = href ? motion.a : motion.button;
|
||||
const linkProps = href ? { href } : {};
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
ref={ref}
|
||||
<Component
|
||||
ref={ref as React.Ref<never>}
|
||||
whileHover={disabled ? {} : { scale: 1.03, y: -3 }}
|
||||
whileTap={disabled ? {} : { scale: 0.97 }}
|
||||
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
|
||||
className={cn(rippleButtonVariants({ variant, size, className }))}
|
||||
onClick={handleClick}
|
||||
disabled={disabled}
|
||||
{...linkProps}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -115,7 +120,7 @@ const RippleButton = React.forwardRef<HTMLButtonElement, RippleButtonProps>(
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.button>
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -243,12 +243,13 @@ interface RippleButtonProps {
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
rippleColor?: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export function RippleButton({ children, onClick, className = '', rippleColor = 'rgba(196, 30, 58, 0.3)' }: RippleButtonProps) {
|
||||
export function RippleButton({ children, onClick, className = '', rippleColor = 'rgba(196, 30, 58, 0.3)', href }: RippleButtonProps) {
|
||||
const [ripples, setRipples] = useState<Array<{ x: number; y: number; id: number }>>([]);
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
|
||||
const button = e.currentTarget;
|
||||
const rect = button.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
@@ -264,13 +265,17 @@ export function RippleButton({ children, onClick, className = '', rippleColor =
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
const Component = href ? motion.a : motion.button;
|
||||
const linkProps = href ? { href } : {};
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
<Component
|
||||
onClick={handleClick}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
|
||||
className={`relative overflow-hidden ${className}`}
|
||||
{...linkProps}
|
||||
>
|
||||
{children}
|
||||
{ripples.map((ripple) => (
|
||||
@@ -287,7 +292,7 @@ export function RippleButton({ children, onClick, className = '', rippleColor =
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</motion.button>
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('Constants', () => {
|
||||
});
|
||||
|
||||
it('should have short name', () => {
|
||||
expect(COMPANY_INFO.shortName).toBe('睿新致遠');
|
||||
expect(COMPANY_INFO.shortName).toBe('睿新致远');
|
||||
});
|
||||
|
||||
it('should have slogan', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export { COMPANY_INFO } from './company';
|
||||
export { NAVIGATION, type NavigationItem } from './navigation';
|
||||
export { STATS, type StatItem } from './stats';
|
||||
export { SERVICES } from './services';
|
||||
export { SERVICES, type Service } from './services';
|
||||
export { PRODUCTS } from './products';
|
||||
export { NEWS, type NewsItem, type NewsCategory } from './news';
|
||||
export { CASES } from './cases';
|
||||
|
||||
@@ -7,10 +7,9 @@ export interface NavigationItem {
|
||||
export const NAVIGATION: NavigationItem[] = [
|
||||
{ id: 'home', label: '首页', href: '/' },
|
||||
{ id: 'services', label: '核心业务', href: '/services' },
|
||||
{ id: 'solutions', label: '解决方案', href: '/solutions' },
|
||||
{ id: 'solutions', label: '行业方案', href: '/solutions' },
|
||||
{ id: 'products', label: '产品服务', href: '/products' },
|
||||
{ id: 'cases', label: '成功案例', href: '/cases' },
|
||||
{ id: 'about', label: '关于我们', href: '/about' },
|
||||
{ id: 'news', label: '新闻动态', href: '/news' },
|
||||
{ id: 'contact', label: '联系', href: '/contact' },
|
||||
{ id: 'contact', label: '联系我们', href: '/contact' },
|
||||
];
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export type Product = (typeof PRODUCTS)[number];
|
||||
|
||||
export const PRODUCTS = [
|
||||
{
|
||||
id: 'erp',
|
||||
@@ -15,30 +17,31 @@ export const PRODUCTS = [
|
||||
'报表分析:丰富的报表模板,支持自定义报表',
|
||||
],
|
||||
benefits: [
|
||||
'数据完全自主可控,满足安全合规要求',
|
||||
'支持本地/私有云/混合云多种部署方式',
|
||||
'提升运营效率30%,减少人工操作',
|
||||
'降低库存成本20%,优化库存周转',
|
||||
'实现数据实时共享,消除信息孤岛',
|
||||
'支持多部门协同,提升整体协作效率',
|
||||
'一次部署永久使用,无持续订阅压力',
|
||||
],
|
||||
process: [
|
||||
'需求调研:深入了解企业业务流程和管理需求',
|
||||
'方案设计:制定符合企业特点的ERP实施方案',
|
||||
'系统配置:根据需求进行系统配置和参数设置',
|
||||
'数据迁移:安全迁移历史数据,确保数据完整性',
|
||||
'用户培训:提供全面的用户培训和操作指导',
|
||||
'上线支持:协助系统上线,提供技术支持',
|
||||
'环境评估:评估服务器环境与网络架构',
|
||||
'方案设计:制定部署方案与数据迁移策略',
|
||||
'系统部署:私有化部署与系统配置',
|
||||
'数据迁移:安全迁移历史数据,确保完整性',
|
||||
'培训交付:用户培训与正式交付上线',
|
||||
],
|
||||
specs: [
|
||||
'支持本地服务器、私有云、混合云部署',
|
||||
'支持多组织、多账套管理',
|
||||
'支持多币种、多语言',
|
||||
'支持移动端访问',
|
||||
'支持API接口集成',
|
||||
'支持自定义报表',
|
||||
'支持信创环境(国产操作系统/数据库)',
|
||||
'支持API接口集成,对接现有系统',
|
||||
'支持移动端访问(APP/微信/钉钉)',
|
||||
'支持数据加密存储与传输',
|
||||
],
|
||||
pricing: {
|
||||
base: '基础版:¥19,800/年',
|
||||
standard: '标准版:¥39,800/年',
|
||||
enterprise: '企业版:¥79,800/年',
|
||||
base: '标准版',
|
||||
standard: '专业版',
|
||||
enterprise: '企业定制版',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -57,30 +60,31 @@ export const PRODUCTS = [
|
||||
'数据分析:客户分析、销售分析、业绩报表',
|
||||
],
|
||||
benefits: [
|
||||
'客户数据私有化存储,保障商业机密',
|
||||
'支持与企业现有系统深度集成',
|
||||
'销售转化率提升25%,提高销售效率',
|
||||
'客户满意度提升40%,增强客户粘性',
|
||||
'销售周期缩短30%,加快成交速度',
|
||||
'支持团队协作,提升整体销售业绩',
|
||||
'灵活定制销售流程,适配业务变化',
|
||||
],
|
||||
process: [
|
||||
'客户调研:了解企业销售流程和客户管理需求',
|
||||
'流程梳理:梳理销售流程,优化客户管理策略',
|
||||
'系统配置:配置客户字段、销售阶段、审批流程',
|
||||
'数据导入:导入历史客户数据和销售记录',
|
||||
'培训上线:培训销售人员,协助系统上线',
|
||||
'持续优化:根据使用情况持续优化系统配置',
|
||||
'需求调研:了解企业销售流程和客户管理需求',
|
||||
'环境评估:评估IT基础设施与集成需求',
|
||||
'方案设计:制定部署方案与系统集成策略',
|
||||
'系统部署:私有化部署与流程配置',
|
||||
'数据导入:导入历史客户数据与销售记录',
|
||||
'培训交付:销售团队培训与系统上线',
|
||||
],
|
||||
specs: [
|
||||
'支持本地服务器、私有云部署',
|
||||
'支持多渠道客户数据整合',
|
||||
'支持自定义销售流程',
|
||||
'支持信创环境适配',
|
||||
'支持API接口,对接ERP/OA等系统',
|
||||
'支持移动端访问',
|
||||
'支持邮件集成',
|
||||
'支持API接口集成',
|
||||
'支持数据加密与权限隔离',
|
||||
],
|
||||
pricing: {
|
||||
base: '基础版:¥9,800/年',
|
||||
standard: '标准版:¥19,800/年',
|
||||
enterprise: '企业版:¥39,800/年',
|
||||
base: '标准版',
|
||||
standard: '专业版',
|
||||
enterprise: '企业定制版',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -99,30 +103,31 @@ export const PRODUCTS = [
|
||||
'SEO优化:内置SEO工具,优化搜索引擎排名',
|
||||
],
|
||||
benefits: [
|
||||
'内容数据自主管理,支持等保合规',
|
||||
'支持私有化部署,内外网隔离访问',
|
||||
'内容发布效率提升50%,加快内容更新',
|
||||
'管理成本降低40%,减少人工投入',
|
||||
'支持多终端访问,提升用户体验',
|
||||
'灵活的权限管理,确保内容安全',
|
||||
'灵活的权限体系,满足多部门协作',
|
||||
],
|
||||
process: [
|
||||
'需求分析:了解企业内容管理需求和业务场景',
|
||||
'架构设计:设计内容架构和分类体系',
|
||||
'系统配置:配置站点、栏目、模板、权限',
|
||||
'内容迁移:迁移历史内容数据',
|
||||
'培训上线:培训内容管理人员,协助系统上线',
|
||||
'运营支持:提供持续的运营支持和技术服务',
|
||||
'需求分析:了解企业内容管理需求与安全要求',
|
||||
'环境评估:评估部署环境与网络隔离需求',
|
||||
'方案设计:制定部署方案与内容架构',
|
||||
'系统部署:私有化部署与权限配置',
|
||||
'内容迁移:迁移历史内容与媒体资源',
|
||||
'培训交付:内容团队培训与系统上线',
|
||||
],
|
||||
specs: [
|
||||
'支持多站点管理',
|
||||
'支持多语言内容',
|
||||
'支持移动端访问',
|
||||
'支持本地服务器、私有云部署',
|
||||
'支持多站点统一管理',
|
||||
'支持信创环境适配',
|
||||
'支持API接口集成',
|
||||
'支持自定义模板',
|
||||
'支持自定义模板与组件',
|
||||
'支持等保二级/三级安全要求',
|
||||
],
|
||||
pricing: {
|
||||
base: '基础版:¥4,800/年',
|
||||
standard: '标准版:¥9,800/年',
|
||||
enterprise: '企业版:¥19,800/年',
|
||||
base: '标准版',
|
||||
standard: '专业版',
|
||||
enterprise: '企业定制版',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -141,30 +146,31 @@ export const PRODUCTS = [
|
||||
'移动看板:支持移动端访问,随时随地查看数据',
|
||||
],
|
||||
benefits: [
|
||||
'分析能力私有化部署,数据不出企业',
|
||||
'支持对接内部数据源,无需数据上云',
|
||||
'决策效率提升60%,加快决策速度',
|
||||
'数据准备时间减少70%,提高数据分析效率',
|
||||
'发现隐藏业务洞察,把握业务机会',
|
||||
'支持数据驱动决策,提升管理水平',
|
||||
'自助式分析,降低数据使用门槛',
|
||||
],
|
||||
process: [
|
||||
'数据评估:评估数据源和数据质量',
|
||||
'平台搭建:搭建数据仓库和BI平台',
|
||||
'模型开发:开发数据分析模型和报表',
|
||||
'数据集成:整合多源数据到统一平台',
|
||||
'培训上线:培训数据分析人员,协助系统上线',
|
||||
'持续优化:持续优化数据模型和报表',
|
||||
'数据评估:评估数据源、数据质量与安全要求',
|
||||
'环境评估:评估服务器性能与数据规模',
|
||||
'方案设计:制定部署方案与数据仓库架构',
|
||||
'系统部署:私有化部署与数据集成',
|
||||
'模型开发:开发分析模型与可视化报表',
|
||||
'培训交付:数据分析培训与系统上线',
|
||||
],
|
||||
specs: [
|
||||
'支持多种数据源',
|
||||
'支持实时数据分析',
|
||||
'支持移动端访问',
|
||||
'支持本地服务器、私有云部署',
|
||||
'支持多种数据源(数据库/文件/API)',
|
||||
'支持信创环境适配',
|
||||
'支持实时与离线数据分析',
|
||||
'支持API接口集成',
|
||||
'支持自定义报表',
|
||||
'支持数据加密与访问审计',
|
||||
],
|
||||
pricing: {
|
||||
base: '基础版:¥14,800/年',
|
||||
standard: '标准版:¥29,800/年',
|
||||
enterprise: '企业版:¥59,800/年',
|
||||
base: '标准版',
|
||||
standard: '专业版',
|
||||
enterprise: '企业定制版',
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export type Service = (typeof SERVICES)[number];
|
||||
|
||||
export const SERVICES = [
|
||||
{
|
||||
id: 'software',
|
||||
@@ -18,6 +20,17 @@ export const SERVICES = [
|
||||
'快速响应市场变化,保持竞争优势',
|
||||
'数据驱动决策,支持业务增长',
|
||||
],
|
||||
challenges: [
|
||||
{ title: '需求不明确', description: '业务部门提不出清晰需求,开发团队反复返工' },
|
||||
{ title: '技术选型困难', description: '技术栈更新快,不知道该选什么技术方案' },
|
||||
{ title: '项目延期', description: '开发进度难以把控,上线时间一拖再拖' },
|
||||
{ title: '维护成本高', description: '系统上线后问题不断,运维压力巨大' },
|
||||
],
|
||||
outcomes: [
|
||||
{ value: '30%', label: '开发效率提升' },
|
||||
{ value: '50%', label: '返工率降低' },
|
||||
{ value: '100%', label: '按时交付率' },
|
||||
],
|
||||
process: [
|
||||
'需求分析:深入了解业务需求,制定详细需求文档',
|
||||
'方案设计:设计系统架构和技术方案',
|
||||
@@ -46,6 +59,17 @@ export const SERVICES = [
|
||||
'优化业务流程,提升运营效率',
|
||||
'增强市场洞察力,把握市场趋势',
|
||||
],
|
||||
challenges: [
|
||||
{ title: '数据孤岛', description: '各系统数据分散,无法整合分析' },
|
||||
{ title: '决策盲区', description: '缺乏数据支撑,决策凭感觉' },
|
||||
{ title: '报表滞后', description: '手工制作报表,时效性差' },
|
||||
{ title: '价值难挖', description: '数据很多,但不知道怎么用' },
|
||||
],
|
||||
outcomes: [
|
||||
{ value: '70%', label: '决策效率提升' },
|
||||
{ value: '实时', label: '数据更新' },
|
||||
{ value: '100+', label: '可视化报表' },
|
||||
],
|
||||
process: [
|
||||
'数据评估:评估数据质量和可用性',
|
||||
'平台搭建:构建数据分析平台',
|
||||
@@ -74,6 +98,17 @@ export const SERVICES = [
|
||||
'提升技术团队整体能力',
|
||||
'加速数字化转型进程,抢占市场先机',
|
||||
],
|
||||
challenges: [
|
||||
{ title: '方向不明', description: '数字化转型不知道从哪里入手' },
|
||||
{ title: '技术债务', description: '历史系统包袱重,新技术难以引入' },
|
||||
{ title: '人才短缺', description: '缺乏专业的技术规划和架构人才' },
|
||||
{ title: '投入浪费', description: 'IT投入不少,但看不到明显效果' },
|
||||
],
|
||||
outcomes: [
|
||||
{ value: '60%', label: '方向明确度' },
|
||||
{ value: '40%', label: '试错成本降低' },
|
||||
{ value: '3x', label: '转型速度提升' },
|
||||
],
|
||||
process: [
|
||||
'现状调研:深入了解企业现状和痛点',
|
||||
'需求分析:梳理业务需求和技术需求',
|
||||
@@ -102,6 +137,17 @@ export const SERVICES = [
|
||||
'获得端到端的完整解决方案',
|
||||
'持续获得行业前沿技术和趋势',
|
||||
],
|
||||
challenges: [
|
||||
{ title: '行业壁垒', description: '不了解行业最佳实践,走弯路' },
|
||||
{ title: '方案碎片化', description: '各系统各自为政,无法协同' },
|
||||
{ title: '实施风险', description: '大型项目实施失败率高' },
|
||||
{ title: '效果难量化', description: '投入产出比不清晰,难以评估' },
|
||||
],
|
||||
outcomes: [
|
||||
{ value: '50%', label: '实施周期缩短' },
|
||||
{ value: '30%', label: '成本降低' },
|
||||
{ value: '95%', label: '客户满意度' },
|
||||
],
|
||||
process: [
|
||||
'行业调研:深入研究行业趋势和客户需求',
|
||||
'方案设计:设计符合行业特点的解决方案',
|
||||
|
||||
@@ -1,32 +1,11 @@
|
||||
import { CASES } from './cases';
|
||||
|
||||
export interface StatItem {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
function calculateYearsOfExperience(): number {
|
||||
const startYear = 2014;
|
||||
const currentYear = new Date().getFullYear();
|
||||
return currentYear - startYear;
|
||||
}
|
||||
|
||||
function calculateUniqueClients(): number {
|
||||
const uniqueClients = new Set(CASES.map(c => c.client));
|
||||
return uniqueClients.size;
|
||||
}
|
||||
|
||||
function getStats(): StatItem[] {
|
||||
const yearsOfExperience = calculateYearsOfExperience();
|
||||
const uniqueClients = calculateUniqueClients();
|
||||
const caseCount = CASES.length;
|
||||
|
||||
return [
|
||||
{ value: `${uniqueClients}+`, label: '企业客户' },
|
||||
{ value: `${caseCount}+`, label: '成功案例' },
|
||||
{ value: `${caseCount}+`, label: '项目交付' },
|
||||
{ value: `${yearsOfExperience}+`, label: '年团队经验' },
|
||||
];
|
||||
}
|
||||
|
||||
export const STATS: StatItem[] = getStats();
|
||||
export const STATS: StatItem[] = [
|
||||
{ value: '12+', label: '年核心成员行业经验' },
|
||||
{ value: '4', label: '核心服务' },
|
||||
{ value: '4', label: '自研产品' },
|
||||
{ value: '5+', label: '行业覆盖' },
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user