|
|
@@ -140,11 +140,21 @@
|
|
|
</picker>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 地址选择 -->
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">所在地区 <text class="required" v-if="isNewUserFlow">*</text></text>
|
|
|
- <AddressPicker ref="addressPicker" />
|
|
|
+ <!-- 所在地区(省市区三级联动,与订单收货人地址一致) -->
|
|
|
+ <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">
|
|
|
@@ -247,17 +257,48 @@
|
|
|
<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 } from '../../utils/api.js'
|
|
|
-import AddressPicker from '../../components/address-picker.vue'
|
|
|
+import config from '@/config.js'
|
|
|
|
|
|
export default {
|
|
|
- components: {
|
|
|
- AddressPicker
|
|
|
- },
|
|
|
data() {
|
|
|
return {
|
|
|
isEdit: false,
|
|
|
@@ -285,8 +326,21 @@ export default {
|
|
|
hobbies: '',
|
|
|
dietPreferences: '',
|
|
|
address: '',
|
|
|
- addressId: null
|
|
|
+ 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)'],
|
|
|
@@ -318,6 +372,19 @@ export default {
|
|
|
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) {
|
|
|
@@ -430,10 +497,143 @@ export default {
|
|
|
onMaritalChange(e) {
|
|
|
this.form.maritalStatus = this.maritalOptions[e.detail.value]
|
|
|
},
|
|
|
- selectMascot(code) {
|
|
|
- this.form.mascot = code
|
|
|
- },
|
|
|
- // 加载已有用户信息
|
|
|
+ selectMascot(code) {
|
|
|
+ this.form.mascot = code
|
|
|
+ },
|
|
|
+ // ===== 省市区三级联动弹窗(与订单收货人地址一致) =====
|
|
|
+ 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()
|
|
|
@@ -454,9 +654,17 @@ export default {
|
|
|
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.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)
|
|
|
}
|
|
|
@@ -473,29 +681,31 @@ export default {
|
|
|
this.form.nickname = phone ? '用户' + phone.substring(phone.length - 4) : '微信用户'
|
|
|
}
|
|
|
|
|
|
- // 从 AddressPicker 读取地址文本
|
|
|
- if (this.$refs.addressPicker && this.$refs.addressPicker.fullAddress) {
|
|
|
- this.form.address = this.$refs.addressPicker.fullAddress
|
|
|
- }
|
|
|
-
|
|
|
- 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
|
|
|
- })
|
|
|
+ // 从省市区街道字段同步完整地址文本
|
|
|
+ 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) {
|
|
|
@@ -935,4 +1145,128 @@ export default {
|
|
|
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;
|
|
|
+}
|
|
|
</style>
|