Files
im-uniapp/components/hrm/GlobalCityPicker.vue

173 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view v-if="visible" class="picker-mask" @tap="close">
<view class="picker-sheet" @tap.stop>
<view class="picker-header">
<text class="picker-title">选择城市</text>
<text class="picker-close" @tap="close">关闭</text>
</view>
<view class="tip-box">
<text class="tip-main">仅精确到城市即可</text>
<text class="tip-sub">如果列表里没有想要的城市可以先在上方补录后再选择</text>
</view>
<view class="add-box">
<input v-model="newCountryName" class="add-input" type="text" placeholder="补录国家" />
<input v-model="newCityName" class="add-input" type="text" placeholder="补录城市" />
<button class="add-btn" :loading="adding" @tap="addNewCity">补录并新增</button>
</view>
<view class="city-path">{{ currentCountry && currentCity ? (currentCountry + ' / ' + currentCity) : '请选择城市' }}</view>
<view class="selector-wrap">
<picker-view class="picker-view" :value="pickerValue" @change="onPickerChange">
<picker-view-column>
<view v-for="item in countryOptions" :key="item.name" class="picker-item">{{ item.name }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="item in currentCities" :key="item.cityId || item.cityName" class="picker-item">{{ item.cityName }}</view>
</picker-view-column>
</picker-view>
</view>
<view class="picker-footer">
<button class="btn ghost" @tap="close">取消</button>
<button class="btn primary" :disabled="!currentCity" @tap="confirm">确定</button>
</view>
</view>
</view>
</template>
<script>
import { listCities, addCity } from '@/api/fad/city'
export default {
props: { visible: Boolean, value: String },
emits: ['update:visible', 'change'],
data() {
return {
loading: false,
adding: false,
countryOptions: [],
cityMap: {},
pickerValue: [0, 0],
currentCountry: '',
currentCity: '',
newCountryName: '',
newCityName: ''
}
},
computed: {
currentCities() {
return this.cityMap[this.currentCountry] || []
}
},
watch: {
visible(v) {
if (v) this.initValue()
}
},
methods: {
async initValue() {
this.currentCountry = ''
this.currentCity = ''
this.newCountryName = ''
this.newCityName = ''
await this.loadOptions()
this.applyDefaultValue()
},
async loadOptions() {
this.loading = true
try {
const res = await listCities({ pageNum: 1, pageSize: 1000 })
const rows = res && (res.rows || (res.data && res.data.rows) || res.data || [])
const map = {}
const countries = []
rows.forEach(item => {
const country = item.countryName || ''
const city = item.cityName || ''
if (!country || !city) return
if (!map[country]) {
map[country] = []
countries.push({ name: country })
}
map[country].push(item)
})
this.countryOptions = countries
this.cityMap = map
} catch (e) {
uni.showToast({ title: e.message || '加载城市失败', icon: 'none' })
} finally {
this.loading = false
}
},
applyDefaultValue() {
const value = String(this.value || '')
for (let i = 0; i < this.countryOptions.length; i++) {
const country = this.countryOptions[i].name
const cityList = this.cityMap[country] || []
const j = cityList.findIndex(item => item.cityName === value)
if (j >= 0) {
this.pickerValue = [i, j]
this.currentCountry = country
this.currentCity = cityList[j].cityName
return
}
}
if (this.countryOptions.length) {
this.currentCountry = this.countryOptions[0].name
this.currentCity = (this.cityMap[this.currentCountry] || [])[0]?.cityName || ''
this.pickerValue = [0, 0]
}
},
close() {
this.$emit('update:visible', false)
},
onPickerChange(e) {
const value = (e.detail.value || []).map(v => Number(v))
const countryIndex = value[0] || 0
const country = this.countryOptions[countryIndex]?.name || ''
const cities = this.cityMap[country] || []
const cityIndex = Math.min(value[1] || 0, Math.max(cities.length - 1, 0))
this.pickerValue = [countryIndex, cityIndex]
this.currentCountry = country
this.currentCity = cities[cityIndex]?.cityName || ''
},
async addNewCity() {
const countryName = String(this.newCountryName || '').trim()
const cityName = String(this.newCityName || '').trim()
if (!countryName || !cityName) {
uni.showToast({ title: '请填写国家和城市', icon: 'none' })
return
}
this.adding = true
try {
await addCity({ countryName, cityName, status: 1, remark: '补录城市' })
uni.showToast({ title: '补录成功', icon: 'success' })
this.newCountryName = ''
this.newCityName = ''
await this.loadOptions()
this.currentCountry = countryName
this.currentCity = cityName
const countryIndex = this.countryOptions.findIndex(item => item.name === countryName)
const cityIndex = (this.cityMap[countryName] || []).findIndex(item => item.cityName === cityName)
this.pickerValue = [Math.max(countryIndex, 0), Math.max(cityIndex, 0)]
} catch (e) {
uni.showToast({ title: e.message || '补录失败', icon: 'none' })
} finally {
this.adding = false
}
},
confirm() {
if (!this.currentCity) return
this.$emit('change', this.currentCity)
this.close()
}
}
}
</script>
<style scoped>
.picker-mask{position:fixed;inset:0;background:rgba(15,23,42,.45);z-index:1000;display:flex;align-items:flex-end}.picker-sheet{width:100%;background:#fff;border-radius:24rpx 24rpx 0 0;padding:20rpx;box-sizing:border-box}.picker-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}.picker-title{font-size:30rpx;font-weight:800;color:#111827}.picker-close{font-size:26rpx;color:#1677ff}.tip-box{padding:12rpx 16rpx;border-radius:16rpx;background:#eff6ff;margin-bottom:12rpx}.tip-main{display:block;font-size:26rpx;font-weight:700;color:#1d4ed8}.tip-sub{display:block;margin-top:6rpx;font-size:22rpx;color:#1e40af;line-height:1.5}.add-box{display:flex;gap:10rpx;flex-wrap:wrap;margin-bottom:12rpx}.add-input{flex:1;min-width:240rpx;height:72rpx;padding:0 16rpx;border:1px solid #e5e7eb;border-radius:16rpx;background:#fff}.add-btn{width:100%;height:72rpx;line-height:72rpx;border-radius:16rpx;background:#10b981;color:#fff;font-size:26rpx}.city-path{padding:12rpx 16rpx;margin-bottom:12rpx;border-radius:16rpx;background:#f8fafc;color:#334155;font-size:24rpx;line-height:1.5}.selector-wrap{height:420rpx;border:1px solid #eef2f7;border-radius:16rpx;overflow:hidden}.picker-view{height:100%}.picker-item{height:84rpx;line-height:84rpx;text-align:center;font-size:28rpx;color:#111827}.picker-footer{display:flex;gap:12rpx;margin-top:16rpx}.btn{flex:1;border-radius:16rpx}.btn.ghost{background:#f3f4f6;color:#334155}.btn.primary{background:linear-gradient(135deg,#1677ff,#4f9dff);color:#fff}.btn.primary[disabled]{background:#9cc2ff;color:#fff}
</style>