特易招,招聘小程序
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.

491 lines
12 KiB

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