特易招,招聘小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

469 lines
11 KiB

4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
  1. <template>
  2. <uv-popup ref="popup" mode="bottom" :round="30"
  3. :safeAreaInsetBottom="false" @close="handleClose">
  4. <view class="address-picker">
  5. <view class="header">
  6. <view class="title">选择地址</view>
  7. <view class="close-btn" @click="close">
  8. <uv-icon name="close" size="40rpx"></uv-icon>
  9. </view>
  10. </view>
  11. <view class="content">
  12. <!-- 左侧一级地址列表 -->
  13. <view class="left-panel">
  14. <scroll-view scroll-y class="scroll-view">
  15. <view
  16. class="address-item"
  17. :class="{ active: selectedProvince && selectedProvince.id === item.id }"
  18. v-for="item in addressList"
  19. :key="item.id"
  20. @click="selectProvince(item)">
  21. {{ item.adress }}
  22. </view>
  23. </scroll-view>
  24. </view>
  25. <!-- 右侧二三级地址列表 -->
  26. <view class="right-panel">
  27. <scroll-view scroll-y class="scroll-view">
  28. <!-- 二级地址 -->
  29. <template v-if="selectedProvince && !selectedCity">
  30. <!-- 选择整个省份选项 -->
  31. <view
  32. v-if="showSelectWholeCity"
  33. class="address-item whole-city-item"
  34. @click="selectWholeProvince">
  35. <uv-icon name="checkmark-circle" size="30rpx" color="#3796F8"></uv-icon>
  36. 选择整个{{ selectedProvince.adress }}
  37. </view>
  38. <view
  39. class="address-item"
  40. :class="{
  41. 'selected': multiple && isCitySelected(item),
  42. 'active': !multiple && selectedCity && selectedCity.id === item.id
  43. }"
  44. v-for="item in cityList"
  45. :key="item.id"
  46. @click="selectCity(item)">
  47. {{ item.adress }}
  48. <uv-icon v-if="multiple && isCitySelected(item)"
  49. name="checkmark-circle" size="30rpx" color="#3796F8"></uv-icon>
  50. </view>
  51. <!-- 多选时的确认按钮 -->
  52. <view v-if="multiple && selectedCities.length > 0"
  53. class="confirm-btn" @click="confirmMultipleCitySelection">
  54. <button class="confirm-button">确认选择城市 ({{ selectedCities.length }})</button>
  55. </view>
  56. </template>
  57. <!-- 三级地址 -->
  58. <template v-if="selectedCity">
  59. <view
  60. class="address-item back-item"
  61. @click="backToCity">
  62. <uv-icon name="arrow-left" size="30rpx"></uv-icon>
  63. 返回{{ selectedProvince.adress }}
  64. </view>
  65. <!-- 选择整个城市选项 -->
  66. <view
  67. v-if="showSelectWholeCity"
  68. class="address-item whole-city-item"
  69. @click="selectWholeCity">
  70. <uv-icon name="checkmark-circle" size="30rpx" color="#3796F8"></uv-icon>
  71. 选择整个{{ selectedCity.adress }}
  72. </view>
  73. <view
  74. class="address-item"
  75. :class="{
  76. 'selected': multiple && isDistrictSelected(item),
  77. 'active': !multiple && selectedDistrict && selectedDistrict.id === item.id
  78. }"
  79. v-for="item in districtList"
  80. :key="item.id"
  81. @click="selectDistrict(item)">
  82. {{ item.adress }}
  83. <uv-icon v-if="multiple && isDistrictSelected(item)"
  84. name="checkmark-circle" size="30rpx" color="#3796F8"></uv-icon>
  85. </view>
  86. <!-- 多选时的确认按钮 -->
  87. <view v-if="multiple && selectedDistricts.length > 0"
  88. class="confirm-btn" @click="confirmMultipleSelection">
  89. <button class="confirm-button">确认选择 ({{ selectedDistricts.length }})</button>
  90. </view>
  91. </template>
  92. </scroll-view>
  93. </view>
  94. </view>
  95. </view>
  96. </uv-popup>
  97. </template>
  98. <script>
  99. import { mapState } from 'vuex'
  100. export default {
  101. name: 'AddressPicker',
  102. props: {
  103. // 是否只选择到市级,不选择区县
  104. onlyCity: {
  105. type: Boolean,
  106. default: false
  107. },
  108. // 是否支持多选区县
  109. multiple: {
  110. type: Boolean,
  111. default: false
  112. },
  113. // 是否显示"选择整个城市"选项
  114. showSelectWholeCity: {
  115. type: Boolean,
  116. default: false
  117. }
  118. },
  119. data() {
  120. return {
  121. selectedProvince: null, // 选中的省份
  122. selectedCity: null, // 选中的城市
  123. selectedDistrict: null, // 选中的区县
  124. selectedCities: [], // 多选时选中的城市列表
  125. selectedDistricts: [], // 多选时选中的区县列表
  126. cityList: [], // 城市列表
  127. districtList: [], // 区县列表
  128. }
  129. },
  130. computed: {
  131. ...mapState(['addressList'])
  132. },
  133. methods: {
  134. // 打开弹窗
  135. open() {
  136. this.$refs.popup.open()
  137. },
  138. // 关闭弹窗
  139. close() {
  140. this.$refs.popup.close()
  141. },
  142. // 弹窗关闭时重置状态
  143. handleClose() {
  144. this.selectedProvince = null
  145. this.selectedCity = null
  146. this.selectedDistrict = null
  147. this.selectedCities = []
  148. this.selectedDistricts = []
  149. this.cityList = []
  150. this.districtList = []
  151. },
  152. // 选择省份
  153. async selectProvince(province) {
  154. this.selectedProvince = province
  155. this.selectedCity = null
  156. this.selectedDistrict = null
  157. this.districtList = []
  158. // 获取城市列表
  159. try {
  160. this.cityList = await this.$store.dispatch('getChildAddressList', province.id)
  161. // 如果没有下级城市,直接确认选择
  162. if (this.cityList.length === 0) {
  163. this.confirm()
  164. }
  165. } catch (error) {
  166. console.error('获取城市列表失败:', error)
  167. this.cityList = []
  168. // 获取失败时也直接确认
  169. this.confirm()
  170. }
  171. },
  172. // 选择城市
  173. async selectCity(city) {
  174. if (this.multiple) {
  175. // 多选模式
  176. const index = this.selectedCities.findIndex(item => item.id === city.id)
  177. if (index > -1) {
  178. // 取消选择
  179. this.selectedCities.splice(index, 1)
  180. } else {
  181. // 添加选择
  182. this.selectedCities.push(city)
  183. }
  184. } else {
  185. // 单选模式
  186. this.selectedCity = city
  187. this.selectedDistrict = null
  188. // 如果只选择到市级,直接确认
  189. if (this.onlyCity) {
  190. this.confirm()
  191. return
  192. }
  193. // 获取区县列表
  194. try {
  195. this.districtList = await this.$store.dispatch('getChildAddressList', city.id)
  196. // 如果没有下级地址,直接确认
  197. if (this.districtList.length === 0) {
  198. this.confirm()
  199. }
  200. } catch (error) {
  201. console.error('获取区县列表失败:', error)
  202. this.districtList = []
  203. // 获取失败时也直接确认
  204. this.confirm()
  205. }
  206. }
  207. },
  208. // 选择区县
  209. selectDistrict(district) {
  210. if (this.multiple) {
  211. // 多选模式
  212. const index = this.selectedDistricts.findIndex(item => item.id === district.id)
  213. if (index > -1) {
  214. // 取消选择
  215. this.selectedDistricts.splice(index, 1)
  216. } else {
  217. // 添加选择
  218. this.selectedDistricts.push(district)
  219. }
  220. } else {
  221. // 单选模式
  222. this.selectedDistrict = district
  223. this.confirm()
  224. }
  225. },
  226. // 检查城市是否被选中(多选模式)
  227. isCitySelected(city) {
  228. return this.selectedCities.some(item => item.id === city.id)
  229. },
  230. // 检查区县是否被选中(多选模式)
  231. isDistrictSelected(district) {
  232. return this.selectedDistricts.some(item => item.id === district.id)
  233. },
  234. // 选择整个省份
  235. selectWholeProvince() {
  236. this.selectedCity = null
  237. this.selectedCities = []
  238. this.selectedDistrict = null
  239. this.selectedDistricts = []
  240. this.confirm()
  241. },
  242. // 选择整个城市
  243. selectWholeCity() {
  244. this.selectedDistrict = null
  245. this.selectedDistricts = []
  246. this.confirm()
  247. },
  248. // 确认多选城市选择
  249. confirmMultipleCitySelection() {
  250. // 直接返回多选城市的数据
  251. this.confirm()
  252. },
  253. // 确认多选选择
  254. confirmMultipleSelection() {
  255. this.confirm()
  256. },
  257. // 返回城市选择
  258. backToCity() {
  259. this.selectedCity = null
  260. this.selectedDistrict = null
  261. this.districtList = []
  262. },
  263. // 确认选择
  264. confirm() {
  265. const result = {
  266. province: this.selectedProvince,
  267. city: this.selectedCity,
  268. district: this.selectedDistrict,
  269. cities: this.selectedCities,
  270. districts: this.selectedDistricts
  271. }
  272. // 生成完整地址文本
  273. let fullAddress = ''
  274. if (this.selectedProvince) {
  275. fullAddress += this.selectedProvince.adress
  276. }
  277. // 多选城市模式
  278. if (this.multiple && this.selectedCities.length > 0) {
  279. const cityNames = this.selectedCities.map(item => item.adress).join(',')
  280. fullAddress += cityNames
  281. result.selectedAddress = this.selectedProvince // 多选城市时返回省份作为选中地址
  282. result.selectedCities = this.selectedCities
  283. } else if (this.selectedCity) {
  284. fullAddress += this.selectedCity.adress
  285. // 多选区县模式
  286. if (this.multiple && this.selectedDistricts.length > 0) {
  287. const districtNames = this.selectedDistricts.map(item => item.adress).join(',')
  288. fullAddress += districtNames
  289. result.selectedAddress = this.selectedCity // 多选时返回城市作为选中地址
  290. result.selectedDistricts = this.selectedDistricts
  291. } else if (this.selectedDistrict) {
  292. // 单选区县模式
  293. fullAddress += this.selectedDistrict.adress
  294. result.selectedAddress = this.selectedDistrict
  295. } else {
  296. // 选择整个城市
  297. result.selectedAddress = this.selectedCity
  298. }
  299. } else {
  300. // 选择整个省份
  301. result.selectedAddress = this.selectedProvince
  302. }
  303. result.fullAddress = fullAddress
  304. this.$emit('confirm', result)
  305. this.close()
  306. }
  307. }
  308. }
  309. </script>
  310. <style scoped lang="scss">
  311. .address-picker {
  312. height: 70vh;
  313. background: #fff;
  314. .header {
  315. display: flex;
  316. justify-content: space-between;
  317. align-items: center;
  318. padding: 30rpx;
  319. border-bottom: 1px solid #eee;
  320. .title {
  321. font-size: 32rpx;
  322. font-weight: bold;
  323. color: #333;
  324. }
  325. .close-btn {
  326. padding: 10rpx;
  327. }
  328. }
  329. .content {
  330. display: flex;
  331. height: calc(70vh - 160rpx);
  332. .left-panel {
  333. width: 240rpx;
  334. border-right: 1px solid #eee;
  335. background: #f8f8f8;
  336. }
  337. .right-panel {
  338. flex: 1;
  339. }
  340. .scroll-view {
  341. height: 100%;
  342. }
  343. .address-item {
  344. padding: 30rpx 20rpx;
  345. font-size: 28rpx;
  346. color: #333;
  347. border-bottom: 1px solid #f0f0f0;
  348. &:last-child {
  349. border-bottom: none;
  350. }
  351. &.active {
  352. background: #fff;
  353. color: $uni-color;
  354. font-weight: bold;
  355. position: relative;
  356. &::after {
  357. content: '';
  358. position: absolute;
  359. right: 0;
  360. top: 0;
  361. bottom: 0;
  362. width: 6rpx;
  363. background: $uni-color;
  364. }
  365. }
  366. &.back-item {
  367. display: flex;
  368. align-items: center;
  369. color: $uni-color;
  370. background: #f8f8f8;
  371. uv-icon {
  372. margin-right: 10rpx;
  373. }
  374. }
  375. &.whole-city-item {
  376. display: flex;
  377. align-items: center;
  378. color: #3796F8;
  379. background: rgba(#3796F8, 0.1);
  380. font-weight: bold;
  381. uv-icon {
  382. margin-right: 10rpx;
  383. }
  384. }
  385. &.selected {
  386. background: rgba($uni-color, 0.1);
  387. color: $uni-color;
  388. font-weight: bold;
  389. position: relative;
  390. &::after {
  391. content: '';
  392. position: absolute;
  393. right: 0;
  394. top: 0;
  395. bottom: 0;
  396. width: 6rpx;
  397. background: $uni-color;
  398. }
  399. }
  400. }
  401. .confirm-btn {
  402. position: sticky;
  403. bottom: 0;
  404. background: #fff;
  405. padding: 20rpx;
  406. border-top: 1px solid #eee;
  407. .confirm-button {
  408. width: 100%;
  409. background: $uni-color;
  410. color: #fff;
  411. border: none;
  412. border-radius: 10rpx;
  413. padding: 20rpx;
  414. font-size: 28rpx;
  415. }
  416. }
  417. }
  418. }
  419. </style>