|
|
@@ -25,12 +25,12 @@
|
|
|
/>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 省市区街道 (4级地址选择) -->
|
|
|
- <view class="form-row address-row" @click="showAddressPicker">
|
|
|
+ <!-- 区域选择 (省市区三级联动) -->
|
|
|
+ <view class="form-row address-row" @click="openRegionPicker">
|
|
|
<text class="form-label">所在地区</text>
|
|
|
<view class="address-value-wrap">
|
|
|
<text v-if="form.fullAddress" class="address-value">{{ form.fullAddress }}</text>
|
|
|
- <text v-else class="address-placeholder">请选择省市区街道</text>
|
|
|
+ <text v-else class="address-placeholder">请选择省市区</text>
|
|
|
<text class="address-arrow">›</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -57,6 +57,40 @@
|
|
|
</view>
|
|
|
</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 in currentRegionItems"
|
|
|
+ :key="item.id || item.code"
|
|
|
+ :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>
|
|
|
+
|
|
|
<!-- Save Button -->
|
|
|
<button
|
|
|
:class="['save-btn', saving ? 'disabled' : '']"
|
|
|
@@ -86,7 +120,17 @@ export default {
|
|
|
street: '',
|
|
|
isDefault: 0,
|
|
|
fullAddress: ''
|
|
|
- }
|
|
|
+ },
|
|
|
+ // 区域选择弹窗
|
|
|
+ showRegionPicker: false,
|
|
|
+ currentRegionLevel: 0, // 0=province, 1=city, 2=district
|
|
|
+ regionLoading: false,
|
|
|
+ provinceList: [],
|
|
|
+ cityList: [],
|
|
|
+ districtList: [],
|
|
|
+ tempProvince: null,
|
|
|
+ tempCity: null,
|
|
|
+ tempDistrict: null
|
|
|
}
|
|
|
},
|
|
|
onLoad(options) {
|
|
|
@@ -98,6 +142,21 @@ export default {
|
|
|
uni.setNavigationBarTitle({ title: '新增地址' })
|
|
|
}
|
|
|
},
|
|
|
+ computed: {
|
|
|
+ regionBreadcrumb() {
|
|
|
+ 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() {
|
|
|
+ if (this.currentRegionLevel === 0) return this.provinceList
|
|
|
+ if (this.currentRegionLevel === 1) return this.cityList
|
|
|
+ if (this.currentRegionLevel === 2) return this.districtList
|
|
|
+ return []
|
|
|
+ }
|
|
|
+ },
|
|
|
methods: {
|
|
|
loadAddress() {
|
|
|
var that = this
|
|
|
@@ -124,11 +183,18 @@ export default {
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- showAddressPicker() {
|
|
|
- // Use multi-column picker for region selection
|
|
|
+ // ===== 区域选择弹窗 =====
|
|
|
+ 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 that = this
|
|
|
- // Load provinces first
|
|
|
- uni.showLoading({ title: '加载地址...' })
|
|
|
+ that.regionLoading = true
|
|
|
uni.request({
|
|
|
url: config.api('/api/region/provinces'),
|
|
|
method: 'POST',
|
|
|
@@ -137,107 +203,115 @@ export default {
|
|
|
'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
},
|
|
|
success: function(res) {
|
|
|
- uni.hideLoading()
|
|
|
+ that.regionLoading = false
|
|
|
if (res.data && res.data.code === 200) {
|
|
|
- var provinces = res.data.data || []
|
|
|
- var provinceList = provinces.map(function(p) { return p.name || p.province })
|
|
|
- uni.showActionSheet({
|
|
|
- itemList: provinceList,
|
|
|
- success: function(pRes) {
|
|
|
- var selectedProvince = provinces[pRes.tapIndex]
|
|
|
- that.form.province = selectedProvince.name || selectedProvince.province
|
|
|
- var provinceId = selectedProvince.id || selectedProvince.code
|
|
|
- that.loadCities(provinceId)
|
|
|
- }
|
|
|
- })
|
|
|
- } else {
|
|
|
- // Fallback: simple text input for full address
|
|
|
- that.showSimpleAddressInput()
|
|
|
+ that.provinceList = res.data.data || []
|
|
|
}
|
|
|
},
|
|
|
fail: function() {
|
|
|
- uni.hideLoading()
|
|
|
- that.showSimpleAddressInput()
|
|
|
+ that.regionLoading = false
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- loadCities(provinceId) {
|
|
|
+ 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()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ loadRegionCities(parentId) {
|
|
|
+ if (!parentId) return
|
|
|
var that = this
|
|
|
- uni.showLoading({ title: '加载城市...' })
|
|
|
+ that.regionLoading = true
|
|
|
uni.request({
|
|
|
url: config.api('/api/region/cities'),
|
|
|
method: 'POST',
|
|
|
- data: { parentId: provinceId },
|
|
|
+ data: { parentId: parentId },
|
|
|
header: {
|
|
|
'Content-Type': 'application/json',
|
|
|
'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
},
|
|
|
success: function(res) {
|
|
|
- uni.hideLoading()
|
|
|
+ that.regionLoading = false
|
|
|
if (res.data && res.data.code === 200) {
|
|
|
- var cities = res.data.data || []
|
|
|
- var cityList = cities.map(function(c) { return c.name || c.city })
|
|
|
- uni.showActionSheet({
|
|
|
- itemList: cityList,
|
|
|
- success: function(cRes) {
|
|
|
- var selectedCity = cities[cRes.tapIndex]
|
|
|
- that.form.city = selectedCity.name || selectedCity.city
|
|
|
- var cityId = selectedCity.id || selectedCity.code
|
|
|
- that.loadDistricts(cityId)
|
|
|
- }
|
|
|
- })
|
|
|
+ that.cityList = res.data.data || []
|
|
|
}
|
|
|
},
|
|
|
fail: function() {
|
|
|
- uni.hideLoading()
|
|
|
+ that.regionLoading = false
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- loadDistricts(cityId) {
|
|
|
+ loadRegionDistricts(parentId) {
|
|
|
+ if (!parentId) return
|
|
|
var that = this
|
|
|
- uni.showLoading({ title: '加载区县...' })
|
|
|
+ that.regionLoading = true
|
|
|
uni.request({
|
|
|
url: config.api('/api/region/districts'),
|
|
|
method: 'POST',
|
|
|
- data: { parentId: cityId },
|
|
|
+ data: { parentId: parentId },
|
|
|
header: {
|
|
|
'Content-Type': 'application/json',
|
|
|
'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
|
|
},
|
|
|
success: function(res) {
|
|
|
- uni.hideLoading()
|
|
|
+ that.regionLoading = false
|
|
|
if (res.data && res.data.code === 200) {
|
|
|
- var districts = res.data.data || []
|
|
|
- var districtList = districts.map(function(d) { return d.name || d.district })
|
|
|
- uni.showActionSheet({
|
|
|
- itemList: districtList,
|
|
|
- success: function(dRes) {
|
|
|
- var selectedDistrict = districts[dRes.tapIndex]
|
|
|
- that.form.district = selectedDistrict.name || selectedDistrict.district
|
|
|
- that.updateFullAddress()
|
|
|
- }
|
|
|
- })
|
|
|
+ that.districtList = res.data.data || []
|
|
|
}
|
|
|
},
|
|
|
fail: function() {
|
|
|
- uni.hideLoading()
|
|
|
+ that.regionLoading = false
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- showSimpleAddressInput() {
|
|
|
- var that = this
|
|
|
- uni.showModal({
|
|
|
- title: '请输入地址',
|
|
|
- content: '请输入省市区街道信息',
|
|
|
- editable: true,
|
|
|
- placeholderText: '如: 北京市朝阳区XX街道',
|
|
|
- success: function(res) {
|
|
|
- if (res.confirm && res.content) {
|
|
|
- that.form.fullAddress = res.content
|
|
|
- that.form.province = res.content
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
+ 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.fullAddress = (this.form.province || '') + (this.form.city || '') + (this.form.district || '') + (this.form.street || '')
|
|
|
@@ -413,4 +487,102 @@ export default {
|
|
|
background: #ddd;
|
|
|
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;
|
|
|
+}
|
|
|
</style>
|