|
|
@@ -1,1316 +1,1316 @@
|
|
|
-<template>
|
|
|
- <view class="user-edit-container">
|
|
|
- <view class="header">
|
|
|
- <text class="title">{{ isEdit ? '完善个人信息' : '选择角色' }}</text>
|
|
|
- <text class="subtitle">{{ isEdit ? '以下信息可帮助您更好地使用服务' : '请选择您的身份类型' }}</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 角色选择(新用户首次登录时显示) -->
|
|
|
- <view class="role-select-section" v-if="!isEdit && showRoleSelect">
|
|
|
- <view
|
|
|
- class="role-card"
|
|
|
- :class="{ active: form.role === 'parent' }"
|
|
|
- @click="selectRole('parent')"
|
|
|
- >
|
|
|
- <text class="role-icon">👨👩👧</text>
|
|
|
- <text class="role-name">家长</text>
|
|
|
- <text class="role-desc">管理孩子任务、审核奖励</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view
|
|
|
- class="role-card"
|
|
|
- :class="{ active: form.role === 'child' }"
|
|
|
- @click="selectRole('child')"
|
|
|
- >
|
|
|
- <text class="role-icon">👶</text>
|
|
|
- <text class="role-name">孩子</text>
|
|
|
- <text class="role-desc">执行任务、赚取积分</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view
|
|
|
- class="role-card"
|
|
|
- :class="{ active: form.role === 'teacher' }"
|
|
|
- @click="selectRole('teacher')"
|
|
|
- >
|
|
|
- <text class="role-icon">👨🏫</text>
|
|
|
- <text class="role-name">成长规划师</text>
|
|
|
- <text class="role-desc">DAN测评、制定成长方案</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <button class="btn-next" @click="confirmRole" :disabled="!form.role">
|
|
|
- 下一步
|
|
|
- </button>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 详细信息填写(选择角色后显示) -->
|
|
|
- <view v-if="isEdit && (!isNewUserFlow || hasFamily)">
|
|
|
- <view class="form-section">
|
|
|
- <!-- 头像 -->
|
|
|
- <view class="form-item avatar-item">
|
|
|
- <text class="label">头像</text>
|
|
|
- <view class="avatar-upload" @click="chooseAvatar">
|
|
|
- <image v-if="form.avatar" :src="form.avatar" class="avatar-preview"></image>
|
|
|
- <view v-else class="avatar-placeholder">
|
|
|
- <text class="iconfont icon-plus">+</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 昵称 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">昵称 <text class="required">*</text></text>
|
|
|
- <input type="text" v-model="form.nickname" placeholder="请输入昵称" class="input" maxlength="20" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 新用户流程:仅展示头像+昵称,其余字段按需在功能页补充 -->
|
|
|
- <view v-if="!isNewUserFlow">
|
|
|
-
|
|
|
- <!-- 真实姓名 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">真实姓名</text>
|
|
|
- <input type="text" v-model="form.realName" placeholder="选填,用于家庭管理" class="input" maxlength="20" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 性别(成长规划师必填) -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">性别 <text class="required" v-if="form.role === 'teacher'">*</text></text>
|
|
|
- <radio-group class="role-select" @change="onGenderChange">
|
|
|
- <label class="role-option">
|
|
|
- <radio value="male" :checked="form.gender === 'male'" />
|
|
|
- <text>男</text>
|
|
|
- </label>
|
|
|
- <label class="role-option">
|
|
|
- <radio value="female" :checked="form.gender === 'female'" />
|
|
|
- <text>女</text>
|
|
|
- </label>
|
|
|
- </radio-group>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 身份证号(成长规划师必填) -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">身份证号 <text class="required" v-if="form.role === 'teacher'">*</text></text>
|
|
|
- <input type="idcard" v-model="form.idCard" placeholder="选填,填写后自动生成生日" class="input" maxlength="18" @input="onIdCardInput" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 生日 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">生日</text>
|
|
|
- <picker mode="date" :value="form.birthday" @change="onBirthdayChange">
|
|
|
- <view class="picker-input">{{ form.birthday || '请选择生日' }}</view>
|
|
|
- </picker>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 出生时辰 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">出生时辰</text>
|
|
|
- <picker mode="selector" :range="birthHourOptions" :value="birthHourIndex" @change="onBirthHourChange">
|
|
|
- <view class="picker-input">{{ form.birthHour || '请选择出生时辰' }}</view>
|
|
|
- </picker>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 民族 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">民族</text>
|
|
|
- <picker mode="selector" :range="ethnicityOptions" :value="ethnicityIndex" @change="onEthnicityChange">
|
|
|
- <view class="picker-input">{{ form.ethnicity || '请选择民族' }}</view>
|
|
|
- </picker>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 血型 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">血型</text>
|
|
|
- <picker mode="selector" :range="bloodTypeOptions" :value="bloodTypeIndex" @change="onBloodTypeChange">
|
|
|
- <view class="picker-input">{{ form.bloodType || '请选择血型' }}</view>
|
|
|
- </picker>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 最高学历 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">最高学历</text>
|
|
|
- <picker mode="selector" :range="educationOptions" :value="educationIndex" @change="onEducationChange">
|
|
|
- <view class="picker-input">{{ form.highestEducation || '请选择最高学历' }}</view>
|
|
|
- </picker>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 婚姻状态 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">婚姻状态</text>
|
|
|
- <picker mode="selector" :range="maritalOptions" :value="maritalIndex" @change="onMaritalChange">
|
|
|
- <view class="picker-input">{{ form.maritalStatus || '请选择婚姻状态' }}</view>
|
|
|
- </picker>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 所在地区(省市区三级联动,与订单收货人地址一致) -->
|
|
|
- <view class="form-item address-row" @click="openRegionPicker">
|
|
|
- <text class="label">所在地区 <text class="required" v-if="isNewUserFlow">*</text></text>
|
|
|
- <view class="address-value-wrap">
|
|
|
- <text v-if="form.address" class="address-value">{{ form.address }}</text>
|
|
|
- <text v-else class="address-placeholder">请选择省市区</text>
|
|
|
- <text class="address-arrow">›</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 详细地址 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">详细地址</text>
|
|
|
- <input type="text" v-model="form.street" placeholder="请输入详细地址(选填)" class="input" maxlength="100" @input="updateFullAddress" />
|
|
|
+<template>
|
|
|
+ <view class="user-edit-container">
|
|
|
+ <view class="header">
|
|
|
+ <text class="title">{{ isEdit ? '完善个人信息' : '选择角色' }}</text>
|
|
|
+ <text class="subtitle">{{ isEdit ? '以下信息可帮助您更好地使用服务' : '请选择您的身份类型' }}</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 兴趣爱好 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">兴趣爱好</text>
|
|
|
- <input type="text" v-model="form.hobbies" placeholder="如: 阅读、运动、音乐" class="input" maxlength="200" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 饮食偏好 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">饮食偏好</text>
|
|
|
- <input type="text" v-model="form.dietPreferences" placeholder="如: 清淡、无辣、低糖" class="input" maxlength="200" />
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 成长规划师额外字段 -->
|
|
|
- <view class="form-section-title" v-if="form.role === 'teacher'">成长规划师信息</view>
|
|
|
-
|
|
|
- <!-- 成长规划师证书号(成长规划师必填) -->
|
|
|
- <view class="form-item" v-if="form.role === 'teacher'">
|
|
|
- <text class="label">成长规划师证书号 <text class="required">*</text></text>
|
|
|
- <input type="text" v-model="form.teacherNo" placeholder="请输入成长规划师证书号" class="input" maxlength="50" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 成长规划师照片(成长规划师必填) -->
|
|
|
- <view class="form-item" v-if="form.role === 'teacher'">
|
|
|
- <text class="label">成长规划师照片 <text class="required">*</text></text>
|
|
|
- <view class="photo-upload" @click="chooseTeacherPhoto">
|
|
|
- <image v-if="form.teacherPhoto" :src="form.teacherPhoto" class="photo-preview" mode="aspectFill"></image>
|
|
|
- <view v-else class="photo-placeholder">
|
|
|
- <text class="plus-icon">+</text>
|
|
|
- <text class="placeholder-text">上传照片</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 成长规划师证书照片(成长规划师必填) -->
|
|
|
- <view class="form-item" v-if="form.role === 'teacher'">
|
|
|
- <text class="label">成长规划师证书 <text class="required">*</text></text>
|
|
|
- <view class="photo-upload" @click="chooseCertificate">
|
|
|
- <image v-if="form.certificateImage" :src="form.certificateImage" class="photo-preview" mode="aspectFill"></image>
|
|
|
- <view v-else class="photo-placeholder">
|
|
|
- <text class="plus-icon">+</text>
|
|
|
- <text class="placeholder-text">上传证书</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- AI助手选择(会员专属) -->
|
|
|
- <view class="family-section" v-if="isEdit && memberLevel !== 'FREE'">
|
|
|
- <view class="section-title">🤖 选择AI助手</view>
|
|
|
- <view class="mascot-cards">
|
|
|
- <view class="mascot-card" :class="{ active: form.mascot === 'xibao' }" @click="selectMascot('xibao')">
|
|
|
- <view class="mascot-card-icon xibao-bg">🌟</view>
|
|
|
- <view class="mascot-card-info">
|
|
|
- <text class="mascot-card-name">浠宝</text>
|
|
|
- <text class="mascot-card-desc">温柔活泼的阳光女孩</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <view class="mascot-card" :class="{ active: form.mascot === 'fubao' }" @click="selectMascot('fubao')">
|
|
|
- <view class="mascot-card-icon fubao-bg">💪</view>
|
|
|
- <view class="mascot-card-info">
|
|
|
- <text class="mascot-card-name">福宝</text>
|
|
|
- <text class="mascot-card-desc">开朗自信的阳光男孩</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <!-- 免费用户提示 -->
|
|
|
- <view class="family-section" v-if="isEdit && memberLevel === 'FREE'">
|
|
|
- <view class="mascot-locked-hint" @click="goUpgrade">
|
|
|
- <text class="locked-icon">🔒</text>
|
|
|
- <text class="locked-text">开通会员解锁更多能力</text>
|
|
|
- <text class="locked-arrow">›</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 新用户流程:保存资料 / 跳过(不再创建或加入家庭) -->
|
|
|
- <view class="family-section" v-if="isEdit && form.role !== 'teacher' && isNewUserFlow">
|
|
|
- <view class="family-actions">
|
|
|
- <button class="btn btn-primary" @click="saveUserInfo" :loading="loading">
|
|
|
- 保存资料
|
|
|
- </button>
|
|
|
- <button class="btn btn-skip" @click="skip">
|
|
|
- 跳过
|
|
|
- </button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 修改个人资料保存按钮(非新用户流程时显示) -->
|
|
|
- <view class="family-section" v-if="isEdit && !isNewUserFlow && form.role !== 'teacher'">
|
|
|
- <view class="family-actions">
|
|
|
- <button class="btn btn-primary" @click="saveUserInfo" :loading="loading">
|
|
|
- 保存修改
|
|
|
- </button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 成长规划师提交按钮 -->
|
|
|
- <view class="family-section" v-if="isEdit && form.role === 'teacher' && isNewUserFlow">
|
|
|
- <view class="family-actions">
|
|
|
- <button class="btn btn-primary" @click="submitTeacherInfo" :loading="loading">
|
|
|
- 提交申请
|
|
|
- </button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <view v-else-if="isEdit">
|
|
|
- <FamilyEmptyState type="card" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 区域选择弹窗(省市区三级联动) -->
|
|
|
- <view class="region-mask" v-if="showRegionPicker" @click="cancelRegion">
|
|
|
- <view class="region-picker" @click.stop>
|
|
|
- <view class="region-header">
|
|
|
- <text class="region-header-btn" @click="cancelRegion">取消</text>
|
|
|
- <text class="region-header-title">选择地区</text>
|
|
|
- <text class="region-header-btn region-confirm" @click="confirmRegion">确定</text>
|
|
|
- </view>
|
|
|
- <view class="region-breadcrumb">
|
|
|
- <text
|
|
|
- v-for="(name, i) in regionBreadcrumb"
|
|
|
- :key="i"
|
|
|
- :class="['crumb-item', i === currentRegionLevel ? 'crumb-active' : '']"
|
|
|
- @click="jumpRegionLevel(i)"
|
|
|
- >{{ name || '请选择' }}</text>
|
|
|
- <text class="crumb-arrow" v-if="currentRegionLevel < 2">›</text>
|
|
|
- </view>
|
|
|
- <scroll-view class="region-list" scroll-y>
|
|
|
- <view
|
|
|
- v-for="(item, idx) in currentRegionItems"
|
|
|
- :key="idx"
|
|
|
- :class="['region-item', isRegionSelected(item) ? 'region-item-selected' : '']"
|
|
|
- @click="selectRegionItem(item)"
|
|
|
- >
|
|
|
- <text class="region-item-text">{{ getRegionName(item) }}</text>
|
|
|
- <text class="region-item-check" v-if="isRegionSelected(item)">✓</text>
|
|
|
- </view>
|
|
|
- <view v-if="regionLoading" class="region-loading">
|
|
|
- <text>加载中...</text>
|
|
|
- </view>
|
|
|
- </scroll-view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script>
|
|
|
-import { updateUserInfo, createFamily, directRegister, getUserInfo, requestJoinByCode, getMyMembership } from '../../utils/api.js'
|
|
|
-import config from '@/config.js'
|
|
|
-
|
|
|
-export default {
|
|
|
- data() {
|
|
|
- return {
|
|
|
- isEdit: false,
|
|
|
- showRoleSelect: true,
|
|
|
- isNewUserFlow: false, // 是否是新用户注册流程
|
|
|
- memberLevel: 'FREE', // 会员等级,用于判断是否解锁AI助手
|
|
|
- form: {
|
|
|
- role: '',
|
|
|
- avatar: '',
|
|
|
- nickname: '',
|
|
|
- realName: '',
|
|
|
- gender: '',
|
|
|
- idCard: '',
|
|
|
- birthday: '',
|
|
|
- birthHour: '',
|
|
|
- inviteCode: '',
|
|
|
- mascot: '',
|
|
|
- phone: '',
|
|
|
- teacherNo: '',
|
|
|
- teacherPhoto: '',
|
|
|
- certificateImage: '',
|
|
|
- ethnicity: '',
|
|
|
- bloodType: '',
|
|
|
- highestEducation: '',
|
|
|
- maritalStatus: '',
|
|
|
- hobbies: '',
|
|
|
- dietPreferences: '',
|
|
|
- address: '',
|
|
|
- province: '',
|
|
|
- city: '',
|
|
|
- district: '',
|
|
|
- street: ''
|
|
|
- },
|
|
|
- // 区域选择弹窗(省市区三级联动,与订单收货人地址一致)
|
|
|
- showRegionPicker: false,
|
|
|
- currentRegionLevel: 0, // 0=省份, 1=城市, 2=区县
|
|
|
- regionLoading: false,
|
|
|
- provinceList: [],
|
|
|
- cityList: [],
|
|
|
- districtList: [],
|
|
|
- tempProvince: null,
|
|
|
- tempCity: null,
|
|
|
+
|
|
|
+ <!-- 角色选择(新用户首次登录时显示) -->
|
|
|
+ <view class="role-select-section" v-if="!isEdit && showRoleSelect">
|
|
|
+ <view
|
|
|
+ class="role-card"
|
|
|
+ :class="{ active: form.role === 'parent' }"
|
|
|
+ @click="selectRole('parent')"
|
|
|
+ >
|
|
|
+ <text class="role-icon">👨👩👧</text>
|
|
|
+ <text class="role-name">家长</text>
|
|
|
+ <text class="role-desc">管理孩子任务、审核奖励</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view
|
|
|
+ class="role-card"
|
|
|
+ :class="{ active: form.role === 'child' }"
|
|
|
+ @click="selectRole('child')"
|
|
|
+ >
|
|
|
+ <text class="role-icon">👶</text>
|
|
|
+ <text class="role-name">孩子</text>
|
|
|
+ <text class="role-desc">执行任务、赚取积分</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view
|
|
|
+ class="role-card"
|
|
|
+ :class="{ active: form.role === 'teacher' }"
|
|
|
+ @click="selectRole('teacher')"
|
|
|
+ >
|
|
|
+ <text class="role-icon">👨🏫</text>
|
|
|
+ <text class="role-name">成长规划师</text>
|
|
|
+ <text class="role-desc">DAN测评、制定成长方案</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <button class="btn-next" @click="confirmRole" :disabled="!form.role">
|
|
|
+ 下一步
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 详细信息填写(选择角色后显示) -->
|
|
|
+ <view v-if="isEdit && (!isNewUserFlow || hasFamily)">
|
|
|
+ <view class="form-section">
|
|
|
+ <!-- 头像 -->
|
|
|
+ <view class="form-item avatar-item">
|
|
|
+ <text class="label">头像</text>
|
|
|
+ <view class="avatar-upload" @click="chooseAvatar">
|
|
|
+ <image v-if="form.avatar" :src="form.avatar" class="avatar-preview"></image>
|
|
|
+ <view v-else class="avatar-placeholder">
|
|
|
+ <text class="iconfont icon-plus">+</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 昵称 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">昵称 <text class="required">*</text></text>
|
|
|
+ <input type="text" v-model="form.nickname" placeholder="请输入昵称" class="input" maxlength="20" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 新用户流程:仅展示头像+昵称,其余字段按需在功能页补充 -->
|
|
|
+ <view v-if="!isNewUserFlow">
|
|
|
+
|
|
|
+ <!-- 真实姓名 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">真实姓名</text>
|
|
|
+ <input type="text" v-model="form.realName" placeholder="选填,用于家庭管理" class="input" maxlength="20" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 性别(成长规划师必填) -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">性别 <text class="required" v-if="form.role === 'teacher'">*</text></text>
|
|
|
+ <radio-group class="role-select" @change="onGenderChange">
|
|
|
+ <label class="role-option">
|
|
|
+ <radio value="male" :checked="form.gender === 'male'" />
|
|
|
+ <text>男</text>
|
|
|
+ </label>
|
|
|
+ <label class="role-option">
|
|
|
+ <radio value="female" :checked="form.gender === 'female'" />
|
|
|
+ <text>女</text>
|
|
|
+ </label>
|
|
|
+ </radio-group>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 身份证号(成长规划师必填) -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">身份证号 <text class="required" v-if="form.role === 'teacher'">*</text></text>
|
|
|
+ <input type="idcard" v-model="form.idCard" placeholder="选填,填写后自动生成生日" class="input" maxlength="18" @input="onIdCardInput" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 生日 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">生日</text>
|
|
|
+ <picker mode="date" :value="form.birthday" @change="onBirthdayChange">
|
|
|
+ <view class="picker-input">{{ form.birthday || '请选择生日' }}</view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 出生时辰 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">出生时辰</text>
|
|
|
+ <picker mode="selector" :range="birthHourOptions" :value="birthHourIndex" @change="onBirthHourChange">
|
|
|
+ <view class="picker-input">{{ form.birthHour || '请选择出生时辰' }}</view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 民族 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">民族</text>
|
|
|
+ <picker mode="selector" :range="ethnicityOptions" :value="ethnicityIndex" @change="onEthnicityChange">
|
|
|
+ <view class="picker-input">{{ form.ethnicity || '请选择民族' }}</view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 血型 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">血型</text>
|
|
|
+ <picker mode="selector" :range="bloodTypeOptions" :value="bloodTypeIndex" @change="onBloodTypeChange">
|
|
|
+ <view class="picker-input">{{ form.bloodType || '请选择血型' }}</view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 最高学历 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">最高学历</text>
|
|
|
+ <picker mode="selector" :range="educationOptions" :value="educationIndex" @change="onEducationChange">
|
|
|
+ <view class="picker-input">{{ form.highestEducation || '请选择最高学历' }}</view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 婚姻状态 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">婚姻状态</text>
|
|
|
+ <picker mode="selector" :range="maritalOptions" :value="maritalIndex" @change="onMaritalChange">
|
|
|
+ <view class="picker-input">{{ form.maritalStatus || '请选择婚姻状态' }}</view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 所在地区(省市区三级联动,与订单收货人地址一致) -->
|
|
|
+ <view class="form-item address-row" @click="openRegionPicker">
|
|
|
+ <text class="label">所在地区 <text class="required" v-if="isNewUserFlow">*</text></text>
|
|
|
+ <view class="address-value-wrap">
|
|
|
+ <text v-if="form.address" class="address-value">{{ form.address }}</text>
|
|
|
+ <text v-else class="address-placeholder">请选择省市区</text>
|
|
|
+ <text class="address-arrow">›</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 详细地址 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">详细地址</text>
|
|
|
+ <input type="text" v-model="form.street" placeholder="请输入详细地址(选填)" class="input" maxlength="100" @input="updateFullAddress" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 兴趣爱好 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">兴趣爱好</text>
|
|
|
+ <input type="text" v-model="form.hobbies" placeholder="如: 阅读、运动、音乐" class="input" maxlength="200" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 饮食偏好 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="label">饮食偏好</text>
|
|
|
+ <input type="text" v-model="form.dietPreferences" placeholder="如: 清淡、无辣、低糖" class="input" maxlength="200" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 成长规划师额外字段 -->
|
|
|
+ <view class="form-section-title" v-if="form.role === 'teacher'">成长规划师信息</view>
|
|
|
+
|
|
|
+ <!-- 成长规划师证书号(成长规划师必填) -->
|
|
|
+ <view class="form-item" v-if="form.role === 'teacher'">
|
|
|
+ <text class="label">成长规划师证书号 <text class="required">*</text></text>
|
|
|
+ <input type="text" v-model="form.teacherNo" placeholder="请输入成长规划师证书号" class="input" maxlength="50" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 成长规划师照片(成长规划师必填) -->
|
|
|
+ <view class="form-item" v-if="form.role === 'teacher'">
|
|
|
+ <text class="label">成长规划师照片 <text class="required">*</text></text>
|
|
|
+ <view class="photo-upload" @click="chooseTeacherPhoto">
|
|
|
+ <image v-if="form.teacherPhoto" :src="form.teacherPhoto" class="photo-preview" mode="aspectFill"></image>
|
|
|
+ <view v-else class="photo-placeholder">
|
|
|
+ <text class="plus-icon">+</text>
|
|
|
+ <text class="placeholder-text">上传照片</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 成长规划师证书照片(成长规划师必填) -->
|
|
|
+ <view class="form-item" v-if="form.role === 'teacher'">
|
|
|
+ <text class="label">成长规划师证书 <text class="required">*</text></text>
|
|
|
+ <view class="photo-upload" @click="chooseCertificate">
|
|
|
+ <image v-if="form.certificateImage" :src="form.certificateImage" class="photo-preview" mode="aspectFill"></image>
|
|
|
+ <view v-else class="photo-placeholder">
|
|
|
+ <text class="plus-icon">+</text>
|
|
|
+ <text class="placeholder-text">上传证书</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 助手选择(会员专属) -->
|
|
|
+ <view class="family-section" v-if="isEdit && memberLevel !== 'FREE'">
|
|
|
+ <view class="section-title">🤖 选择助手</view>
|
|
|
+ <view class="mascot-cards">
|
|
|
+ <view class="mascot-card" :class="{ active: form.mascot === 'xibao' }" @click="selectMascot('xibao')">
|
|
|
+ <view class="mascot-card-icon xibao-bg">🌟</view>
|
|
|
+ <view class="mascot-card-info">
|
|
|
+ <text class="mascot-card-name">浠宝</text>
|
|
|
+ <text class="mascot-card-desc">温柔活泼的阳光女孩</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="mascot-card" :class="{ active: form.mascot === 'fubao' }" @click="selectMascot('fubao')">
|
|
|
+ <view class="mascot-card-icon fubao-bg">💪</view>
|
|
|
+ <view class="mascot-card-info">
|
|
|
+ <text class="mascot-card-name">福宝</text>
|
|
|
+ <text class="mascot-card-desc">开朗自信的阳光男孩</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 免费用户提示 -->
|
|
|
+ <view class="family-section" v-if="isEdit && memberLevel === 'FREE'">
|
|
|
+ <view class="mascot-locked-hint" @click="goUpgrade">
|
|
|
+ <text class="locked-icon">🔒</text>
|
|
|
+ <text class="locked-text">开通会员解锁更多能力</text>
|
|
|
+ <text class="locked-arrow">›</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 新用户流程:保存资料 / 跳过(不再创建或加入家庭) -->
|
|
|
+ <view class="family-section" v-if="isEdit && form.role !== 'teacher' && isNewUserFlow">
|
|
|
+ <view class="family-actions">
|
|
|
+ <button class="btn btn-primary" @click="saveUserInfo" :loading="loading">
|
|
|
+ 保存资料
|
|
|
+ </button>
|
|
|
+ <button class="btn btn-skip" @click="skip">
|
|
|
+ 跳过
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 修改个人资料保存按钮(非新用户流程时显示) -->
|
|
|
+ <view class="family-section" v-if="isEdit && !isNewUserFlow && form.role !== 'teacher'">
|
|
|
+ <view class="family-actions">
|
|
|
+ <button class="btn btn-primary" @click="saveUserInfo" :loading="loading">
|
|
|
+ 保存修改
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 成长规划师提交按钮 -->
|
|
|
+ <view class="family-section" v-if="isEdit && form.role === 'teacher' && isNewUserFlow">
|
|
|
+ <view class="family-actions">
|
|
|
+ <button class="btn btn-primary" @click="submitTeacherInfo" :loading="loading">
|
|
|
+ 提交申请
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="isEdit">
|
|
|
+ <FamilyEmptyState type="card" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 区域选择弹窗(省市区三级联动) -->
|
|
|
+ <view class="region-mask" v-if="showRegionPicker" @click="cancelRegion">
|
|
|
+ <view class="region-picker" @click.stop>
|
|
|
+ <view class="region-header">
|
|
|
+ <text class="region-header-btn" @click="cancelRegion">取消</text>
|
|
|
+ <text class="region-header-title">选择地区</text>
|
|
|
+ <text class="region-header-btn region-confirm" @click="confirmRegion">确定</text>
|
|
|
+ </view>
|
|
|
+ <view class="region-breadcrumb">
|
|
|
+ <text
|
|
|
+ v-for="(name, i) in regionBreadcrumb"
|
|
|
+ :key="i"
|
|
|
+ :class="['crumb-item', i === currentRegionLevel ? 'crumb-active' : '']"
|
|
|
+ @click="jumpRegionLevel(i)"
|
|
|
+ >{{ name || '请选择' }}</text>
|
|
|
+ <text class="crumb-arrow" v-if="currentRegionLevel < 2">›</text>
|
|
|
+ </view>
|
|
|
+ <scroll-view class="region-list" scroll-y>
|
|
|
+ <view
|
|
|
+ v-for="(item, idx) in currentRegionItems"
|
|
|
+ :key="idx"
|
|
|
+ :class="['region-item', isRegionSelected(item) ? 'region-item-selected' : '']"
|
|
|
+ @click="selectRegionItem(item)"
|
|
|
+ >
|
|
|
+ <text class="region-item-text">{{ getRegionName(item) }}</text>
|
|
|
+ <text class="region-item-check" v-if="isRegionSelected(item)">✓</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="regionLoading" class="region-loading">
|
|
|
+ <text>加载中...</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { updateUserInfo, createFamily, directRegister, getUserInfo, requestJoinByCode, getMyMembership } from '../../utils/api.js'
|
|
|
+import config from '@/config.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ isEdit: false,
|
|
|
+ showRoleSelect: true,
|
|
|
+ isNewUserFlow: false, // 是否是新用户注册流程
|
|
|
+ memberLevel: 'FREE', // 会员等级,用于判断是否解锁助手
|
|
|
+ form: {
|
|
|
+ role: '',
|
|
|
+ avatar: '',
|
|
|
+ nickname: '',
|
|
|
+ realName: '',
|
|
|
+ gender: '',
|
|
|
+ idCard: '',
|
|
|
+ birthday: '',
|
|
|
+ birthHour: '',
|
|
|
+ inviteCode: '',
|
|
|
+ mascot: '',
|
|
|
+ phone: '',
|
|
|
+ teacherNo: '',
|
|
|
+ teacherPhoto: '',
|
|
|
+ certificateImage: '',
|
|
|
+ ethnicity: '',
|
|
|
+ bloodType: '',
|
|
|
+ highestEducation: '',
|
|
|
+ maritalStatus: '',
|
|
|
+ hobbies: '',
|
|
|
+ dietPreferences: '',
|
|
|
+ address: '',
|
|
|
+ province: '',
|
|
|
+ city: '',
|
|
|
+ district: '',
|
|
|
+ street: ''
|
|
|
+ },
|
|
|
+ // 区域选择弹窗(省市区三级联动,与订单收货人地址一致)
|
|
|
+ showRegionPicker: false,
|
|
|
+ currentRegionLevel: 0, // 0=省份, 1=城市, 2=区县
|
|
|
+ regionLoading: false,
|
|
|
+ provinceList: [],
|
|
|
+ cityList: [],
|
|
|
+ districtList: [],
|
|
|
+ tempProvince: null,
|
|
|
+ tempCity: null,
|
|
|
tempDistrict: null,
|
|
|
- ethnicityOptions: ['汉族','蒙古族','回族','藏族','维吾尔族','苗族','彝族','壮族','布依族','朝鲜族','满族','侗族','瑶族','白族','土家族','哈尼族','哈萨克族','傣族','黎族','其他'],
|
|
|
- bloodTypeOptions: ['A','B','AB','O','未知'],
|
|
|
- birthHourOptions: ['子时(23-01)','丑时(01-03)','寅时(03-05)','卯时(05-07)','辰时(07-09)','巳时(09-11)','午时(11-13)','未时(13-15)','申时(15-17)','酉时(17-19)','戌时(19-21)','亥时(21-23)'],
|
|
|
- birthHourValues: ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'],
|
|
|
- educationOptions: ['小学','初中','高中','中专','大专','本科','硕士','博士'],
|
|
|
- maritalOptions: ['未婚','已婚','离异','丧偶'],
|
|
|
- loading: false,
|
|
|
- userId: '',
|
|
|
- token: ''
|
|
|
- }
|
|
|
- },
|
|
|
- computed: {
|
|
|
- ethnicityIndex() {
|
|
|
- return this.ethnicityOptions.indexOf(this.form.ethnicity)
|
|
|
- },
|
|
|
- bloodTypeIndex() {
|
|
|
- return this.bloodTypeOptions.indexOf(this.form.bloodType)
|
|
|
- },
|
|
|
- birthHourIndex() {
|
|
|
- return this.birthHourValues.indexOf(this.form.birthHour)
|
|
|
- },
|
|
|
- educationIndex() {
|
|
|
- return this.educationOptions.indexOf(this.form.highestEducation)
|
|
|
- },
|
|
|
- maritalIndex() {
|
|
|
- return this.maritalOptions.indexOf(this.form.maritalStatus)
|
|
|
- },
|
|
|
- hasFamily: function() {
|
|
|
- if (this.token) return true
|
|
|
- return this.$store.getters.hasFamily
|
|
|
- },
|
|
|
- regionBreadcrumb: function() {
|
|
|
- var names = []
|
|
|
- names.push(this.tempProvince ? this.getRegionName(this.tempProvince) : '省份')
|
|
|
- names.push(this.tempCity ? this.getRegionName(this.tempCity) : '城市')
|
|
|
- names.push(this.tempDistrict ? this.getRegionName(this.tempDistrict) : '区县')
|
|
|
- return names
|
|
|
- },
|
|
|
- currentRegionItems: function() {
|
|
|
- if (this.currentRegionLevel === 0) return this.provinceList
|
|
|
- if (this.currentRegionLevel === 1) return this.cityList
|
|
|
- if (this.currentRegionLevel === 2) return this.districtList
|
|
|
- return []
|
|
|
- },
|
|
|
+ ethnicityOptions: ['汉族','蒙古族','回族','藏族','维吾尔族','苗族','彝族','壮族','布依族','朝鲜族','满族','侗族','瑶族','白族','土家族','哈尼族','哈萨克族','傣族','黎族','其他'],
|
|
|
+ bloodTypeOptions: ['A','B','AB','O','未知'],
|
|
|
+ birthHourOptions: ['子时(23-01)','丑时(01-03)','寅时(03-05)','卯时(05-07)','辰时(07-09)','巳时(09-11)','午时(11-13)','未时(13-15)','申时(15-17)','酉时(17-19)','戌时(19-21)','亥时(21-23)'],
|
|
|
+ birthHourValues: ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'],
|
|
|
+ educationOptions: ['小学','初中','高中','中专','大专','本科','硕士','博士'],
|
|
|
+ maritalOptions: ['未婚','已婚','离异','丧偶'],
|
|
|
+ loading: false,
|
|
|
+ userId: '',
|
|
|
+ token: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ ethnicityIndex() {
|
|
|
+ return this.ethnicityOptions.indexOf(this.form.ethnicity)
|
|
|
+ },
|
|
|
+ bloodTypeIndex() {
|
|
|
+ return this.bloodTypeOptions.indexOf(this.form.bloodType)
|
|
|
+ },
|
|
|
+ birthHourIndex() {
|
|
|
+ return this.birthHourValues.indexOf(this.form.birthHour)
|
|
|
+ },
|
|
|
+ educationIndex() {
|
|
|
+ return this.educationOptions.indexOf(this.form.highestEducation)
|
|
|
+ },
|
|
|
+ maritalIndex() {
|
|
|
+ return this.maritalOptions.indexOf(this.form.maritalStatus)
|
|
|
+ },
|
|
|
+ hasFamily: function() {
|
|
|
+ if (this.token) return true
|
|
|
+ return this.$store.getters.hasFamily
|
|
|
+ },
|
|
|
+ regionBreadcrumb: function() {
|
|
|
+ var names = []
|
|
|
+ names.push(this.tempProvince ? this.getRegionName(this.tempProvince) : '省份')
|
|
|
+ names.push(this.tempCity ? this.getRegionName(this.tempCity) : '城市')
|
|
|
+ names.push(this.tempDistrict ? this.getRegionName(this.tempDistrict) : '区县')
|
|
|
+ return names
|
|
|
+ },
|
|
|
+ currentRegionItems: function() {
|
|
|
+ if (this.currentRegionLevel === 0) return this.provinceList
|
|
|
+ if (this.currentRegionLevel === 1) return this.cityList
|
|
|
+ if (this.currentRegionLevel === 2) return this.districtList
|
|
|
+ return []
|
|
|
+ },
|
|
|
},
|
|
|
- onLoad(options) {
|
|
|
- if (options.token && options.userId) {
|
|
|
- this.token = options.token
|
|
|
- this.userId = options.userId
|
|
|
-
|
|
|
- if (options.autoParent === 'true') {
|
|
|
- // 首次登录:跳过角色选择,默认 parent,直接进入信息补充页
|
|
|
- this.showRoleSelect = false
|
|
|
- this.isEdit = true
|
|
|
- this.isNewUserFlow = true
|
|
|
- this.form.role = 'parent'
|
|
|
- } else {
|
|
|
- // 检查是否需要选择角色(新用户)- 只有明确传递 needsRoleSelect=true 才显示选择
|
|
|
- this.showRoleSelect = options.needsRoleSelect === 'true'
|
|
|
- this.isEdit = !this.showRoleSelect
|
|
|
- this.isNewUserFlow = options.needsRoleSelect === 'true'
|
|
|
- }
|
|
|
- } else if (options.phone && (options.autoParent === 'true' || options.needsRoleSelect === 'true')) {
|
|
|
- // 测试环境:没有token,只有手机号,是新用户注册流程
|
|
|
- this.showRoleSelect = options.needsRoleSelect === 'true'
|
|
|
- this.isEdit = !this.showRoleSelect
|
|
|
- this.isNewUserFlow = true
|
|
|
- if (options.autoParent === 'true') {
|
|
|
- this.form.role = 'parent'
|
|
|
- }
|
|
|
- // 将手机号传递给表单
|
|
|
- this.form.phone = options.phone
|
|
|
- } else {
|
|
|
- // 其他情况:默认进入编辑模式(如从"我的"页面进入)
|
|
|
- this.showRoleSelect = false
|
|
|
- this.isEdit = true
|
|
|
- this.isNewUserFlow = false
|
|
|
- }
|
|
|
-
|
|
|
- // 非新用户流程时,加载已有用户信息
|
|
|
- if (this.isEdit && !this.isNewUserFlow) {
|
|
|
- this.loadUserInfo()
|
|
|
- }
|
|
|
- // 加载会员等级(用于AI助手解锁)
|
|
|
- var self = this
|
|
|
- getMyMembership().then(function(res) {
|
|
|
- if (res.data && res.data.memberLevel) {
|
|
|
- self.memberLevel = res.data.memberLevel
|
|
|
- }
|
|
|
- }).catch(function() {})
|
|
|
- },
|
|
|
- methods: {
|
|
|
- selectRole(role) {
|
|
|
- this.form.role = role
|
|
|
- },
|
|
|
- confirmRole() {
|
|
|
- if (!this.form.role) {
|
|
|
- uni.showToast({ title: '请选择角色', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- this.showRoleSelect = false
|
|
|
- this.isEdit = true
|
|
|
-
|
|
|
- // 测试环境:直接注册用户(不需要验证码)
|
|
|
- if (!this.token && this.form.phone) {
|
|
|
- // 自动进入家庭绑定流程
|
|
|
- }
|
|
|
- },
|
|
|
- chooseAvatar() {
|
|
|
- uni.chooseImage({
|
|
|
- count: 1,
|
|
|
- success: (res) => {
|
|
|
- this.form.avatar = res.tempFilePaths[0]
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- chooseTeacherPhoto() {
|
|
|
- uni.chooseImage({
|
|
|
- count: 1,
|
|
|
- success: (res) => {
|
|
|
- this.form.teacherPhoto = res.tempFilePaths[0]
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- chooseCertificate() {
|
|
|
- uni.chooseImage({
|
|
|
- count: 1,
|
|
|
- success: (res) => {
|
|
|
- this.form.certificateImage = res.tempFilePaths[0]
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- onGenderChange(e) {
|
|
|
- this.form.gender = e.detail.value
|
|
|
- },
|
|
|
- onIdCardInput(e) {
|
|
|
- const idCard = e.detail.value
|
|
|
- if (idCard.length === 18) {
|
|
|
- // 根据身份证号自动提取生日
|
|
|
- const year = idCard.substring(6, 10)
|
|
|
- const month = idCard.substring(10, 12)
|
|
|
- const day = idCard.substring(12, 14)
|
|
|
- this.form.birthday = `${year}-${month}-${day}`
|
|
|
- }
|
|
|
- },
|
|
|
- onBirthdayChange(e) {
|
|
|
- this.form.birthday = e.detail.value
|
|
|
- },
|
|
|
- onEthnicityChange(e) {
|
|
|
- this.form.ethnicity = this.ethnicityOptions[e.detail.value]
|
|
|
- },
|
|
|
- onBloodTypeChange(e) {
|
|
|
- this.form.bloodType = this.bloodTypeOptions[e.detail.value]
|
|
|
- },
|
|
|
- onBirthHourChange(e) {
|
|
|
- this.form.birthHour = this.birthHourValues[e.detail.value]
|
|
|
- },
|
|
|
- onEducationChange(e) {
|
|
|
- this.form.highestEducation = this.educationOptions[e.detail.value]
|
|
|
- },
|
|
|
- onMaritalChange(e) {
|
|
|
- this.form.maritalStatus = this.maritalOptions[e.detail.value]
|
|
|
- },
|
|
|
- selectMascot(code) {
|
|
|
- this.form.mascot = code
|
|
|
- },
|
|
|
- goUpgrade() {
|
|
|
- uni.navigateTo({ url: '/pages/membership/upgrade' })
|
|
|
- },
|
|
|
- // ===== 省市区三级联动弹窗(与订单收货人地址一致) =====
|
|
|
- openRegionPicker() {
|
|
|
- this.tempProvince = this.form.province ? { name: this.form.province, id: null } : null
|
|
|
- this.tempCity = this.form.city ? { name: this.form.city, id: null } : null
|
|
|
- this.tempDistrict = this.form.district ? { name: this.form.district, id: null } : null
|
|
|
- this.currentRegionLevel = 0
|
|
|
- this.showRegionPicker = true
|
|
|
- this.loadRegionProvinces()
|
|
|
- },
|
|
|
- loadRegionProvinces() {
|
|
|
- var self = this
|
|
|
- self.regionLoading = true
|
|
|
- uni.request({
|
|
|
- url: config.api('/api/region/provinces'),
|
|
|
- method: 'POST',
|
|
|
- header: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- 'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
- },
|
|
|
- success: function(res) {
|
|
|
- self.regionLoading = false
|
|
|
- if (res.data && res.data.code === 200) {
|
|
|
- self.provinceList = res.data.data || []
|
|
|
- }
|
|
|
- },
|
|
|
- fail: function() {
|
|
|
- self.regionLoading = false
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- loadRegionCities(parentId) {
|
|
|
- if (!parentId) return
|
|
|
- var self = this
|
|
|
- self.regionLoading = true
|
|
|
- uni.request({
|
|
|
- url: config.api('/api/region/cities'),
|
|
|
- method: 'POST',
|
|
|
- data: { parentId: parentId },
|
|
|
- header: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- 'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
- },
|
|
|
- success: function(res) {
|
|
|
- self.regionLoading = false
|
|
|
- if (res.data && res.data.code === 200) {
|
|
|
- self.cityList = res.data.data || []
|
|
|
- }
|
|
|
- },
|
|
|
- fail: function() {
|
|
|
- self.regionLoading = false
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- loadRegionDistricts(parentId) {
|
|
|
- if (!parentId) return
|
|
|
- var self = this
|
|
|
- self.regionLoading = true
|
|
|
- uni.request({
|
|
|
- url: config.api('/api/region/districts'),
|
|
|
- method: 'POST',
|
|
|
- data: { parentId: parentId },
|
|
|
- header: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- 'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
- },
|
|
|
- success: function(res) {
|
|
|
- self.regionLoading = false
|
|
|
- if (res.data && res.data.code === 200) {
|
|
|
- self.districtList = res.data.data || []
|
|
|
- }
|
|
|
- },
|
|
|
- fail: function() {
|
|
|
- self.regionLoading = false
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- selectRegionItem(item) {
|
|
|
- if (this.currentRegionLevel === 0) {
|
|
|
- this.tempProvince = item
|
|
|
- this.tempCity = null
|
|
|
- this.tempDistrict = null
|
|
|
- this.cityList = []
|
|
|
- this.districtList = []
|
|
|
- this.currentRegionLevel = 1
|
|
|
- this.loadRegionCities(this.getRegionId(item))
|
|
|
- } else if (this.currentRegionLevel === 1) {
|
|
|
- this.tempCity = item
|
|
|
- this.tempDistrict = null
|
|
|
- this.districtList = []
|
|
|
- this.currentRegionLevel = 2
|
|
|
- this.loadRegionDistricts(this.getRegionId(item))
|
|
|
- } else if (this.currentRegionLevel === 2) {
|
|
|
- this.tempDistrict = item
|
|
|
- this.confirmRegion()
|
|
|
- }
|
|
|
- },
|
|
|
- confirmRegion() {
|
|
|
- if (this.tempProvince) {
|
|
|
- this.form.province = this.getRegionName(this.tempProvince)
|
|
|
- }
|
|
|
- if (this.tempCity) {
|
|
|
- this.form.city = this.getRegionName(this.tempCity)
|
|
|
- }
|
|
|
- if (this.tempDistrict) {
|
|
|
- this.form.district = this.getRegionName(this.tempDistrict)
|
|
|
- }
|
|
|
- this.updateFullAddress()
|
|
|
- this.showRegionPicker = false
|
|
|
- },
|
|
|
- cancelRegion() {
|
|
|
- this.showRegionPicker = false
|
|
|
- },
|
|
|
- jumpRegionLevel(level) {
|
|
|
- if (level < this.currentRegionLevel) {
|
|
|
- this.currentRegionLevel = level
|
|
|
- }
|
|
|
- },
|
|
|
- getRegionName(item) {
|
|
|
- return item && (item.name || item.province || item.city || item.district || '')
|
|
|
- },
|
|
|
- getRegionId(item) {
|
|
|
- return item && (item.id || item.code)
|
|
|
- },
|
|
|
- isRegionSelected(item) {
|
|
|
- var name = this.getRegionName(item)
|
|
|
- if (this.currentRegionLevel === 0) return this.tempProvince && this.getRegionName(this.tempProvince) === name
|
|
|
- if (this.currentRegionLevel === 1) return this.tempCity && this.getRegionName(this.tempCity) === name
|
|
|
- if (this.currentRegionLevel === 2) return this.tempDistrict && this.getRegionName(this.tempDistrict) === name
|
|
|
- return false
|
|
|
- },
|
|
|
- updateFullAddress() {
|
|
|
- this.form.address = (this.form.province || '') + (this.form.city || '') + (this.form.district || '') + (this.form.street || '')
|
|
|
- },
|
|
|
+ onLoad(options) {
|
|
|
+ if (options.token && options.userId) {
|
|
|
+ this.token = options.token
|
|
|
+ this.userId = options.userId
|
|
|
+
|
|
|
+ if (options.autoParent === 'true') {
|
|
|
+ // 首次登录:跳过角色选择,默认 parent,直接进入信息补充页
|
|
|
+ this.showRoleSelect = false
|
|
|
+ this.isEdit = true
|
|
|
+ this.isNewUserFlow = true
|
|
|
+ this.form.role = 'parent'
|
|
|
+ } else {
|
|
|
+ // 检查是否需要选择角色(新用户)- 只有明确传递 needsRoleSelect=true 才显示选择
|
|
|
+ this.showRoleSelect = options.needsRoleSelect === 'true'
|
|
|
+ this.isEdit = !this.showRoleSelect
|
|
|
+ this.isNewUserFlow = options.needsRoleSelect === 'true'
|
|
|
+ }
|
|
|
+ } else if (options.phone && (options.autoParent === 'true' || options.needsRoleSelect === 'true')) {
|
|
|
+ // 测试环境:没有token,只有手机号,是新用户注册流程
|
|
|
+ this.showRoleSelect = options.needsRoleSelect === 'true'
|
|
|
+ this.isEdit = !this.showRoleSelect
|
|
|
+ this.isNewUserFlow = true
|
|
|
+ if (options.autoParent === 'true') {
|
|
|
+ this.form.role = 'parent'
|
|
|
+ }
|
|
|
+ // 将手机号传递给表单
|
|
|
+ this.form.phone = options.phone
|
|
|
+ } else {
|
|
|
+ // 其他情况:默认进入编辑模式(如从"我的"页面进入)
|
|
|
+ this.showRoleSelect = false
|
|
|
+ this.isEdit = true
|
|
|
+ this.isNewUserFlow = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 非新用户流程时,加载已有用户信息
|
|
|
+ if (this.isEdit && !this.isNewUserFlow) {
|
|
|
+ this.loadUserInfo()
|
|
|
+ }
|
|
|
+ // 加载会员等级(用于助手解锁)
|
|
|
+ var self = this
|
|
|
+ getMyMembership().then(function(res) {
|
|
|
+ if (res.data && res.data.memberLevel) {
|
|
|
+ self.memberLevel = res.data.memberLevel
|
|
|
+ }
|
|
|
+ }).catch(function() {})
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ selectRole(role) {
|
|
|
+ this.form.role = role
|
|
|
+ },
|
|
|
+ confirmRole() {
|
|
|
+ if (!this.form.role) {
|
|
|
+ uni.showToast({ title: '请选择角色', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.showRoleSelect = false
|
|
|
+ this.isEdit = true
|
|
|
+
|
|
|
+ // 测试环境:直接注册用户(不需要验证码)
|
|
|
+ if (!this.token && this.form.phone) {
|
|
|
+ // 自动进入家庭绑定流程
|
|
|
+ }
|
|
|
+ },
|
|
|
+ chooseAvatar() {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ success: (res) => {
|
|
|
+ this.form.avatar = res.tempFilePaths[0]
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ chooseTeacherPhoto() {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ success: (res) => {
|
|
|
+ this.form.teacherPhoto = res.tempFilePaths[0]
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ chooseCertificate() {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ success: (res) => {
|
|
|
+ this.form.certificateImage = res.tempFilePaths[0]
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onGenderChange(e) {
|
|
|
+ this.form.gender = e.detail.value
|
|
|
+ },
|
|
|
+ onIdCardInput(e) {
|
|
|
+ const idCard = e.detail.value
|
|
|
+ if (idCard.length === 18) {
|
|
|
+ // 根据身份证号自动提取生日
|
|
|
+ const year = idCard.substring(6, 10)
|
|
|
+ const month = idCard.substring(10, 12)
|
|
|
+ const day = idCard.substring(12, 14)
|
|
|
+ this.form.birthday = `${year}-${month}-${day}`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onBirthdayChange(e) {
|
|
|
+ this.form.birthday = e.detail.value
|
|
|
+ },
|
|
|
+ onEthnicityChange(e) {
|
|
|
+ this.form.ethnicity = this.ethnicityOptions[e.detail.value]
|
|
|
+ },
|
|
|
+ onBloodTypeChange(e) {
|
|
|
+ this.form.bloodType = this.bloodTypeOptions[e.detail.value]
|
|
|
+ },
|
|
|
+ onBirthHourChange(e) {
|
|
|
+ this.form.birthHour = this.birthHourValues[e.detail.value]
|
|
|
+ },
|
|
|
+ onEducationChange(e) {
|
|
|
+ this.form.highestEducation = this.educationOptions[e.detail.value]
|
|
|
+ },
|
|
|
+ onMaritalChange(e) {
|
|
|
+ this.form.maritalStatus = this.maritalOptions[e.detail.value]
|
|
|
+ },
|
|
|
+ selectMascot(code) {
|
|
|
+ this.form.mascot = code
|
|
|
+ },
|
|
|
+ goUpgrade() {
|
|
|
+ uni.navigateTo({ url: '/pages/membership/upgrade' })
|
|
|
+ },
|
|
|
+ // ===== 省市区三级联动弹窗(与订单收货人地址一致) =====
|
|
|
+ openRegionPicker() {
|
|
|
+ this.tempProvince = this.form.province ? { name: this.form.province, id: null } : null
|
|
|
+ this.tempCity = this.form.city ? { name: this.form.city, id: null } : null
|
|
|
+ this.tempDistrict = this.form.district ? { name: this.form.district, id: null } : null
|
|
|
+ this.currentRegionLevel = 0
|
|
|
+ this.showRegionPicker = true
|
|
|
+ this.loadRegionProvinces()
|
|
|
+ },
|
|
|
+ loadRegionProvinces() {
|
|
|
+ var self = this
|
|
|
+ self.regionLoading = true
|
|
|
+ uni.request({
|
|
|
+ url: config.api('/api/region/provinces'),
|
|
|
+ method: 'POST',
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ 'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
+ },
|
|
|
+ success: function(res) {
|
|
|
+ self.regionLoading = false
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ self.provinceList = res.data.data || []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self.regionLoading = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ loadRegionCities(parentId) {
|
|
|
+ if (!parentId) return
|
|
|
+ var self = this
|
|
|
+ self.regionLoading = true
|
|
|
+ uni.request({
|
|
|
+ url: config.api('/api/region/cities'),
|
|
|
+ method: 'POST',
|
|
|
+ data: { parentId: parentId },
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ 'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
+ },
|
|
|
+ success: function(res) {
|
|
|
+ self.regionLoading = false
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ self.cityList = res.data.data || []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self.regionLoading = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ loadRegionDistricts(parentId) {
|
|
|
+ if (!parentId) return
|
|
|
+ var self = this
|
|
|
+ self.regionLoading = true
|
|
|
+ uni.request({
|
|
|
+ url: config.api('/api/region/districts'),
|
|
|
+ method: 'POST',
|
|
|
+ data: { parentId: parentId },
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ 'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
+ },
|
|
|
+ success: function(res) {
|
|
|
+ self.regionLoading = false
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ self.districtList = res.data.data || []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self.regionLoading = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ selectRegionItem(item) {
|
|
|
+ if (this.currentRegionLevel === 0) {
|
|
|
+ this.tempProvince = item
|
|
|
+ this.tempCity = null
|
|
|
+ this.tempDistrict = null
|
|
|
+ this.cityList = []
|
|
|
+ this.districtList = []
|
|
|
+ this.currentRegionLevel = 1
|
|
|
+ this.loadRegionCities(this.getRegionId(item))
|
|
|
+ } else if (this.currentRegionLevel === 1) {
|
|
|
+ this.tempCity = item
|
|
|
+ this.tempDistrict = null
|
|
|
+ this.districtList = []
|
|
|
+ this.currentRegionLevel = 2
|
|
|
+ this.loadRegionDistricts(this.getRegionId(item))
|
|
|
+ } else if (this.currentRegionLevel === 2) {
|
|
|
+ this.tempDistrict = item
|
|
|
+ this.confirmRegion()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ confirmRegion() {
|
|
|
+ if (this.tempProvince) {
|
|
|
+ this.form.province = this.getRegionName(this.tempProvince)
|
|
|
+ }
|
|
|
+ if (this.tempCity) {
|
|
|
+ this.form.city = this.getRegionName(this.tempCity)
|
|
|
+ }
|
|
|
+ if (this.tempDistrict) {
|
|
|
+ this.form.district = this.getRegionName(this.tempDistrict)
|
|
|
+ }
|
|
|
+ this.updateFullAddress()
|
|
|
+ this.showRegionPicker = false
|
|
|
+ },
|
|
|
+ cancelRegion() {
|
|
|
+ this.showRegionPicker = false
|
|
|
+ },
|
|
|
+ jumpRegionLevel(level) {
|
|
|
+ if (level < this.currentRegionLevel) {
|
|
|
+ this.currentRegionLevel = level
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getRegionName(item) {
|
|
|
+ return item && (item.name || item.province || item.city || item.district || '')
|
|
|
+ },
|
|
|
+ getRegionId(item) {
|
|
|
+ return item && (item.id || item.code)
|
|
|
+ },
|
|
|
+ isRegionSelected(item) {
|
|
|
+ var name = this.getRegionName(item)
|
|
|
+ if (this.currentRegionLevel === 0) return this.tempProvince && this.getRegionName(this.tempProvince) === name
|
|
|
+ if (this.currentRegionLevel === 1) return this.tempCity && this.getRegionName(this.tempCity) === name
|
|
|
+ if (this.currentRegionLevel === 2) return this.tempDistrict && this.getRegionName(this.tempDistrict) === name
|
|
|
+ return false
|
|
|
+ },
|
|
|
+ updateFullAddress() {
|
|
|
+ this.form.address = (this.form.province || '') + (this.form.city || '') + (this.form.district || '') + (this.form.street || '')
|
|
|
+ },
|
|
|
// 加载已有用户信息
|
|
|
- async loadUserInfo() {
|
|
|
- try {
|
|
|
- const res = await getUserInfo()
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- const userData = res.data
|
|
|
- this.form.nickname = userData.nickname || ''
|
|
|
- this.form.realName = userData.realName || ''
|
|
|
- this.form.gender = userData.gender || ''
|
|
|
- this.form.idCard = userData.idCard || ''
|
|
|
- // 生日只取日期部分,去掉时间和 ISO 格式的 T 分隔符
|
|
|
- this.form.birthday = userData.birthday ? String(userData.birthday).split(/[T ]/)[0] : ''
|
|
|
- this.form.avatar = userData.avatar || ''
|
|
|
- this.form.role = userData.role || ''
|
|
|
- this.form.phone = userData.phone || ''
|
|
|
- this.form.ethnicity = userData.ethnicity || ''
|
|
|
- this.form.bloodType = userData.bloodType || ''
|
|
|
- this.form.highestEducation = userData.highestEducation || ''
|
|
|
- this.form.maritalStatus = userData.maritalStatus || ''
|
|
|
- this.form.hobbies = userData.hobbies || ''
|
|
|
- this.form.dietPreferences = userData.dietPreferences || ''
|
|
|
- this.form.mascot = userData.mascot || ''
|
|
|
- this.form.address = userData.address || ''
|
|
|
- this.form.province = userData.province || ''
|
|
|
- this.form.city = userData.city || ''
|
|
|
- this.form.district = userData.district || ''
|
|
|
- this.form.street = userData.street || ''
|
|
|
- // 若无完整地址文本但有省市区,则拼接
|
|
|
- if (!this.form.address && (this.form.province || this.form.city || this.form.district)) {
|
|
|
- this.updateFullAddress()
|
|
|
- }
|
|
|
+ async loadUserInfo() {
|
|
|
+ try {
|
|
|
+ const res = await getUserInfo()
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ const userData = res.data
|
|
|
+ this.form.nickname = userData.nickname || ''
|
|
|
+ this.form.realName = userData.realName || ''
|
|
|
+ this.form.gender = userData.gender || ''
|
|
|
+ this.form.idCard = userData.idCard || ''
|
|
|
+ // 生日只取日期部分,去掉时间和 ISO 格式的 T 分隔符
|
|
|
+ this.form.birthday = userData.birthday ? String(userData.birthday).split(/[T ]/)[0] : ''
|
|
|
+ this.form.avatar = userData.avatar || ''
|
|
|
+ this.form.role = userData.role || ''
|
|
|
+ this.form.phone = userData.phone || ''
|
|
|
+ this.form.ethnicity = userData.ethnicity || ''
|
|
|
+ this.form.bloodType = userData.bloodType || ''
|
|
|
+ this.form.highestEducation = userData.highestEducation || ''
|
|
|
+ this.form.maritalStatus = userData.maritalStatus || ''
|
|
|
+ this.form.hobbies = userData.hobbies || ''
|
|
|
+ this.form.dietPreferences = userData.dietPreferences || ''
|
|
|
+ this.form.mascot = userData.mascot || ''
|
|
|
+ this.form.address = userData.address || ''
|
|
|
+ this.form.province = userData.province || ''
|
|
|
+ this.form.city = userData.city || ''
|
|
|
+ this.form.district = userData.district || ''
|
|
|
+ this.form.street = userData.street || ''
|
|
|
+ // 若无完整地址文本但有省市区,则拼接
|
|
|
+ if (!this.form.address && (this.form.province || this.form.city || this.form.district)) {
|
|
|
+ this.updateFullAddress()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载用户信息失败', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 保存修改
|
|
|
+ async saveUserInfo() {
|
|
|
+ // 新用户流程:昵称可跳过(跳过时用默认昵称);老用户流程:昵称必填
|
|
|
+ if (!this.isNewUserFlow && !this.form.nickname) {
|
|
|
+ uni.showToast({ title: '请输入昵称', icon: 'none' })
|
|
|
+ return
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- console.error('加载用户信息失败', e)
|
|
|
- }
|
|
|
- },
|
|
|
- // 保存修改
|
|
|
- async saveUserInfo() {
|
|
|
- // 新用户流程:昵称可跳过(跳过时用默认昵称);老用户流程:昵称必填
|
|
|
- if (!this.isNewUserFlow && !this.form.nickname) {
|
|
|
- uni.showToast({ title: '请输入昵称', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (this.isNewUserFlow && !this.form.nickname) {
|
|
|
- var phone = this.form.phone || ''
|
|
|
- this.form.nickname = phone ? '用户' + phone.substring(phone.length - 4) : '微信用户'
|
|
|
- }
|
|
|
-
|
|
|
- // 从省市区街道字段同步完整地址文本
|
|
|
- this.updateFullAddress()
|
|
|
-
|
|
|
- this.loading = true
|
|
|
- try {
|
|
|
- await updateUserInfo({
|
|
|
- nickname: this.form.nickname,
|
|
|
- realName: this.form.realName,
|
|
|
- gender: this.form.gender,
|
|
|
- idCard: this.form.idCard,
|
|
|
- birthday: this.form.birthday,
|
|
|
- avatar: this.form.avatar,
|
|
|
- mascot: this.form.mascot,
|
|
|
- ethnicity: this.form.ethnicity,
|
|
|
- bloodType: this.form.bloodType,
|
|
|
- highestEducation: this.form.highestEducation,
|
|
|
- maritalStatus: this.form.maritalStatus,
|
|
|
- hobbies: this.form.hobbies,
|
|
|
- dietPreferences: this.form.dietPreferences,
|
|
|
- address: this.form.address,
|
|
|
- province: this.form.province,
|
|
|
- city: this.form.city,
|
|
|
- district: this.form.district,
|
|
|
- street: this.form.street
|
|
|
+ if (this.isNewUserFlow && !this.form.nickname) {
|
|
|
+ var phone = this.form.phone || ''
|
|
|
+ this.form.nickname = phone ? '用户' + phone.substring(phone.length - 4) : '微信用户'
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从省市区街道字段同步完整地址文本
|
|
|
+ this.updateFullAddress()
|
|
|
+
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ await updateUserInfo({
|
|
|
+ nickname: this.form.nickname,
|
|
|
+ realName: this.form.realName,
|
|
|
+ gender: this.form.gender,
|
|
|
+ idCard: this.form.idCard,
|
|
|
+ birthday: this.form.birthday,
|
|
|
+ avatar: this.form.avatar,
|
|
|
+ mascot: this.form.mascot,
|
|
|
+ ethnicity: this.form.ethnicity,
|
|
|
+ bloodType: this.form.bloodType,
|
|
|
+ highestEducation: this.form.highestEducation,
|
|
|
+ maritalStatus: this.form.maritalStatus,
|
|
|
+ hobbies: this.form.hobbies,
|
|
|
+ dietPreferences: this.form.dietPreferences,
|
|
|
+ address: this.form.address,
|
|
|
+ province: this.form.province,
|
|
|
+ city: this.form.city,
|
|
|
+ district: this.form.district,
|
|
|
+ street: this.form.street
|
|
|
})
|
|
|
- uni.showToast({ title: '保存成功', icon: 'success' })
|
|
|
- setTimeout(() => {
|
|
|
- if (this.isNewUserFlow) {
|
|
|
- this.navigateToHome()
|
|
|
- } else {
|
|
|
- uni.navigateBack()
|
|
|
- }
|
|
|
- }, 1500)
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: '保存失败', icon: 'none' })
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
- },
|
|
|
- async submitTeacherInfo() {
|
|
|
- // 验证必填字段
|
|
|
- if (!this.form.nickname) {
|
|
|
- uni.showToast({ title: '请输入昵称', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!this.form.gender) {
|
|
|
- uni.showToast({ title: '请选择性别', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!this.form.idCard) {
|
|
|
- uni.showToast({ title: '请输入身份证号', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!this.form.teacherNo) {
|
|
|
- uni.showToast({ title: '请输入成长规划师证书号', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!this.form.teacherPhoto) {
|
|
|
- uni.showToast({ title: '请上传成长规划师照片', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!this.form.certificateImage) {
|
|
|
- uni.showToast({ title: '请上传成长规划师证书', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- this.loading = true
|
|
|
- try {
|
|
|
- await updateUserInfo({
|
|
|
- ...this.form,
|
|
|
- roles: this.form.role
|
|
|
- })
|
|
|
- uni.showToast({ title: '提交成功,请等待审核' })
|
|
|
- setTimeout(() => {
|
|
|
- uni.switchTab({ url: '/pages/index-home/index' })
|
|
|
- }, 1500)
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: '提交失败', icon: 'none' })
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
- },
|
|
|
- async joinFamilyAction() {
|
|
|
- if (!this.form.inviteCode) {
|
|
|
- uni.showToast({ title: '请输入邀请码', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if (!this.form.nickname) {
|
|
|
- uni.showToast({ title: '请输入昵称', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- this.loading = true
|
|
|
- try {
|
|
|
- let userId = this.userId
|
|
|
-
|
|
|
- // 测试环境:没有token,需要先注册用户
|
|
|
- if (!this.token && this.form.phone) {
|
|
|
- const regRes = await directRegister({
|
|
|
- phone: this.form.phone,
|
|
|
- nickname: this.form.nickname,
|
|
|
- avatar: this.form.avatar,
|
|
|
- role: this.form.role,
|
|
|
- mascot: this.form.mascot,
|
|
|
- birthday: this.form.birthday,
|
|
|
- gender: this.form.gender,
|
|
|
- inviteCode: this.form.inviteCode
|
|
|
- })
|
|
|
- userId = regRes.data.userId
|
|
|
- // 保存登录信息
|
|
|
- uni.setStorageSync('token', regRes.data.token)
|
|
|
- uni.setStorageSync('userId', userId)
|
|
|
- uni.setStorageSync('role', regRes.data.role)
|
|
|
- // 邀请码注册不写入 familyId(加入需管理员审批)
|
|
|
- } else {
|
|
|
- await updateUserInfo(this.form)
|
|
|
- await requestJoinByCode(this.form.inviteCode)
|
|
|
- }
|
|
|
-
|
|
|
- uni.showToast({ title: '申请已提交,等待管理员审批' })
|
|
|
- setTimeout(() => {
|
|
|
- this.navigateToHome()
|
|
|
- }, 1500)
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: e.message || '加入失败', icon: 'none' })
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
- },
|
|
|
- async createFamilyAction() {
|
|
|
- if (!this.form.nickname) {
|
|
|
- uni.showToast({ title: '请输入昵称', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- this.loading = true
|
|
|
- try {
|
|
|
- let userId = this.userId
|
|
|
-
|
|
|
- // 测试环境:没有token,需要先注册用户
|
|
|
- if (!this.token && this.form.phone) {
|
|
|
- const regRes = await directRegister({
|
|
|
- phone: this.form.phone,
|
|
|
- nickname: this.form.nickname,
|
|
|
- avatar: this.form.avatar,
|
|
|
- role: this.form.role,
|
|
|
- mascot: this.form.mascot,
|
|
|
- birthday: this.form.birthday,
|
|
|
- gender: this.form.gender
|
|
|
- })
|
|
|
- userId = regRes.data.userId
|
|
|
- // 保存登录信息
|
|
|
- uni.setStorageSync('token', regRes.data.token)
|
|
|
- uni.setStorageSync('userId', userId)
|
|
|
- uni.setStorageSync('role', regRes.data.role)
|
|
|
- // 注册不写入 familyId(后端根据 userId 获取)
|
|
|
- } else {
|
|
|
- await updateUserInfo(this.form)
|
|
|
- const famRes = await createFamily(this.form.nickname + '的家庭')
|
|
|
- // 同步 familyId 到 store(hasFamily getter 依赖)
|
|
|
- if (famRes && famRes.data) {
|
|
|
- this.$store.commit('setFamilyId', famRes.data)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- uni.showToast({ title: '创建成功' })
|
|
|
- setTimeout(() => {
|
|
|
- this.navigateToHome()
|
|
|
- }, 1500)
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: e.message || '创建失败', icon: 'none' })
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
- },
|
|
|
- skip() {
|
|
|
- this.navigateToHome()
|
|
|
- },
|
|
|
- navigateToHome() {
|
|
|
- // 保存 mascot 到 userInfo 存储,供 AI 聊天页读取
|
|
|
- if (this.form.mascot) {
|
|
|
- try {
|
|
|
- const userInfo = uni.getStorageSync('userInfo') || {}
|
|
|
- userInfo.mascot = this.form.mascot
|
|
|
- uni.setStorageSync('userInfo', userInfo)
|
|
|
- } catch (e) {
|
|
|
- console.error('保存 mascot 失败', e)
|
|
|
- }
|
|
|
- }
|
|
|
- // 用 switchTab 跳转带底部 TabBar 的首页(行/身/智/心/富)
|
|
|
- // parent-index/child-index 是分包页面(非 TabBar 页),用 navigateTo 会丢失底部 TabBar
|
|
|
- uni.switchTab({ url: '/pages/index-home/index' })
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped>
|
|
|
-.user-edit-container {
|
|
|
- min-height: 100vh;
|
|
|
- background: #f5f5f5;
|
|
|
- padding: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.header {
|
|
|
- margin-bottom: 40rpx;
|
|
|
- text-align: center;
|
|
|
-}
|
|
|
-
|
|
|
-.title {
|
|
|
- font-size: 40rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- display: block;
|
|
|
- margin-bottom: 10rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.subtitle {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-/* 角色选择 */
|
|
|
-.role-select-section {
|
|
|
- margin-bottom: 40rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.role-card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 16rpx;
|
|
|
- padding: 40rpx;
|
|
|
- margin-bottom: 20rpx;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- border: 2rpx solid transparent;
|
|
|
- transition: all 0.3s;
|
|
|
-}
|
|
|
-
|
|
|
-.role-card.active {
|
|
|
- border-color: #4CAF50;
|
|
|
- background: #f8fdf8;
|
|
|
-}
|
|
|
-
|
|
|
-.role-icon {
|
|
|
- font-size: 80rpx;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.role-name {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.role-desc {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-next {
|
|
|
- background: #4CAF50;
|
|
|
- color: #fff;
|
|
|
- border-radius: 40rpx;
|
|
|
- margin-top: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-/* 表单 */
|
|
|
-.form-section {
|
|
|
- background: #fff;
|
|
|
- border-radius: 16rpx;
|
|
|
- padding: 30rpx;
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.form-section-title {
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 20rpx;
|
|
|
- padding-bottom: 16rpx;
|
|
|
- border-bottom: 1rpx solid #f0f0f0;
|
|
|
-}
|
|
|
-
|
|
|
-.form-item {
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.label {
|
|
|
- display: block;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.required {
|
|
|
- color: #f44336;
|
|
|
-}
|
|
|
-
|
|
|
-.input, .picker-input {
|
|
|
- padding: 20rpx;
|
|
|
- background: #f5f5f5;
|
|
|
- border-radius: 12rpx;
|
|
|
- font-size: 28rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.role-select {
|
|
|
- display: flex;
|
|
|
- gap: 40rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.role-option {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-/* 头像上传 */
|
|
|
-.avatar-upload {
|
|
|
- width: 150rpx;
|
|
|
- height: 150rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.avatar-preview {
|
|
|
- width: 150rpx;
|
|
|
- height: 150rpx;
|
|
|
- border-radius: 50%;
|
|
|
-}
|
|
|
-
|
|
|
-.avatar-placeholder {
|
|
|
- width: 150rpx;
|
|
|
- height: 150rpx;
|
|
|
- background: #f5f5f5;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 60rpx;
|
|
|
- color: #ccc;
|
|
|
-}
|
|
|
-
|
|
|
-/* 照片上传 */
|
|
|
-.photo-upload {
|
|
|
- width: 300rpx;
|
|
|
- height: 200rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.photo-preview {
|
|
|
- width: 300rpx;
|
|
|
- height: 200rpx;
|
|
|
- border-radius: 12rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.photo-placeholder {
|
|
|
- width: 300rpx;
|
|
|
- height: 200rpx;
|
|
|
- background: #f5f5f5;
|
|
|
- border-radius: 12rpx;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.plus-icon {
|
|
|
- font-size: 60rpx;
|
|
|
- color: #ccc;
|
|
|
-}
|
|
|
-
|
|
|
-.placeholder-text {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #999;
|
|
|
- margin-top: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-/* AI助手选择 */
|
|
|
-.mascot-cards {
|
|
|
- display: flex;
|
|
|
- gap: 20rpx;
|
|
|
- margin-top: 16rpx;
|
|
|
-}
|
|
|
-.mascot-card {
|
|
|
- flex: 1;
|
|
|
- background: #f8f8f8;
|
|
|
- border-radius: 16rpx;
|
|
|
- padding: 24rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 16rpx;
|
|
|
- border: 2rpx solid transparent;
|
|
|
- transition: all 0.3s;
|
|
|
-}
|
|
|
-.mascot-card.active {
|
|
|
- border-color: #4CAF50;
|
|
|
- background: #f0fdf4;
|
|
|
-}
|
|
|
-.mascot-card-icon {
|
|
|
- width: 72rpx;
|
|
|
- height: 72rpx;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 36rpx;
|
|
|
- flex-shrink: 0;
|
|
|
-}
|
|
|
-.xibao-bg {
|
|
|
- background: linear-gradient(135deg, #FFF0DB, #FFD6A5);
|
|
|
-}
|
|
|
-.fubao-bg {
|
|
|
- background: linear-gradient(135deg, #FFF8E1, #FCD34D);
|
|
|
-}
|
|
|
-.mascot-card-info {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 4rpx;
|
|
|
-}
|
|
|
-.mascot-card-name {
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-.mascot-card-desc {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-/* 家庭绑定 */
|
|
|
-.family-section {
|
|
|
- background: #fff;
|
|
|
- border-radius: 16rpx;
|
|
|
- padding: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section-title {
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.family-actions {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 20rpx;
|
|
|
- margin-top: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.btn {
|
|
|
- border-radius: 40rpx;
|
|
|
- font-size: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-primary {
|
|
|
- background: #4CAF50;
|
|
|
- color: #fff;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-skip {
|
|
|
- background: #f5f5f5;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-/* ===== 区域选择弹窗 ===== */
|
|
|
-.region-mask {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: rgba(0, 0, 0, 0.5);
|
|
|
- z-index: 999;
|
|
|
- display: flex;
|
|
|
- align-items: flex-end;
|
|
|
-}
|
|
|
-.region-picker {
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx 20rpx 0 0;
|
|
|
- width: 100%;
|
|
|
- max-height: 70vh;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
-}
|
|
|
-.region-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: 24rpx 30rpx;
|
|
|
- border-bottom: 1rpx solid #f0f0f0;
|
|
|
-}
|
|
|
-.region-header-btn {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-.region-confirm {
|
|
|
- color: #F97316;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-.region-header-title {
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-.region-breadcrumb {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 20rpx 30rpx;
|
|
|
- border-bottom: 1rpx solid #f5f5f5;
|
|
|
-}
|
|
|
-.crumb-item {
|
|
|
- font-size: 26rpx;
|
|
|
- color: #999;
|
|
|
- padding: 8rpx 12rpx;
|
|
|
- border-radius: 8rpx;
|
|
|
-}
|
|
|
-.crumb-item.crumb-active {
|
|
|
- color: #F97316;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-.crumb-arrow {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #ccc;
|
|
|
- margin: 0 8rpx;
|
|
|
-}
|
|
|
-.region-list {
|
|
|
- max-height: 50vh;
|
|
|
- overflow-y: auto;
|
|
|
-}
|
|
|
-.region-item {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: 28rpx 30rpx;
|
|
|
- border-bottom: 1rpx solid #f5f5f5;
|
|
|
-}
|
|
|
-.region-item:active {
|
|
|
- background: #FFF7ED;
|
|
|
-}
|
|
|
-.region-item-text {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-.region-item-check {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #F97316;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-.region-item-selected {
|
|
|
- background: #FFF7ED;
|
|
|
-}
|
|
|
-.region-item-selected .region-item-text {
|
|
|
- color: #F97316;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-.region-loading {
|
|
|
- padding: 40rpx;
|
|
|
- text-align: center;
|
|
|
- font-size: 26rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-/* ===== 地址行(所在地区) ===== */
|
|
|
-.address-row {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
-}
|
|
|
-.address-value-wrap {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- flex: 1;
|
|
|
- justify-content: flex-end;
|
|
|
-}
|
|
|
-.address-value {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-.address-placeholder {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-.address-arrow {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #ccc;
|
|
|
- margin-left: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-/* AI助手锁定提示 */
|
|
|
-.mascot-locked-hint {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 28rpx 32rpx;
|
|
|
- background: linear-gradient(135deg, #FEF3C7, #FDE68A);
|
|
|
- border-radius: 20rpx;
|
|
|
- margin-top: 16rpx;
|
|
|
-}
|
|
|
-.locked-icon {
|
|
|
- font-size: 36rpx;
|
|
|
- margin-right: 16rpx;
|
|
|
-}
|
|
|
-.locked-text {
|
|
|
- flex: 1;
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #92400e;
|
|
|
-}
|
|
|
-.locked-arrow {
|
|
|
- font-size: 32rpx;
|
|
|
- color: #92400e;
|
|
|
- margin-left: 8rpx;
|
|
|
-}
|
|
|
-</style>
|
|
|
+ uni.showToast({ title: '保存成功', icon: 'success' })
|
|
|
+ setTimeout(() => {
|
|
|
+ if (this.isNewUserFlow) {
|
|
|
+ this.navigateToHome()
|
|
|
+ } else {
|
|
|
+ uni.navigateBack()
|
|
|
+ }
|
|
|
+ }, 1500)
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '保存失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async submitTeacherInfo() {
|
|
|
+ // 验证必填字段
|
|
|
+ if (!this.form.nickname) {
|
|
|
+ uni.showToast({ title: '请输入昵称', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!this.form.gender) {
|
|
|
+ uni.showToast({ title: '请选择性别', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!this.form.idCard) {
|
|
|
+ uni.showToast({ title: '请输入身份证号', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!this.form.teacherNo) {
|
|
|
+ uni.showToast({ title: '请输入成长规划师证书号', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!this.form.teacherPhoto) {
|
|
|
+ uni.showToast({ title: '请上传成长规划师照片', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!this.form.certificateImage) {
|
|
|
+ uni.showToast({ title: '请上传成长规划师证书', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ await updateUserInfo({
|
|
|
+ ...this.form,
|
|
|
+ roles: this.form.role
|
|
|
+ })
|
|
|
+ uni.showToast({ title: '提交成功,请等待审核' })
|
|
|
+ setTimeout(() => {
|
|
|
+ uni.switchTab({ url: '/pages/index-home/index' })
|
|
|
+ }, 1500)
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '提交失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async joinFamilyAction() {
|
|
|
+ if (!this.form.inviteCode) {
|
|
|
+ uni.showToast({ title: '请输入邀请码', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.form.nickname) {
|
|
|
+ uni.showToast({ title: '请输入昵称', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ let userId = this.userId
|
|
|
+
|
|
|
+ // 测试环境:没有token,需要先注册用户
|
|
|
+ if (!this.token && this.form.phone) {
|
|
|
+ const regRes = await directRegister({
|
|
|
+ phone: this.form.phone,
|
|
|
+ nickname: this.form.nickname,
|
|
|
+ avatar: this.form.avatar,
|
|
|
+ role: this.form.role,
|
|
|
+ mascot: this.form.mascot,
|
|
|
+ birthday: this.form.birthday,
|
|
|
+ gender: this.form.gender,
|
|
|
+ inviteCode: this.form.inviteCode
|
|
|
+ })
|
|
|
+ userId = regRes.data.userId
|
|
|
+ // 保存登录信息
|
|
|
+ uni.setStorageSync('token', regRes.data.token)
|
|
|
+ uni.setStorageSync('userId', userId)
|
|
|
+ uni.setStorageSync('role', regRes.data.role)
|
|
|
+ // 邀请码注册不写入 familyId(加入需管理员审批)
|
|
|
+ } else {
|
|
|
+ await updateUserInfo(this.form)
|
|
|
+ await requestJoinByCode(this.form.inviteCode)
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showToast({ title: '申请已提交,等待管理员审批' })
|
|
|
+ setTimeout(() => {
|
|
|
+ this.navigateToHome()
|
|
|
+ }, 1500)
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: e.message || '加入失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async createFamilyAction() {
|
|
|
+ if (!this.form.nickname) {
|
|
|
+ uni.showToast({ title: '请输入昵称', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ let userId = this.userId
|
|
|
+
|
|
|
+ // 测试环境:没有token,需要先注册用户
|
|
|
+ if (!this.token && this.form.phone) {
|
|
|
+ const regRes = await directRegister({
|
|
|
+ phone: this.form.phone,
|
|
|
+ nickname: this.form.nickname,
|
|
|
+ avatar: this.form.avatar,
|
|
|
+ role: this.form.role,
|
|
|
+ mascot: this.form.mascot,
|
|
|
+ birthday: this.form.birthday,
|
|
|
+ gender: this.form.gender
|
|
|
+ })
|
|
|
+ userId = regRes.data.userId
|
|
|
+ // 保存登录信息
|
|
|
+ uni.setStorageSync('token', regRes.data.token)
|
|
|
+ uni.setStorageSync('userId', userId)
|
|
|
+ uni.setStorageSync('role', regRes.data.role)
|
|
|
+ // 注册不写入 familyId(后端根据 userId 获取)
|
|
|
+ } else {
|
|
|
+ await updateUserInfo(this.form)
|
|
|
+ const famRes = await createFamily(this.form.nickname + '的家庭')
|
|
|
+ // 同步 familyId 到 store(hasFamily getter 依赖)
|
|
|
+ if (famRes && famRes.data) {
|
|
|
+ this.$store.commit('setFamilyId', famRes.data)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showToast({ title: '创建成功' })
|
|
|
+ setTimeout(() => {
|
|
|
+ this.navigateToHome()
|
|
|
+ }, 1500)
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: e.message || '创建失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ skip() {
|
|
|
+ this.navigateToHome()
|
|
|
+ },
|
|
|
+ navigateToHome() {
|
|
|
+ // 保存 mascot 到 userInfo 存储,供聊天页读取
|
|
|
+ if (this.form.mascot) {
|
|
|
+ try {
|
|
|
+ const userInfo = uni.getStorageSync('userInfo') || {}
|
|
|
+ userInfo.mascot = this.form.mascot
|
|
|
+ uni.setStorageSync('userInfo', userInfo)
|
|
|
+ } catch (e) {
|
|
|
+ console.error('保存 mascot 失败', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 用 switchTab 跳转带底部 TabBar 的首页(行/身/智/心/富)
|
|
|
+ // parent-index/child-index 是分包页面(非 TabBar 页),用 navigateTo 会丢失底部 TabBar
|
|
|
+ uni.switchTab({ url: '/pages/index-home/index' })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.user-edit-container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: #f5f5f5;
|
|
|
+ padding: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.header {
|
|
|
+ margin-bottom: 40rpx;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.title {
|
|
|
+ font-size: 40rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.subtitle {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+/* 角色选择 */
|
|
|
+.role-select-section {
|
|
|
+ margin-bottom: 40rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.role-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 40rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ border: 2rpx solid transparent;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.role-card.active {
|
|
|
+ border-color: #4CAF50;
|
|
|
+ background: #f8fdf8;
|
|
|
+}
|
|
|
+
|
|
|
+.role-icon {
|
|
|
+ font-size: 80rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.role-name {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.role-desc {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-next {
|
|
|
+ background: #4CAF50;
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ margin-top: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表单 */
|
|
|
+.form-section {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 30rpx;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-section-title {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ padding-bottom: 16rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.form-item {
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.label {
|
|
|
+ display: block;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.required {
|
|
|
+ color: #f44336;
|
|
|
+}
|
|
|
+
|
|
|
+.input, .picker-input {
|
|
|
+ padding: 20rpx;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.role-select {
|
|
|
+ display: flex;
|
|
|
+ gap: 40rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.role-option {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 头像上传 */
|
|
|
+.avatar-upload {
|
|
|
+ width: 150rpx;
|
|
|
+ height: 150rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-preview {
|
|
|
+ width: 150rpx;
|
|
|
+ height: 150rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-placeholder {
|
|
|
+ width: 150rpx;
|
|
|
+ height: 150rpx;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 60rpx;
|
|
|
+ color: #ccc;
|
|
|
+}
|
|
|
+
|
|
|
+/* 照片上传 */
|
|
|
+.photo-upload {
|
|
|
+ width: 300rpx;
|
|
|
+ height: 200rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-preview {
|
|
|
+ width: 300rpx;
|
|
|
+ height: 200rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-placeholder {
|
|
|
+ width: 300rpx;
|
|
|
+ height: 200rpx;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.plus-icon {
|
|
|
+ font-size: 60rpx;
|
|
|
+ color: #ccc;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 助手选择 */
|
|
|
+.mascot-cards {
|
|
|
+ display: flex;
|
|
|
+ gap: 20rpx;
|
|
|
+ margin-top: 16rpx;
|
|
|
+}
|
|
|
+.mascot-card {
|
|
|
+ flex: 1;
|
|
|
+ background: #f8f8f8;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 24rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16rpx;
|
|
|
+ border: 2rpx solid transparent;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+.mascot-card.active {
|
|
|
+ border-color: #4CAF50;
|
|
|
+ background: #f0fdf4;
|
|
|
+}
|
|
|
+.mascot-card-icon {
|
|
|
+ width: 72rpx;
|
|
|
+ height: 72rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 36rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.xibao-bg {
|
|
|
+ background: linear-gradient(135deg, #FFF0DB, #FFD6A5);
|
|
|
+}
|
|
|
+.fubao-bg {
|
|
|
+ background: linear-gradient(135deg, #FFF8E1, #FCD34D);
|
|
|
+}
|
|
|
+.mascot-card-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4rpx;
|
|
|
+}
|
|
|
+.mascot-card-name {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+.mascot-card-desc {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+/* 家庭绑定 */
|
|
|
+.family-section {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.family-actions {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20rpx;
|
|
|
+ margin-top: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.btn {
|
|
|
+ border-radius: 40rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-primary {
|
|
|
+ background: #4CAF50;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-skip {
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 区域选择弹窗 ===== */
|
|
|
+.region-mask {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ z-index: 999;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+}
|
|
|
+.region-picker {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 20rpx 20rpx 0 0;
|
|
|
+ width: 100%;
|
|
|
+ max-height: 70vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+.region-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 24rpx 30rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+}
|
|
|
+.region-header-btn {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.region-confirm {
|
|
|
+ color: #F97316;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.region-header-title {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+.region-breadcrumb {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
+ border-bottom: 1rpx solid #f5f5f5;
|
|
|
+}
|
|
|
+.crumb-item {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #999;
|
|
|
+ padding: 8rpx 12rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+.crumb-item.crumb-active {
|
|
|
+ color: #F97316;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.crumb-arrow {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #ccc;
|
|
|
+ margin: 0 8rpx;
|
|
|
+}
|
|
|
+.region-list {
|
|
|
+ max-height: 50vh;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+.region-item {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 28rpx 30rpx;
|
|
|
+ border-bottom: 1rpx solid #f5f5f5;
|
|
|
+}
|
|
|
+.region-item:active {
|
|
|
+ background: #FFF7ED;
|
|
|
+}
|
|
|
+.region-item-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+.region-item-check {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #F97316;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.region-item-selected {
|
|
|
+ background: #FFF7ED;
|
|
|
+}
|
|
|
+.region-item-selected .region-item-text {
|
|
|
+ color: #F97316;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.region-loading {
|
|
|
+ padding: 40rpx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 地址行(所在地区) ===== */
|
|
|
+.address-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.address-value-wrap {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex: 1;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+.address-value {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+.address-placeholder {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.address-arrow {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #ccc;
|
|
|
+ margin-left: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 助手锁定提示 */
|
|
|
+.mascot-locked-hint {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 28rpx 32rpx;
|
|
|
+ background: linear-gradient(135deg, #FEF3C7, #FDE68A);
|
|
|
+ border-radius: 20rpx;
|
|
|
+ margin-top: 16rpx;
|
|
|
+}
|
|
|
+.locked-icon {
|
|
|
+ font-size: 36rpx;
|
|
|
+ margin-right: 16rpx;
|
|
|
+}
|
|
|
+.locked-text {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #92400e;
|
|
|
+}
|
|
|
+.locked-arrow {
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #92400e;
|
|
|
+ margin-left: 8rpx;
|
|
|
+}
|
|
|
+</style>
|