1833 lines
48 KiB
Vue
1833 lines
48 KiB
Vue
<template>
|
||
<view class="profile-edit-page">
|
||
<!-- 固定头部区域 -->
|
||
<view class="fixed-header">
|
||
<!-- 自定义导航栏(带渐变背景) -->
|
||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="navbar-content">
|
||
<view class="navbar-back" @click="handleBack">
|
||
<text class="back-icon">‹</text>
|
||
</view>
|
||
<text class="navbar-title">填写资料</text>
|
||
<view class="navbar-placeholder"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 步骤指示器(白色背景,不在渐变内) -->
|
||
<view class="step-indicator">
|
||
<view
|
||
v-for="(step, index) in steps"
|
||
:key="index"
|
||
class="step-item"
|
||
:class="{ active: currentStep === index, completed: currentStep > index }"
|
||
>
|
||
<view class="step-dot">{{ index + 1 }}</view>
|
||
<text class="step-label">{{ step.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 占位区域 -->
|
||
<view class="header-placeholder" :style="{ height: headerHeight + 'px' }"></view>
|
||
|
||
<!-- 步骤内容 -->
|
||
<view class="step-content">
|
||
<!-- 步骤1: 基础信息 -->
|
||
<view v-show="currentStep === 0" class="form-section">
|
||
<view class="section-title">基础信息</view>
|
||
|
||
<!-- 照片上传 -->
|
||
<view class="photo-upload-section">
|
||
<view class="photo-label">上传照片(最多5张)</view>
|
||
<view class="photo-grid">
|
||
<view
|
||
v-for="(photo, index) in formData.photos"
|
||
:key="index"
|
||
class="photo-item"
|
||
>
|
||
<image :src="photo.url" mode="aspectFill" class="photo-image" />
|
||
<view class="photo-delete" @click="handleDeletePhoto(index)">×</view>
|
||
</view>
|
||
<view
|
||
v-if="formData.photos.length < 5"
|
||
class="photo-add"
|
||
@click="handleChoosePhoto"
|
||
>
|
||
<text class="add-icon">+</text>
|
||
<text class="add-text">添加照片</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 照片公开设置 -->
|
||
<view class="form-item">
|
||
<text class="form-label">照片是否公开</text>
|
||
<view class="radio-group">
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: formData.isPhotoPublic }"
|
||
@click="formData.isPhotoPublic = true"
|
||
>
|
||
<view class="radio-dot"></view>
|
||
<text>公开</text>
|
||
</view>
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: !formData.isPhotoPublic }"
|
||
@click="formData.isPhotoPublic = false"
|
||
>
|
||
<view class="radio-dot"></view>
|
||
<text>私密</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 关系 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">您与相亲者的关系</text>
|
||
<picker
|
||
:value="relationshipIndex"
|
||
:range="relationshipOptions"
|
||
range-key="label"
|
||
@change="handleRelationshipChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ relationshipOptions[relationshipIndex]?.label || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 姓氏 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">姓氏</text>
|
||
<input
|
||
v-model="formData.surname"
|
||
placeholder="请输入姓氏"
|
||
maxlength="2"
|
||
@blur="handleSurnameBlur"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 昵称(自动生成) -->
|
||
<view class="form-item">
|
||
<text class="form-label">昵称(自动生成)</text>
|
||
<input
|
||
:value="formData.nickname"
|
||
disabled
|
||
placeholder="填写关系和姓氏后自动生成"
|
||
placeholder-class="input-placeholder"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 相亲者性别 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">相亲者性别</text>
|
||
<view class="radio-group">
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: formData.childGender === 1 }"
|
||
@click="formData.childGender = 1"
|
||
>
|
||
<view class="radio-dot"></view>
|
||
<text>男</text>
|
||
</view>
|
||
<view
|
||
class="radio-item"
|
||
:class="{ active: formData.childGender === 2 }"
|
||
@click="formData.childGender = 2"
|
||
>
|
||
<view class="radio-dot"></view>
|
||
<text>女</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 出生年份 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">出生年份</text>
|
||
<picker
|
||
:value="birthYearIndex"
|
||
:range="birthYearOptions"
|
||
@change="handleBirthYearChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ formData.birthYear || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 籍贯 -->
|
||
<view class="form-item">
|
||
<text class="form-label">籍贯</text>
|
||
<picker
|
||
mode="region"
|
||
:value="[formData.homeProvince, formData.homeCity, formData.homeDistrict]"
|
||
@change="handleHomeLocationChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ homeLocationText || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 步骤2: 详细信息 -->
|
||
<view v-show="currentStep === 1" class="form-section">
|
||
<view class="section-title">详细信息</view>
|
||
|
||
<!-- 学历 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">学历</text>
|
||
<picker
|
||
:value="educationIndex"
|
||
:range="educationOptions"
|
||
range-key="label"
|
||
@change="handleEducationChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ educationOptions[educationIndex]?.label || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 工作地点 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">工作地点</text>
|
||
<picker
|
||
mode="region"
|
||
:value="[formData.workProvince, formData.workCity, formData.workDistrict]"
|
||
@change="handleWorkLocationChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ workLocationText || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 职业 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">职业</text>
|
||
<input
|
||
v-model="formData.occupation"
|
||
placeholder="请输入职业"
|
||
maxlength="20"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 月收入 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">月收入</text>
|
||
<picker
|
||
:value="incomeIndex"
|
||
:range="incomeOptions"
|
||
range-key="label"
|
||
@change="handleIncomeChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ incomeOptions[incomeIndex]?.label || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 身高 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">身高(cm)</text>
|
||
<picker
|
||
:value="heightIndex"
|
||
:range="heightOptions"
|
||
@change="handleHeightChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ formData.height ? formData.height + 'cm' : '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 体重 -->
|
||
<view class="form-item">
|
||
<text class="form-label">体重(kg)</text>
|
||
<picker
|
||
:value="weightIndex"
|
||
:range="weightOptions"
|
||
@change="handleWeightChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ formData.weight ? formData.weight + 'kg' : '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 房产状态 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">房产状态</text>
|
||
<picker
|
||
:value="houseIndex"
|
||
:range="houseOptions"
|
||
range-key="label"
|
||
@change="handleHouseChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ houseOptions[houseIndex]?.label || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 车辆状态 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">车辆状态</text>
|
||
<picker
|
||
:value="carIndex"
|
||
:range="carOptions"
|
||
range-key="label"
|
||
@change="handleCarChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ carOptions[carIndex]?.label || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 婚姻状态 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">婚姻状态</text>
|
||
<picker
|
||
:value="marriageIndex"
|
||
:range="marriageOptions"
|
||
range-key="label"
|
||
@change="handleMarriageChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ marriageOptions[marriageIndex]?.label || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 期望结婚时间 -->
|
||
<view class="form-item">
|
||
<text class="form-label">期望结婚时间</text>
|
||
<picker
|
||
:value="expectMarryIndex"
|
||
:range="expectMarryOptions"
|
||
range-key="label"
|
||
@change="handleExpectMarryChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ expectMarryOptions[expectMarryIndex]?.label || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 步骤3: 自我介绍 -->
|
||
<view v-show="currentStep === 2" class="form-section">
|
||
<view class="section-title">自我介绍</view>
|
||
|
||
<view class="form-item textarea-item">
|
||
<text class="form-label required">介绍一下相亲者</text>
|
||
<textarea
|
||
v-model="formData.introduction"
|
||
placeholder="请介绍相亲者的性格、爱好、优点等,让对方更了解TA"
|
||
maxlength="500"
|
||
:show-count="true"
|
||
/>
|
||
<text class="char-count">{{ formData.introduction.length }}/500</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 步骤4: 择偶要求 -->
|
||
<view v-show="currentStep === 3" class="form-section">
|
||
<view class="section-title">择偶要求</view>
|
||
|
||
<!-- 年龄范围 -->
|
||
<view class="form-item">
|
||
<text class="form-label">年龄范围</text>
|
||
<view class="range-picker">
|
||
<picker
|
||
:value="requirementAgeMinIndex"
|
||
:range="ageRangeOptions"
|
||
@change="handleAgeMinChange"
|
||
>
|
||
<view class="picker-value small">{{ formData.requirement.ageMin || '不限' }}</view>
|
||
</picker>
|
||
<text class="range-separator">至</text>
|
||
<picker
|
||
:value="requirementAgeMaxIndex"
|
||
:range="ageRangeOptions"
|
||
@change="handleAgeMaxChange"
|
||
>
|
||
<view class="picker-value small">{{ formData.requirement.ageMax || '不限' }}</view>
|
||
</picker>
|
||
<text class="range-unit">岁</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 身高范围 -->
|
||
<view class="form-item">
|
||
<text class="form-label">身高范围</text>
|
||
<view class="range-picker">
|
||
<picker
|
||
:value="requirementHeightMinIndex"
|
||
:range="heightRangeOptions"
|
||
@change="handleHeightMinChange"
|
||
>
|
||
<view class="picker-value small">{{ formData.requirement.heightMin || '不限' }}</view>
|
||
</picker>
|
||
<text class="range-separator">至</text>
|
||
<picker
|
||
:value="requirementHeightMaxIndex"
|
||
:range="heightRangeOptions"
|
||
@change="handleHeightMaxChange"
|
||
>
|
||
<view class="picker-value small">{{ formData.requirement.heightMax || '不限' }}</view>
|
||
</picker>
|
||
<text class="range-unit">cm</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 学历要求 -->
|
||
<view class="form-item">
|
||
<text class="form-label">学历要求</text>
|
||
<view class="checkbox-group">
|
||
<view
|
||
v-for="edu in educationOptions"
|
||
:key="edu.value"
|
||
class="checkbox-item"
|
||
:class="{ active: formData.requirement.education.includes(edu.value) }"
|
||
@click="toggleEducationRequirement(edu.value)"
|
||
>
|
||
{{ edu.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 意向城市1 -->
|
||
<view class="form-item">
|
||
<text class="form-label">意向城市1</text>
|
||
<picker
|
||
mode="region"
|
||
:level="'city'"
|
||
:value="[formData.requirement.city1Province, formData.requirement.city1City]"
|
||
@change="handleCity1Change"
|
||
>
|
||
<view class="picker-value">
|
||
{{ city1Text || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 意向城市2 -->
|
||
<view class="form-item">
|
||
<text class="form-label">意向城市2</text>
|
||
<picker
|
||
mode="region"
|
||
:level="'city'"
|
||
:value="[formData.requirement.city2Province, formData.requirement.city2City]"
|
||
@change="handleCity2Change"
|
||
>
|
||
<view class="picker-value">
|
||
{{ city2Text || '请选择' }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 房产要求 -->
|
||
<view class="form-item">
|
||
<text class="form-label">房产要求</text>
|
||
<view class="checkbox-group">
|
||
<view
|
||
v-for="house in houseOptions"
|
||
:key="house.value"
|
||
class="checkbox-item"
|
||
:class="{ active: formData.requirement.houseStatus.includes(house.value) }"
|
||
@click="toggleHouseRequirement(house.value)"
|
||
>
|
||
{{ house.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 车辆要求 -->
|
||
<view class="form-item">
|
||
<text class="form-label">车辆要求</text>
|
||
<view class="checkbox-group">
|
||
<view
|
||
v-for="car in carOptions"
|
||
:key="car.value"
|
||
class="checkbox-item"
|
||
:class="{ active: formData.requirement.carStatus.includes(car.value) }"
|
||
@click="toggleCarRequirement(car.value)"
|
||
>
|
||
{{ car.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 婚姻状态要求 -->
|
||
<view class="form-item">
|
||
<text class="form-label">婚姻状态要求</text>
|
||
<view class="checkbox-group">
|
||
<view
|
||
v-for="marriage in marriageOptions"
|
||
:key="marriage.value"
|
||
class="checkbox-item"
|
||
:class="{ active: formData.requirement.marriageStatus.includes(marriage.value) }"
|
||
@click="toggleMarriageRequirement(marriage.value)"
|
||
>
|
||
{{ marriage.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 期望月收入 -->
|
||
<view class="form-item">
|
||
<text class="form-label">期望月收入</text>
|
||
<picker
|
||
:value="requirementIncomeMinIndex"
|
||
:range="incomeRangeOptions"
|
||
@change="handleIncomeMinChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ getIncomeLabel(formData.requirement.monthlyIncomeMin) }}
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 步骤5: 联系方式 -->
|
||
<view v-show="currentStep === 4" class="form-section">
|
||
<view class="section-title">联系方式</view>
|
||
|
||
<!-- 微信号 -->
|
||
<view class="form-item">
|
||
<text class="form-label required">微信号</text>
|
||
<input
|
||
v-model="formData.weChatNo"
|
||
placeholder="请输入微信号"
|
||
maxlength="30"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 手机号验证 -->
|
||
<view class="form-item">
|
||
<text class="form-label">手机号验证</text>
|
||
<button
|
||
v-if="!phoneVerified"
|
||
class="verify-btn"
|
||
open-type="getPhoneNumber"
|
||
@getphonenumber="handleGetPhoneNumber"
|
||
>
|
||
点击验证手机号
|
||
</button>
|
||
<view v-else class="phone-verified">
|
||
<text class="phone-number">{{ formData.phone }}</text>
|
||
<text class="verified-tag">已验证</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部按钮 -->
|
||
<view class="bottom-buttons">
|
||
<button
|
||
v-if="currentStep > 0"
|
||
class="btn-prev"
|
||
@click="handlePrevStep"
|
||
>
|
||
上一步
|
||
</button>
|
||
<button
|
||
v-if="currentStep < steps.length - 1"
|
||
class="btn-next"
|
||
@click="handleNextStep"
|
||
>
|
||
下一步
|
||
</button>
|
||
<button
|
||
v-if="currentStep === steps.length - 1"
|
||
class="btn-submit"
|
||
:loading="submitting"
|
||
@click="handleSubmit"
|
||
>
|
||
提交资料
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||
import { useUserStore } from '@/store/user.js'
|
||
import { createOrUpdate, getMyProfile, uploadPhotos, deletePhoto, cleanupOrphanPhotos } from '@/api/profile.js'
|
||
import { generateNickname, getBirthYearRange } from '@/utils/format.js'
|
||
import config from '@/config/index.js'
|
||
|
||
const userStore = useUserStore()
|
||
|
||
// 从统一配置获取静态资源地址
|
||
const STATIC_URL = config.STATIC_BASE_URL
|
||
|
||
// 状态栏高度
|
||
const statusBarHeight = ref(20)
|
||
const headerHeight = ref(120)
|
||
|
||
// 获取系统信息
|
||
const getSystemInfo = () => {
|
||
uni.getSystemInfo({
|
||
success: (res) => {
|
||
statusBarHeight.value = res.statusBarHeight || 20
|
||
// 计算头部总高度:状态栏 + 导航栏(44px) + 步骤指示器(约100px)
|
||
headerHeight.value = statusBarHeight.value + 44 + 100
|
||
}
|
||
})
|
||
}
|
||
|
||
// 返回确认
|
||
const handleBack = () => {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要离开吗?已填写的内容将不会保存',
|
||
confirmText: '离开',
|
||
cancelText: '继续填写',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
uni.navigateBack()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 步骤配置
|
||
const steps = [
|
||
{ label: '基础信息' },
|
||
{ label: '详细信息' },
|
||
{ label: '介绍' },
|
||
{ label: '要求' },
|
||
{ label: '联系方式' }
|
||
]
|
||
|
||
const currentStep = ref(0)
|
||
const submitting = ref(false)
|
||
const phoneVerified = ref(false)
|
||
|
||
// 表单数据
|
||
const formData = reactive({
|
||
photos: [],
|
||
isPhotoPublic: true,
|
||
relationship: 1, // 默认选择"父亲"
|
||
surname: '',
|
||
nickname: '',
|
||
childGender: 1, // 默认选择"男"
|
||
birthYear: 0,
|
||
education: 1, // 默认选择"高中及以下"
|
||
workProvince: '',
|
||
workCity: '',
|
||
workDistrict: '',
|
||
homeProvince: '',
|
||
homeCity: '',
|
||
homeDistrict: '',
|
||
occupation: '',
|
||
monthlyIncome: 1, // 默认选择"5000以下"
|
||
height: 0,
|
||
weight: 0,
|
||
houseStatus: 5, // 默认选择"租房"
|
||
carStatus: 2, // 默认选择"无车"
|
||
marriageStatus: 1, // 默认选择"未婚"
|
||
expectMarryTime: 0,
|
||
introduction: '',
|
||
weChatNo: '',
|
||
phone: '', // 验证后的手机号
|
||
requirement: {
|
||
ageMin: 0,
|
||
ageMax: 0,
|
||
heightMin: 0,
|
||
heightMax: 0,
|
||
education: [],
|
||
city1Province: '',
|
||
city1City: '',
|
||
city2Province: '',
|
||
city2City: '',
|
||
monthlyIncomeMin: 0,
|
||
monthlyIncomeMax: 0,
|
||
houseStatus: [],
|
||
carStatus: [],
|
||
marriageStatus: []
|
||
}
|
||
})
|
||
|
||
// 选项配置
|
||
const relationshipOptions = [
|
||
{ value: 1, label: '父亲' },
|
||
{ value: 2, label: '母亲' },
|
||
{ value: 3, label: '本人' }
|
||
]
|
||
|
||
const educationOptions = [
|
||
{ value: 1, label: '高中' },
|
||
{ value: 2, label: '中专' },
|
||
{ value: 3, label: '大专' },
|
||
{ value: 4, label: '本科' },
|
||
{ value: 5, label: '研究生' },
|
||
{ value: 6, label: '博士及以上' }
|
||
]
|
||
|
||
const incomeOptions = [
|
||
{ value: 1, label: '5000以下' },
|
||
{ value: 2, label: '5000-10000' },
|
||
{ value: 3, label: '10000-20000' },
|
||
{ value: 4, label: '20000-50000' },
|
||
{ value: 5, label: '50000以上' }
|
||
]
|
||
|
||
const houseOptions = [
|
||
{ value: 1, label: '现居地已购房' },
|
||
{ value: 2, label: '家乡已购房' },
|
||
{ value: 3, label: '婚后购房' },
|
||
{ value: 4, label: '父母同住' },
|
||
{ value: 5, label: '租房' },
|
||
{ value: 6, label: '近期有购房计划' }
|
||
]
|
||
|
||
const carOptions = [
|
||
{ value: 1, label: '已购车' },
|
||
{ value: 2, label: '无车' },
|
||
{ value: 3, label: '近期购车' }
|
||
]
|
||
|
||
const marriageOptions = [
|
||
{ value: 1, label: '未婚' },
|
||
{ value: 2, label: '离异未育' },
|
||
{ value: 3, label: '离异已育' }
|
||
]
|
||
|
||
const expectMarryOptions = [
|
||
{ value: 1, label: '尽快结婚' },
|
||
{ value: 2, label: '一到两年内' },
|
||
{ value: 3, label: '孩子满意就结婚' }
|
||
]
|
||
|
||
// 出生年份选项 (Property 9: Birth Year Range)
|
||
const birthYearOptions = computed(() => getBirthYearRange())
|
||
|
||
// 身高选项 (140-220cm)
|
||
const heightOptions = computed(() => {
|
||
const options = []
|
||
for (let h = 140; h <= 220; h++) {
|
||
options.push(h)
|
||
}
|
||
return options
|
||
})
|
||
|
||
// 体重选项 (30-150kg)
|
||
const weightOptions = computed(() => {
|
||
const options = []
|
||
for (let w = 30; w <= 150; w++) {
|
||
options.push(w)
|
||
}
|
||
return options
|
||
})
|
||
|
||
// 年龄范围选项 (18-60)
|
||
const ageRangeOptions = computed(() => {
|
||
const options = ['不限']
|
||
for (let a = 18; a <= 60; a++) {
|
||
options.push(a)
|
||
}
|
||
return options
|
||
})
|
||
|
||
// 身高范围选项
|
||
const heightRangeOptions = computed(() => {
|
||
const options = ['不限']
|
||
for (let h = 140; h <= 220; h++) {
|
||
options.push(h)
|
||
}
|
||
return options
|
||
})
|
||
|
||
// 月收入范围选项
|
||
const incomeRangeOptions = ['不限', '5000以下', '5000-10000', '10000-20000', '20000-50000', '50000以上']
|
||
|
||
// 获取月收入标签
|
||
const getIncomeLabel = (value) => {
|
||
if (!value) return '不限'
|
||
const labels = {
|
||
1: '5000以下',
|
||
2: '5000-10000',
|
||
3: '10000-20000',
|
||
4: '20000-50000',
|
||
5: '50000以上'
|
||
}
|
||
return labels[value] || '不限'
|
||
}
|
||
|
||
// 选中索引计算
|
||
const relationshipIndex = computed(() => {
|
||
const idx = relationshipOptions.findIndex(o => o.value === formData.relationship)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const birthYearIndex = computed(() => {
|
||
const idx = birthYearOptions.value.indexOf(formData.birthYear)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const educationIndex = computed(() => {
|
||
const idx = educationOptions.findIndex(o => o.value === formData.education)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const incomeIndex = computed(() => {
|
||
const idx = incomeOptions.findIndex(o => o.value === formData.monthlyIncome)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const heightIndex = computed(() => {
|
||
const idx = heightOptions.value.indexOf(formData.height)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const weightIndex = computed(() => {
|
||
const idx = weightOptions.value.indexOf(formData.weight)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const houseIndex = computed(() => {
|
||
const idx = houseOptions.findIndex(o => o.value === formData.houseStatus)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const carIndex = computed(() => {
|
||
const idx = carOptions.findIndex(o => o.value === formData.carStatus)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const marriageIndex = computed(() => {
|
||
const idx = marriageOptions.findIndex(o => o.value === formData.marriageStatus)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
const expectMarryIndex = computed(() => {
|
||
const idx = expectMarryOptions.findIndex(o => o.value === formData.expectMarryTime)
|
||
return idx >= 0 ? idx : 0
|
||
})
|
||
|
||
// 择偶要求索引
|
||
const requirementAgeMinIndex = computed(() => {
|
||
if (!formData.requirement.ageMin) return 0
|
||
return ageRangeOptions.value.indexOf(formData.requirement.ageMin)
|
||
})
|
||
|
||
const requirementAgeMaxIndex = computed(() => {
|
||
if (!formData.requirement.ageMax) return 0
|
||
return ageRangeOptions.value.indexOf(formData.requirement.ageMax)
|
||
})
|
||
|
||
const requirementHeightMinIndex = computed(() => {
|
||
if (!formData.requirement.heightMin) return 0
|
||
return heightRangeOptions.value.indexOf(formData.requirement.heightMin)
|
||
})
|
||
|
||
const requirementHeightMaxIndex = computed(() => {
|
||
if (!formData.requirement.heightMax) return 0
|
||
return heightRangeOptions.value.indexOf(formData.requirement.heightMax)
|
||
})
|
||
|
||
const requirementIncomeMinIndex = computed(() => {
|
||
if (!formData.requirement.monthlyIncomeMin) return 0
|
||
return formData.requirement.monthlyIncomeMin
|
||
})
|
||
|
||
// 地点文本
|
||
const workLocationText = computed(() => {
|
||
if (formData.workProvince && formData.workCity) {
|
||
return `${formData.workProvince} ${formData.workCity} ${formData.workDistrict || ''}`
|
||
}
|
||
return ''
|
||
})
|
||
|
||
const homeLocationText = computed(() => {
|
||
if (formData.homeProvince && formData.homeCity) {
|
||
return `${formData.homeProvince} ${formData.homeCity} ${formData.homeDistrict || ''}`
|
||
}
|
||
return ''
|
||
})
|
||
|
||
const city1Text = computed(() => {
|
||
if (formData.requirement.city1Province && formData.requirement.city1City) {
|
||
return `${formData.requirement.city1Province} ${formData.requirement.city1City}`
|
||
}
|
||
return ''
|
||
})
|
||
|
||
const city2Text = computed(() => {
|
||
if (formData.requirement.city2Province && formData.requirement.city2City) {
|
||
return `${formData.requirement.city2Province} ${formData.requirement.city2City}`
|
||
}
|
||
return ''
|
||
})
|
||
|
||
// 监听关系和姓氏变化,自动生成昵称 (Property 8: Nickname Auto-Generation)
|
||
watch(
|
||
() => [formData.relationship, formData.surname, formData.childGender],
|
||
([relationship, surname, gender]) => {
|
||
if (relationship && surname) {
|
||
formData.nickname = generateNickname(relationship, surname, gender || 1)
|
||
}
|
||
}
|
||
)
|
||
|
||
// 选择器变更处理
|
||
const handleRelationshipChange = (e) => {
|
||
formData.relationship = relationshipOptions[e.detail.value].value
|
||
}
|
||
|
||
const handleBirthYearChange = (e) => {
|
||
formData.birthYear = birthYearOptions.value[e.detail.value]
|
||
}
|
||
|
||
const handleEducationChange = (e) => {
|
||
formData.education = educationOptions[e.detail.value].value
|
||
}
|
||
|
||
const handleWorkLocationChange = (e) => {
|
||
const [province, city, district] = e.detail.value
|
||
formData.workProvince = province
|
||
formData.workCity = city
|
||
formData.workDistrict = district || ''
|
||
}
|
||
|
||
const handleHomeLocationChange = (e) => {
|
||
const [province, city, district] = e.detail.value
|
||
formData.homeProvince = province
|
||
formData.homeCity = city
|
||
formData.homeDistrict = district || ''
|
||
}
|
||
|
||
const handleIncomeChange = (e) => {
|
||
formData.monthlyIncome = incomeOptions[e.detail.value].value
|
||
}
|
||
|
||
const handleHeightChange = (e) => {
|
||
formData.height = heightOptions.value[e.detail.value]
|
||
}
|
||
|
||
const handleWeightChange = (e) => {
|
||
formData.weight = weightOptions.value[e.detail.value]
|
||
}
|
||
|
||
const handleHouseChange = (e) => {
|
||
formData.houseStatus = houseOptions[e.detail.value].value
|
||
}
|
||
|
||
const handleCarChange = (e) => {
|
||
formData.carStatus = carOptions[e.detail.value].value
|
||
}
|
||
|
||
const handleMarriageChange = (e) => {
|
||
formData.marriageStatus = marriageOptions[e.detail.value].value
|
||
}
|
||
|
||
const handleExpectMarryChange = (e) => {
|
||
formData.expectMarryTime = expectMarryOptions[e.detail.value].value
|
||
}
|
||
|
||
const handleSurnameBlur = () => {
|
||
// 姓氏输入完成后触发昵称生成
|
||
if (formData.relationship && formData.surname) {
|
||
formData.nickname = generateNickname(formData.relationship, formData.surname, formData.childGender || 1)
|
||
}
|
||
}
|
||
|
||
// 择偶要求处理
|
||
const handleAgeMinChange = (e) => {
|
||
const val = ageRangeOptions.value[e.detail.value]
|
||
formData.requirement.ageMin = val === '不限' ? 0 : val
|
||
}
|
||
|
||
const handleAgeMaxChange = (e) => {
|
||
const val = ageRangeOptions.value[e.detail.value]
|
||
formData.requirement.ageMax = val === '不限' ? 0 : val
|
||
}
|
||
|
||
const handleHeightMinChange = (e) => {
|
||
const val = heightRangeOptions.value[e.detail.value]
|
||
formData.requirement.heightMin = val === '不限' ? 0 : val
|
||
}
|
||
|
||
const handleHeightMaxChange = (e) => {
|
||
const val = heightRangeOptions.value[e.detail.value]
|
||
formData.requirement.heightMax = val === '不限' ? 0 : val
|
||
}
|
||
|
||
const handleIncomeMinChange = (e) => {
|
||
const idx = e.detail.value
|
||
formData.requirement.monthlyIncomeMin = idx === 0 ? 0 : idx
|
||
}
|
||
|
||
const handleCity1Change = (e) => {
|
||
const [province, city] = e.detail.value
|
||
formData.requirement.city1Province = province
|
||
formData.requirement.city1City = city
|
||
}
|
||
|
||
const handleCity2Change = (e) => {
|
||
const [province, city] = e.detail.value
|
||
formData.requirement.city2Province = province
|
||
formData.requirement.city2City = city
|
||
}
|
||
|
||
// 多选切换
|
||
const toggleEducationRequirement = (value) => {
|
||
const idx = formData.requirement.education.indexOf(value)
|
||
if (idx >= 0) {
|
||
formData.requirement.education.splice(idx, 1)
|
||
} else {
|
||
formData.requirement.education.push(value)
|
||
}
|
||
}
|
||
|
||
const toggleHouseRequirement = (value) => {
|
||
const idx = formData.requirement.houseStatus.indexOf(value)
|
||
if (idx >= 0) {
|
||
formData.requirement.houseStatus.splice(idx, 1)
|
||
} else {
|
||
formData.requirement.houseStatus.push(value)
|
||
}
|
||
}
|
||
|
||
const toggleCarRequirement = (value) => {
|
||
const idx = formData.requirement.carStatus.indexOf(value)
|
||
if (idx >= 0) {
|
||
formData.requirement.carStatus.splice(idx, 1)
|
||
} else {
|
||
formData.requirement.carStatus.push(value)
|
||
}
|
||
}
|
||
|
||
const toggleMarriageRequirement = (value) => {
|
||
const idx = formData.requirement.marriageStatus.indexOf(value)
|
||
if (idx >= 0) {
|
||
formData.requirement.marriageStatus.splice(idx, 1)
|
||
} else {
|
||
formData.requirement.marriageStatus.push(value)
|
||
}
|
||
}
|
||
|
||
// 照片处理 (Property 10: Photo Upload Limit)
|
||
const handleChoosePhoto = () => {
|
||
// 检查照片数量限制
|
||
if (formData.photos.length >= 5) {
|
||
uni.showToast({ title: '最多上传5张照片', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
const remainingCount = 5 - formData.photos.length
|
||
|
||
uni.chooseImage({
|
||
count: remainingCount,
|
||
sizeType: ['compressed'],
|
||
sourceType: ['album', 'camera'],
|
||
success: async (res) => {
|
||
const tempFilePaths = res.tempFilePaths
|
||
|
||
// 再次检查总数不超过5张
|
||
if (formData.photos.length + tempFilePaths.length > 5) {
|
||
uni.showToast({ title: '最多上传5张照片', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
uni.showLoading({ title: '上传中...' })
|
||
|
||
try {
|
||
const uploadRes = await uploadPhotos(tempFilePaths)
|
||
if (uploadRes && uploadRes.success && uploadRes.data?.photos) {
|
||
// 添加到照片列表
|
||
uploadRes.data.photos.forEach(photo => {
|
||
if (formData.photos.length < 5) {
|
||
formData.photos.push({
|
||
id: photo.id || null, // 新上传的照片可能没有 id
|
||
url: photo.url || photo.photoUrl
|
||
})
|
||
}
|
||
})
|
||
} else {
|
||
uni.showToast({ title: '上传失败', icon: 'none' })
|
||
}
|
||
} catch (error) {
|
||
console.error('上传照片错误:', error)
|
||
uni.showToast({ title: error.message || '上传失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleDeletePhoto = async (index) => {
|
||
const photo = formData.photos[index]
|
||
|
||
if (photo.id) {
|
||
try {
|
||
await deletePhoto(photo.id)
|
||
} catch (error) {
|
||
console.error('删除照片失败:', error)
|
||
}
|
||
}
|
||
|
||
formData.photos.splice(index, 1)
|
||
}
|
||
|
||
// 手机号验证 - 处理微信获取手机号回调
|
||
const handleGetPhoneNumber = async (e) => {
|
||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||
uni.showToast({ title: '取消授权', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
// 获取到 code,发送给后端解密
|
||
const code = e.detail.code
|
||
if (!code) {
|
||
uni.showToast({ title: '获取手机号失败', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
uni.showLoading({ title: '验证中...' })
|
||
|
||
try {
|
||
const { decryptPhone } = await import('@/api/user.js')
|
||
const res = await decryptPhone(code)
|
||
|
||
if (res && res.code === 0 && res.data?.phone) {
|
||
formData.phone = res.data.phone
|
||
phoneVerified.value = true
|
||
uni.showToast({ title: '验证成功', icon: 'success' })
|
||
} else {
|
||
uni.showToast({ title: res?.message || '验证失败', icon: 'none' })
|
||
}
|
||
} catch (error) {
|
||
console.error('验证手机号失败:', error)
|
||
uni.showToast({ title: '验证失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
// 步骤验证
|
||
const validateStep = (step) => {
|
||
switch (step) {
|
||
case 0: // 基础信息
|
||
if (!formData.relationship) {
|
||
uni.showToast({ title: '请选择与相亲者的关系', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.surname) {
|
||
uni.showToast({ title: '请输入姓氏', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.childGender) {
|
||
uni.showToast({ title: '请选择相亲者性别', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.birthYear) {
|
||
uni.showToast({ title: '请选择出生年份', icon: 'none' })
|
||
return false
|
||
}
|
||
return true
|
||
|
||
case 1: // 详细信息
|
||
if (!formData.education) {
|
||
uni.showToast({ title: '请选择学历', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.workProvince || !formData.workCity) {
|
||
uni.showToast({ title: '请选择工作地点', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.occupation) {
|
||
uni.showToast({ title: '请输入职业', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.monthlyIncome) {
|
||
uni.showToast({ title: '请选择月收入', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.height) {
|
||
uni.showToast({ title: '请选择身高', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.houseStatus) {
|
||
uni.showToast({ title: '请选择房产状态', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.carStatus) {
|
||
uni.showToast({ title: '请选择车辆状态', icon: 'none' })
|
||
return false
|
||
}
|
||
if (!formData.marriageStatus) {
|
||
uni.showToast({ title: '请选择婚姻状态', icon: 'none' })
|
||
return false
|
||
}
|
||
return true
|
||
|
||
case 2: // 自我介绍
|
||
if (!formData.introduction || formData.introduction.trim().length < 10) {
|
||
uni.showToast({ title: '请输入至少10个字的介绍', icon: 'none' })
|
||
return false
|
||
}
|
||
return true
|
||
|
||
case 3: // 择偶要求
|
||
// 择偶要求为可选,不做强制验证
|
||
return true
|
||
|
||
case 4: // 联系方式
|
||
if (!formData.weChatNo) {
|
||
uni.showToast({ title: '请输入微信号', icon: 'none' })
|
||
return false
|
||
}
|
||
return true
|
||
|
||
default:
|
||
return true
|
||
}
|
||
}
|
||
|
||
// 步骤导航
|
||
const handlePrevStep = () => {
|
||
if (currentStep.value > 0) {
|
||
currentStep.value--
|
||
}
|
||
}
|
||
|
||
const handleNextStep = () => {
|
||
if (validateStep(currentStep.value)) {
|
||
if (currentStep.value < steps.length - 1) {
|
||
currentStep.value++
|
||
}
|
||
}
|
||
}
|
||
|
||
// 提交资料
|
||
const handleSubmit = async () => {
|
||
if (!validateStep(currentStep.value)) {
|
||
return
|
||
}
|
||
|
||
submitting.value = true
|
||
|
||
try {
|
||
const submitData = {
|
||
...formData,
|
||
photos: formData.photos.map(p => p.id).filter(Boolean)
|
||
}
|
||
|
||
const res = await createOrUpdate(submitData)
|
||
|
||
if (res && res.code === 0) {
|
||
// 更新用户状态
|
||
userStore.updateUserInfo({ isProfileCompleted: true })
|
||
|
||
uni.showToast({ title: '资料提交成功', icon: 'success' })
|
||
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1500)
|
||
} else {
|
||
uni.showToast({ title: res?.message || '提交失败', icon: 'none' })
|
||
}
|
||
} catch (error) {
|
||
console.error('提交资料失败:', error)
|
||
uni.showToast({ title: '提交失败,请重试', icon: 'none' })
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
// 加载已有资料
|
||
const loadProfile = async () => {
|
||
try {
|
||
const res = await getMyProfile()
|
||
if (res && res.code === 0 && res.data) {
|
||
const profile = res.data
|
||
|
||
// 填充表单数据
|
||
formData.isPhotoPublic = profile.isPhotoPublic ?? true
|
||
formData.relationship = profile.relationship || 1
|
||
formData.surname = profile.surname || ''
|
||
formData.nickname = profile.nickname || ''
|
||
formData.childGender = profile.childGender || 1
|
||
formData.birthYear = profile.birthYear || 0
|
||
formData.education = profile.education || 1
|
||
formData.workProvince = profile.workProvince || ''
|
||
formData.workCity = profile.workCity || ''
|
||
formData.workDistrict = profile.workDistrict || ''
|
||
formData.homeProvince = profile.homeProvince || ''
|
||
formData.homeCity = profile.homeCity || ''
|
||
formData.homeDistrict = profile.homeDistrict || ''
|
||
formData.occupation = profile.occupation || ''
|
||
formData.monthlyIncome = profile.monthlyIncome || 1
|
||
formData.height = profile.height || 0
|
||
formData.weight = profile.weight || 0
|
||
formData.houseStatus = profile.houseStatus || 5
|
||
formData.carStatus = profile.carStatus || 2
|
||
formData.marriageStatus = profile.marriageStatus || 1
|
||
formData.expectMarryTime = profile.expectMarryTime || 0
|
||
formData.introduction = profile.introduction || ''
|
||
formData.weChatNo = profile.weChatNo || ''
|
||
|
||
// 照片 - 需要拼接完整URL(使用统一配置的地址)
|
||
if (profile.photos && profile.photos.length > 0) {
|
||
formData.photos = profile.photos.map(p => ({
|
||
id: p.id,
|
||
url: p.photoUrl.startsWith('http') ? p.photoUrl : `${STATIC_URL}${p.photoUrl}`
|
||
}))
|
||
}
|
||
|
||
// 择偶要求
|
||
if (profile.requirement) {
|
||
formData.requirement = {
|
||
ageMin: profile.requirement.ageMin || 0,
|
||
ageMax: profile.requirement.ageMax || 0,
|
||
heightMin: profile.requirement.heightMin || 0,
|
||
heightMax: profile.requirement.heightMax || 0,
|
||
education: profile.requirement.education || [],
|
||
city1Province: profile.requirement.city1Province || '',
|
||
city1City: profile.requirement.city1City || '',
|
||
city2Province: profile.requirement.city2Province || '',
|
||
city2City: profile.requirement.city2City || '',
|
||
monthlyIncomeMin: profile.requirement.monthlyIncomeMin || 0,
|
||
monthlyIncomeMax: profile.requirement.monthlyIncomeMax || 0,
|
||
houseStatus: profile.requirement.houseStatus || [],
|
||
carStatus: profile.requirement.carStatus || [],
|
||
marriageStatus: profile.requirement.marriageStatus || []
|
||
}
|
||
}
|
||
} else {
|
||
// 用户没有 Profile 记录,清理之前可能上传的孤立照片
|
||
try {
|
||
await cleanupOrphanPhotos()
|
||
} catch (e) {
|
||
console.log('清理孤立照片:', e.message)
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('加载资料失败:', error)
|
||
// 加载失败也尝试清理孤立照片
|
||
try {
|
||
await cleanupOrphanPhotos()
|
||
} catch (e) {
|
||
console.log('清理孤立照片:', e.message)
|
||
}
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
getSystemInfo()
|
||
loadProfile()
|
||
})
|
||
</script>
|
||
|
||
<script>
|
||
// 页面生命周期需要在普通 script 中定义
|
||
export default {
|
||
onBackPress() {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要离开吗?已填写的内容将不会保存',
|
||
confirmText: '离开',
|
||
cancelText: '继续填写',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
uni.navigateBack()
|
||
}
|
||
}
|
||
})
|
||
return true // 返回 true 阻止默认返回行为
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.profile-edit-page {
|
||
min-height: 100vh;
|
||
background-color: #f8f8f8;
|
||
padding-bottom: 140rpx;
|
||
}
|
||
|
||
// 固定头部区域
|
||
.fixed-header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 100;
|
||
}
|
||
|
||
// 自定义导航栏(水平渐变背景)
|
||
.custom-navbar {
|
||
position: relative;
|
||
z-index: 1;
|
||
background: linear-gradient(90deg, #FFDEE0 0%, #FF939C 100%);
|
||
|
||
.navbar-content {
|
||
height: 44px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 24rpx;
|
||
|
||
.navbar-back {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.back-icon {
|
||
font-size: 64rpx;
|
||
color: #333;
|
||
font-weight: 300;
|
||
}
|
||
}
|
||
|
||
.navbar-title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.navbar-placeholder {
|
||
width: 80rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 占位区域
|
||
.header-placeholder {
|
||
width: 100%;
|
||
}
|
||
|
||
// 步骤指示器(白色背景)
|
||
.step-indicator {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 30rpx 40rpx;
|
||
background-color: #fff;
|
||
|
||
.step-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
flex: 1;
|
||
|
||
.step-dot {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 50%;
|
||
background-color: #e0e0e0;
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.step-label {
|
||
font-size: 22rpx;
|
||
color: #666;
|
||
}
|
||
|
||
&.active {
|
||
.step-dot {
|
||
background: #FF6B6B;
|
||
color: #fff;
|
||
}
|
||
.step-label {
|
||
color: #FF6B6B;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
&.completed {
|
||
.step-dot {
|
||
background-color: #4cd964;
|
||
color: #fff;
|
||
}
|
||
.step-label {
|
||
color: #4cd964;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 表单区域
|
||
.step-content {
|
||
padding: 20rpx;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.form-section {
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 30rpx;
|
||
padding-bottom: 20rpx;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
}
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 30rpx;
|
||
|
||
.form-label {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 16rpx;
|
||
|
||
&.required::before {
|
||
content: '*';
|
||
color: #ff6b6b;
|
||
margin-right: 4rpx;
|
||
}
|
||
}
|
||
|
||
input {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
padding: 0 20rpx;
|
||
background-color: #f8f8f8;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
|
||
&[disabled] {
|
||
color: #999;
|
||
background-color: #f0f0f0;
|
||
}
|
||
}
|
||
|
||
.form-tip {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
&.textarea-item {
|
||
textarea {
|
||
width: 100%;
|
||
height: 240rpx;
|
||
padding: 20rpx;
|
||
background-color: #f8f8f8;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.char-count {
|
||
display: block;
|
||
text-align: right;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 选择器
|
||
.picker-value {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: 80rpx;
|
||
padding: 0 20rpx;
|
||
background-color: #f8f8f8;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
|
||
&.small {
|
||
width: 160rpx;
|
||
justify-content: center;
|
||
}
|
||
|
||
.picker-arrow {
|
||
font-size: 20rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
// 单选组
|
||
.radio-group {
|
||
display: flex;
|
||
gap: 30rpx;
|
||
|
||
.radio-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16rpx 30rpx;
|
||
background-color: #f8f8f8;
|
||
border-radius: 12rpx;
|
||
|
||
.radio-dot {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #ddd;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
&.active {
|
||
background-color: #fff0f0;
|
||
|
||
.radio-dot {
|
||
border-color: #ff6b6b;
|
||
background-color: #ff6b6b;
|
||
position: relative;
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
border-radius: 50%;
|
||
background-color: #fff;
|
||
}
|
||
}
|
||
|
||
text {
|
||
color: #ff6b6b;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 多选组
|
||
.checkbox-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
|
||
.checkbox-item {
|
||
padding: 12rpx 24rpx;
|
||
background-color: #f8f8f8;
|
||
border-radius: 8rpx;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
|
||
&.active {
|
||
background-color: #fff0f0;
|
||
color: #ff6b6b;
|
||
border: 1rpx solid #ff6b6b;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 范围选择器
|
||
.range-picker {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.range-separator {
|
||
margin: 0 16rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.range-unit {
|
||
margin-left: 12rpx;
|
||
color: #666;
|
||
font-size: 26rpx;
|
||
}
|
||
}
|
||
|
||
// 照片上传
|
||
.photo-upload-section {
|
||
margin-bottom: 30rpx;
|
||
|
||
.photo-label {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.photo-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
|
||
.photo-item {
|
||
position: relative;
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
border-radius: 12rpx;
|
||
overflow: hidden;
|
||
|
||
.photo-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.photo-delete {
|
||
position: absolute;
|
||
top: 8rpx;
|
||
right: 8rpx;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
border-radius: 50%;
|
||
color: #fff;
|
||
font-size: 28rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
}
|
||
|
||
.photo-add {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
background-color: #f8f8f8;
|
||
border-radius: 12rpx;
|
||
border: 2rpx dashed #ddd;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.add-icon {
|
||
font-size: 48rpx;
|
||
color: #999;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.add-text {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 验证按钮
|
||
.verify-btn {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
background-color: #f8f8f8;
|
||
color: #333;
|
||
font-size: 28rpx;
|
||
border-radius: 12rpx;
|
||
border: none;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
&[disabled] {
|
||
background-color: #f0f0f0;
|
||
color: #ccc;
|
||
}
|
||
}
|
||
|
||
// 已验证手机号显示
|
||
.phone-verified {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: 80rpx;
|
||
padding: 0 20rpx;
|
||
background-color: #f0fff0;
|
||
border-radius: 12rpx;
|
||
|
||
.phone-number {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.verified-tag {
|
||
font-size: 24rpx;
|
||
color: #4cd964;
|
||
padding: 4rpx 12rpx;
|
||
background-color: #e8f8e8;
|
||
border-radius: 8rpx;
|
||
}
|
||
}
|
||
|
||
// 底部按钮
|
||
.bottom-buttons {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 999;
|
||
display: flex;
|
||
gap: 20rpx;
|
||
padding: 20rpx 30rpx;
|
||
background-color: #fff;
|
||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||
|
||
button {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
border-radius: 44rpx;
|
||
font-size: 30rpx;
|
||
border: none;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
}
|
||
|
||
.btn-prev {
|
||
background-color: #f5f5f5;
|
||
color: #666;
|
||
}
|
||
|
||
.btn-next, .btn-submit {
|
||
background: linear-gradient(135deg, #ff6b6b 0%, #ff5252 100%);
|
||
color: #fff;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<!-- 非 scoped 样式,用于 placeholder-class -->
|
||
<style lang="scss">
|
||
.input-placeholder {
|
||
color: #999 !important;
|
||
font-size: 28rpx !important;
|
||
}
|
||
</style>
|