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

503 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
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. let selectedId = '' // 用于传给后端的ID
  277. let selectedIds = [] // 多选时的ID数组
  278. if (this.selectedProvince) {
  279. fullAddress += this.selectedProvince.adress
  280. }
  281. // 多选城市模式
  282. if (this.multiple && this.selectedCities.length > 0) {
  283. const cityNames = this.selectedCities.map(item => item.adress).join(',')
  284. fullAddress = cityNames
  285. result.selectedAddress = this.selectedProvince // 多选城市时返回省份作为选中地址
  286. result.selectedCities = this.selectedCities
  287. // 返回多选城市的ID数组
  288. selectedIds = this.selectedCities.map(item => item.id)
  289. result.selectedIds = selectedIds
  290. } else if (this.selectedCity) {
  291. fullAddress += this.selectedCity.adress
  292. // 多选区县模式
  293. if (this.multiple && this.selectedDistricts.length > 0) {
  294. const districtNames = this.selectedDistricts.map(item => item.adress).join(',')
  295. fullAddress += districtNames
  296. result.selectedAddress = this.selectedCity // 多选时返回城市作为选中地址
  297. result.selectedDistricts = this.selectedDistricts
  298. // 返回多选区县的ID数组
  299. selectedIds = this.selectedDistricts.map(item => item.id)
  300. result.selectedIds = selectedIds
  301. } else if (this.selectedDistrict) {
  302. // 单选区县模式
  303. fullAddress += this.selectedDistrict.adress
  304. result.selectedAddress = this.selectedDistrict
  305. selectedId = this.selectedDistrict.id
  306. } else {
  307. // 选择整个城市
  308. result.selectedAddress = this.selectedCity
  309. selectedId = this.selectedCity.id
  310. }
  311. } else {
  312. // 选择整个省份
  313. result.selectedAddress = this.selectedProvince
  314. selectedId = this.selectedProvince.id
  315. }
  316. result.fullAddress = fullAddress
  317. result.selectedId = selectedId // 单选时的ID
  318. result.selectedIds = selectedIds // 多选时的ID数组
  319. this.$emit('confirm', result)
  320. this.close()
  321. }
  322. }
  323. }
  324. </script>
  325. <style scoped lang="scss">
  326. .address-picker {
  327. height: 70vh;
  328. background: #fff;
  329. .header {
  330. display: flex;
  331. justify-content: space-between;
  332. align-items: center;
  333. padding: 30rpx;
  334. border-bottom: 1px solid #eee;
  335. .title {
  336. font-size: 32rpx;
  337. font-weight: bold;
  338. color: #333;
  339. }
  340. .close-btn {
  341. padding: 10rpx;
  342. }
  343. }
  344. .content {
  345. display: flex;
  346. height: calc(70vh - 160rpx);
  347. .left-panel {
  348. width: 240rpx;
  349. border-right: 1px solid #eee;
  350. background: #f8f8f8;
  351. }
  352. .right-panel {
  353. flex: 1;
  354. }
  355. .scroll-view {
  356. height: 100%;
  357. }
  358. .address-item {
  359. padding: 30rpx 20rpx;
  360. font-size: 28rpx;
  361. color: #333;
  362. border-bottom: 1px solid #f0f0f0;
  363. &:last-child {
  364. border-bottom: none;
  365. }
  366. &.active {
  367. background: #fff;
  368. color: $uni-color;
  369. font-weight: bold;
  370. position: relative;
  371. &::after {
  372. content: '';
  373. position: absolute;
  374. right: 0;
  375. top: 0;
  376. bottom: 0;
  377. width: 6rpx;
  378. background: $uni-color;
  379. }
  380. }
  381. &.back-item {
  382. display: flex;
  383. align-items: center;
  384. color: $uni-color;
  385. background: #f8f8f8;
  386. uv-icon {
  387. margin-right: 10rpx;
  388. }
  389. }
  390. &.whole-city-item {
  391. display: flex;
  392. align-items: center;
  393. color: #3796F8;
  394. background: rgba(#3796F8, 0.1);
  395. font-weight: bold;
  396. uv-icon {
  397. margin-right: 10rpx;
  398. }
  399. }
  400. &.selected {
  401. background: rgba($uni-color, 0.1);
  402. color: $uni-color;
  403. font-weight: bold;
  404. position: relative;
  405. &::after {
  406. content: '';
  407. position: absolute;
  408. right: 0;
  409. top: 0;
  410. bottom: 0;
  411. width: 6rpx;
  412. background: $uni-color;
  413. }
  414. }
  415. }
  416. .left-panel {
  417. .select-all-item {
  418. display: flex;
  419. align-items: center;
  420. color: #3796F8;
  421. background: rgba(#3796F8, 0.1);
  422. font-weight: bold;
  423. padding: 30rpx 20rpx;
  424. font-size: 28rpx;
  425. border-bottom: 1px solid #f0f0f0;
  426. uv-icon {
  427. margin-right: 10rpx;
  428. }
  429. }
  430. }
  431. .confirm-btn {
  432. position: sticky;
  433. bottom: 0;
  434. background: #fff;
  435. padding: 20rpx;
  436. border-top: 1px solid #eee;
  437. .confirm-button {
  438. width: 100%;
  439. background: $uni-color;
  440. color: #fff;
  441. border: none;
  442. border-radius: 10rpx;
  443. padding: 20rpx;
  444. font-size: 28rpx;
  445. }
  446. }
  447. }
  448. }
  449. </style>