From 40384ec024e7e426a25e0860f9bde424d54db65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Sat, 25 Apr 2026 08:44:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E7=BD=91?= =?UTF-8?q?=E7=AB=99=E9=A1=B5=E9=9D=A2=E7=BB=93=E6=9E=84=E5=92=8C=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增强服务数据模型,添加 challenges 和 outcomes 字段 - 简化统计数据配置,改为静态定义 - 重构多个页面组件,优化代码结构 - 新增产品、服务、解决方案相关的布局和组件 - 更新样式和动画配置 - 优化测试用例和类型定义 - 修复 ESLint 错误:移除不必要的 useEffect 和未使用的导入 --- .gitignore | 5 +- package-lock.json | 662 ++++++++++++++---- package.json | 1 + src/app/(marketing)/about/client.tsx | 55 +- src/app/(marketing)/about/page.test.tsx | 2 +- src/app/(marketing)/about/page.tsx | 2 +- src/app/(marketing)/cases/layout.tsx | 4 +- src/app/(marketing)/cases/page.tsx | 30 +- src/app/(marketing)/contact/layout.tsx | 4 +- src/app/(marketing)/home-content.tsx | 17 +- src/app/(marketing)/layout.tsx | 66 +- .../news/[slug]/NewsDetailClient.tsx | 16 +- src/app/(marketing)/news/layout.tsx | 14 + src/app/(marketing)/news/page.tsx | 4 +- src/app/(marketing)/products/[id]/page.tsx | 199 +----- .../products/[id]/product-detail-client.tsx | 69 ++ src/app/(marketing)/products/layout.tsx | 14 + src/app/(marketing)/products/page.tsx | 34 +- src/app/(marketing)/services/[id]/client.tsx | 344 +++------ src/app/(marketing)/services/[id]/page.tsx | 2 +- src/app/(marketing)/services/layout.tsx | 14 + src/app/(marketing)/solutions/[id]/page.tsx | 31 + .../solutions/[id]/solution-detail-client.tsx | 218 ++++++ src/app/(marketing)/solutions/layout.tsx | 4 +- src/app/(marketing)/solutions/page.tsx | 280 ++------ src/app/(marketing)/team/client.tsx | 4 +- src/app/(marketing)/team/page.tsx | 2 +- src/app/globals.css | 2 +- src/app/layout.tsx | 8 +- src/app/not-found.tsx | 12 +- src/app/privacy/page.tsx | 5 +- src/app/terms/page.tsx | 6 +- src/components/layout/footer.tsx | 6 +- src/components/layout/header.test.tsx | 12 +- src/components/layout/header.tsx | 51 +- src/components/layout/product-footer.tsx | 104 +++ src/components/layout/product-header.tsx | 96 +++ src/components/layout/service-footer.tsx | 98 +++ src/components/layout/service-header.tsx | 90 +++ src/components/products/index.ts | 8 + .../products/product-benefits-section.tsx | 68 ++ .../products/product-cta-section.tsx | 57 ++ .../products/product-features-section.tsx | 137 ++++ .../products/product-hero-section.tsx | 116 +++ .../products/product-overview-section.tsx | 38 + .../products/product-pricing-section.tsx | 140 ++++ .../products/product-process-section.tsx | 75 ++ .../products/product-specs-section.tsx | 41 ++ src/components/sections/about-section.tsx | 76 +- src/components/sections/cases-section.tsx | 127 ++-- .../sections/hero-section-atoms.tsx | 46 +- src/components/sections/hero-section.test.tsx | 6 +- src/components/sections/hero-stats-ssr.tsx | 2 +- .../sections/home-solutions-section.tsx | 1 - src/components/sections/products-section.tsx | 37 +- src/components/sections/services-section.tsx | 31 +- src/components/sections/team-section.tsx | 132 +++- src/components/seo/structured-data.tsx | 13 +- .../services/service-cases-section.tsx | 70 ++ .../services/service-challenges-section.tsx | 64 ++ .../services/service-cta-section.tsx | 75 ++ .../services/service-features-section.tsx | 55 ++ .../services/service-hero-section.tsx | 143 ++++ .../services/service-outcomes-section.tsx | 93 +++ .../services/service-process-section.tsx | 89 +++ .../solutions/accompany-section.tsx | 109 +++ .../solutions/consulting-section.tsx | 108 +++ .../solutions/tech-solution-section.tsx | 110 +++ src/components/ui/floating-cta.tsx | 69 ++ src/components/ui/ripple-button.tsx | 23 +- src/lib/animations.tsx | 13 +- src/lib/constants.test.ts | 2 +- src/lib/constants/index.ts | 2 +- src/lib/constants/navigation.ts | 5 +- src/lib/constants/products.ts | 130 ++-- src/lib/constants/services.ts | 46 ++ src/lib/constants/stats.ts | 33 +- 77 files changed, 3751 insertions(+), 1226 deletions(-) create mode 100644 src/app/(marketing)/news/layout.tsx create mode 100644 src/app/(marketing)/products/[id]/product-detail-client.tsx create mode 100644 src/app/(marketing)/products/layout.tsx create mode 100644 src/app/(marketing)/services/layout.tsx create mode 100644 src/app/(marketing)/solutions/[id]/page.tsx create mode 100644 src/app/(marketing)/solutions/[id]/solution-detail-client.tsx create mode 100644 src/components/layout/product-footer.tsx create mode 100644 src/components/layout/product-header.tsx create mode 100644 src/components/layout/service-footer.tsx create mode 100644 src/components/layout/service-header.tsx create mode 100644 src/components/products/index.ts create mode 100644 src/components/products/product-benefits-section.tsx create mode 100644 src/components/products/product-cta-section.tsx create mode 100644 src/components/products/product-features-section.tsx create mode 100644 src/components/products/product-hero-section.tsx create mode 100644 src/components/products/product-overview-section.tsx create mode 100644 src/components/products/product-pricing-section.tsx create mode 100644 src/components/products/product-process-section.tsx create mode 100644 src/components/products/product-specs-section.tsx create mode 100644 src/components/services/service-cases-section.tsx create mode 100644 src/components/services/service-challenges-section.tsx create mode 100644 src/components/services/service-cta-section.tsx create mode 100644 src/components/services/service-features-section.tsx create mode 100644 src/components/services/service-hero-section.tsx create mode 100644 src/components/services/service-outcomes-section.tsx create mode 100644 src/components/services/service-process-section.tsx create mode 100644 src/components/solutions/accompany-section.tsx create mode 100644 src/components/solutions/consulting-section.tsx create mode 100644 src/components/solutions/tech-solution-section.tsx create mode 100644 src/components/ui/floating-cta.tsx diff --git a/.gitignore b/.gitignore index 0e75c60..f73c3e7 100644 --- a/.gitignore +++ b/.gitignore @@ -294,4 +294,7 @@ findings.md AGENTS.md # dogfood -dogfood-output/ \ No newline at end of file +dogfood-output/ + +# docs +docs/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d283ffe..85836e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index c955d58..2fdc12e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/(marketing)/about/client.tsx b/src/app/(marketing)/about/client.tsx index 4f65859..3de819a 100644 --- a/src/app/(marketing)/about/client.tsx +++ b/src/app/(marketing)/about/client.tsx @@ -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() {

- -

核心价值观

-
+
{values.map((value, idx) => ( ({ jest.mock('@/lib/constants', () => ({ COMPANY_INFO: { name: '四川睿新致远科技有限公司', - shortName: '睿新致遠', + shortName: '睿新致远', description: '以智慧连接数字趋势,以伙伴身份陪您成长', address: '四川省成都市龙泉驿区', email: 'contact@ruixin.com', diff --git a/src/app/(marketing)/about/page.tsx b/src/app/(marketing)/about/page.tsx index 6d06ae2..a20f633 100644 --- a/src/app/(marketing)/about/page.tsx +++ b/src/app/(marketing)/about/page.tsx @@ -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}的品牌故事。我们不只是技术供应商,更是您数字化转型的成长伙伴。以智慧连接数字趋势,以伙伴身份陪您成长。`, }; diff --git a/src/app/(marketing)/cases/layout.tsx b/src/app/(marketing)/cases/layout.tsx index 16d6a3f..d9475c2 100644 --- a/src/app/(marketing)/cases/layout.tsx +++ b/src/app/(marketing)/cases/layout.tsx @@ -1,8 +1,8 @@ import { Metadata } from 'next'; export const metadata: Metadata = { - title: '客户案例 - 睿新致远', - description: '与谁同行,决定能走多远', + title: '客户案例', + description: '了解诺瓦隆在各行业的成功案例,包括制造业、政务服务、酒店管理、智慧农业等领域的数字化转型实践。', }; export default function CasesLayout({ diff --git a/src/app/(marketing)/cases/page.tsx b/src/app/(marketing)/cases/page.tsx index f87c740..473bcb1 100644 --- a/src/app/(marketing)/cases/page.tsx +++ b/src/app/(marketing)/cases/page.tsx @@ -230,23 +230,25 @@ export default function CasesPage() { 让我们与您同行,共创美好未来

- - - - - + - + +
diff --git a/src/app/(marketing)/contact/layout.tsx b/src/app/(marketing)/contact/layout.tsx index 3c16dfb..8f11303 100644 --- a/src/app/(marketing)/contact/layout.tsx +++ b/src/app/(marketing)/contact/layout.tsx @@ -1,6 +1,6 @@ export const metadata = { - title: '联系我们 - 四川睿新致远科技有限公司', - description: '无论您有任何问题或合作意向,我们都很乐意与您交流', + title: '联系我们', + description: '联系四川睿新致远科技有限公司(诺瓦隆)。无论您有任何关于企业数字化转型、软件开发、数据分析等方面的咨询需求,我们都期待与您交流。', }; export default function ContactLayout({ children }: { children: React.ReactNode }) { diff --git a/src/app/(marketing)/home-content.tsx b/src/app/(marketing)/home-content.tsx index da64f7f..d7c1550 100644 --- a/src/app/(marketing)/home-content.tsx +++ b/src/app/(marketing)/home-content.tsx @@ -37,14 +37,6 @@ const ProductsSection = dynamic( } ); -const CasesSection = dynamic( - () => import('@/components/sections/cases-section').then(mod => ({ default: mod.CasesSection })), - { - loading: () => , - 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 (
+ {/* 墨韵分割线 */} +
+ {/* 墨韵分割线 */} +
- + {/* 墨韵分割线 */} +
diff --git a/src/app/(marketing)/layout.tsx b/src/app/(marketing)/layout.tsx index 19525e1..cf9553f 100644 --- a/src/app/(marketing)/layout.tsx +++ b/src/app/(marketing)/layout.tsx @@ -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 = { '/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 ( +
+ + +
+ {children} +
+
+ +
+ ); + } + + // 服务详情页使用独立布局(ServiceHeader + ServiceFooter) + if (isServiceDetail) { + return ( +
+ + +
+ {children} +
+
+ +
+ ); + } + + // 其他页面使用主站布局(Header + Footer) return (
diff --git a/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx b/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx index eb6a528..09053c8 100644 --- a/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx +++ b/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx @@ -114,17 +114,17 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) { )}
- - - - - + - + +
diff --git a/src/app/(marketing)/news/layout.tsx b/src/app/(marketing)/news/layout.tsx new file mode 100644 index 0000000..5d90afc --- /dev/null +++ b/src/app/(marketing)/news/layout.tsx @@ -0,0 +1,14 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: '新闻动态', + description: '了解四川睿新致远科技有限公司(诺瓦隆)的最新动态、产品发布和行业资讯。', +}; + +export default function NewsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(marketing)/news/page.tsx b/src/app/(marketing)/news/page.tsx index d431525..6ccb065 100644 --- a/src/app/(marketing)/news/page.tsx +++ b/src/app/(marketing)/news/page.tsx @@ -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); diff --git a/src/app/(marketing)/products/[id]/page.tsx b/src/app/(marketing)/products/[id]/page.tsx index e5fb91b..08b15d9 100644 --- a/src/app/(marketing)/products/[id]/page.tsx +++ b/src/app/(marketing)/products/[id]/page.tsx @@ -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 ( -
-
-
- -
-
- {product.category} -
-

- {product.title} -

-

- {product.description} -

-
-
-
- -
-
-
-

产品概述

-

- {product.overview} -

-
- -
-

- - 核心功能 -

-
- {product.features.map((feature, index) => ( -
-
- -
- {feature} -
- ))} -
-
- -
-

- - 产品优势 -

-
- {product.benefits.map((benefit, index) => ( -
-
- -
- {benefit} -
- ))} -
-
- -
-

- - 实施流程 -

-
- {product.process.map((step, index) => ( -
-
- {index + 1} -
-
-

{step}

- {index < product.process.length - 1 && ( -
- )} -
-
- ))} -
-
- -
-

- - 技术规格 -

-
- {product.specs.map((spec, index) => ( -
-
- {spec} -
- ))} -
-
- -
-

- - 价格方案 -

-
-
-

基础版

-

{product.pricing.base}

-
    -
  • - - 基础功能模块 -
  • -
  • - - 邮件支持 -
  • -
  • - - 标准报表 -
  • -
-
-
-
- 推荐 -
-

标准版

-

{product.pricing.standard}

-
    -
  • - - 全部功能模块 -
  • -
  • - - 电话支持 -
  • -
  • - - 自定义报表 -
  • -
-
-
-

企业版

-

{product.pricing.enterprise}

-
    -
  • - - 全部功能模块 -
  • -
  • - - 专属客服 -
  • -
  • - - 定制开发 -
  • -
-
-
-
- -
- - -
-
-
-
- ); + return ; } diff --git a/src/app/(marketing)/products/[id]/product-detail-client.tsx b/src/app/(marketing)/products/[id]/product-detail-client.tsx new file mode 100644 index 0000000..792a8aa --- /dev/null +++ b/src/app/(marketing)/products/[id]/product-detail-client.tsx @@ -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 ( +
+

加载中...

+
+ ); + } + + return ( +
+ + + + + + + +
+ ); +} diff --git a/src/app/(marketing)/products/layout.tsx b/src/app/(marketing)/products/layout.tsx new file mode 100644 index 0000000..e9edf68 --- /dev/null +++ b/src/app/(marketing)/products/layout.tsx @@ -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}; +} diff --git a/src/app/(marketing)/products/page.tsx b/src/app/(marketing)/products/page.tsx index 33a1459..51fb8e8 100644 --- a/src/app/(marketing)/products/page.tsx +++ b/src/app/(marketing)/products/page.tsx @@ -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() { ) : ( <>
- {paginatedProducts.map((product, index) => ( - ( + - + {product.category} @@ -161,14 +161,14 @@ export default function ProductsPage() {
- + +
- + ))}
@@ -231,16 +231,12 @@ export default function ProductsPage() {

我们的专业团队可以根据您的业务需求,提供量身定制的产品开发和系统集成服务

- + + +
diff --git a/src/app/(marketing)/services/[id]/client.tsx b/src/app/(marketing)/services/[id]/client.tsx index 55266d5..346d873 100644 --- a/src/app/(marketing)/services/[id]/client.tsx +++ b/src/app/(marketing)/services/[id]/client.tsx @@ -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> = { - Code: () => ( - - - - ), - BarChart3: () => ( - - - - ), - Lightbulb: () => ( - - - - ), - Puzzle: () => ( - - - - ), -}; - -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(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 ( -
-
-
- -
-
-
- {Icon && } -
-
- - 核心业务 - -

- {service.title} -

-
-
-

- {service.description} +

+
+ +
+

+ 想了解我们的服务如何帮助您的企业?

-
-
-
- -
-
- -
-
-
- -
-

- 您可能面临的挑战 -

-
-
- {serviceChallenges.map((challenge, index) => ( -
-

{challenge.title}

-

{challenge.description}

-
- ))} -
-
- -
-
-
- -
-

- 我们如何帮助您 -

-
-

- {service.overview} +

+ 预约一次免费咨询,获取专属数字化转型建议

-
- {service.features.map((feature, index) => ( -
- - {feature} -
- ))} +
+ + 免费咨询 + + + 预约演示 +
-
- -
-
-
- -
-

- 服务流程 -

-
-
- {service.process.map((step, index) => ( -
-
- {index + 1} -
-
-

{step}

-
-
- ))} -
-
- -
-
-
- -
-

- 您将获得的改变 -

-
-
- {serviceOutcomes.map((outcome, index) => ( -
-
- {outcome.value} -
-
{outcome.label}
-
- ))} -
-
-

- {service.benefits.map(b => b).join(';')} -

-
-
- -
-
-
- -
-

- 相关案例 -

-
-
- {relatedCases.map((caseItem) => ( - - - {caseItem.industry} - -

- {caseItem.title} -

-

- {caseItem.description} -

-
- ))} -
-
- -
- - - - - -
-
+
); } + +export function ServiceDetailClient({ service }: ServiceDetailClientProps) { + if (!service) { + return ( +
+

加载中...

+
+ ); + } + + return ( +
+ + + + + + + + +
+ ); +} diff --git a/src/app/(marketing)/services/[id]/page.tsx b/src/app/(marketing)/services/[id]/page.tsx index 1bc9f5d..77b15d7 100644 --- a/src/app/(marketing)/services/[id]/page.tsx +++ b/src/app/(marketing)/services/[id]/page.tsx @@ -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() { diff --git a/src/app/(marketing)/services/layout.tsx b/src/app/(marketing)/services/layout.tsx new file mode 100644 index 0000000..2182be4 --- /dev/null +++ b/src/app/(marketing)/services/layout.tsx @@ -0,0 +1,14 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: '核心业务', + description: '诺瓦隆(四川睿新致远科技)提供软件开发、数据分析、技术咨询、解决方案四大核心业务,助力企业实现全方位数字化转型。', +}; + +export default function ServicesLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(marketing)/solutions/[id]/page.tsx b/src/app/(marketing)/solutions/[id]/page.tsx new file mode 100644 index 0000000..ac7717b --- /dev/null +++ b/src/app/(marketing)/solutions/[id]/page.tsx @@ -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 { + 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 ; +} diff --git a/src/app/(marketing)/solutions/[id]/solution-detail-client.tsx b/src/app/(marketing)/solutions/[id]/solution-detail-client.tsx new file mode 100644 index 0000000..286db2d --- /dev/null +++ b/src/app/(marketing)/solutions/[id]/solution-detail-client.tsx @@ -0,0 +1,218 @@ +'use client'; + +import { SOLUTIONS } from '@/lib/constants/solutions'; +import { PRODUCTS } from '@/lib/constants/products'; +import { StaticLink } from '@/components/ui/static-link'; +import { CheckCircle, Phone } 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 ( +
+

加载中...

+
+ ); + } + + return ( +
+ {/* Section 1: Hero */} +
+
+
+ + {solution.industry} + + +

+ {solution.title} +

+
+ +

{solution.subtitle}

+
+ +

+ {solution.description} +

+
+ +
+ + 预约演示 + + + 获取定制方案 + +
+
+
+
+
+ + {/* Section 2: Challenges */} +
+
+ +

行业痛点

+

您是否也面临这些挑战?

+
+ + {solution.challenges.map((challenge, index) => ( + + +
+ + {index + 1} + +

{challenge}

+
+
+
+ ))} +
+
+
+ + {/* Section 3: Our Solutions */} +
+
+ +

我们的解决方案

+

针对行业痛点,量身定制

+
+ + {solution.solutions.map((item, index) => ( + +
+ +

{item}

+
+
+ ))} +
+
+
+ + {/* Section 4: Related Products */} + {solution.relatedProducts.length > 0 && ( +
+
+
+

+ 推荐产品 +

+

即将上市,敬请期待

+
+
+ {solution.relatedProducts.map((productId) => { + const product = PRODUCTS.find((p) => p.id === productId); + if (!product) {return null;} + return ( + + +
+ + {product.category} + +

+ {product.title} +

+

{product.description}

+
+
+
+ ); + })} +
+
+
+ )} + + {/* Section 6: CTA */} +
+ +
+ + +
+ +
+
+ +

+ 准备好开启{solution.industry}数字化转型了吗? +

+
+ +

+ 我们的专家团队将为您量身定制专属解决方案 +

+
+ +
+ + 免费获取方案 + + + + 电话咨询 + +
+
+
+
+
+
+ ); +} diff --git a/src/app/(marketing)/solutions/layout.tsx b/src/app/(marketing)/solutions/layout.tsx index cef9157..29178f7 100644 --- a/src/app/(marketing)/solutions/layout.tsx +++ b/src/app/(marketing)/solutions/layout.tsx @@ -1,8 +1,8 @@ import { Metadata } from 'next'; export const metadata: Metadata = { - title: '解决方案 - 睿新致远', - description: '三种角色,一种身份——您的成长伙伴', + title: '行业方案', + description: '诺瓦隆为制造业、政务服务、酒店管理、智慧农业等行业提供定制化数字化转型解决方案,助力企业实现数字化升级。', }; export default function SolutionsLayout({ diff --git a/src/app/(marketing)/solutions/page.tsx b/src/app/(marketing)/solutions/page.tsx index 92237cf..a5e0bf0 100644 --- a/src/app/(marketing)/solutions/page.tsx +++ b/src/app/(marketing)/solutions/page.tsx @@ -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() {
- - -
-
- -
-
-

- 模块一:数字化转型咨询 · 参谋伙伴 -

-

- 帮您看清前路,迈对第一步 -

-
-
-
-

- 数字化转型最大的成本,是走错方向的成本。 -

-

- 我们用行业智慧帮您洞察趋势,用理性分析帮您避开陷阱。 -

-

- 不堆砌概念,只帮您想清楚:该不该做、做什么、怎么做。 -

-
- -
-

- - 核心价值点 -

-
-
-
- 行业趋势洞察报告 -
-
-
- 数字化转型成熟度评估 -
-
-
- 个性化实施路径规划 -
-
-
- -
- -
- - - -
-
- -
-
-

- 模块二:信息技术解决方案 · 技术伙伴 -

-

- 让技术真正为业务服务 -

-
-
- -
-

- 我们不追逐“最火”的技术,只选择“最对”的技术。 -

-

- 将前沿技术深度融入您的业务场景,让每一行代码都产生业务价值。 -

-

- 您不必懂技术原理,只需要看见业务在增长。 -

-
- -
-

- - 核心价值点 -

-
-
-
- 业务场景深度调研 -
-
-
- 技术方案定制开发 -
-
-
- 敏捷交付快速迭代 -
-
-
- -
- -
- - - -
-
- -
-
-

- 模块三:长期陪跑服务 · 同行伙伴 -

-

- 交付只是开始,陪伴才是常态 -

-
-
- -
-

- 项目上线那天,是我们真正成为伙伴的开始。 -

-

- 我们建立长效服务机制,定期回访、持续优化、随时响应。 -

-

- 在您需要的时候,我们始终在场。 -

-
- -
-

- - 核心价值点 -

-
-
-
- 专属客户成功经理 -
-
-
- 季度业务复盘会 -
-
-
- 7×24小时响应通道 -
-
-
- -
- -
- + + +
+ {/* 行业解决方案入口 */} +
+
+
+

+ 行业解决方案 +

+

+ 针对不同行业的数字化痛点,提供定制化解决方案 +

+
+
+ {SOLUTIONS.map((solution, index) => ( + + +
+
+ + {solution.industry} + +
+

+ {solution.title} +

+

+ {solution.description} +

+
+ 查看方案 + +
+
+
+
+ ))} +
+
+
+
- - @@ -55,7 +59,7 @@ jest.mock('@/components/ui/button', () => ({ jest.mock('@/lib/constants', () => ({ COMPANY_INFO: { name: '四川睿新致远科技有限公司', - shortName: '睿新致遠', + shortName: '睿新致远', }, NAVIGATION: [ { id: 'home', label: '首页', href: '/' }, diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index b20f9e2..d899824 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -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(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,7 +109,7 @@ function HeaderContent() { aria-label="返回首页" > {COMPANY_INFO.name}
+ +
+
+
+ + ); +} + +export function ProductHeader() { + return ( + + + + ); +} diff --git a/src/components/layout/service-footer.tsx b/src/components/layout/service-footer.tsx new file mode 100644 index 0000000..44290bf --- /dev/null +++ b/src/components/layout/service-footer.tsx @@ -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 ( +
+ {/* 顶部装饰线 */} +
+ + {/* CTA 区域 */} +
+
+

+ 准备开始您的数字化转型之旅? +

+

+ 我们的专业团队将根据您的需求,提供量身定制的服务方案 +

+
+ + + 免费咨询 + + + + + 查看全部服务 + + +
+
+
+ + {/* 底部信息 */} +
+
+
+
+ + © {new Date().getFullYear()} {COMPANY_INFO.shortName}. All rights reserved. + +
+ + +
+
+
+
+ ); +} diff --git a/src/components/layout/service-header.tsx b/src/components/layout/service-header.tsx new file mode 100644 index 0000000..3159fcf --- /dev/null +++ b/src/components/layout/service-header.tsx @@ -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 ( + +
+
+
+
+ + Novalon + + + + 立即咨询 + +
+ + + +
+
+
+
+ ); +} + +export function ServiceHeader() { + return ( + + + + ); +} diff --git a/src/components/products/index.ts b/src/components/products/index.ts new file mode 100644 index 0000000..d9def21 --- /dev/null +++ b/src/components/products/index.ts @@ -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'; diff --git a/src/components/products/product-benefits-section.tsx b/src/components/products/product-benefits-section.tsx new file mode 100644 index 0000000..21c5806 --- /dev/null +++ b/src/components/products/product-benefits-section.tsx @@ -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 ( + + + {numberInfo && ( +
+ + + +
+ )} +

{benefit}

+
+
+ ); +} + +export function ProductBenefitsSection({ product }: ProductBenefitsSectionProps) { + const ref = useRef(null); + + return ( +
+
+ +

+ 产品优势 +

+
+ + + {product.benefits.map((benefit, index) => ( + + ))} + + +
+
+ ); +} diff --git a/src/components/products/product-cta-section.tsx b/src/components/products/product-cta-section.tsx new file mode 100644 index 0000000..87d08b5 --- /dev/null +++ b/src/components/products/product-cta-section.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { Phone } from 'lucide-react'; +import { InkReveal, FadeUp, FloatingElement, RippleButton } from '@/lib/animations'; + +export function ProductCTASection() { + return ( +
+ {/* 右上角装饰圆形 */} + +
+ + + {/* 左下角装饰圆形 */} + +
+ + +
+
+ +

+ 产品即将上市 +

+
+ + +

+ 我们正在全力研发中,敬请期待。如有合作意向,欢迎联系我们。 +

+
+ + +
+ + 联系我们 + + + + + 电话咨询 + +
+
+
+
+
+ ); +} diff --git a/src/components/products/product-features-section.tsx b/src/components/products/product-features-section.tsx new file mode 100644 index 0000000..eec92da --- /dev/null +++ b/src/components/products/product-features-section.tsx @@ -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 ( +
+
+
+ {/* 左侧:编号和文字 */} +
+ {/* 编号 - InkReveal 模糊揭示 */} + + + {number} + + + + {/* 功能标题 - ScrollReveal + slideInLeft */} + +

+ {title} +

+
+ + {/* 功能描述 - FadeUp */} + {description && ( + +

+ {description} +

+
+ )} +
+ + {/* 右侧:InkCard 弹簧物理悬浮 + PulseElement 脉冲同心圆 */} +
+ + +
+
+
+ + +
+
+
+
+ ); +} + +export function ProductFeaturesSection({ product }: ProductFeaturesSectionProps) { + const sectionRef = useRef(null); + + return ( +
+ {/* 标题 - ScrollReveal + inkRevealVariants 模糊揭示 */} +
+
+ +

+ 核心功能 +

+
+ +

+ 全方位覆盖企业核心业务场景 +

+
+
+
+ + {/* 功能列表 */} + {product.features.map((feature, index) => ( + + + {(index === 1 || index === 3) && ( +
+
+
+

+ 想了解更多功能细节? +

+

+ 预约一次 30 分钟在线演示,了解产品如何适配您的业务 +

+
+ + 预约演示 + + + 获取方案 + +
+
+
+
+ )} +
+ ))} +
+ ); +} diff --git a/src/components/products/product-hero-section.tsx b/src/components/products/product-hero-section.tsx new file mode 100644 index 0000000..91538b7 --- /dev/null +++ b/src/components/products/product-hero-section.tsx @@ -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(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 ( +
+ {/* 背景特效 */} + + + + {/* 内容 */} +
+
+ {/* 分类标签 - 印章按压效果 */} + + 即将上市 + + + {/* 产品名称 - 模糊揭示入场 */} + +

+ {product.title} +

+
+ + {/* 价值主张 - 模糊揭示入场 */} + +

+ {product.description} +

+
+ + {/* CTA 按钮 */} + +
+ + 敬请期待 + +
+
+ +
+
+ + {/* 滚动指示器 - 浮动元素包裹 */} + + +
+ +
+
+
+
+ ); +} diff --git a/src/components/products/product-overview-section.tsx b/src/components/products/product-overview-section.tsx new file mode 100644 index 0000000..b672e65 --- /dev/null +++ b/src/components/products/product-overview-section.tsx @@ -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 ( +
+
+
+ {/* 标题 - 左对齐,slideInLeft 入场 */} + +

+ 产品概述 +

+
+ + {/* 朱砂红装饰线 - InkReveal 入场 */} + +
+ + + {/* 概述文字 - InkReveal 包裹整段,替代 TextReveal */} + +

+ {product.overview} +

+
+
+
+
+ ); +} diff --git a/src/components/products/product-pricing-section.tsx b/src/components/products/product-pricing-section.tsx new file mode 100644 index 0000000..b0f38e8 --- /dev/null +++ b/src/components/products/product-pricing-section.tsx @@ -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 = ( +
+ {isRecommended && ( + +
+ 推荐 +
+
+ )} + +

{name}

+ +

+ {description} +

+ +
    + {features.map((feature, index) => ( +
  • + + + {feature} + +
  • + ))} +
+ + + {ctaText} + +
+ ); + + if (isRecommended) { + return ( + + + {cardContent} + + + ); + } + + return ( + + {cardContent} + + ); +} + +export function ProductPricingSection({ product }: ProductPricingSectionProps) { + return ( +
+
+ +

+ 版本对比 +

+
+ + +

+ 根据企业规模和需求,选择最适合的部署方案 +

+
+ +
+ + + +
+
+
+ ); +} diff --git a/src/components/products/product-process-section.tsx b/src/components/products/product-process-section.tsx new file mode 100644 index 0000000..035c0d6 --- /dev/null +++ b/src/components/products/product-process-section.tsx @@ -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 ( + +
+ +
+ {index + 1} +
+
+ {index < total - 1 && ( +
+ )} +
+ + +

{title}

+ {description && ( +

{description}

+ )} +
+ + ); +} + +export function ProductProcessSection({ product }: ProductProcessSectionProps) { + const ref = useRef(null); + + return ( +
+
+
+ +

+ 实施流程 +

+
+ + + {product.process.map((step, index) => ( + + ))} + +
+
+
+ ); +} diff --git a/src/components/products/product-specs-section.tsx b/src/components/products/product-specs-section.tsx new file mode 100644 index 0000000..0ac2861 --- /dev/null +++ b/src/components/products/product-specs-section.tsx @@ -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(null); + + return ( +
+
+ +

+ 技术规格 +

+
+ + + {product.specs.map((spec, index) => ( + + +
+ {spec} + + + ))} + +
+
+ ); +} diff --git a/src/components/sections/about-section.tsx b/src/components/sections/about-section.tsx index fa2950e..ae13cda 100644 --- a/src/components/sections/about-section.tsx +++ b/src/components/sections/about-section.tsx @@ -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 (
+ {/* 网格背景 */}
+
- + {/* 标题 */}

关于 {COMPANY_INFO.shortName} @@ -34,43 +42,55 @@ export function AboutSection() {

-
+ {/* 品牌理念引用 */} +

“企业需要的,不是一个高高在上的‘专家’,也不是一个做完就跑的‘卖家’,而是一个能坐下来、一起想办法的同行者。”

我们只做一件事:成为您数字化转型路上,信得过的成长伙伴。

-
- - - {STATS.map((stat, idx) => ( - - -
{stat.value}
-
{stat.label}
-
-
- ))}
+ {/* 核心价值观 */} + {VALUES.map((value) => ( +
+
+ +
+

{value.title}

+

{value.description}

+
+ ))} +
+ + {/* CTA */} + - + + +
diff --git a/src/components/sections/cases-section.tsx b/src/components/sections/cases-section.tsx index 2b81ae0..df0f69f 100644 --- a/src/components/sections/cases-section.tsx +++ b/src/components/sections/cases-section.tsx @@ -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> = { + '酒店管理': Hotel, + '制造业': Factory, + '政务服务': Landmark, + '智慧农业': Sprout, +}; + +const industryColorMap: Record = { + '酒店管理': { 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 ( -
+
- +
{ - }} - onSwipeRight={() => { - }} + onSwipeLeft={() => {}} + onSwipeRight={() => {}} className="md:hidden" > -
- {CASES.map((caseItem, index) => ( - - - -
- -
- - {caseItem.industry} - +
+ {CASES.map((caseItem, index) => { + const IndustryIcon = industryIconMap[caseItem.industry] || Building2; + const colors = industryColorMap[caseItem.industry] || defaultColors; + return ( + + + + {/* 行业图标区域 - 使用差异化配色 */} +
+ {/* 装饰性几何元素 */} +
+
+ + + +
+ + {caseItem.industry} + +
+ + {/* 成果数据标签 */} + {caseItem.results && caseItem.results.length > 0 && ( +
+ {caseItem.results.slice(0, 2).map((result, i) => ( + + + {result.value} + + ))} +
+ )}
-
- -
- - {caseItem.client} -
-

- {caseItem.title} -

-

- {caseItem.description} -

-
- - - - ))} + + +
+
+ {caseItem.client} +
+

+ {caseItem.title} +

+

+ {caseItem.description} +

+
+ 查看详情 + +
+ + + + + ); + })}
, HeroFeatures: () =>
安全可靠高效便捷专业服务
, @@ -129,7 +129,7 @@ describe('HeroSection', () => { it('should render company name', () => { render(} />); - expect(screen.getByText('睿新致遠')).toBeInTheDocument(); + expect(screen.getByText('睿新致远')).toBeInTheDocument(); }); it('should render features', () => { diff --git a/src/components/sections/hero-stats-ssr.tsx b/src/components/sections/hero-stats-ssr.tsx index bd0dff4..f565977 100644 --- a/src/components/sections/hero-stats-ssr.tsx +++ b/src/components/sections/hero-stats-ssr.tsx @@ -2,7 +2,7 @@ import { STATS } from '@/lib/constants'; export function HeroStatsSSR() { return ( -
+
{STATS.map((stat) => (
-
+

我们的产品

@@ -35,15 +37,13 @@ export function ProductsSection() { {PRODUCTS.length > 0 ? (
- {PRODUCTS.map((product, idx) => ( - ( + - + {product.category} @@ -85,14 +85,14 @@ export function ProductsSection() {
- + +
-
+ ))}
) : ( @@ -119,15 +119,12 @@ export function ProductsSection() {

我们的专业团队可以根据您的业务需求,提供量身定制的产品开发和系统集成服务

- + + +
diff --git a/src/components/sections/services-section.tsx b/src/components/sections/services-section.tsx index 4b0e63b..14bfe70 100644 --- a/src/components/sections/services-section.tsx +++ b/src/components/sections/services-section.tsx @@ -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> = { @@ -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" > +

我们的 核心业务

-

+

专业技术团队,为您提供全方位的数字化解决方案

{SERVICES.length > 0 ? (
- {SERVICES.map((service, index) => { + {SERVICES.map((service) => { const Icon = iconMap[service.icon]; return ( - - +
{Icon && } @@ -67,7 +66,7 @@ export function ServicesSection() { - + ); })}
@@ -83,12 +82,12 @@ export function ServicesSection() { transition={{ duration: 0.6, delay: 0.4 }} className="text-center mt-12" > - + + +
diff --git a/src/components/sections/team-section.tsx b/src/components/sections/team-section.tsx index 2d5235b..5328dd5 100644 --- a/src/components/sections/team-section.tsx +++ b/src/components/sections/team-section.tsx @@ -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 (
+ {/* 背景装饰 */} +
+
+ {/* 标题区 */} 团队

- 核心团队从事技术咨询、企业数字化等行业 12 年+,开发团队成员来自于多个大型传统 IT 企业,具备扎实的工程能力和丰富的行业经验。 + 来自大型IT企业的核心团队,既懂技术又懂业务,能深入理解客户场景,提供真正落地的解决方案。

-
- {TEAM_HIGHLIGHTS.map((item, idx) => { - const Icon = item.icon; + {/* 团队数据概览 */} + + {TEAM_STATS.map((stat) => ( +
+
+ {stat.value} +
+
{stat.label}
+
+ ))} +
+ + {/* 团队成员卡片 */} +
+ {TEAM_MEMBERS.map((member, idx) => { + const Icon = member.icon; return ( -
-
- +
+ {/* 头像区 */} +
+
+ +
+
+

{member.name}

+ {member.initials} +
+
+ + {/* 简介 */} +

{member.bio}

+ + {/* 专业领域标签 */} +
+ {member.specialties.map((spec) => ( + + {spec} + + ))}
-

{item.title}

-

{item.description}

); })}
+ {/* CTA */} - + + +
diff --git a/src/components/seo/structured-data.tsx b/src/components/seo/structured-data.tsx index 1a69dca..5937e8a 100644 --- a/src/components/seo/structured-data.tsx +++ b/src/components/seo/structured-data.tsx @@ -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 ( diff --git a/src/components/services/service-cases-section.tsx b/src/components/services/service-cases-section.tsx new file mode 100644 index 0000000..f665f0f --- /dev/null +++ b/src/components/services/service-cases-section.tsx @@ -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 = { + 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(null); + const relatedCaseIds = SERVICE_CASE_MAP[service.id] || []; + const displayCases = relatedCaseIds + .map(id => CASES.find(c => c.id === id)) + .filter((c): c is NonNullable => Boolean(c)); + + return ( +
+
+ {/* 标题区 */} + +

+ 相关案例 +

+
+ + {/* 案例卡片网格 */} + + {displayCases.map((caseItem) => ( + + + + {/* 行业标签 */} + + {caseItem.industry} + + {/* 案例标题 */} +

+ {caseItem.title} +

+ {/* 案例描述 - 限制两行 */} +

+ {caseItem.description} +

+
+
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/services/service-challenges-section.tsx b/src/components/services/service-challenges-section.tsx new file mode 100644 index 0000000..5e2026c --- /dev/null +++ b/src/components/services/service-challenges-section.tsx @@ -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(null); + + return ( +
+
+ {/* 标题区 */} + +

+ 您可能面临的挑战 +

+
+ + {/* 朱砂红装饰线 */} +
+ + {/* 副标题描述 */} + +

+ 了解您在当前业务场景中可能遇到的痛点,我们已准备好针对性的解决方案。 +

+
+ + {/* 挑战卡片网格 */} + + {service.challenges.map((challenge, index) => ( + + +

+ {challenge.title} +

+

+ {challenge.description} +

+
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/services/service-cta-section.tsx b/src/components/services/service-cta-section.tsx new file mode 100644 index 0000000..09b2fe7 --- /dev/null +++ b/src/components/services/service-cta-section.tsx @@ -0,0 +1,75 @@ +'use client'; + +/** + * ServiceCTASection - 行动号召区域 + * + * 朱砂红渐变背景,配合 FloatingElement 装饰圆形, + * InkReveal 包裹标题,FadeUp 包裹描述和按钮组。 + * 主按钮链接到联系页,次按钮链接到服务列表页。 + */ +import { Phone } from 'lucide-react'; +import { InkReveal, FadeUp, FloatingElement, RippleButton } from '@/lib/animations'; + +export function ServiceCTASection() { + return ( +
+ {/* 右上角装饰圆形 */} + +
+ + + {/* 左下角装饰圆形 */} + +
+ + +
+
+ {/* 标题 */} + +

+ 免费获取企业数字化诊断报告 +

+
+ + {/* 描述文字 */} + +

+ 我们的专家团队将为您量身定制数字化转型方案,助力企业降本增效 +

+
+ + {/* 按钮组 */} + +
+ + 免费获取 + + + + 预约演示 + + + + + 电话咨询 + +
+
+
+
+
+ ); +} diff --git a/src/components/services/service-features-section.tsx b/src/components/services/service-features-section.tsx new file mode 100644 index 0000000..1fbabdf --- /dev/null +++ b/src/components/services/service-features-section.tsx @@ -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(null); + + return ( +
+
+ {/* 标题区 - 居中 */} + +

+ 我们如何帮助您 +

+
+ + {/* 服务概览 */} + +

+ {service.overview} +

+
+ + {/* 功能列表 */} + + {service.features.map((feature, index) => ( + +
+ + + {feature} + +
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/services/service-hero-section.tsx b/src/components/services/service-hero-section.tsx new file mode 100644 index 0000000..7ac2a60 --- /dev/null +++ b/src/components/services/service-hero-section.tsx @@ -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(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 ( +
+ {/* 背景特效层 */} + + + + {/* 主内容区 */} +
+
+ {/* 分类标签 - 印章按压效果 */} + + 核心业务 + + + {/* 服务标题 - 模糊揭示入场 */} + +

+ {service.title} +

+
+ + {/* 服务描述 - 模糊揭示入场 */} + +

+ {service.description} +

+
+ + {/* CTA 按钮组 - 涟漪效果 */} + +
+ + 预约演示 + + + 免费咨询 + + + 了解详情 + +
+
+ +
+
+ + {/* 滚动指示器 - 浮动元素包裹 */} + + +
+ +
+
+
+
+ ); +} diff --git a/src/components/services/service-outcomes-section.tsx b/src/components/services/service-outcomes-section.tsx new file mode 100644 index 0000000..3705229 --- /dev/null +++ b/src/components/services/service-outcomes-section.tsx @@ -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 ( + + + {/* 数字动画区域 */} +
+ {numberValue !== null ? ( + + + {/* 保留非数字后缀(如 %、+ 等) */} + {outcome.value.replace(/(\d+)/, '')} + + ) : ( + + {outcome.value} + + )} +
+ {/* 标签文字 */} +

{outcome.label}

+
+
+ ); +} + +export function ServiceOutcomesSection({ service }: ServiceOutcomesSectionProps) { + const sectionRef = useRef(null); + + return ( +
+
+ {/* 标题区 */} + +

+ 您将获得的改变 +

+
+ + {/* 成果卡片网格 */} + + {service.outcomes.map((outcome, index) => ( + + ))} + + + {/* benefits 汇总文本 */} + +
+

+ {service.benefits.join(';')} +

+
+
+ +
+
+ ); +} diff --git a/src/components/services/service-process-section.tsx b/src/components/services/service-process-section.tsx new file mode 100644 index 0000000..007c0ba --- /dev/null +++ b/src/components/services/service-process-section.tsx @@ -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 ( + + {/* 左侧:编号圆形 + 渐变连接线 */} +
+ +
+ {index + 1} +
+
+ {/* 最后一步不显示连接线 */} + {index < total - 1 && ( +
+ )} +
+ + {/* 右侧:步骤标题和描述 */} + +

{title}

+ {description && ( +

{description}

+ )} +
+ + ); +} + +export function ServiceProcessSection({ service }: ServiceProcessSectionProps) { + const sectionRef = useRef(null); + + return ( +
+
+
+ {/* 标题区 - 居中 */} + +

+ 服务流程 +

+
+ + {/* 步骤列表 */} + + {service.process.map((step, index) => ( + + ))} + +
+
+
+ ); +} diff --git a/src/components/solutions/accompany-section.tsx b/src/components/solutions/accompany-section.tsx new file mode 100644 index 0000000..3ccb070 --- /dev/null +++ b/src/components/solutions/accompany-section.tsx @@ -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 ( + + {/* 标题区域:图标 + 标题 + 副标题 */} +
+
+ +
+
+ +

+ 模块三:长期陪跑服务 · 同行伙伴 +

+
+ +

+ 交付只是开始,陪伴才是常态 +

+
+
+
+ + {/* 描述段落 */} +
+ +

+ 项目上线那天,是我们真正成为伙伴的开始。 +

+
+ +

+ 我们建立长效服务机制,定期回访、持续优化、随时响应。 +

+
+ +

+ 在您需要的时候,我们始终在场。 +

+
+
+ + {/* 核心价值点 */} +
+ +

+ + 核心价值点 +

+
+ + {valuePoints.map((point) => ( + +
+
+ {point} +
+ + ))} + +
+ + {/* CTA 按钮 */} + +
+ +
+
+ + ); +} diff --git a/src/components/solutions/consulting-section.tsx b/src/components/solutions/consulting-section.tsx new file mode 100644 index 0000000..f8686c9 --- /dev/null +++ b/src/components/solutions/consulting-section.tsx @@ -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 ( + + {/* 标题区域:图标 + 标题 + 副标题 */} +
+
+ +
+
+ +

+ 模块一:数字化转型咨询 · 参谋伙伴 +

+
+ +

+ 帮您看清前路,迈对第一步 +

+
+
+
+ + {/* 描述段落 */} +
+ +

+ 数字化转型最大的成本,是走错方向的成本。 +

+
+ +

+ 我们用行业智慧帮您洞察趋势,用理性分析帮您避开陷阱。 +

+
+ +

+ 不堆砌概念,只帮您想清楚:该不该做、做什么、怎么做。 +

+
+
+ + {/* 核心价值点 */} +
+ +

+ + 核心价值点 +

+
+ + {valuePoints.map((point) => ( + +
+
+ {point} +
+ + ))} + +
+ + {/* CTA 按钮 */} + +
+ +
+
+ + ); +} diff --git a/src/components/solutions/tech-solution-section.tsx b/src/components/solutions/tech-solution-section.tsx new file mode 100644 index 0000000..34dfc72 --- /dev/null +++ b/src/components/solutions/tech-solution-section.tsx @@ -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 ( + + {/* 标题区域:图标 + 标题 + 副标题 */} +
+
+ +
+
+ +

+ 模块二:信息技术解决方案 · 技术伙伴 +

+
+ +

+ 让技术真正为业务服务 +

+
+
+
+ + {/* 描述段落 */} +
+ +

+ 我们不追逐“最火”的技术,只选择“最对”的技术。 +

+
+ +

+ 将前沿技术深度融入您的业务场景,让每一行代码都产生业务价值。 +

+
+ +

+ 您不必懂技术原理,只需要看见业务在增长。 +

+
+
+ + {/* 核心价值点 */} +
+ +

+ + 核心价值点 +

+
+ + {valuePoints.map((point) => ( + +
+
+ {point} +
+ + ))} + +
+ + {/* CTA 按钮(outline 样式) */} + +
+ +
+
+ + ); +} diff --git a/src/components/ui/floating-cta.tsx b/src/components/ui/floating-cta.tsx new file mode 100644 index 0000000..82e91dd --- /dev/null +++ b/src/components/ui/floating-cta.tsx @@ -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 ( + + {isVisible && ( + + {/* 关闭按钮 */} + + + {/* 主 CTA 按钮 */} + + + {/* 脉冲光晕 */} + + + 立即咨询 + + + + )} + + ); +} diff --git a/src/components/ui/ripple-button.tsx b/src/components/ui/ripple-button.tsx index bbdcdbe..c34a52c 100644 --- a/src/components/ui/ripple-button.tsx +++ b/src/components/ui/ripple-button.tsx @@ -50,19 +50,20 @@ export interface RippleButtonProps rippleColor?: string; rippleDuration?: number; children?: React.ReactNode; - onClick?: (e: React.MouseEvent) => void; - onKeyDown?: (e: React.KeyboardEvent) => void; + onClick?: (e: React.MouseEvent) => void; + onKeyDown?: (e: React.KeyboardEvent) => void; className?: string; disabled?: boolean; + href?: string; } -const RippleButton = React.forwardRef( - ({ className, variant, size, rippleColor, rippleDuration = 800, onClick, children, disabled, ...props }, ref) => { +const RippleButton = React.forwardRef( + ({ className, variant, size, rippleColor, rippleDuration = 800, onClick, children, disabled, href, ...props }, ref) => { const [ripples, setRipples] = React.useState([]); - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: React.MouseEvent) => { 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( return 'rgba(255, 255, 255, 0.4)'; }; + const Component = href ? motion.a : motion.button; + const linkProps = href ? { href } : {}; + return ( - } 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( /> ))} - + ); } ); diff --git a/src/lib/animations.tsx b/src/lib/animations.tsx index 8d6102c..498a30e 100644 --- a/src/lib/animations.tsx +++ b/src/lib/animations.tsx @@ -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>([]); - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: React.MouseEvent) => { 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 ( - {children} {ripples.map((ripple) => ( @@ -287,7 +292,7 @@ export function RippleButton({ children, onClick, className = '', rippleColor = }} /> ))} - + ); } diff --git a/src/lib/constants.test.ts b/src/lib/constants.test.ts index 974fe90..fb742b2 100644 --- a/src/lib/constants.test.ts +++ b/src/lib/constants.test.ts @@ -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', () => { diff --git a/src/lib/constants/index.ts b/src/lib/constants/index.ts index e7f9e0a..ac1e73c 100644 --- a/src/lib/constants/index.ts +++ b/src/lib/constants/index.ts @@ -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'; diff --git a/src/lib/constants/navigation.ts b/src/lib/constants/navigation.ts index f3b60ed..66203e6 100644 --- a/src/lib/constants/navigation.ts +++ b/src/lib/constants/navigation.ts @@ -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' }, ]; diff --git a/src/lib/constants/products.ts b/src/lib/constants/products.ts index 292227e..fe4db50 100644 --- a/src/lib/constants/products.ts +++ b/src/lib/constants/products.ts @@ -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; diff --git a/src/lib/constants/services.ts b/src/lib/constants/services.ts index 7a2db5a..6f2b9d5 100644 --- a/src/lib/constants/services.ts +++ b/src/lib/constants/services.ts @@ -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: [ '行业调研:深入研究行业趋势和客户需求', '方案设计:设计符合行业特点的解决方案', diff --git a/src/lib/constants/stats.ts b/src/lib/constants/stats.ts index 50ca99c..6f91fae 100644 --- a/src/lib/constants/stats.ts +++ b/src/lib/constants/stats.ts @@ -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: '行业覆盖' }, +];