This commit is contained in:
parent
3214f66fb9
commit
96869761e2
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Design QA
|
||||||
|
|
||||||
|
- source visual truth path: `C:\Users\29169\AppData\Local\Temp\codex-clipboard-5202e314-49da-48b8-a154-05e5d4a96bcc.png`
|
||||||
|
- implementation screenshot path: unavailable
|
||||||
|
- viewport: target 750 x 1624
|
||||||
|
- state: default form state
|
||||||
|
|
||||||
|
## Full-view comparison evidence
|
||||||
|
|
||||||
|
The source image was opened at its original resolution and used to reproduce the page structure, spacing, typography hierarchy, form controls, green action button, and footer content. A browser-rendered implementation screenshot could not be captured because the repository's existing H5 production build fails in the legacy Babel post-processing stage with an invalid `development` compilation target.
|
||||||
|
|
||||||
|
## Focused region comparison evidence
|
||||||
|
|
||||||
|
Blocked for the same reason. Source details were inspected directly, but a rendered comparison artifact is unavailable.
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
- No code-level P0/P1 issue was found by the WeChat production build or stylesheet validation.
|
||||||
|
- Visual fidelity remains unverified in a browser-rendered 750 x 1624 comparison.
|
||||||
|
|
||||||
|
## Comparison history
|
||||||
|
|
||||||
|
- Initial implementation completed from the supplied reference.
|
||||||
|
- No visual comparison iteration could be performed because the H5 preview build is blocked by the existing Babel target configuration.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Primary interactions implemented: service type selection, all text inputs, agreement selection, policy dialogs, validation feedback, and success dialog.
|
||||||
|
- WeChat production build: passed.
|
||||||
|
- Stylesheet validation: passed.
|
||||||
|
- Browser console check: blocked because no browser-rendered preview was available.
|
||||||
|
|
||||||
|
final result: blocked
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '门店企业标注快办',
|
navigationBarTitleText: '门店企业标注快办',
|
||||||
navigationBarBackgroundColor: '#05256e',
|
navigationBarBackgroundColor: '#ffffff',
|
||||||
navigationBarTextStyle: 'white',
|
navigationBarTextStyle: 'black',
|
||||||
backgroundColor: '#f3f7ff',
|
backgroundColor: '#f2f7fb',
|
||||||
backgroundTextStyle: 'light'
|
backgroundTextStyle: 'dark'
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
/* stylelint-disable selector-class-pattern -- The project requires BEM class names. */
|
/* stylelint-disable selector-class-pattern -- The project requires BEM class names. */
|
||||||
|
|
||||||
.landing-page {
|
.landing-page {
|
||||||
--landing-midnight: #05256e;
|
--landing-green: #07c160;
|
||||||
--landing-service-blue: #0b57d0;
|
--landing-green-dark: #06ae56;
|
||||||
--landing-action-blue: #1677ff;
|
--landing-canvas: #f2f7fb;
|
||||||
--landing-signal-cyan: #5fc8ff;
|
|
||||||
--landing-canvas: #f3f7ff;
|
|
||||||
--landing-surface: #fff;
|
--landing-surface: #fff;
|
||||||
--landing-ink: #102a56;
|
--landing-ink: #17181a;
|
||||||
--landing-muted: #425f8c;
|
--landing-muted: #606772;
|
||||||
--landing-border: #d7e4ff;
|
--landing-placeholder: #959ba3;
|
||||||
|
--landing-border: #c7ccd2;
|
||||||
|
--landing-error: #d94242;
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding-bottom: calc(132px + constant(safe-area-inset-bottom));
|
padding: 54px 30px calc(38px + env(safe-area-inset-bottom));
|
||||||
padding-bottom: calc(132px + env(safe-area-inset-bottom));
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: var(--landing-ink);
|
color: var(--landing-ink);
|
||||||
|
|
@ -21,295 +20,195 @@
|
||||||
background: var(--landing-canvas);
|
background: var(--landing-canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (color: oklch(0 0 0)) {
|
.landing-page__intro {
|
||||||
.landing-page {
|
padding: 0 10px;
|
||||||
--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 {
|
.landing-page__title {
|
||||||
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;
|
display: block;
|
||||||
color: var(--landing-surface);
|
font-size: 48px;
|
||||||
font-size: 54px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.18;
|
line-height: 1.35;
|
||||||
letter-spacing: -1px;
|
letter-spacing: -1px;
|
||||||
text-wrap: balance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-page__hero-description {
|
.landing-page__description {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 620px;
|
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
color: rgb(255 255 255 / 88%);
|
color: var(--landing-muted);
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
text-wrap: pretty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-page__hero-visual {
|
.landing-page__notice {
|
||||||
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;
|
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;
|
margin-top: 14px;
|
||||||
color: var(--landing-ink);
|
color: var(--landing-muted);
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__form {
|
||||||
|
margin-top: 30px;
|
||||||
|
padding: 28px;
|
||||||
|
border: 1px solid #d9dde1;
|
||||||
|
border-radius: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--landing-surface);
|
||||||
|
box-shadow: 0 8px 28px rgb(40 65 86 / 5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__field + .landing-page__field {
|
||||||
|
margin-top: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 12px;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-page__section--services {
|
.landing-page__service-types {
|
||||||
margin-top: 56px;
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-page__services {
|
.landing-page__service-type {
|
||||||
margin-top: 28px;
|
display: flex;
|
||||||
overflow: hidden;
|
height: 72px;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
border: 1px solid var(--landing-border);
|
border: 1px solid var(--landing-border);
|
||||||
border-radius: 24px;
|
border-radius: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 28px;
|
||||||
background: var(--landing-surface);
|
background: var(--landing-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-page__service {
|
.landing-page__service-type--selected {
|
||||||
|
border-color: rgb(7 193 96 / 45%);
|
||||||
|
background: rgb(7 193 96 / 3%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__radio {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 30px 26px;
|
gap: 12px;
|
||||||
border-bottom: 1px solid var(--landing-border);
|
}
|
||||||
|
|
||||||
|
.landing-page__input,
|
||||||
|
.landing-page__textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--landing-border);
|
||||||
|
border-radius: 10px;
|
||||||
box-sizing: border-box;
|
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);
|
color: var(--landing-ink);
|
||||||
|
font-size: 28px;
|
||||||
|
background: var(--landing-surface);
|
||||||
|
transition: border-color 160ms ease, box-shadow 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__input {
|
||||||
|
height: 72px;
|
||||||
|
padding: 0 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__textarea {
|
||||||
|
height: 116px;
|
||||||
|
padding: 18px 22px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__input:focus,
|
||||||
|
.landing-page__textarea:focus {
|
||||||
|
border-color: var(--landing-green);
|
||||||
|
box-shadow: 0 0 0 3px rgb(7 193 96 / 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__input--error {
|
||||||
|
border-color: var(--landing-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__placeholder {
|
||||||
|
color: var(--landing-placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__error,
|
||||||
|
.landing-page__agreement-error {
|
||||||
|
display: block;
|
||||||
|
margin-top: 8px;
|
||||||
|
color: var(--landing-error);
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__agreement {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 26px 18px 0;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__checkbox {
|
||||||
|
margin-right: 10px;
|
||||||
|
transform: scale(0.88);
|
||||||
|
transform-origin: left center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__agreement-text {
|
||||||
|
color: var(--landing-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__link {
|
||||||
|
color: var(--landing-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__agreement-error {
|
||||||
|
margin-right: 18px;
|
||||||
|
margin-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page__submit {
|
||||||
|
height: 84px;
|
||||||
|
margin: 24px 10px 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.4;
|
line-height: 84px;
|
||||||
}
|
background: var(--landing-green);
|
||||||
|
|
||||||
.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;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-page__call-button::after {
|
.landing-page__submit::after {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-page__call-button--pressed {
|
.landing-page__submit--pressed {
|
||||||
background: #0849b4;
|
background: var(--landing-green-dark);
|
||||||
|
transform: scale(0.99);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes landing-rise {
|
.landing-page__footer {
|
||||||
from {
|
display: flex;
|
||||||
opacity: 0;
|
align-items: center;
|
||||||
transform: translateY(20px);
|
margin-top: 28px;
|
||||||
}
|
flex-direction: column;
|
||||||
|
color: var(--landing-muted);
|
||||||
to {
|
font-size: 22px;
|
||||||
opacity: 1;
|
line-height: 1.75;
|
||||||
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) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.landing-page__hero-copy,
|
.landing-page__input,
|
||||||
.landing-page__hero-visual {
|
.landing-page__textarea,
|
||||||
animation: none;
|
.landing-page__submit {
|
||||||
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,132 +1,213 @@
|
||||||
import { Button, Image, Text, View } from '@tarojs/components'
|
import { Button, Checkbox, CheckboxGroup, Input, Radio, RadioGroup, Text, Textarea, View } from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import heroImage from '@/assets/images/landing-storefronts.jpg'
|
import { useState } from 'react'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
const CONTACT_PHONE = '4001166311'
|
type ServiceType = 'new' | 'update'
|
||||||
|
|
||||||
const INDUSTRIES = [
|
type ApplicationForm = {
|
||||||
{ mark: '百', name: '百货' },
|
serviceType: ServiceType
|
||||||
{ mark: '超', name: '超市' },
|
storeName: string
|
||||||
{ mark: '便', name: '便利店' },
|
storeAddress: string
|
||||||
]
|
contactName: string
|
||||||
|
contactPhone: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
const SERVICES = [
|
type FormErrors = Partial<Record<keyof ApplicationForm | 'agreement', string>>
|
||||||
{
|
|
||||||
mark: '推',
|
|
||||||
name: '推广',
|
|
||||||
description: '为门店推广提供支持',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mark: '曝',
|
|
||||||
name: '门店曝光',
|
|
||||||
description: '提高门店曝光量',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const getErrorMessage = (error: unknown) => {
|
const INITIAL_FORM: ApplicationForm = {
|
||||||
if (error instanceof Error) {
|
serviceType: 'new',
|
||||||
return error.message
|
storeName: '',
|
||||||
|
storeAddress: '',
|
||||||
|
contactName: '',
|
||||||
|
contactPhone: '',
|
||||||
|
description: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const PHONE_PATTERN = /^1[3-9]\d{9}$/
|
||||||
|
|
||||||
|
const validateForm = (form: ApplicationForm, agreed: boolean) => {
|
||||||
|
const errors: FormErrors = {}
|
||||||
|
|
||||||
|
if (!form.storeName.trim()) errors.storeName = '请输入门店名称'
|
||||||
|
if (!form.storeAddress.trim()) errors.storeAddress = '请输入门店地址'
|
||||||
|
if (!form.contactName.trim()) errors.contactName = '请输入联系人姓名'
|
||||||
|
|
||||||
|
if (!form.contactPhone.trim()) {
|
||||||
|
errors.contactPhone = '请输入联系电话'
|
||||||
|
} else if (!PHONE_PATTERN.test(form.contactPhone.trim())) {
|
||||||
|
errors.contactPhone = '请输入正确的手机号码'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof error === 'object' && error && 'errMsg' in error) {
|
if (!form.description.trim()) errors.description = '请描述需要办理的内容'
|
||||||
return String((error as { errMsg?: string }).errMsg)
|
if (!agreed) errors.agreement = '请阅读并同意隐私政策和服务说明'
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Landing () {
|
export default function Landing () {
|
||||||
const handleCall = async () => {
|
const [form, setForm] = useState<ApplicationForm>(INITIAL_FORM)
|
||||||
try {
|
const [agreed, setAgreed] = useState(false)
|
||||||
await Taro.makePhoneCall({ phoneNumber: CONTACT_PHONE })
|
const [errors, setErrors] = useState<FormErrors>({})
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = getErrorMessage(error)
|
|
||||||
|
|
||||||
if (errorMessage.toLowerCase().includes('cancel')) {
|
const updateField = <K extends keyof ApplicationForm>(field: K, value: ApplicationForm[K]) => {
|
||||||
return
|
setForm((current) => ({ ...current, [field]: value }))
|
||||||
}
|
setErrors((current) => ({ ...current, [field]: undefined }))
|
||||||
|
}
|
||||||
|
|
||||||
console.error('Failed to make phone call.', error)
|
const showPolicy = (type: 'privacy' | 'service') => {
|
||||||
Taro.showToast({
|
const isPrivacy = type === 'privacy'
|
||||||
title: '暂时无法拨打,请稍后重试',
|
|
||||||
icon: 'none'
|
Taro.showModal({
|
||||||
})
|
title: isPrivacy ? '隐私政策' : '服务说明',
|
||||||
|
content: isPrivacy
|
||||||
|
? '您填写的信息仅用于本次门店标注服务的联系与确认,我们将妥善保护您的个人信息。'
|
||||||
|
: '提交申请后,工作人员将在服务时间内联系您核对资料。具体办理结果以实际审核情况为准。',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '我知道了',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const validationErrors = validateForm(form, agreed)
|
||||||
|
const firstError = Object.values(validationErrors)[0]
|
||||||
|
|
||||||
|
setErrors(validationErrors)
|
||||||
|
|
||||||
|
if (firstError) {
|
||||||
|
Taro.showToast({ title: firstError, icon: 'none' })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Taro.showModal({
|
||||||
|
title: '提交成功',
|
||||||
|
content: '您的申请已提交,工作人员将在服务时间内联系您确认。',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '我知道了',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='landing-page'>
|
<View className='landing-page'>
|
||||||
<View className='landing-page__hero'>
|
<View className='landing-page__intro'>
|
||||||
<View className='landing-page__hero-copy'>
|
<Text className='landing-page__title'>门店企业标注,在线提交</Text>
|
||||||
<Text className='landing-page__hero-title'>门店企业标注快办</Text>
|
<Text className='landing-page__description'>
|
||||||
<Text className='landing-page__hero-description'>
|
提供门店新增标注、信息修改等企业服务,提交资料后由工作人员联系确认。
|
||||||
面向百货、超市与便利店,提供门店企业标注快办服务。
|
</Text>
|
||||||
</Text>
|
<Text className='landing-page__notice'>第三方企业服务,非地图平台官方服务</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='landing-page__form'>
|
||||||
|
<View className='landing-page__field'>
|
||||||
|
<Text className='landing-page__label'>办理类型</Text>
|
||||||
|
<RadioGroup
|
||||||
|
className='landing-page__service-types'
|
||||||
|
onChange={(event) => updateField('serviceType', event.detail.value as ServiceType)}
|
||||||
|
>
|
||||||
|
<View className={`landing-page__service-type ${form.serviceType === 'new' ? 'landing-page__service-type--selected' : ''}`}>
|
||||||
|
<Radio className='landing-page__radio' value='new' checked={form.serviceType === 'new'} color='#07c160'>新增标注</Radio>
|
||||||
|
</View>
|
||||||
|
<View className={`landing-page__service-type ${form.serviceType === 'update' ? 'landing-page__service-type--selected' : ''}`}>
|
||||||
|
<Radio className='landing-page__radio' value='update' checked={form.serviceType === 'update'} color='#07c160'>信息修改</Radio>
|
||||||
|
</View>
|
||||||
|
</RadioGroup>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className='landing-page__hero-visual'>
|
<View className='landing-page__field'>
|
||||||
<Image
|
<Text className='landing-page__label'>门店名称</Text>
|
||||||
className='landing-page__hero-image'
|
<Input
|
||||||
src={heroImage}
|
className={`landing-page__input ${errors.storeName ? 'landing-page__input--error' : ''}`}
|
||||||
mode='widthFix'
|
value={form.storeName}
|
||||||
ariaLabel='百货、超市和便利店门店剪影'
|
maxlength={50}
|
||||||
|
placeholder='请输入营业执照上的门店名称'
|
||||||
|
placeholderClass='landing-page__placeholder'
|
||||||
|
onInput={(event) => updateField('storeName', event.detail.value)}
|
||||||
/>
|
/>
|
||||||
|
{errors.storeName && <Text className='landing-page__error'>{errors.storeName}</Text>}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='landing-page__field'>
|
||||||
|
<Text className='landing-page__label'>门店地址</Text>
|
||||||
|
<Input
|
||||||
|
className={`landing-page__input ${errors.storeAddress ? 'landing-page__input--error' : ''}`}
|
||||||
|
value={form.storeAddress}
|
||||||
|
maxlength={100}
|
||||||
|
placeholder='请输入完整经营地址'
|
||||||
|
placeholderClass='landing-page__placeholder'
|
||||||
|
onInput={(event) => updateField('storeAddress', event.detail.value)}
|
||||||
|
/>
|
||||||
|
{errors.storeAddress && <Text className='landing-page__error'>{errors.storeAddress}</Text>}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='landing-page__field'>
|
||||||
|
<Text className='landing-page__label'>联系人</Text>
|
||||||
|
<Input
|
||||||
|
className={`landing-page__input ${errors.contactName ? 'landing-page__input--error' : ''}`}
|
||||||
|
value={form.contactName}
|
||||||
|
maxlength={30}
|
||||||
|
placeholder='请输入联系人姓名'
|
||||||
|
placeholderClass='landing-page__placeholder'
|
||||||
|
onInput={(event) => updateField('contactName', event.detail.value)}
|
||||||
|
/>
|
||||||
|
{errors.contactName && <Text className='landing-page__error'>{errors.contactName}</Text>}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='landing-page__field'>
|
||||||
|
<Text className='landing-page__label'>联系电话</Text>
|
||||||
|
<Input
|
||||||
|
className={`landing-page__input ${errors.contactPhone ? 'landing-page__input--error' : ''}`}
|
||||||
|
value={form.contactPhone}
|
||||||
|
type='number'
|
||||||
|
maxlength={11}
|
||||||
|
placeholder='请输入手机号码'
|
||||||
|
placeholderClass='landing-page__placeholder'
|
||||||
|
onInput={(event) => updateField('contactPhone', event.detail.value)}
|
||||||
|
/>
|
||||||
|
{errors.contactPhone && <Text className='landing-page__error'>{errors.contactPhone}</Text>}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='landing-page__field'>
|
||||||
|
<Text className='landing-page__label'>补充说明</Text>
|
||||||
|
<Textarea
|
||||||
|
className={`landing-page__textarea ${errors.description ? 'landing-page__input--error' : ''}`}
|
||||||
|
value={form.description}
|
||||||
|
maxlength={300}
|
||||||
|
placeholder='请描述需要办理的内容'
|
||||||
|
placeholderClass='landing-page__placeholder'
|
||||||
|
onInput={(event) => updateField('description', event.detail.value)}
|
||||||
|
/>
|
||||||
|
{errors.description && <Text className='landing-page__error'>{errors.description}</Text>}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className='landing-page__content'>
|
<CheckboxGroup
|
||||||
<View className='landing-page__section'>
|
className='landing-page__agreement'
|
||||||
<Text className='landing-page__section-title'>服务行业</Text>
|
onChange={(event) => {
|
||||||
<Text className='landing-page__section-note'>覆盖常见线下零售业态</Text>
|
setAgreed(event.detail.value.includes('agreed'))
|
||||||
|
setErrors((current) => ({ ...current, agreement: undefined }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox className='landing-page__checkbox' value='agreed' checked={agreed} color='#07c160' />
|
||||||
|
<Text className='landing-page__agreement-text'>我已阅读并同意</Text>
|
||||||
|
<Text className='landing-page__link' onClick={() => showPolicy('privacy')}>《隐私政策》</Text>
|
||||||
|
<Text className='landing-page__agreement-text'>和</Text>
|
||||||
|
<Text className='landing-page__link' onClick={() => showPolicy('service')}>《服务说明》</Text>
|
||||||
|
</CheckboxGroup>
|
||||||
|
{errors.agreement && <Text className='landing-page__agreement-error'>{errors.agreement}</Text>}
|
||||||
|
|
||||||
<View className='landing-page__industries'>
|
<Button
|
||||||
{INDUSTRIES.map((industry) => (
|
className='landing-page__submit'
|
||||||
<View className='landing-page__industry' key={industry.name}>
|
hoverClass='landing-page__submit--pressed'
|
||||||
<View className='landing-page__industry-mark'>
|
onClick={handleSubmit}
|
||||||
<Text>{industry.mark}</Text>
|
>
|
||||||
</View>
|
提交申请
|
||||||
<Text className='landing-page__industry-name'>{industry.name}</Text>
|
</Button>
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className='landing-page__section landing-page__section--services'>
|
<View className='landing-page__footer'>
|
||||||
<Text className='landing-page__section-title'>核心服务</Text>
|
<Text>服务时间:工作日 9:00-18:00</Text>
|
||||||
<Text className='landing-page__section-note'>围绕门店现场提供业务支持</Text>
|
<Text>客服电话:400-800-1234</Text>
|
||||||
|
<Text>第三方企业服务,请根据实际需求自愿提交</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>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue