- 后台全面引入 Naive UI 组件库,统一 UI 规范 - 前台 usePublicApi 切换到 API 调用(GET /api/content/[key]) - 前后台数据源统一(site_content 表) - 产品线统一管理(tour/camp/course 三套编辑器) - 产品数据归一化(itinerary/faq/pricing 格式统一) - 表单提交 API 改写入 submissions 表 - 新增 submissions 标记已读 API - site-content PUT 支持 upsert - 修复前台 bug(stories 路由冲突、占位符警告、selector breadcrumb) - 消除 PageHero 类型警告(useSEO reactive 修复) - 25 个前台页面全部 HTTP 200,展示效果不变 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
996 行
23 KiB
Vue
996 行
23 KiB
Vue
<template>
|
||
<div class="page-customize">
|
||
<LayoutPageHero
|
||
:title="seo.h1"
|
||
subtitle="填写出行需求,定制师2小时内回复,免费咨询"
|
||
:breadcrumbs="[{ text: '在线定制' }]"
|
||
background-image="/images/seasons/summer-hero.jpg"
|
||
/>
|
||
|
||
<!-- TL;DR -->
|
||
<section class="page-tldr" aria-label="在线定制摘要">
|
||
<div class="container">
|
||
<div class="page-tldr-inner">
|
||
<p>在线提交定制需求:填写出行人数、时间、偏好,定制师2小时内免费回复专属方案。已服务10000+组家庭,支持微信/电话/表单多渠道咨询。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 信任指标条 -->
|
||
<section v-if="customize" class="trust-bar">
|
||
<div class="container">
|
||
<ul class="trust-list">
|
||
<li v-for="stat in customize.trustStats" :key="stat.label" class="trust-item">
|
||
<span class="trust-value">{{ stat.value }}<em class="trust-unit">{{ stat.unit }}</em></span>
|
||
<span class="trust-label">{{ stat.label }}</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 表单主区域 -->
|
||
<section class="section">
|
||
<div class="container">
|
||
<div class="customize-layout">
|
||
|
||
<!-- 表单主体 -->
|
||
<div class="form-wrap">
|
||
|
||
<!-- 提交成功状态 -->
|
||
<div v-if="submitted" class="success-card">
|
||
<div class="success-check">✓</div>
|
||
<h2 class="success-title">需求已收到!</h2>
|
||
<p class="success-desc">
|
||
定制师将在 <strong>2小时内</strong> 通过微信与您联系,<br>
|
||
请保持手机畅通。
|
||
</p>
|
||
<div class="success-wechat">
|
||
<span class="wechat-label">也可主动添加定制师微信</span>
|
||
<span class="wechat-id">{{ customize.contact.wechat }}</span>
|
||
</div>
|
||
<p class="success-tip">{{ customize.contact.tip }}</p>
|
||
<button class="btn-reset" @click="resetForm">重新填写</button>
|
||
</div>
|
||
|
||
<!-- 定制表单 -->
|
||
<form v-else class="customize-form" novalidate @submit.prevent="handleSubmit">
|
||
|
||
<!-- 1. 出行人数 -->
|
||
<fieldset class="form-section">
|
||
<legend class="section-legend">
|
||
<span class="legend-num">1</span>出行人数
|
||
</legend>
|
||
<div class="people-row">
|
||
<div class="form-field">
|
||
<label for="adults">成人</label>
|
||
<div class="number-ctrl">
|
||
<button type="button" aria-label="减少成人数" @click="decrement('adults', 1)">−</button>
|
||
<span class="number-display" aria-live="polite">{{ form.adults }}</span>
|
||
<button type="button" aria-label="增加成人数" @click="increment('adults', 20)">+</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-field">
|
||
<label for="children">儿童</label>
|
||
<div class="number-ctrl">
|
||
<button type="button" aria-label="减少儿童数" @click="decrement('children', 0)">−</button>
|
||
<span class="number-display" aria-live="polite">{{ form.children }}</span>
|
||
<button type="button" aria-label="增加儿童数" @click="increment('children', 10)">+</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 儿童年龄 -->
|
||
<div v-if="form.children > 0" class="children-ages">
|
||
<p class="ages-hint">请填写每位儿童的年龄(岁)</p>
|
||
<div class="ages-row">
|
||
<div v-for="i in form.children" :key="i" class="age-field">
|
||
<label :for="`child-age-${i}`">孩子 {{ i }}</label>
|
||
<select :id="`child-age-${i}`" v-model="form.childAges[i - 1]">
|
||
<option value="">请选择</option>
|
||
<option v-for="age in 18" :key="age" :value="age">{{ age }} 岁</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<!-- 2. 出行计划 -->
|
||
<fieldset class="form-section">
|
||
<legend class="section-legend">
|
||
<span class="legend-num">2</span>出行计划
|
||
</legend>
|
||
<div class="two-col">
|
||
<div class="form-field">
|
||
<label for="departure-date">出行日期</label>
|
||
<input
|
||
id="departure-date"
|
||
v-model="form.date"
|
||
type="date"
|
||
:min="minDate"
|
||
class="input-date"
|
||
>
|
||
</div>
|
||
<div class="form-field">
|
||
<label for="duration">出行天数</label>
|
||
<select id="duration" v-model="form.duration">
|
||
<option value="">请选择</option>
|
||
<option v-for="d in customize.durations" :key="d.value" :value="d.value">
|
||
{{ d.label }}
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<!-- 3. 特殊需求 -->
|
||
<fieldset class="form-section">
|
||
<legend class="section-legend">
|
||
<span class="legend-num">3</span>特殊需求
|
||
<span class="legend-hint">可多选</span>
|
||
</legend>
|
||
<div class="checkbox-grid">
|
||
<label
|
||
v-for="item in customize.activities"
|
||
:key="item.value"
|
||
class="checkbox-item"
|
||
:class="{ 'is-checked': form.activities.includes(item.value) }"
|
||
>
|
||
<input
|
||
type="checkbox"
|
||
:value="item.value"
|
||
v-model="form.activities"
|
||
>
|
||
<span class="check-label">{{ item.label }}</span>
|
||
</label>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<!-- 4. 预算范围 -->
|
||
<fieldset class="form-section">
|
||
<legend class="section-legend">
|
||
<span class="legend-num">4</span>预算范围
|
||
<span class="legend-hint">总费用参考</span>
|
||
</legend>
|
||
<div class="radio-grid">
|
||
<label
|
||
v-for="b in customize.budgets"
|
||
:key="b.value"
|
||
class="radio-item"
|
||
:class="{ 'is-checked': form.budget === b.value }"
|
||
>
|
||
<input type="radio" :value="b.value" v-model="form.budget">
|
||
<span>{{ b.label }}</span>
|
||
</label>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<!-- 5. 联系方式 -->
|
||
<fieldset class="form-section">
|
||
<legend class="section-legend">
|
||
<span class="legend-num">5</span>联系方式
|
||
</legend>
|
||
<div class="contact-fields">
|
||
<div class="form-field" :class="{ 'has-error': errors.name }">
|
||
<label for="contact-name">
|
||
您的姓名 <em class="required">*</em>
|
||
</label>
|
||
<input
|
||
id="contact-name"
|
||
v-model.trim="form.name"
|
||
type="text"
|
||
placeholder="请输入姓名"
|
||
autocomplete="name"
|
||
>
|
||
<span v-if="errors.name" class="error-msg" role="alert">{{ errors.name }}</span>
|
||
</div>
|
||
<div class="form-field" :class="{ 'has-error': errors.phone }">
|
||
<label for="contact-phone">
|
||
手机号码 <em class="required">*</em>
|
||
</label>
|
||
<input
|
||
id="contact-phone"
|
||
v-model.trim="form.phone"
|
||
type="tel"
|
||
placeholder="请输入手机号"
|
||
autocomplete="tel"
|
||
>
|
||
<span v-if="errors.phone" class="error-msg" role="alert">{{ errors.phone }}</span>
|
||
</div>
|
||
<div class="form-field">
|
||
<label for="contact-wechat">微信号</label>
|
||
<input
|
||
id="contact-wechat"
|
||
v-model.trim="form.wechat"
|
||
type="text"
|
||
placeholder="与手机号相同可不填"
|
||
>
|
||
</div>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<!-- 6. 补充说明 -->
|
||
<fieldset class="form-section">
|
||
<legend class="section-legend">
|
||
<span class="legend-num">6</span>补充说明
|
||
<span class="legend-hint">选填</span>
|
||
</legend>
|
||
<textarea
|
||
v-model="form.remarks"
|
||
rows="4"
|
||
placeholder="其他特殊需求、出行偏好、想去的地方……"
|
||
class="form-textarea"
|
||
/>
|
||
</fieldset>
|
||
|
||
<!-- 提交按钮 -->
|
||
<div class="form-submit">
|
||
<button type="submit" class="btn-submit">
|
||
提交定制需求
|
||
</button>
|
||
<p class="submit-hint">提交后定制师2小时内联系您,免费咨询,无任何套路</p>
|
||
</div>
|
||
|
||
</form>
|
||
</div>
|
||
|
||
<!-- 侧边辅助信息 -->
|
||
<aside class="customize-aside">
|
||
<div class="aside-card">
|
||
<h3 class="aside-title">定制服务流程</h3>
|
||
<ol class="process-list">
|
||
<li v-for="(step, i) in customize.process" :key="i" class="process-item">
|
||
<span class="process-num">{{ i + 1 }}</span>
|
||
<div class="process-text">
|
||
<strong>{{ step.title }}</strong>
|
||
<p>{{ step.desc }}</p>
|
||
</div>
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<div class="aside-card aside-wechat">
|
||
<h3 class="aside-title">不想填表?</h3>
|
||
<p class="aside-desc">直接添加定制师微信,随时咨询:</p>
|
||
<div class="wechat-id-display">{{ customize.contact.wechat }}</div>
|
||
<p class="wechat-tip">{{ customize.contact.tip }}</p>
|
||
</div>
|
||
</aside>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 内链闭环 -->
|
||
<CommonSectionCTA
|
||
title="想先了解产品?"
|
||
description="查看我们的行程版本,找到最适合您家庭的天数"
|
||
:links="[
|
||
{ to: '/products', text: '查看4-7天行程版本' },
|
||
{ to: '/faq', text: '查看常见问题解答' },
|
||
]"
|
||
bg="gray"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
|
||
|
||
const { data: customize } = await usePublicApi('customize')
|
||
|
||
const seo = useSEO('customize')
|
||
useBreadcrumbSchema([
|
||
{ text: '首页', to: '/' },
|
||
{ text: '在线定制' },
|
||
])
|
||
|
||
// 表单状态
|
||
const form = reactive({
|
||
adults: 2,
|
||
children: 0,
|
||
childAges: [],
|
||
date: '',
|
||
duration: '',
|
||
activities: [],
|
||
budget: '',
|
||
name: '',
|
||
phone: '',
|
||
wechat: '',
|
||
remarks: '',
|
||
})
|
||
|
||
const errors = reactive({ name: '', phone: '' })
|
||
const submitted = ref(false)
|
||
const submitting = ref(false)
|
||
const submitError = ref('')
|
||
|
||
// 最早可选出行日期:明天
|
||
const minDate = computed(() => {
|
||
const d = new Date()
|
||
d.setDate(d.getDate() + 1)
|
||
return d.toISOString().split('T')[0]
|
||
})
|
||
|
||
// 同步儿童年龄数组长度
|
||
watch(() => form.children, (newVal) => {
|
||
const current = form.childAges
|
||
form.childAges = Array.from({ length: newVal }, (_, i) => current[i] ?? '')
|
||
})
|
||
|
||
function increment(field, max) {
|
||
if (form[field] < max) form[field]++
|
||
}
|
||
|
||
function decrement(field, min = 1) {
|
||
if (form[field] > min) form[field]--
|
||
}
|
||
|
||
function validate() {
|
||
errors.name = form.name ? '' : '请填写您的姓名'
|
||
errors.phone = /^1[3-9]\d{9}$/.test(form.phone) ? '' : '请填写正确的手机号码'
|
||
return !errors.name && !errors.phone
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
if (!validate()) return
|
||
if (submitting.value) return
|
||
|
||
submitting.value = true
|
||
submitError.value = ''
|
||
|
||
try {
|
||
await $fetch('/api/customize', {
|
||
method: 'POST',
|
||
body: {
|
||
name: form.name,
|
||
phone: form.phone,
|
||
wechat: form.wechat,
|
||
adults: form.adults,
|
||
children: form.children,
|
||
childAges: form.childAges,
|
||
date: form.date,
|
||
duration: form.duration,
|
||
activities: form.activities,
|
||
budget: form.budget,
|
||
remarks: form.remarks,
|
||
},
|
||
})
|
||
submitted.value = true
|
||
} catch (err) {
|
||
const msg = err?.data?.message || err?.message || '提交失败,请稍后重试'
|
||
submitError.value = msg
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
function resetForm() {
|
||
Object.assign(form, {
|
||
adults: 2,
|
||
children: 0,
|
||
childAges: [],
|
||
date: '',
|
||
duration: '',
|
||
activities: [],
|
||
budget: '',
|
||
name: '',
|
||
phone: '',
|
||
wechat: '',
|
||
remarks: '',
|
||
})
|
||
errors.name = ''
|
||
errors.phone = ''
|
||
submitted.value = false
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
// 信任指标条
|
||
.trust-bar {
|
||
background: @color-bg-warm;
|
||
padding: @space-lg 0;
|
||
border-bottom: 1px solid @color-border;
|
||
}
|
||
|
||
.trust-list {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: @space-2xl;
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
flex-wrap: wrap;
|
||
|
||
.respond-lg({
|
||
gap: @space-xl;
|
||
});
|
||
}
|
||
|
||
.trust-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: @space-xs;
|
||
}
|
||
|
||
.trust-value {
|
||
font-size: @font-size-2xl;
|
||
font-weight: 700;
|
||
color: @color-primary;
|
||
line-height: 1;
|
||
|
||
.trust-unit {
|
||
font-size: @font-size-md;
|
||
font-style: normal;
|
||
font-weight: 500;
|
||
margin-left: 2px;
|
||
}
|
||
}
|
||
|
||
.trust-label {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
}
|
||
|
||
// 整体布局
|
||
.customize-layout {
|
||
display: grid;
|
||
grid-template-columns: 1fr 320px;
|
||
gap: @space-2xl;
|
||
align-items: start;
|
||
|
||
.respond-lg({
|
||
grid-template-columns: 1fr;
|
||
});
|
||
}
|
||
|
||
// 表单容器
|
||
.form-wrap {
|
||
background: @color-bg-white;
|
||
border-radius: @border-radius-lg;
|
||
border: 1px solid @color-border;
|
||
overflow: hidden;
|
||
}
|
||
|
||
// 成功状态卡片
|
||
.success-card {
|
||
padding: @space-2xl;
|
||
text-align: center;
|
||
|
||
.respond-lg({
|
||
padding: @space-xl @space-lg;
|
||
});
|
||
}
|
||
|
||
.success-check {
|
||
width: 64px;
|
||
height: 64px;
|
||
border-radius: 50%;
|
||
background: @color-primary;
|
||
color: #fff;
|
||
font-size: 28px;
|
||
line-height: 64px;
|
||
margin: 0 auto @space-lg;
|
||
}
|
||
|
||
.success-title {
|
||
font-size: @font-size-xl;
|
||
font-weight: 700;
|
||
color: @color-text-primary;
|
||
margin-bottom: @space-sm;
|
||
}
|
||
|
||
.success-desc {
|
||
font-size: @font-size-md;
|
||
color: @color-text-secondary;
|
||
line-height: 1.6;
|
||
margin-bottom: @space-xl;
|
||
}
|
||
|
||
.success-wechat {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: @space-xs;
|
||
padding: @space-md @space-xl;
|
||
background: @color-bg-warm;
|
||
border-radius: @border-radius-md;
|
||
margin-bottom: @space-sm;
|
||
}
|
||
|
||
.wechat-label {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
}
|
||
|
||
.wechat-id {
|
||
font-size: @font-size-lg;
|
||
font-weight: 700;
|
||
color: @color-primary;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.success-tip {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
margin-bottom: @space-xl;
|
||
}
|
||
|
||
.btn-reset {
|
||
display: inline-block;
|
||
padding: @space-sm @space-xl;
|
||
border: 1px solid @color-border;
|
||
border-radius: @border-radius-md;
|
||
background: transparent;
|
||
color: @color-text-secondary;
|
||
font-size: @font-size-sm;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
border-color: @color-primary;
|
||
color: @color-primary;
|
||
}
|
||
}
|
||
|
||
// 表单整体
|
||
.customize-form {
|
||
padding: 0;
|
||
}
|
||
|
||
// 每个表单分区
|
||
.form-section {
|
||
border: none;
|
||
border-bottom: 1px solid @color-border;
|
||
margin: 0;
|
||
padding: @space-xl @space-2xl;
|
||
|
||
.respond-lg({
|
||
padding: @space-lg;
|
||
});
|
||
|
||
&:last-of-type {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
|
||
.section-legend {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: @space-sm;
|
||
font-size: @font-size-md;
|
||
font-weight: 600;
|
||
color: @color-text-primary;
|
||
margin-bottom: @space-lg;
|
||
padding: 0;
|
||
float: none;
|
||
width: 100%;
|
||
}
|
||
|
||
.legend-num {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 50%;
|
||
background: @color-primary;
|
||
color: #fff;
|
||
font-size: @font-size-xs;
|
||
font-weight: 700;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.legend-hint {
|
||
font-size: @font-size-sm;
|
||
font-weight: 400;
|
||
color: @color-text-secondary;
|
||
margin-left: auto;
|
||
}
|
||
|
||
// 出行人数
|
||
.people-row {
|
||
display: flex;
|
||
gap: @space-2xl;
|
||
|
||
.respond-lg({
|
||
gap: @space-xl;
|
||
});
|
||
}
|
||
|
||
.form-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: @space-xs;
|
||
|
||
label {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
font-weight: 500;
|
||
}
|
||
|
||
&.has-error {
|
||
input,
|
||
select {
|
||
border-color: @color-error;
|
||
}
|
||
}
|
||
}
|
||
|
||
.number-ctrl {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
border: 1px solid @color-border;
|
||
border-radius: @border-radius-md;
|
||
overflow: hidden;
|
||
width: fit-content;
|
||
|
||
button {
|
||
width: 40px;
|
||
height: 40px;
|
||
background: @color-bg-warm;
|
||
border: none;
|
||
font-size: @font-size-lg;
|
||
color: @color-text-primary;
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
line-height: 1;
|
||
|
||
&:hover {
|
||
background: @color-border;
|
||
}
|
||
}
|
||
}
|
||
|
||
.number-display {
|
||
display: inline-block;
|
||
min-width: 48px;
|
||
text-align: center;
|
||
font-size: @font-size-lg;
|
||
font-weight: 600;
|
||
color: @color-text-primary;
|
||
padding: 0 @space-sm;
|
||
line-height: 38px;
|
||
}
|
||
|
||
// 儿童年龄
|
||
.children-ages {
|
||
margin-top: @space-lg;
|
||
padding: @space-lg;
|
||
background: @color-bg-warm;
|
||
border-radius: @border-radius-md;
|
||
}
|
||
|
||
.ages-hint {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
margin-bottom: @space-md;
|
||
}
|
||
|
||
.ages-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: @space-md;
|
||
}
|
||
|
||
.age-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: @space-xs;
|
||
|
||
label {
|
||
font-size: @font-size-xs;
|
||
color: @color-text-secondary;
|
||
}
|
||
|
||
select {
|
||
width: 100px;
|
||
}
|
||
}
|
||
|
||
// 双列布局(出行计划)
|
||
.two-col {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: @space-lg;
|
||
|
||
.respond-lg({
|
||
grid-template-columns: 1fr;
|
||
});
|
||
}
|
||
|
||
// 通用输入样式
|
||
input[type='text'],
|
||
input[type='tel'],
|
||
input[type='date'],
|
||
select,
|
||
.form-textarea {
|
||
width: 100%;
|
||
padding: @space-sm @space-md;
|
||
border: 1px solid @color-border;
|
||
border-radius: @border-radius-md;
|
||
font-size: @font-size-md;
|
||
color: @color-text-primary;
|
||
background: @color-bg-white;
|
||
transition: border-color 0.15s;
|
||
outline: none;
|
||
font-family: inherit;
|
||
|
||
&:focus {
|
||
border-color: @color-primary;
|
||
box-shadow: 0 0 0 3px fade(@color-primary, 12%);
|
||
}
|
||
|
||
&::placeholder {
|
||
color: @color-text-placeholder;
|
||
}
|
||
}
|
||
|
||
.input-date {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.form-textarea {
|
||
resize: vertical;
|
||
min-height: 100px;
|
||
}
|
||
|
||
// 错误提示
|
||
.error-msg {
|
||
font-size: @font-size-xs;
|
||
color: @color-error;
|
||
}
|
||
|
||
.required {
|
||
font-style: normal;
|
||
color: @color-error;
|
||
}
|
||
|
||
// 多选特殊需求
|
||
.checkbox-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: @space-sm;
|
||
}
|
||
|
||
.checkbox-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: @space-xs;
|
||
padding: @space-sm @space-md;
|
||
border: 1.5px solid @color-border;
|
||
border-radius: @border-radius-md;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
user-select: none;
|
||
|
||
input[type='checkbox'] {
|
||
display: none;
|
||
}
|
||
|
||
.check-label {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-primary;
|
||
}
|
||
|
||
&:hover {
|
||
border-color: @color-primary;
|
||
}
|
||
|
||
&.is-checked {
|
||
border-color: @color-primary;
|
||
background: fade(@color-primary, 8%);
|
||
|
||
.check-label {
|
||
color: @color-primary;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 单选预算
|
||
.radio-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: @space-sm;
|
||
}
|
||
|
||
.radio-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: @space-xs;
|
||
padding: @space-sm @space-md;
|
||
border: 1.5px solid @color-border;
|
||
border-radius: @border-radius-md;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
user-select: none;
|
||
|
||
input[type='radio'] {
|
||
display: none;
|
||
}
|
||
|
||
span {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-primary;
|
||
}
|
||
|
||
&:hover {
|
||
border-color: @color-primary;
|
||
}
|
||
|
||
&.is-checked {
|
||
border-color: @color-primary;
|
||
background: fade(@color-primary, 8%);
|
||
|
||
span {
|
||
color: @color-primary;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 联系方式字段组
|
||
.contact-fields {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr 1fr;
|
||
gap: @space-lg;
|
||
|
||
.respond-lg({
|
||
grid-template-columns: 1fr;
|
||
});
|
||
}
|
||
|
||
// 提交区
|
||
.form-submit {
|
||
padding: @space-xl @space-2xl;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: @space-sm;
|
||
|
||
.respond-lg({
|
||
padding: @space-lg;
|
||
align-items: stretch;
|
||
});
|
||
}
|
||
|
||
.btn-submit {
|
||
display: inline-block;
|
||
padding: @space-md @space-2xl;
|
||
background: @color-primary;
|
||
color: #fff;
|
||
font-size: @font-size-md;
|
||
font-weight: 600;
|
||
border: none;
|
||
border-radius: @border-radius-md;
|
||
cursor: pointer;
|
||
transition: background 0.2s, transform 0.1s;
|
||
letter-spacing: 0.5px;
|
||
|
||
&:hover {
|
||
background: @color-primary-dark;
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.respond-lg({
|
||
width: 100%;
|
||
text-align: center;
|
||
padding: @space-md;
|
||
});
|
||
}
|
||
|
||
.submit-hint {
|
||
font-size: @font-size-xs;
|
||
color: @color-text-secondary;
|
||
}
|
||
|
||
// 侧边栏
|
||
.customize-aside {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: @space-lg;
|
||
position: sticky;
|
||
top: 84px;
|
||
|
||
.respond-lg({
|
||
position: static;
|
||
});
|
||
}
|
||
|
||
.aside-card {
|
||
background: @color-bg-white;
|
||
border: 1px solid @color-border;
|
||
border-radius: @border-radius-lg;
|
||
padding: @space-xl;
|
||
}
|
||
|
||
.aside-title {
|
||
font-size: @font-size-md;
|
||
font-weight: 600;
|
||
color: @color-text-primary;
|
||
margin-bottom: @space-lg;
|
||
}
|
||
|
||
.aside-desc {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
margin-bottom: @space-md;
|
||
}
|
||
|
||
// 流程步骤
|
||
.process-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: @space-md;
|
||
}
|
||
|
||
.process-item {
|
||
display: flex;
|
||
gap: @space-md;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.process-num {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
background: fade(@color-primary, 12%);
|
||
color: @color-primary;
|
||
font-size: @font-size-sm;
|
||
font-weight: 700;
|
||
flex-shrink: 0;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.process-text {
|
||
strong {
|
||
display: block;
|
||
font-size: @font-size-sm;
|
||
color: @color-text-primary;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
p {
|
||
font-size: @font-size-xs;
|
||
color: @color-text-secondary;
|
||
line-height: 1.5;
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
// 微信侧边卡片
|
||
.aside-wechat {
|
||
background: @color-bg-warm;
|
||
border-color: transparent;
|
||
}
|
||
|
||
.wechat-id-display {
|
||
font-size: @font-size-lg;
|
||
font-weight: 700;
|
||
color: @color-primary;
|
||
padding: @space-sm @space-md;
|
||
background: @color-bg-white;
|
||
border-radius: @border-radius-md;
|
||
text-align: center;
|
||
margin-bottom: @space-sm;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.wechat-tip {
|
||
font-size: @font-size-xs;
|
||
color: @color-text-secondary;
|
||
text-align: center;
|
||
margin: 0;
|
||
}
|
||
</style>
|