This commit is contained in:
parent
e67110eafd
commit
a170b1372b
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"files": [
|
||||
"src/index.html"
|
||||
],
|
||||
"insertBefore": "</body>",
|
||||
"commentSyntax": "html",
|
||||
"cspChecked": true
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# AGENTS.md
|
||||
|
||||
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
|
||||
|
||||
`corp-mp` is a **Taro 4.2** cross-platform app (React 18 + TypeScript + SCSS, compiled with **Vite**). The primary target is the WeChat mini-program (`weapp`); Taro also compiles it to H5, Alipay, Swan, TT, QQ, JD, RN, and Harmony.
|
||||
|
||||
## Commands
|
||||
|
||||
Package manager is **pnpm** (a `pnpm-lock.yaml` is committed).
|
||||
|
||||
```bash
|
||||
pnpm install # install deps
|
||||
pnpm dev:weapp # WeChat mini-program dev build with --watch → ./dist
|
||||
pnpm build:weapp # production mini-program build → ./dist
|
||||
pnpm dev:h5 / build:h5 # H5 target
|
||||
```
|
||||
|
||||
Other targets follow `pnpm dev:<platform>` / `pnpm build:<platform>` (`alipay`, `swan`, `tt`, `qq`, `jd`, `rn`, `harmony-hybrid`).
|
||||
|
||||
**Running the mini-program:** builds output to `./dist`. Open `./dist` in WeChat DevTools (`project.config.json` already points `miniprogramRoot` there; appId `wx6d4f6f29c41aff93`).
|
||||
|
||||
**There is no test runner and no lint script in `package.json`.** ESLint (`taro/react`) and Stylelint are configured but run manually. The only enforced gate is a Husky `commit-msg` hook running **commitlint** (Conventional Commits) — non-conforming commit messages are rejected.
|
||||
|
||||
## Architecture
|
||||
|
||||
The app is a thin native shell around a business H5, with a native payment bridge. Two pages, registered in `src/app.config.ts`:
|
||||
|
||||
**1. `pages/index/index.tsx` — auth + WebView host.** On load it calls `Taro.login()` to get the WeChat `code`, then renders a full-screen `<WebView>` pointing at the business H5 (`http://niubsw.com/...?mpCode=<code>`). All business UI lives in that remote H5, not in this repo.
|
||||
|
||||
**2. `pages/pay/index.tsx` — native payment bridge.** The H5 cannot invoke WeChat JSAPI pay while running inside the mini-program WebView, so it hands off to this native page. The handoff contract:
|
||||
- The H5 detects it's inside the mini-program and calls `wxSdk.miniProgram.navigateTo({ url: '/pages/pay/index?order=<b64>&formInfo=<b64>' })`.
|
||||
- Params are **base64-encoded JSON**, produced H5-side by `utf8ToBase64(v) = btoa(unescape(encodeURIComponent(JSON.stringify(v))))`.
|
||||
- `order` already contains the complete WeChat JSAPI fields (`timeStamp`/`nonceStr`/`package`/`signType`/`paySign`). The page decodes it, auto-fires `Taro.requestPayment`, then `navigateBack()`s to the H5 on success. No backend call is made here.
|
||||
- `temp.txt` (repo root) holds the H5-side snippet documenting this contract — keep the two sides in sync.
|
||||
|
||||
**`src/utils/base64.ts`** is the strict inverse of the H5 encoder. It hand-rolls a base64 decoder because **weapp has no global `atob`**, and normalizes `+`→space mangling from URL routing. Reuse `decodeOrderParam<T>(raw)` for any future base64 route params.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Page = folder** under `src/pages/<name>/` with `index.tsx` + `index.scss` + `index.config.ts`. New pages **must** be added to the `pages` array in `src/app.config.ts` or they won't route.
|
||||
- **Lifecycle:** use Taro hooks — `useLoad` for page init (reads route params via `Taro.getCurrentInstance().router?.params`), `useLaunch` for app init — not bare `useEffect`.
|
||||
- **Native API calls must be env-guarded:** wrap `weapp`-only APIs (e.g. `Taro.requestPayment`) in `if (Taro.getEnv() === Taro.ENV_TYPE.WEAPP)` with a fallback branch for other targets.
|
||||
- **Styling:** SCSS with BEM (`block__element--modifier`); CSS Modules are disabled. Design width is `750`, so author dimensions in `px` — Taro converts to `rpx`/`rem` at compile time.
|
||||
- **Imports:** `@/*` is aliased to `src/*` (see `tsconfig.json`).
|
||||
- `strictNullChecks`, `noUnusedLocals`, and `noUnusedParameters` are on; `noImplicitAny` is off.
|
||||
|
||||
A more verbose Chinese-language version of these notes lives in `GEMINI.md`.
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
name: 门店管理服务
|
||||
description: 面向线下门店经营负责人的蓝色科技感业务落地页
|
||||
colors:
|
||||
midnight-navy: "#05256E"
|
||||
service-blue: "#0B57D0"
|
||||
action-blue: "#1677FF"
|
||||
signal-cyan: "#5FC8FF"
|
||||
canvas-blue: "#F3F7FF"
|
||||
pure-white: "#FFFFFF"
|
||||
trust-ink: "#102A56"
|
||||
supporting-ink: "#425F8C"
|
||||
soft-border: "#D7E4FF"
|
||||
typography:
|
||||
display:
|
||||
fontFamily: "-apple-system, BlinkMacSystemFont, PingFang SC, Microsoft YaHei, sans-serif"
|
||||
fontSize: "56px"
|
||||
fontWeight: 700
|
||||
lineHeight: 1.18
|
||||
letterSpacing: "-1px"
|
||||
title:
|
||||
fontFamily: "-apple-system, BlinkMacSystemFont, PingFang SC, Microsoft YaHei, sans-serif"
|
||||
fontSize: "32px"
|
||||
fontWeight: 700
|
||||
lineHeight: 1.4
|
||||
body:
|
||||
fontFamily: "-apple-system, BlinkMacSystemFont, PingFang SC, Microsoft YaHei, sans-serif"
|
||||
fontSize: "28px"
|
||||
fontWeight: 400
|
||||
lineHeight: 1.65
|
||||
label:
|
||||
fontFamily: "-apple-system, BlinkMacSystemFont, PingFang SC, Microsoft YaHei, sans-serif"
|
||||
fontSize: "24px"
|
||||
fontWeight: 600
|
||||
lineHeight: 1.4
|
||||
rounded:
|
||||
sm: "12px"
|
||||
md: "24px"
|
||||
lg: "36px"
|
||||
pill: "999px"
|
||||
spacing:
|
||||
xs: "8px"
|
||||
sm: "16px"
|
||||
md: "24px"
|
||||
lg: "32px"
|
||||
xl: "48px"
|
||||
section: "72px"
|
||||
components:
|
||||
call-button:
|
||||
backgroundColor: "{colors.action-blue}"
|
||||
textColor: "{colors.pure-white}"
|
||||
rounded: "{rounded.pill}"
|
||||
height: "88px"
|
||||
padding: "0 36px"
|
||||
industry-mark:
|
||||
backgroundColor: "{colors.canvas-blue}"
|
||||
textColor: "{colors.service-blue}"
|
||||
rounded: "{rounded.md}"
|
||||
size: "112px"
|
||||
service-item:
|
||||
backgroundColor: "{colors.pure-white}"
|
||||
textColor: "{colors.trust-ink}"
|
||||
rounded: "{rounded.md}"
|
||||
padding: "28px"
|
||||
---
|
||||
|
||||
# Design System: 门店管理服务
|
||||
|
||||
## Overview
|
||||
|
||||
**Creative North Star: "清晨开店前的蓝色灯箱"**
|
||||
|
||||
页面像门店营业前刚亮起的蓝色灯箱:清晰、稳定、有秩序。深蓝主视觉承担品牌识别,纯白内容面保证业务说明快速可读,青蓝只用于光线和细节提示。
|
||||
|
||||
整个系统拒绝地图工具、表单后台和政务门户的视觉语言,也拒绝用大量发光、玻璃面板或彩虹图标堆叠科技感。布局应让经营负责人在短时间内认出自己的行业、理解服务内容并完成电话咨询。
|
||||
|
||||
**Key Characteristics:**
|
||||
|
||||
- 深蓝主视觉与浅色信息面的明确分区,不用面板覆盖主视觉。
|
||||
- 门店剪影完整呈现,真实业务文案始终由原生组件渲染。
|
||||
- 行业标识收纳在统一横向信息带中,服务说明舒展,唯一主操作固定在底部。
|
||||
- 轻量入场动效有静态降级,不阻塞内容显示。
|
||||
|
||||
## Colors
|
||||
|
||||
以深蓝建立信任,以亮蓝承载行动,以青蓝提供有限的光感;内容区保持接近纯白,正文使用带蓝相的深色文字。
|
||||
|
||||
### Primary
|
||||
|
||||
- **午夜门楣蓝:** 用于原生导航栏、首屏深色背景与最深层光影。
|
||||
- **服务蓝:** 用于主视觉过渡、行业标识和服务序号。
|
||||
- **行动蓝:** 仅用于拨号按钮和关键交互状态。
|
||||
|
||||
### Secondary
|
||||
|
||||
- **信号青:** 只用于插画轮廓、光带和小面积高光,不承载正文。
|
||||
|
||||
### Neutral
|
||||
|
||||
- **清晨画布:** 页面内容背景,带极轻蓝相。
|
||||
- **纯白:** 服务内容与固定电话栏的主要表面。
|
||||
- **信任墨蓝:** 标题和正文主色。
|
||||
- **辅助墨蓝:** 次要说明文字,仍需满足 AA 对比度。
|
||||
- **柔和分隔蓝:** 仅用于完整边框和分隔线。
|
||||
|
||||
**The Blue Commitment Rule.** 蓝色覆盖首屏主视觉,但内容区必须回到高对比浅色表面;禁止用第三种高饱和色争夺注意力。
|
||||
|
||||
## Typography
|
||||
|
||||
**Display Font:** 系统无衬线字体(优先 PingFang SC)
|
||||
**Body Font:** 系统无衬线字体(优先 PingFang SC)
|
||||
|
||||
**Character:** 单一系统字体通过字号和字重建立层级,加载稳定,适合微信小程序和中文快速扫读。
|
||||
|
||||
### Hierarchy
|
||||
|
||||
- **Display**(700,56px,1.18):仅用于首屏“门店管理服务”。
|
||||
- **Title**(700,32px,1.4):用于“服务行业”“核心服务”和服务名称。
|
||||
- **Body**(400,28px,1.65):用于业务说明,单行长度受移动端容器约束。
|
||||
- **Label**(600,24px,1.4):用于行业名称、电话提示和辅助标签。
|
||||
|
||||
**The Plain Language Rule.** 标题直接命名业务,按钮使用“立即拨打”等动词加对象表达;禁止口号式套话和全大写正文。
|
||||
|
||||
## Elevation
|
||||
|
||||
系统以色块、完整边框和有限的环境阴影建立层次。内容表面默认平坦,固定电话栏可使用向上的柔和阴影与页面分离,主视觉光晕只存在于蓝色背景内部。
|
||||
|
||||
### Shadow Vocabulary
|
||||
|
||||
- **内容环境影:** 低透明度、大模糊半径,只用于主内容容器与蓝色主视觉交界处。
|
||||
- **固定栏上浮影:** 从底部栏向上扩散,说明其固定层级,不模拟厚重卡片。
|
||||
|
||||
**The Flat Content Rule.** 服务信息依靠留白与分隔,不给每个条目添加独立厚重阴影。
|
||||
|
||||
## Components
|
||||
|
||||
### Buttons
|
||||
|
||||
- **Shape:** 44px 视觉高度的胶囊按钮(88px 设计稿高度)。
|
||||
- **Primary:** 行动蓝底、纯白粗体文字,左右内边距 36px。
|
||||
- **Active / Focus:** 按下时轻微降低亮度;焦点通过完整轮廓表达,不移动布局。
|
||||
|
||||
### Chips
|
||||
|
||||
- **Style:** 三个行业标识共用一个横向信息带,以 1px 分隔线划分;每项由浅蓝圆形字符标识和行业名称组成。
|
||||
- **State:** 全部为静态信息,不提供选中、悬停或点击状态。
|
||||
|
||||
### Cards / Containers
|
||||
|
||||
- **Corner Style:** 主内容容器 36px,服务条目 24px。
|
||||
- **Background:** 纯白或清晨画布。
|
||||
- **Shadow Strategy:** 主容器允许环境影,服务条目使用完整边框或分隔线。
|
||||
- **Internal Padding:** 24px 至 40px,保持移动端舒适密度。
|
||||
|
||||
### Navigation
|
||||
|
||||
- 原生导航栏使用午夜门楣蓝背景、纯白标题“门店管理服务”,不放 Logo 或自定义返回控件。
|
||||
|
||||
### Fixed Consultation Bar
|
||||
|
||||
- 白色固定底栏包含“业务咨询”、电话号码和“立即拨打”按钮。
|
||||
- 底部使用安全区内边距,页面正文预留等高空间,任何屏幕都不得遮挡内容。
|
||||
|
||||
## Do's and Don'ts
|
||||
|
||||
### Do:
|
||||
|
||||
- **Do** 让百货、超市、便利店在首屏直接可见。
|
||||
- **Do** 使用原生文字承载业务内容,插画加载失败时信息仍完整。
|
||||
- **Do** 按图片宽度展示门店主视觉,只裁去上方空蓝区域,完整保留门店建筑与地面基线。
|
||||
- **Do** 保持正文至少 4.5:1 对比度、按钮约 44px 触控高度。
|
||||
- **Do** 为所有入场动画提供 `prefers-reduced-motion` 静态降级。
|
||||
- **Do** 使用完整边框、留白和色块建立层级。
|
||||
|
||||
### Don't:
|
||||
|
||||
- **Don't** 做地图、标注、定位、坐标或地址管理界面。
|
||||
- **Don't** 做表单页、政务门户或业务办理后台的视觉复刻。
|
||||
- **Don't** 使用 Logo、彩虹分类图标、夸张赛博霓虹或装饰性玻璃拟态。
|
||||
- **Don't** 把行业与服务包装成可点击但没有目标的假入口。
|
||||
- **Don't** 使用负 margin 内容面板覆盖门店图片,或用深色渐变压暗门店底部。
|
||||
- **Don't** 使用渐变文字、侧边粗色条、嵌套卡片或重复的图标卡片网格。
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Product
|
||||
|
||||
## Register
|
||||
|
||||
brand
|
||||
|
||||
## Users
|
||||
|
||||
面向百货、超市与便利店的经营负责人和运营负责人。他们通常在移动端快速了解服务范围,并希望用最短路径联系业务人员。
|
||||
|
||||
## Product Purpose
|
||||
|
||||
落地页用于清楚介绍门店管理服务覆盖的行业与能力,并将没有 `scene` 的自然访问流量转化为电话咨询。成功标准是访问者能在首屏理解业务对象,在一次滚动内看到完整服务范围,并随时拨打咨询电话。
|
||||
|
||||
## Brand Personality
|
||||
|
||||
可靠、利落、专业。文案直接说明业务,不夸大结果,不使用营销套话;视觉有明确科技感,但不牺牲可读性和可信度。
|
||||
|
||||
## Anti-references
|
||||
|
||||
- 不做地图、标注、定位、坐标或地址管理界面。
|
||||
- 不做表单页、政务门户或业务办理后台的视觉复刻。
|
||||
- 不使用 Logo、彩虹分类图标、夸张赛博霓虹或装饰性玻璃拟态。
|
||||
- 不把行业与服务包装成可点击但没有目标的假入口。
|
||||
|
||||
## Design Principles
|
||||
|
||||
- **先讲清服务对象。** 首屏必须直接出现百货、超市与便利店。
|
||||
- **唯一行动清晰。** 电话咨询是页面唯一主操作,并始终可见。
|
||||
- **科技感服务于信任。** 蓝色、光线和门店插画建立专业氛围,不制造地图或数据监控联想。
|
||||
- **信息先于装饰。** 即使插画未加载,标题、服务范围和联系电话仍完整可用。
|
||||
- **移动端快速扫读。** 使用短文案、强层级和足够触控尺寸。
|
||||
|
||||
## Accessibility & Inclusion
|
||||
|
||||
以移动端 WCAG AA 为基线:正文对比度至少 4.5:1,大字号至少 3:1,交互区域视觉尺寸不小于约 44px,不仅依赖颜色传达信息,并为减少动态效果提供静态降级。
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
export default defineAppConfig({
|
||||
pages: [
|
||||
'pages/index/index',
|
||||
'pages/landing/index',
|
||||
'pages/pay/index',
|
||||
],
|
||||
window: {
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
|
|
@ -5,7 +5,8 @@ import './index.scss'
|
|||
// xunmeng2,devcon
|
||||
|
||||
const LINK_PREFIX = '/p/'
|
||||
const DEFAULT_LINK_CODE = 'Gbym'
|
||||
const DEFAULT_LINK_CODE = 'Gbym'
|
||||
const LANDING_PAGE_URL = '/pages/landing/index'
|
||||
|
||||
function isValidLinkCode (code: string) {
|
||||
return Boolean(code) && !/[/?#&=]/.test(code)
|
||||
|
|
@ -31,9 +32,26 @@ function normalizeQrLinkCode (scene?: string) {
|
|||
|
||||
export default function Index () {
|
||||
const [url, setUrl] = useState('')
|
||||
|
||||
useLoad(async (options) => {
|
||||
const scene = typeof options?.scene === 'string' ? options.scene.trim() : ''
|
||||
|
||||
if (!scene) {
|
||||
try {
|
||||
await Taro.redirectTo({ url: LANDING_PAGE_URL })
|
||||
} catch (error) {
|
||||
console.error('Failed to open landing page.', error)
|
||||
Taro.showToast({
|
||||
title: '页面加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const wxCode = await Taro.login()
|
||||
const linkCode = normalizeQrLinkCode(options?.scene as string | undefined)
|
||||
const linkCode = normalizeQrLinkCode(scene)
|
||||
|
||||
console.log('Page loaded.', wxCode)
|
||||
// setUrl(`https://niubsw.com${LINK_PREFIX}${encodeURIComponent(linkCode)}?mpCode=${encodeURIComponent(wxCode.code)}`)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
export default definePageConfig({
|
||||
navigationBarTitleText: '门店管理服务',
|
||||
navigationBarBackgroundColor: '#05256e',
|
||||
navigationBarTextStyle: 'white',
|
||||
backgroundColor: '#f3f7ff',
|
||||
backgroundTextStyle: 'light'
|
||||
})
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
/* stylelint-disable selector-class-pattern -- The project requires BEM class names. */
|
||||
|
||||
.landing-page {
|
||||
--landing-midnight: #05256e;
|
||||
--landing-service-blue: #0b57d0;
|
||||
--landing-action-blue: #1677ff;
|
||||
--landing-signal-cyan: #5fc8ff;
|
||||
--landing-canvas: #f3f7ff;
|
||||
--landing-surface: #fff;
|
||||
--landing-ink: #102a56;
|
||||
--landing-muted: #425f8c;
|
||||
--landing-border: #d7e4ff;
|
||||
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(132px + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(132px + env(safe-area-inset-bottom));
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
color: var(--landing-ink);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: var(--landing-canvas);
|
||||
}
|
||||
|
||||
@supports (color: oklch(0 0 0)) {
|
||||
.landing-page {
|
||||
--landing-midnight: oklch(29.95% 0.1316 262.9deg);
|
||||
--landing-service-blue: oklch(49.49% 0.1992 260.6deg);
|
||||
--landing-action-blue: oklch(59.87% 0.2194 259deg);
|
||||
--landing-signal-cyan: oklch(79.25% 0.1244 234.3deg);
|
||||
--landing-canvas: oklch(97.54% 0.0115 264.5deg);
|
||||
--landing-surface: oklch(100% 0 0deg);
|
||||
--landing-ink: oklch(29.25% 0.086 260.3deg);
|
||||
--landing-muted: oklch(48.32% 0.0808 258.9deg);
|
||||
--landing-border: oklch(91.74% 0.0395 265.1deg);
|
||||
}
|
||||
}
|
||||
|
||||
.landing-page__hero {
|
||||
position: relative;
|
||||
padding: 52px 32px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 36px 36px;
|
||||
box-sizing: border-box;
|
||||
background:
|
||||
radial-gradient(circle at 88% 12%, rgb(95 200 255 / 18%) 0, rgb(95 200 255 / 0%) 30%),
|
||||
linear-gradient(145deg, var(--landing-midnight) 0%, #073b9e 52%, var(--landing-service-blue) 100%);
|
||||
}
|
||||
|
||||
.landing-page__hero-copy {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 640px;
|
||||
animation: landing-rise 620ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
|
||||
.landing-page__hero-title {
|
||||
display: block;
|
||||
color: var(--landing-surface);
|
||||
font-size: 54px;
|
||||
font-weight: 700;
|
||||
line-height: 1.18;
|
||||
letter-spacing: -1px;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.landing-page__hero-description {
|
||||
display: block;
|
||||
max-width: 620px;
|
||||
margin-top: 18px;
|
||||
color: rgb(255 255 255 / 88%);
|
||||
font-size: 28px;
|
||||
line-height: 1.65;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.landing-page__hero-visual {
|
||||
position: relative;
|
||||
height: 340px;
|
||||
margin: 20px -32px 0;
|
||||
overflow: hidden;
|
||||
animation: landing-reveal 760ms 100ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
|
||||
.landing-page__hero-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-top: -68px;
|
||||
}
|
||||
|
||||
.landing-page__content {
|
||||
padding: 40px 28px 72px;
|
||||
box-sizing: border-box;
|
||||
background: var(--landing-canvas);
|
||||
}
|
||||
|
||||
.landing-page__section-title {
|
||||
display: block;
|
||||
color: var(--landing-ink);
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.landing-page__section-note {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
color: var(--landing-muted);
|
||||
font-size: 28px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.landing-page__industries {
|
||||
display: flex;
|
||||
margin-top: 32px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--landing-border);
|
||||
border-radius: 28px;
|
||||
background: var(--landing-surface);
|
||||
}
|
||||
|
||||
.landing-page__industry {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 28px 8px 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.landing-page__industry + .landing-page__industry {
|
||||
border-left: 1px solid var(--landing-border);
|
||||
}
|
||||
|
||||
.landing-page__industry-mark {
|
||||
display: flex;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgb(22 119 255 / 18%);
|
||||
border-radius: 50%;
|
||||
color: var(--landing-service-blue);
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
background: rgb(22 119 255 / 8%);
|
||||
}
|
||||
|
||||
.landing-page__industry-name {
|
||||
margin-top: 14px;
|
||||
color: var(--landing-ink);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.landing-page__section--services {
|
||||
margin-top: 56px;
|
||||
}
|
||||
|
||||
.landing-page__services {
|
||||
margin-top: 28px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--landing-border);
|
||||
border-radius: 24px;
|
||||
background: var(--landing-surface);
|
||||
}
|
||||
|
||||
.landing-page__service {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
padding: 30px 26px;
|
||||
border-bottom: 1px solid var(--landing-border);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.landing-page__service:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.landing-page__service-mark {
|
||||
display: flex;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
flex: 0 0 72px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
color: var(--landing-surface);
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
background: var(--landing-service-blue);
|
||||
}
|
||||
|
||||
.landing-page__service-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.landing-page__service-name {
|
||||
color: var(--landing-ink);
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.landing-page__service-description {
|
||||
margin-top: 8px;
|
||||
color: var(--landing-muted);
|
||||
font-size: 28px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.landing-page__consultation {
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
padding: 18px 24px;
|
||||
padding-bottom: calc(18px + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(18px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid var(--landing-border);
|
||||
box-sizing: border-box;
|
||||
background: var(--landing-surface);
|
||||
box-shadow: 0 -12px 36px rgb(16 42 86 / 14%);
|
||||
}
|
||||
|
||||
.landing-page__consultation-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.landing-page__consultation-label {
|
||||
color: var(--landing-muted);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.landing-page__consultation-phone {
|
||||
margin-top: 2px;
|
||||
color: var(--landing-ink);
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.landing-page__call-button {
|
||||
min-width: 200px;
|
||||
height: 88px;
|
||||
margin: 0;
|
||||
padding: 0 32px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
color: var(--landing-surface);
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 88px;
|
||||
background: var(--landing-action-blue);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.landing-page__call-button::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.landing-page__call-button--pressed {
|
||||
background: #0849b4;
|
||||
}
|
||||
|
||||
@keyframes landing-rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes landing-reveal {
|
||||
from {
|
||||
opacity: 0.7;
|
||||
clip-path: inset(28% 0 0 0);
|
||||
transform: translateY(18px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
clip-path: inset(0 0 0 0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.landing-page__hero-copy,
|
||||
.landing-page__hero-visual {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import { Button, Image, Text, View } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import heroImage from '@/assets/images/landing-storefronts.jpg'
|
||||
import './index.scss'
|
||||
|
||||
const CONTACT_PHONE = '4001166311'
|
||||
|
||||
const INDUSTRIES = [
|
||||
{ mark: '百', name: '百货' },
|
||||
{ mark: '超', name: '超市' },
|
||||
{ mark: '便', name: '便利店' },
|
||||
]
|
||||
|
||||
const SERVICES = [
|
||||
{
|
||||
mark: '关',
|
||||
name: '公关',
|
||||
description: '协助门店沟通与活动协调',
|
||||
},
|
||||
{
|
||||
mark: '推',
|
||||
name: '推广',
|
||||
description: '支持门店推广与现场执行',
|
||||
},
|
||||
{
|
||||
mark: '调',
|
||||
name: '市场调查',
|
||||
description: '开展门店走访、信息采集与反馈',
|
||||
},
|
||||
]
|
||||
|
||||
const getErrorMessage = (error: unknown) => {
|
||||
if (error instanceof Error) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
if (typeof error === 'object' && error && 'errMsg' in error) {
|
||||
return String((error as { errMsg?: string }).errMsg)
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
export default function Landing () {
|
||||
const handleCall = async () => {
|
||||
try {
|
||||
await Taro.makePhoneCall({ phoneNumber: CONTACT_PHONE })
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error)
|
||||
|
||||
if (errorMessage.toLowerCase().includes('cancel')) {
|
||||
return
|
||||
}
|
||||
|
||||
console.error('Failed to make phone call.', error)
|
||||
Taro.showToast({
|
||||
title: '暂时无法拨打,请稍后重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='landing-page'>
|
||||
<View className='landing-page__hero'>
|
||||
<View className='landing-page__hero-copy'>
|
||||
<Text className='landing-page__hero-title'>门店管理服务</Text>
|
||||
<Text className='landing-page__hero-description'>
|
||||
面向百货、超市与便利店,提供公关、推广与市场调查服务。
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className='landing-page__hero-visual'>
|
||||
<Image
|
||||
className='landing-page__hero-image'
|
||||
src={heroImage}
|
||||
mode='widthFix'
|
||||
ariaLabel='百货、超市和便利店门店剪影'
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='landing-page__content'>
|
||||
<View className='landing-page__section'>
|
||||
<Text className='landing-page__section-title'>服务行业</Text>
|
||||
<Text className='landing-page__section-note'>覆盖常见线下零售业态</Text>
|
||||
|
||||
<View className='landing-page__industries'>
|
||||
{INDUSTRIES.map((industry) => (
|
||||
<View className='landing-page__industry' key={industry.name}>
|
||||
<View className='landing-page__industry-mark'>
|
||||
<Text>{industry.mark}</Text>
|
||||
</View>
|
||||
<Text className='landing-page__industry-name'>{industry.name}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='landing-page__section landing-page__section--services'>
|
||||
<Text className='landing-page__section-title'>核心服务</Text>
|
||||
<Text className='landing-page__section-note'>围绕门店现场提供业务支持</Text>
|
||||
|
||||
<View className='landing-page__services'>
|
||||
{SERVICES.map((service) => (
|
||||
<View className='landing-page__service' key={service.name}>
|
||||
<View className='landing-page__service-mark'>
|
||||
<Text>{service.mark}</Text>
|
||||
</View>
|
||||
<View className='landing-page__service-copy'>
|
||||
<Text className='landing-page__service-name'>{service.name}</Text>
|
||||
<Text className='landing-page__service-description'>
|
||||
{service.description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='landing-page__consultation'>
|
||||
<View className='landing-page__consultation-copy'>
|
||||
<Text className='landing-page__consultation-label'>业务咨询</Text>
|
||||
<Text className='landing-page__consultation-phone'>{CONTACT_PHONE}</Text>
|
||||
</View>
|
||||
<Button
|
||||
className='landing-page__call-button'
|
||||
hoverClass='landing-page__call-button--pressed'
|
||||
ariaLabel={`拨打业务咨询电话 ${CONTACT_PHONE}`}
|
||||
onClick={handleCall}
|
||||
>
|
||||
立即拨打
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue